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/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": {
|
"@types/color-name": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
"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/javascript-mutator": "^3.2.4",
|
||||||
"@stryker-mutator/jest-runner": "^3.2.4",
|
"@stryker-mutator/jest-runner": "^3.2.4",
|
||||||
"@svgr/webpack": "^4.3.3",
|
"@svgr/webpack": "^4.3.3",
|
||||||
|
"@types/classnames": "^2.2.10",
|
||||||
"@types/enzyme": "^3.10.5",
|
"@types/enzyme": "^3.10.5",
|
||||||
"@types/jest": "^26.0.10",
|
"@types/jest": "^26.0.10",
|
||||||
"@types/moment": "^2.13.0",
|
"@types/moment": "^2.13.0",
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React, { FC, useEffect } from 'react';
|
import React, { FC, useEffect } from 'react';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { RouterProps } from 'react-router';
|
import { RouterProps } from 'react-router';
|
||||||
|
import classNames from 'classnames';
|
||||||
import NoMenuLayout from '../common/NoMenuLayout';
|
import NoMenuLayout from '../common/NoMenuLayout';
|
||||||
|
import { StateFlagTimeout } from '../utils/helpers/hooks';
|
||||||
import { ServerForm } from './helpers/ServerForm';
|
import { ServerForm } from './helpers/ServerForm';
|
||||||
import { ImportServersBtnProps } from './helpers/ImportServersBtn';
|
import { ImportServersBtnProps } from './helpers/ImportServersBtn';
|
||||||
import { NewServerData, RegularServer } from './data';
|
import { NewServerData, RegularServer } from './data';
|
||||||
|
@ -14,10 +16,26 @@ interface CreateServerProps extends RouterProps {
|
||||||
resetSelectedServer: Function;
|
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,
|
{ createServer, history: { push }, resetSelectedServer }: CreateServerProps,
|
||||||
) => {
|
) => {
|
||||||
const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME);
|
const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME);
|
||||||
|
const [ errorImporting, setErrorImporting ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME);
|
||||||
const handleSubmit = (serverData: NewServerData) => {
|
const handleSubmit = (serverData: NewServerData) => {
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
|
|
||||||
|
@ -32,19 +50,12 @@ const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useStateFlagT
|
||||||
return (
|
return (
|
||||||
<NoMenuLayout>
|
<NoMenuLayout>
|
||||||
<ServerForm onSubmit={handleSubmit}>
|
<ServerForm onSubmit={handleSubmit}>
|
||||||
<ImportServersBtn onImport={setServersImported} />
|
<ImportServersBtn onImport={setServersImported} onImportError={setErrorImporting} />
|
||||||
<button className="btn btn-outline-primary">Create server</button>
|
<button className="btn btn-outline-primary">Create server</button>
|
||||||
</ServerForm>
|
</ServerForm>
|
||||||
|
|
||||||
{serversImported && (
|
{serversImported && <Result type="success">Servers properly imported. You can now select one from the list :)</Result>}
|
||||||
<div className="row create-server__import-success-msg">
|
{errorImporting && <Result type="error">The servers could not be imported. Make sure the format is correct.</Result>}
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
</NoMenuLayout>
|
</NoMenuLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,16 +7,20 @@ type Ref<T> = RefObject<T> | MutableRefObject<T>;
|
||||||
|
|
||||||
export interface ImportServersBtnProps {
|
export interface ImportServersBtnProps {
|
||||||
onImport?: () => void;
|
onImport?: () => void;
|
||||||
|
onImportError?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportServersBtnConnectProps {
|
interface ImportServersBtnConnectProps extends ImportServersBtnProps {
|
||||||
createServers: (servers: Server[]) => void;
|
createServers: (servers: Server[]) => void;
|
||||||
fileRef: Ref<HTMLInputElement>;
|
fileRef: Ref<HTMLInputElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => (
|
const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ({
|
||||||
{ createServers, fileRef, onImport = () => {} }: ImportServersBtnConnectProps & ImportServersBtnProps,
|
createServers,
|
||||||
) => {
|
fileRef,
|
||||||
|
onImport = () => {},
|
||||||
|
onImportError = () => {},
|
||||||
|
}: ImportServersBtnConnectProps) => {
|
||||||
const ref = fileRef ?? useRef<HTMLInputElement>();
|
const ref = fileRef ?? useRef<HTMLInputElement>();
|
||||||
const onChange = async ({ target }: ChangeEvent<HTMLInputElement>) =>
|
const onChange = async ({ target }: ChangeEvent<HTMLInputElement>) =>
|
||||||
importServersFromFile(target.files?.[0])
|
importServersFromFile(target.files?.[0])
|
||||||
|
@ -25,7 +29,8 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => (
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// Reset input after processing file
|
// Reset input after processing file
|
||||||
(target as { value: string | null }).value = null;
|
(target as { value: string | null }).value = null;
|
||||||
});
|
})
|
||||||
|
.catch(onImportError);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<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 createServerMock = jest.fn();
|
||||||
const push = jest.fn();
|
const push = jest.fn();
|
||||||
const historyMock = Mock.of<History>({ push });
|
const historyMock = Mock.of<History>({ push });
|
||||||
const createWrapper = (serversImported = false) => {
|
const createWrapper = (serversImported = false, importFailed = false) => {
|
||||||
const CreateServer = createServerConstruct(ImportServersBtn, () => [ serversImported, () => '' ]);
|
const useStateFlagTimeout = jest.fn()
|
||||||
|
.mockReturnValueOnce([ serversImported, () => '' ])
|
||||||
|
.mockReturnValueOnce([ importFailed, () => '' ]);
|
||||||
|
const CreateServer = createServerConstruct(ImportServersBtn, useStateFlagTimeout);
|
||||||
|
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<CreateServer createServer={createServerMock} resetSelectedServer={identity} history={historyMock} />,
|
<CreateServer createServer={createServerMock} resetSelectedServer={identity} history={historyMock} />,
|
||||||
|
@ -31,13 +34,23 @@ describe('<CreateServer />', () => {
|
||||||
const wrapper = createWrapper();
|
const wrapper = createWrapper();
|
||||||
|
|
||||||
expect(wrapper.find(ServerForm)).toHaveLength(1);
|
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', () => {
|
it('shows success message when imported is true', () => {
|
||||||
const wrapper = createWrapper(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', () => {
|
it('creates server and redirects to it when form is submitted', () => {
|
||||||
|
|
Loading…
Reference in a new issue