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 (
-
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 }) => (
-
+