diff --git a/CHANGELOG.md b/CHANGELOG.md index ad01b43a..d7eaf534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * [#315](https://github.com/shlinkio/shlink-web-client/issues/315) Now you can tell if you want to validate the long URL when using Shlink >=2.4. ### Changed -* *Nothing* +* [#267](https://github.com/shlinkio/shlink-web-client/issues/267) Added some subtle but important improvements on UI/UX. ### Deprecated * *Nothing* diff --git a/src/common/AsideMenu.scss b/src/common/AsideMenu.scss index dda916e9..af1935b7 100644 --- a/src/common/AsideMenu.scss +++ b/src/common/AsideMenu.scss @@ -4,7 +4,8 @@ $asideMenuMobileWidth: 280px; .aside-menu { - background-color: #f7f7f7; + background-color: white; + box-shadow: rgba(0, 0, 0, .05) 0 8px 15px; position: fixed !important; padding-top: 13px; padding-bottom: 10px; diff --git a/src/common/MainHeader.tsx b/src/common/MainHeader.tsx index 54faa970..be06d9d0 100644 --- a/src/common/MainHeader.tsx +++ b/src/common/MainHeader.tsx @@ -1,4 +1,4 @@ -import { faPlus as plusIcon, faChevronDown as arrowIcon, faCogs as cogsIcon } from '@fortawesome/free-solid-svg-icons'; +import { faChevronDown as arrowIcon, faCogs as cogsIcon } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FC, useEffect } from 'react'; import { Link } from 'react-router-dom'; @@ -15,7 +15,6 @@ const MainHeader = (ServersDropdown: FC) => ({ location }: RouteComponentProps) useEffect(close, [ location ]); - const createServerPath = '/server/create'; const settingsPath = '/settings'; const toggleClass = classNames('main-header__toggle-icon', { 'main-header__toggle-icon--opened': isOpen }); @@ -32,15 +31,10 @@ const MainHeader = (ServersDropdown: FC) => ({ location }: RouteComponentProps) diff --git a/src/common/NoMenuLayout.scss b/src/common/NoMenuLayout.scss index de288432..ffacd0af 100644 --- a/src/common/NoMenuLayout.scss +++ b/src/common/NoMenuLayout.scss @@ -1,3 +1,3 @@ .no-menu-wrapper { - padding: 40px 20px 20px; + padding: 30px 20px 20px; } diff --git a/src/common/NoMenuLayout.tsx b/src/common/NoMenuLayout.tsx index ac4f1dcc..dfddde05 100644 --- a/src/common/NoMenuLayout.tsx +++ b/src/common/NoMenuLayout.tsx @@ -1,6 +1,6 @@ import { FC } from 'react'; import './NoMenuLayout.scss'; -const NoMenuLayout: FC = ({ children }) =>
{children}
; +const NoMenuLayout: FC = ({ children }) =>
{children}
; export default NoMenuLayout; diff --git a/src/index.scss b/src/index.scss index aa734ab8..c7973396 100644 --- a/src/index.scss +++ b/src/index.scss @@ -6,6 +6,7 @@ html, body, #root { height: 100%; + background: #f5f6fe; } * { @@ -16,6 +17,18 @@ body, background-color: $mainColor !important; } +.card { + box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .075); +} + +.card-header { + background-color: white; +} + +.card-footer { + background-color: rgba(255, 255, 255, .5); +} + .dropdown-item:not(:disabled) { cursor: pointer; } diff --git a/src/servers/CreateServer.tsx b/src/servers/CreateServer.tsx index ab0e2d5c..7181344b 100644 --- a/src/servers/CreateServer.tsx +++ b/src/servers/CreateServer.tsx @@ -44,7 +44,7 @@ const CreateServer = (ImportServersBtn: FC, useStateFlagT return ( - + Add new server} onSubmit={handleSubmit}> diff --git a/src/servers/EditServer.tsx b/src/servers/EditServer.tsx index d8871748..40c52c68 100644 --- a/src/servers/EditServer.tsx +++ b/src/servers/EditServer.tsx @@ -23,7 +23,11 @@ export const EditServer = (ServerError: FC) => withSelectedServer - + Edit "{selectedServer.name}"} + initialValues={selectedServer} + onSubmit={handleSubmit} + > diff --git a/src/servers/Overview.tsx b/src/servers/Overview.tsx index d3726638..a209693b 100644 --- a/src/servers/Overview.tsx +++ b/src/servers/Overview.tsx @@ -49,13 +49,13 @@ export const Overview = ( <>
- + Visits {loadingVisits ? 'Loading...' : prettify(visitsCount)}
- + Short URLs {loading ? 'Loading...' : prettify(shortUrls?.pagination.totalItems ?? 0)} @@ -63,7 +63,7 @@ export const Overview = (
- + Tags {loadingTags ? 'Loading...' : prettify(tagsList.tags.length)} diff --git a/src/servers/ServersDropdown.tsx b/src/servers/ServersDropdown.tsx index a787c467..81045e42 100644 --- a/src/servers/ServersDropdown.tsx +++ b/src/servers/ServersDropdown.tsx @@ -1,6 +1,8 @@ import { isEmpty, values } from 'ramda'; import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap'; import { Link } from 'react-router-dom'; +import { faPlus as plusIcon, faFileDownload as exportIcon, faServer as serverIcon } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import ServersExporter from './services/ServersExporter'; import { isServerWithId, SelectedServer, ServersMap } from './data'; @@ -11,10 +13,15 @@ export interface ServersDropdownProps { const ServersDropdown = (serversExporter: ServersExporter) => ({ servers, selectedServer }: ServersDropdownProps) => { const serversList = values(servers); + const createServerItem = ( + + Add server + + ); const renderServers = () => { if (isEmpty(serversList)) { - return Add a server first...; + return createServerItem; } return ( @@ -30,8 +37,9 @@ const ServersDropdown = (serversExporter: ServersExporter) => ({ servers, select ))} + {createServerItem} serversExporter.exportServers()}> - Export servers + Export servers ); @@ -39,7 +47,9 @@ const ServersDropdown = (serversExporter: ServersExporter) => ({ servers, select return ( - Servers + + Servers + {renderServers()} ); diff --git a/src/servers/ServersListGroup.scss b/src/servers/ServersListGroup.scss index 9f869468..478fb19d 100644 --- a/src/servers/ServersListGroup.scss +++ b/src/servers/ServersListGroup.scss @@ -3,6 +3,7 @@ .servers-list__list-group { width: 100%; max-width: 400px; + box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .075); } .servers-list__server-item.servers-list__server-item { diff --git a/src/servers/helpers/ServerError.tsx b/src/servers/helpers/ServerError.tsx index 73538012..17d14be6 100644 --- a/src/servers/helpers/ServerError.tsx +++ b/src/servers/helpers/ServerError.tsx @@ -4,6 +4,7 @@ import Message from '../../utils/Message'; import ServersListGroup from '../ServersListGroup'; import { DeleteServerButtonProps } from '../DeleteServerButton'; import { isServerWithId, SelectedServer, ServersMap } from '../data'; +import NoMenuLayout from '../../common/NoMenuLayout'; import './ServerError.scss'; interface ServerErrorProps { @@ -14,32 +15,34 @@ interface ServerErrorProps { export const ServerError = (DeleteServerButton: FC): FC => ( { servers, selectedServer }, ) => ( -
-
- - {!isServerWithId(selectedServer) && 'Could not find this Shlink server.'} - {isServerWithId(selectedServer) && ( - <> -

Oops! Could not connect to this Shlink server.

- Make sure you have internet connection, and the server is properly configured and on-line. - - )} -
-
- - - These are the Shlink servers currently configured. Choose one of - them or add a new one. - - - {isServerWithId(selectedServer) && ( -
-
- Alternatively, if you think you may have miss-configured this server, you - can remove it or  - edit it. -
+ +
+
+ + {!isServerWithId(selectedServer) && 'Could not find this Shlink server.'} + {isServerWithId(selectedServer) && ( + <> +

Oops! Could not connect to this Shlink server.

+ Make sure you have internet connection, and the server is properly configured and on-line. + + )} +
- )} -
+ + + These are the Shlink servers currently configured. Choose one of + them or add a new one. + + + {isServerWithId(selectedServer) && ( +
+
+ Alternatively, if you think you may have miss-configured this server, you + can remove it or  + edit it. +
+
+ )} +
+ ); diff --git a/src/servers/helpers/ServerForm.scss b/src/servers/helpers/ServerForm.scss new file mode 100644 index 00000000..97b1ae22 --- /dev/null +++ b/src/servers/helpers/ServerForm.scss @@ -0,0 +1,3 @@ +.server-form .form-group:last-child { + margin-bottom: 0; +} diff --git a/src/servers/helpers/ServerForm.tsx b/src/servers/helpers/ServerForm.tsx index 8836772f..d0d7bf66 100644 --- a/src/servers/helpers/ServerForm.tsx +++ b/src/servers/helpers/ServerForm.tsx @@ -1,14 +1,17 @@ -import { FC, useEffect, useState } from 'react'; -import { HorizontalFormGroup } from '../../utils/HorizontalFormGroup'; +import { FC, ReactNode, useEffect, useState } from 'react'; +import { FormGroupContainer } from '../../utils/FormGroupContainer'; import { handleEventPreventingDefault } from '../../utils/utils'; import { ServerData } from '../data'; +import { SimpleCard } from '../../utils/SimpleCard'; +import './ServerForm.scss'; interface ServerFormProps { onSubmit: (server: ServerData) => void; initialValues?: ServerData; + title?: ReactNode; } -export const ServerForm: FC = ({ onSubmit, initialValues, children }) => { +export const ServerForm: FC = ({ onSubmit, initialValues, children, title }) => { const [ name, setName ] = useState(''); const [ url, setUrl ] = useState(''); const [ apiKey, setApiKey ] = useState(''); @@ -21,10 +24,12 @@ export const ServerForm: FC = ({ onSubmit, initialValues, child }, [ initialValues ]); return ( -
- Name - URL - API key + + + Name + URL + API key +
{children}
diff --git a/src/servers/helpers/withSelectedServer.tsx b/src/servers/helpers/withSelectedServer.tsx index d0a08b88..742bf160 100644 --- a/src/servers/helpers/withSelectedServer.tsx +++ b/src/servers/helpers/withSelectedServer.tsx @@ -2,6 +2,7 @@ import { FC, useEffect } from 'react'; import { RouteComponentProps } from 'react-router'; import Message from '../../utils/Message'; import { isNotFoundServer, SelectedServer } from '../data'; +import NoMenuLayout from '../../common/NoMenuLayout'; interface WithSelectedServerProps extends RouteComponentProps<{ serverId: string }> { selectServer: (serverId: string) => void; @@ -18,9 +19,9 @@ export function withSelectedServer(WrappedComponent: FC - -
+ + + ); } diff --git a/src/short-urls/Paginator.scss b/src/short-urls/Paginator.scss index 0e8142e2..03b784d0 100644 --- a/src/short-urls/Paginator.scss +++ b/src/short-urls/Paginator.scss @@ -1,7 +1,7 @@ .short-urls-paginator { position: sticky; bottom: 0; - background-color: rgba(white, .8); + background-color: rgba(255, 255, 255, .5); padding: .75rem 0; border-top: 1px solid rgba(black, .125); } diff --git a/src/short-urls/ShortUrls.tsx b/src/short-urls/ShortUrls.tsx index 0b86f35d..d6dea6bc 100644 --- a/src/short-urls/ShortUrls.tsx +++ b/src/short-urls/ShortUrls.tsx @@ -1,4 +1,5 @@ import { FC, useEffect, useState } from 'react'; +import { Card } from 'reactstrap'; import Paginator from './Paginator'; import { ShortUrlsListProps } from './ShortUrlsList'; @@ -17,10 +18,10 @@ const ShortUrls = (SearchBar: FC, ShortUrlsList: FC) => (pro return ( <>
-
+ -
+
); }; diff --git a/src/short-urls/ShortUrlsTable.tsx b/src/short-urls/ShortUrlsTable.tsx index 2c799020..c1a1fb77 100644 --- a/src/short-urls/ShortUrlsTable.tsx +++ b/src/short-urls/ShortUrlsTable.tsx @@ -30,7 +30,7 @@ export const ShortUrlsTable = (ShortUrlsRow: FC) => ({ const orderableColumnsClasses = classNames('short-urls-table__header-cell', { 'short-urls-table__header-cell--with-action': !!orderByColumn, }); - const tableClasses = classNames('table table-striped table-hover', className); + const tableClasses = classNames('table table-hover', className); const renderShortUrls = () => { if (error) { diff --git a/src/short-urls/helpers/EditShortUrlModal.tsx b/src/short-urls/helpers/EditShortUrlModal.tsx index 868512d8..019ad5bc 100644 --- a/src/short-urls/helpers/EditShortUrlModal.tsx +++ b/src/short-urls/helpers/EditShortUrlModal.tsx @@ -18,7 +18,7 @@ const EditShortUrlModal = ({ isOpen, toggle, shortUrl, shortUrlEdition, editShor const doEdit = async () => editShortUrl(shortUrl.shortCode, shortUrl.domain, longUrl).then(toggle); return ( - + Edit long URL for diff --git a/src/tags/TagCard.scss b/src/tags/TagCard.scss index 4bff8b45..ce6849aa 100644 --- a/src/tags/TagCard.scss +++ b/src/tags/TagCard.scss @@ -2,10 +2,6 @@ margin-bottom: .5rem; } -.tag-card__header.tag-card__header { - background-color: #eeeeee; -} - .tag-card__header.tag-card__header, .tag-card__body.tag-card__body { padding: .75rem; diff --git a/src/utils/FormGroupContainer.tsx b/src/utils/FormGroupContainer.tsx new file mode 100644 index 00000000..9c182291 --- /dev/null +++ b/src/utils/FormGroupContainer.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react'; +import { v4 as uuid } from 'uuid'; +import { InputType } from 'reactstrap/lib/Input'; + +interface FormGroupContainer { + value: string; + onChange: (newValue: string) => void; + id?: string; + type?: InputType; + required?: boolean; +} + +export const FormGroupContainer: FC = ( + { children, value, onChange, id = uuid(), type = 'text', required = true }, +) => ( +
+ + onChange(e.target.value)} + /> +
+); diff --git a/src/utils/HorizontalFormGroup.tsx b/src/utils/HorizontalFormGroup.tsx deleted file mode 100644 index b46a5d1a..00000000 --- a/src/utils/HorizontalFormGroup.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { FC } from 'react'; -import { v4 as uuid } from 'uuid'; -import { InputType } from 'reactstrap/lib/Input'; - -interface HorizontalFormGroupProps { - value: string; - onChange: (newValue: string) => void; - id?: string; - type?: InputType; - required?: boolean; -} - -export const HorizontalFormGroup: FC = ( - { children, value, onChange, id = uuid(), type = 'text', required = true }, -) => ( -
- -
- onChange(e.target.value)} - /> -
-
-); diff --git a/src/utils/Message.tsx b/src/utils/Message.tsx index cc7c71da..70363983 100644 --- a/src/utils/Message.tsx +++ b/src/utils/Message.tsx @@ -26,14 +26,21 @@ const getTextClassForType = (type: MessageType) => { interface MessageProps { noMargin?: boolean; loading?: boolean; + fullWidth?: boolean; type?: MessageType; } -const Message: FC = ({ children, loading = false, noMargin = false, type = 'default' }) => { - const cardClasses = classNames('bg-light', getClassForType(type), { 'mt-4': !noMargin }); +const Message: FC = ( + { children, loading = false, noMargin = false, type = 'default', fullWidth = false }, +) => { + const cardClasses = classNames(getClassForType(type), { 'mt-4': !noMargin }); + const classes = classNames({ + 'col-md-12': fullWidth, + 'col-md-10 offset-md-1': !fullWidth, + }); return ( -
+

{loading && } diff --git a/src/utils/SimpleCard.tsx b/src/utils/SimpleCard.tsx index ccee2de2..e50213e7 100644 --- a/src/utils/SimpleCard.tsx +++ b/src/utils/SimpleCard.tsx @@ -1,8 +1,9 @@ import { CardProps } from 'reactstrap/lib/Card'; import { Card, CardBody, CardHeader } from 'reactstrap'; +import { ReactNode } from 'react'; -interface SimpleCardProps extends CardProps { - title?: string; +interface SimpleCardProps extends Omit { + title?: ReactNode; } export const SimpleCard = ({ title, children, ...rest }: SimpleCardProps) => ( diff --git a/src/visits/VisitsHeader.tsx b/src/visits/VisitsHeader.tsx index 102a62f4..29e531aa 100644 --- a/src/visits/VisitsHeader.tsx +++ b/src/visits/VisitsHeader.tsx @@ -15,7 +15,7 @@ interface VisitsHeaderProps { const VisitsHeader: FC = ({ visits, goBack, shortUrl, children, title }) => (
- +