mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-03-16 04:23:47 +00:00
Improvements on ManageServers
This commit is contained in:
@@ -27,7 +27,7 @@ const ImportResult = ({ type }: { type: 'error' | 'success' }) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useStateFlagTimeout: StateFlagTimeout) => (
|
const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useStateFlagTimeout: StateFlagTimeout) => (
|
||||||
{ servers, createServer, history: { push } }: CreateServerProps,
|
{ servers, createServer, history: { push, goBack } }: CreateServerProps,
|
||||||
) => {
|
) => {
|
||||||
const hasServers = !!Object.keys(servers).length;
|
const hasServers = !!Object.keys(servers).length;
|
||||||
const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME);
|
const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME);
|
||||||
@@ -44,6 +44,7 @@ const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useStateFlagT
|
|||||||
<ServerForm title={<h5 className="mb-0">Add new server</h5>} onSubmit={handleSubmit}>
|
<ServerForm title={<h5 className="mb-0">Add new server</h5>} onSubmit={handleSubmit}>
|
||||||
{!hasServers &&
|
{!hasServers &&
|
||||||
<ImportServersBtn tooltipPlacement="top" onImport={setServersImported} onImportError={setErrorImporting} />}
|
<ImportServersBtn tooltipPlacement="top" onImport={setServersImported} onImportError={setErrorImporting} />}
|
||||||
|
{hasServers && <Button outline onClick={goBack}>Cancel</Button>}
|
||||||
<Button outline color="primary" className="ml-2">Create server</Button>
|
<Button outline color="primary" className="ml-2">Create server</Button>
|
||||||
</ServerForm>
|
</ServerForm>
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface EditServerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const EditServer = (ServerError: FC) => withSelectedServer<EditServerProps>((
|
export const EditServer = (ServerError: FC) => withSelectedServer<EditServerProps>((
|
||||||
{ editServer, selectedServer, history: { push, goBack } },
|
{ editServer, selectedServer, history: { goBack } },
|
||||||
) => {
|
) => {
|
||||||
if (!isServerWithId(selectedServer)) {
|
if (!isServerWithId(selectedServer)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -18,7 +18,7 @@ export const EditServer = (ServerError: FC) => withSelectedServer<EditServerProp
|
|||||||
|
|
||||||
const handleSubmit = (serverData: ServerData) => {
|
const handleSubmit = (serverData: ServerData) => {
|
||||||
editServer(selectedServer.id, serverData);
|
editServer(selectedServer.id, serverData);
|
||||||
push(`/server/${selectedServer.id}`);
|
goBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { Button } from 'reactstrap';
|
import { Button, Row } from 'reactstrap';
|
||||||
import { faFileDownload as exportIcon, faPlus as plusIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faFileDownload as exportIcon, faPlus as plusIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
@@ -41,15 +41,19 @@ export const ManageServers = (
|
|||||||
<NoMenuLayout>
|
<NoMenuLayout>
|
||||||
<SearchField className="mb-3" onChange={filterServers} />
|
<SearchField className="mb-3" onChange={filterServers} />
|
||||||
|
|
||||||
<div className="mb-3 d-flex flex-row">
|
<Row className="mb-3">
|
||||||
<ImportServersBtn onImportError={setErrorImporting} />
|
<div className="col-md-6 d-flex d-md-block mb-2 mb-md-0">
|
||||||
<Button outline className="ml-2" onClick={async () => serversExporter.exportServers()}>
|
<ImportServersBtn className="flex-fill" onImportError={setErrorImporting} />
|
||||||
<FontAwesomeIcon icon={exportIcon} fixedWidth /> Export servers
|
<Button outline className="ml-2 flex-fill" onClick={async () => serversExporter.exportServers()}>
|
||||||
</Button>
|
<FontAwesomeIcon icon={exportIcon} fixedWidth /> Export servers
|
||||||
<Button outline color="primary" className="ml-auto" tag={Link} to="/server/create">
|
</Button>
|
||||||
<FontAwesomeIcon icon={plusIcon} fixedWidth /> Add a server
|
</div>
|
||||||
</Button>
|
<div className="col-md-6 text-md-right d-flex d-md-block">
|
||||||
</div>
|
<Button outline color="primary" className="flex-fill" tag={Link} to="/server/create">
|
||||||
|
<FontAwesomeIcon icon={plusIcon} fixedWidth /> Add a server
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
|
||||||
<SimpleCard title="Shlink servers">
|
<SimpleCard title="Shlink servers">
|
||||||
<table className="table table-hover mb-0">
|
<table className="table table-hover mb-0">
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const ManageServersRow = (
|
|||||||
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit server
|
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit server
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem>
|
<DropdownItem>
|
||||||
<FontAwesomeIcon icon={autoConnectIcon} fixedWidth /> {isAutoConnect ? 'Unset' : 'Set'} auto-connect
|
<FontAwesomeIcon icon={autoConnectIcon} fixedWidth /> {isAutoConnect ? 'Do not a' : 'A'}uto-connect
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem divider />
|
<DropdownItem divider />
|
||||||
<DropdownItem className="dropdown-item--danger" onClick={showModal}>
|
<DropdownItem className="dropdown-item--danger" onClick={showModal}>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export interface ImportServersBtnProps {
|
|||||||
onImport?: () => void;
|
onImport?: () => void;
|
||||||
onImportError?: (error: Error) => void;
|
onImportError?: (error: Error) => void;
|
||||||
tooltipPlacement?: 'top' | 'bottom';
|
tooltipPlacement?: 'top' | 'bottom';
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportServersBtnConnectProps extends ImportServersBtnProps {
|
interface ImportServersBtnConnectProps extends ImportServersBtnProps {
|
||||||
@@ -25,6 +26,7 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ({
|
|||||||
onImport = () => {},
|
onImport = () => {},
|
||||||
onImportError = () => {},
|
onImportError = () => {},
|
||||||
tooltipPlacement = 'bottom',
|
tooltipPlacement = 'bottom',
|
||||||
|
className = '',
|
||||||
}: ImportServersBtnConnectProps) => {
|
}: ImportServersBtnConnectProps) => {
|
||||||
const ref = fileRef ?? useRef<HTMLInputElement>();
|
const ref = fileRef ?? useRef<HTMLInputElement>();
|
||||||
const onChange = async ({ target }: ChangeEvent<HTMLInputElement>) =>
|
const onChange = async ({ target }: ChangeEvent<HTMLInputElement>) =>
|
||||||
@@ -39,7 +41,7 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button outline id="importBtn" onClick={() => ref.current?.click()}>
|
<Button outline id="importBtn" className={className} onClick={() => ref.current?.click()}>
|
||||||
<FontAwesomeIcon icon={importIcon} fixedWidth /> Import from file
|
<FontAwesomeIcon icon={importIcon} fixedWidth /> Import from file
|
||||||
</Button>
|
</Button>
|
||||||
<UncontrolledTooltip placement={tooltipPlacement} target="importBtn">
|
<UncontrolledTooltip placement={tooltipPlacement} target="importBtn">
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ describe('<EditServer />', () => {
|
|||||||
let wrapper: ReactWrapper;
|
let wrapper: ReactWrapper;
|
||||||
const ServerError = jest.fn();
|
const ServerError = jest.fn();
|
||||||
const editServerMock = jest.fn();
|
const editServerMock = jest.fn();
|
||||||
const push = jest.fn();
|
const goBack = jest.fn();
|
||||||
const historyMock = Mock.of<History>({ push });
|
const historyMock = Mock.of<History>({ goBack });
|
||||||
const match = Mock.of<match<{ serverId: string }>>({
|
const match = Mock.of<match<{ serverId: string }>>({
|
||||||
params: { serverId: 'abc123' },
|
params: { serverId: 'abc123' },
|
||||||
});
|
});
|
||||||
@@ -50,6 +50,6 @@ describe('<EditServer />', () => {
|
|||||||
form.simulate('submit', {});
|
form.simulate('submit', {});
|
||||||
|
|
||||||
expect(editServerMock).toHaveBeenCalledTimes(1);
|
expect(editServerMock).toHaveBeenCalledTimes(1);
|
||||||
expect(push).toHaveBeenCalledTimes(1);
|
expect(goBack).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,25 +15,43 @@ describe('<ImportServersBtn />', () => {
|
|||||||
const fileRef = {
|
const fileRef = {
|
||||||
current: Mock.of<HTMLInputElement>({ click }),
|
current: Mock.of<HTMLInputElement>({ click }),
|
||||||
};
|
};
|
||||||
|
const ImportServersBtn = importServersBtnConstruct(serversImporterMock);
|
||||||
beforeEach(() => {
|
const createWrapper = (className?: string) => {
|
||||||
jest.clearAllMocks();
|
|
||||||
|
|
||||||
const ImportServersBtn = importServersBtnConstruct(serversImporterMock);
|
|
||||||
|
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<ImportServersBtn createServers={createServersMock} fileRef={fileRef} onImport={onImportMock} />,
|
<ImportServersBtn
|
||||||
|
createServers={createServersMock}
|
||||||
|
className={className}
|
||||||
|
fileRef={fileRef}
|
||||||
|
onImport={onImportMock}
|
||||||
|
/>,
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
return wrapper;
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(jest.clearAllMocks);
|
||||||
afterEach(() => wrapper.unmount());
|
afterEach(() => wrapper.unmount());
|
||||||
|
|
||||||
it('renders a button, a tooltip and a file input', () => {
|
it('renders a button, a tooltip and a file input', () => {
|
||||||
|
const wrapper = createWrapper();
|
||||||
|
|
||||||
expect(wrapper.find('#importBtn')).toHaveLength(1);
|
expect(wrapper.find('#importBtn')).toHaveLength(1);
|
||||||
expect(wrapper.find(UncontrolledTooltip)).toHaveLength(1);
|
expect(wrapper.find(UncontrolledTooltip)).toHaveLength(1);
|
||||||
expect(wrapper.find('.import-servers-btn__csv-select')).toHaveLength(1);
|
expect(wrapper.find('.import-servers-btn__csv-select')).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[ undefined, '' ],
|
||||||
|
[ 'foo', 'foo' ],
|
||||||
|
[ 'bar', 'bar' ],
|
||||||
|
])('allows a class name to be provided', (providedClassName, expectedClassName) => {
|
||||||
|
const wrapper = createWrapper(providedClassName);
|
||||||
|
|
||||||
|
expect(wrapper.find('#importBtn').prop('className')).toEqual(expectedClassName);
|
||||||
|
});
|
||||||
|
|
||||||
it('triggers click on file ref when button is clicked', () => {
|
it('triggers click on file ref when button is clicked', () => {
|
||||||
|
const wrapper = createWrapper();
|
||||||
const btn = wrapper.find('#importBtn');
|
const btn = wrapper.find('#importBtn');
|
||||||
|
|
||||||
btn.simulate('click');
|
btn.simulate('click');
|
||||||
@@ -42,6 +60,7 @@ describe('<ImportServersBtn />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('imports servers when file input changes', (done) => {
|
it('imports servers when file input changes', (done) => {
|
||||||
|
const wrapper = createWrapper();
|
||||||
const file = wrapper.find('.import-servers-btn__csv-select');
|
const file = wrapper.find('.import-servers-btn__csv-select');
|
||||||
|
|
||||||
file.simulate('change', { target: { files: [ '' ] } });
|
file.simulate('change', { target: { files: [ '' ] } });
|
||||||
|
|||||||
Reference in New Issue
Block a user