mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Migrate CreateServer component to Typescript
This commit is contained in:
parent
2db85c2783
commit
7c67fa4149
7 changed files with 88 additions and 46 deletions
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -3056,6 +3056,12 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/classnames": {
|
||||
"version": "2.2.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz",
|
||||
"integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
"@stryker-mutator/javascript-mutator": "^3.2.4",
|
||||
"@stryker-mutator/jest-runner": "^3.2.4",
|
||||
"@svgr/webpack": "^4.3.3",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/jest": "^26.0.10",
|
||||
"@types/moment": "^2.13.0",
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import React, { FC, useEffect } from 'react';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { RouterProps } from 'react-router';
|
||||
import classNames from 'classnames';
|
||||
import NoMenuLayout from '../common/NoMenuLayout';
|
||||
import { StateFlagTimeout } from '../utils/helpers/hooks';
|
||||
import { ServerForm } from './helpers/ServerForm';
|
||||
import { ImportServersBtnProps } from './helpers/ImportServersBtn';
|
||||
import { NewServerData, RegularServer } from './data';
|
||||
|
@ -14,10 +16,26 @@ interface CreateServerProps extends RouterProps {
|
|||
resetSelectedServer: Function;
|
||||
}
|
||||
|
||||
const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useStateFlagTimeout: Function) => (
|
||||
const Result: FC<{ type: 'success' | 'error' }> = ({ children, type }) => (
|
||||
<div className="row">
|
||||
<div className="col-md-10 offset-md-1">
|
||||
<div
|
||||
className={classNames('p-2 mt-3 text-white text-center', {
|
||||
'bg-main': type === 'success',
|
||||
'bg-danger': type === 'error',
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useStateFlagTimeout: StateFlagTimeout) => (
|
||||
{ createServer, history: { push }, resetSelectedServer }: CreateServerProps,
|
||||
) => {
|
||||
const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME);
|
||||
const [ errorImporting, setErrorImporting ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME);
|
||||
const handleSubmit = (serverData: NewServerData) => {
|
||||
const id = uuid();
|
||||
|
||||
|
@ -32,19 +50,12 @@ const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useStateFlagT
|
|||
return (
|
||||
<NoMenuLayout>
|
||||
<ServerForm onSubmit={handleSubmit}>
|
||||
<ImportServersBtn onImport={setServersImported} />
|
||||
<ImportServersBtn onImport={setServersImported} onImportError={setErrorImporting} />
|
||||
<button className="btn btn-outline-primary">Create server</button>
|
||||
</ServerForm>
|
||||
|
||||
{serversImported && (
|
||||
<div className="row create-server__import-success-msg">
|
||||
<div className="col-md-10 offset-md-1">
|
||||
<div className="p-2 mt-3 bg-main text-white text-center">
|
||||
Servers properly imported. You can now select one from the list :)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{serversImported && <Result type="success">Servers properly imported. You can now select one from the list :)</Result>}
|
||||
{errorImporting && <Result type="error">The servers could not be imported. Make sure the format is correct.</Result>}
|
||||
</NoMenuLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,16 +7,20 @@ type Ref<T> = RefObject<T> | MutableRefObject<T>;
|
|||
|
||||
export interface ImportServersBtnProps {
|
||||
onImport?: () => void;
|
||||
onImportError?: () => void;
|
||||
}
|
||||
|
||||
interface ImportServersBtnConnectProps {
|
||||
interface ImportServersBtnConnectProps extends ImportServersBtnProps {
|
||||
createServers: (servers: Server[]) => void;
|
||||
fileRef: Ref<HTMLInputElement>;
|
||||
}
|
||||
|
||||
const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => (
|
||||
{ createServers, fileRef, onImport = () => {} }: ImportServersBtnConnectProps & ImportServersBtnProps,
|
||||
) => {
|
||||
const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ({
|
||||
createServers,
|
||||
fileRef,
|
||||
onImport = () => {},
|
||||
onImportError = () => {},
|
||||
}: ImportServersBtnConnectProps) => {
|
||||
const ref = fileRef ?? useRef<HTMLInputElement>();
|
||||
const onChange = async ({ target }: ChangeEvent<HTMLInputElement>) =>
|
||||
importServersFromFile(target.files?.[0])
|
||||
|
@ -25,7 +29,8 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => (
|
|||
.then(() => {
|
||||
// Reset input after processing file
|
||||
(target as { value: string | null }).value = null;
|
||||
});
|
||||
})
|
||||
.catch(onImportError);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { useState, useRef } from 'react';
|
||||
|
||||
const DEFAULT_DELAY = 2000;
|
||||
|
||||
export const useStateFlagTimeout = (setTimeout, clearTimeout) => (initialValue = false, delay = DEFAULT_DELAY) => {
|
||||
const [ flag, setFlag ] = useState(initialValue);
|
||||
const timeout = useRef(undefined);
|
||||
const callback = () => {
|
||||
setFlag(!initialValue);
|
||||
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current);
|
||||
}
|
||||
|
||||
timeout.current = setTimeout(() => setFlag(initialValue), delay);
|
||||
};
|
||||
|
||||
return [ flag, callback ];
|
||||
};
|
||||
|
||||
// Return [ flag, toggle, enable, disable ]
|
||||
export const useToggle = (initialValue = false) => {
|
||||
const [ flag, setFlag ] = useState(initialValue);
|
||||
|
||||
return [ flag, () => setFlag(!flag), () => setFlag(true), () => setFlag(false) ];
|
||||
};
|
32
src/utils/helpers/hooks.ts
Normal file
32
src/utils/helpers/hooks.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { useState, useRef } from 'react';
|
||||
|
||||
const DEFAULT_DELAY = 2000;
|
||||
|
||||
export type StateFlagTimeout = (initialValue?: boolean, delay?: number) => [ boolean, () => void ];
|
||||
|
||||
export const useStateFlagTimeout = (
|
||||
setTimeout: (callback: Function, timeout: number) => number,
|
||||
clearTimeout: (timer: number) => void,
|
||||
): StateFlagTimeout => (initialValue = false, delay = DEFAULT_DELAY) => {
|
||||
const [ flag, setFlag ] = useState<boolean>(initialValue);
|
||||
const timeout = useRef<number | undefined>(undefined);
|
||||
const callback = () => {
|
||||
setFlag(!initialValue);
|
||||
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current);
|
||||
}
|
||||
|
||||
timeout.current = setTimeout(() => setFlag(initialValue), delay);
|
||||
};
|
||||
|
||||
return [ flag, callback ];
|
||||
};
|
||||
|
||||
type ToggleResult = [ boolean, (flag: boolean) => void, () => void, () => void ];
|
||||
|
||||
export const useToggle = (initialValue = false): ToggleResult => {
|
||||
const [ flag, setFlag ] = useState<boolean>(initialValue);
|
||||
|
||||
return [ flag, () => setFlag(!flag), () => setFlag(true), () => setFlag(false) ];
|
||||
};
|
|
@ -12,8 +12,11 @@ describe('<CreateServer />', () => {
|
|||
const createServerMock = jest.fn();
|
||||
const push = jest.fn();
|
||||
const historyMock = Mock.of<History>({ push });
|
||||
const createWrapper = (serversImported = false) => {
|
||||
const CreateServer = createServerConstruct(ImportServersBtn, () => [ serversImported, () => '' ]);
|
||||
const createWrapper = (serversImported = false, importFailed = false) => {
|
||||
const useStateFlagTimeout = jest.fn()
|
||||
.mockReturnValueOnce([ serversImported, () => '' ])
|
||||
.mockReturnValueOnce([ importFailed, () => '' ]);
|
||||
const CreateServer = createServerConstruct(ImportServersBtn, useStateFlagTimeout);
|
||||
|
||||
wrapper = shallow(
|
||||
<CreateServer createServer={createServerMock} resetSelectedServer={identity} history={historyMock} />,
|
||||
|
@ -31,13 +34,23 @@ describe('<CreateServer />', () => {
|
|||
const wrapper = createWrapper();
|
||||
|
||||
expect(wrapper.find(ServerForm)).toHaveLength(1);
|
||||
expect(wrapper.find('.create-server__import-success-msg')).toHaveLength(0);
|
||||
expect(wrapper.find('Result')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('shows success message when imported is true', () => {
|
||||
const wrapper = createWrapper(true);
|
||||
const result = wrapper.find('Result');
|
||||
|
||||
expect(wrapper.find('.create-server__import-success-msg')).toHaveLength(1);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result.prop('type')).toEqual('success');
|
||||
});
|
||||
|
||||
it('shows error message when import failed', () => {
|
||||
const wrapper = createWrapper(false, true);
|
||||
const result = wrapper.find('Result');
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result.prop('type')).toEqual('error');
|
||||
});
|
||||
|
||||
it('creates server and redirects to it when form is submitted', () => {
|
||||
|
|
Loading…
Reference in a new issue