mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Finished migrating servers module to TS
This commit is contained in:
parent
ef630af154
commit
c0f5d9c12c
8 changed files with 130 additions and 142 deletions
|
@ -1,5 +1,5 @@
|
||||||
import React, { FC, useEffect } from 'react';
|
import React, { FC, useEffect } from 'react';
|
||||||
import { Route, RouteChildrenProps, Switch } from 'react-router-dom';
|
import { Route, Switch } from 'react-router-dom';
|
||||||
import { EventData, Swipeable } from 'react-swipeable';
|
import { EventData, Swipeable } from 'react-swipeable';
|
||||||
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
|
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
@ -7,15 +7,11 @@ import classNames from 'classnames';
|
||||||
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
|
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
|
||||||
import { useToggle } from '../utils/helpers/hooks';
|
import { useToggle } from '../utils/helpers/hooks';
|
||||||
import { versionMatch } from '../utils/helpers/version';
|
import { versionMatch } from '../utils/helpers/version';
|
||||||
import { isReachableServer, SelectedServer } from '../servers/data';
|
import { isReachableServer } from '../servers/data';
|
||||||
import NotFound from './NotFound';
|
import NotFound from './NotFound';
|
||||||
import { AsideMenuProps } from './AsideMenu';
|
import { AsideMenuProps } from './AsideMenu';
|
||||||
import './MenuLayout.scss';
|
import './MenuLayout.scss';
|
||||||
|
|
||||||
interface MenuLayoutProps extends RouteChildrenProps {
|
|
||||||
selectedServer: SelectedServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MenuLayout = (
|
const MenuLayout = (
|
||||||
TagsList: FC,
|
TagsList: FC,
|
||||||
ShortUrls: FC,
|
ShortUrls: FC,
|
||||||
|
@ -25,7 +21,7 @@ const MenuLayout = (
|
||||||
TagVisits: FC,
|
TagVisits: FC,
|
||||||
ShlinkVersions: FC,
|
ShlinkVersions: FC,
|
||||||
ServerError: FC,
|
ServerError: FC,
|
||||||
) => withSelectedServer(({ location, selectedServer }: MenuLayoutProps) => {
|
) => withSelectedServer(({ location, selectedServer }) => {
|
||||||
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
|
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
|
||||||
|
|
||||||
useEffect(() => hideSidebar(), [ location ]);
|
useEffect(() => hideSidebar(), [ location ]);
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Button } from 'reactstrap';
|
|
||||||
import NoMenuLayout from '../common/NoMenuLayout';
|
|
||||||
import { ServerForm } from './helpers/ServerForm';
|
|
||||||
import { withSelectedServer } from './helpers/withSelectedServer';
|
|
||||||
import { serverType } from './prop-types';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
editServer: PropTypes.func,
|
|
||||||
selectedServer: serverType,
|
|
||||||
history: PropTypes.shape({
|
|
||||||
push: PropTypes.func,
|
|
||||||
goBack: PropTypes.func,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EditServer = (ServerError) => {
|
|
||||||
const EditServerComp = ({ editServer, selectedServer, history: { push, goBack } }) => {
|
|
||||||
const handleSubmit = (serverData) => {
|
|
||||||
editServer(selectedServer.id, serverData);
|
|
||||||
push(`/server/${selectedServer.id}/list-short-urls/1`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NoMenuLayout>
|
|
||||||
<ServerForm initialValues={selectedServer} onSubmit={handleSubmit}>
|
|
||||||
<Button outline className="mr-2" onClick={goBack}>Cancel</Button>
|
|
||||||
<Button outline color="primary">Save</Button>
|
|
||||||
</ServerForm>
|
|
||||||
</NoMenuLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
EditServerComp.propTypes = propTypes;
|
|
||||||
|
|
||||||
return withSelectedServer(EditServerComp, ServerError);
|
|
||||||
};
|
|
32
src/servers/EditServer.tsx
Normal file
32
src/servers/EditServer.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { Button } from 'reactstrap';
|
||||||
|
import NoMenuLayout from '../common/NoMenuLayout';
|
||||||
|
import { ServerForm } from './helpers/ServerForm';
|
||||||
|
import { withSelectedServer } from './helpers/withSelectedServer';
|
||||||
|
import { isServerWithId, ServerData } from './data';
|
||||||
|
|
||||||
|
interface EditServerProps {
|
||||||
|
editServer: (serverId: string, serverData: ServerData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditServer = (ServerError: FC) => withSelectedServer<EditServerProps>((
|
||||||
|
{ editServer, selectedServer, history: { push, goBack } },
|
||||||
|
) => {
|
||||||
|
if (!isServerWithId(selectedServer)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = (serverData: ServerData) => {
|
||||||
|
editServer(selectedServer.id, serverData);
|
||||||
|
push(`/server/${selectedServer.id}/list-short-urls/1`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NoMenuLayout>
|
||||||
|
<ServerForm initialValues={selectedServer} onSubmit={handleSubmit}>
|
||||||
|
<Button outline className="mr-2" onClick={goBack}>Cancel</Button>
|
||||||
|
<Button outline color="primary">Save</Button>
|
||||||
|
</ServerForm>
|
||||||
|
</NoMenuLayout>
|
||||||
|
);
|
||||||
|
}, ServerError);
|
|
@ -1,55 +0,0 @@
|
||||||
import { isEmpty, values } from 'ramda';
|
|
||||||
import React from 'react';
|
|
||||||
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { serverType } from './prop-types';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
servers: PropTypes.object,
|
|
||||||
selectedServer: serverType,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ServersDropdown = (serversExporter) => {
|
|
||||||
const ServersDropdownComp = ({ servers, selectedServer }) => {
|
|
||||||
const serversList = values(servers);
|
|
||||||
|
|
||||||
const renderServers = () => {
|
|
||||||
if (isEmpty(serversList)) {
|
|
||||||
return <DropdownItem disabled><i>Add a server first...</i></DropdownItem>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
{serversList.map(({ name, id }) => (
|
|
||||||
<DropdownItem
|
|
||||||
key={id}
|
|
||||||
tag={Link}
|
|
||||||
to={`/server/${id}/list-short-urls/1`}
|
|
||||||
active={selectedServer && selectedServer.id === id}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</DropdownItem>
|
|
||||||
))}
|
|
||||||
<DropdownItem divider />
|
|
||||||
<DropdownItem className="servers-dropdown__export-item" onClick={() => serversExporter.exportServers()}>
|
|
||||||
Export servers
|
|
||||||
</DropdownItem>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<UncontrolledDropdown nav inNavbar>
|
|
||||||
<DropdownToggle nav caret>Servers</DropdownToggle>
|
|
||||||
<DropdownMenu right>{renderServers()}</DropdownMenu>
|
|
||||||
</UncontrolledDropdown>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ServersDropdownComp.propTypes = propTypes;
|
|
||||||
|
|
||||||
return ServersDropdownComp;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ServersDropdown;
|
|
49
src/servers/ServersDropdown.tsx
Normal file
49
src/servers/ServersDropdown.tsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { isEmpty, values } from 'ramda';
|
||||||
|
import React from 'react';
|
||||||
|
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import ServersExporter from './services/ServersExporter';
|
||||||
|
import { isServerWithId, SelectedServer, ServersMap } from './data';
|
||||||
|
|
||||||
|
export interface ServersDropdownProps {
|
||||||
|
servers: ServersMap;
|
||||||
|
selectedServer: SelectedServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServersDropdown = (serversExporter: ServersExporter) => ({ servers, selectedServer }: ServersDropdownProps) => {
|
||||||
|
const serversList = values(servers);
|
||||||
|
|
||||||
|
const renderServers = () => {
|
||||||
|
if (isEmpty(serversList)) {
|
||||||
|
return <DropdownItem disabled><i>Add a server first...</i></DropdownItem>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{serversList.map(({ name, id }) => (
|
||||||
|
<DropdownItem
|
||||||
|
key={id}
|
||||||
|
tag={Link}
|
||||||
|
to={`/server/${id}/list-short-urls/1`}
|
||||||
|
active={isServerWithId(selectedServer) && selectedServer.id === id}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</DropdownItem>
|
||||||
|
))}
|
||||||
|
<DropdownItem divider />
|
||||||
|
<DropdownItem className="servers-dropdown__export-item" onClick={async () => serversExporter.exportServers()}>
|
||||||
|
Export servers
|
||||||
|
</DropdownItem>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UncontrolledDropdown nav inNavbar>
|
||||||
|
<DropdownToggle nav caret>Servers</DropdownToggle>
|
||||||
|
<DropdownMenu right>{renderServers()}</DropdownMenu>
|
||||||
|
</UncontrolledDropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServersDropdown;
|
|
@ -8,9 +8,8 @@ interface WithSelectedServerProps extends RouteChildrenProps<{ serverId: string
|
||||||
selectedServer: SelectedServer;
|
selectedServer: SelectedServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const withSelectedServer = (WrappedComponent: FC<WithSelectedServerProps>, ServerError: FC) => (
|
export function withSelectedServer<T = {}>(WrappedComponent: FC<WithSelectedServerProps & T>, ServerError: FC) {
|
||||||
props: WithSelectedServerProps,
|
return (props: WithSelectedServerProps & T) => {
|
||||||
) => {
|
|
||||||
const { selectServer, selectedServer, match } = props;
|
const { selectServer, selectedServer, match } = props;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -27,3 +26,4 @@ export const withSelectedServer = (WrappedComponent: FC<WithSelectedServerProps>
|
||||||
|
|
||||||
return <WrappedComponent {...props} />;
|
return <WrappedComponent {...props} />;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount, ReactWrapper } from 'enzyme';
|
||||||
|
import { Mock } from 'ts-mockery';
|
||||||
|
import { History, Location } from 'history';
|
||||||
|
import { match } from 'react-router'; // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
import { EditServer as editServerConstruct } from '../../src/servers/EditServer';
|
import { EditServer as editServerConstruct } from '../../src/servers/EditServer';
|
||||||
import { ServerForm } from '../../src/servers/helpers/ServerForm';
|
import { ServerForm } from '../../src/servers/helpers/ServerForm';
|
||||||
|
import { ReachableServer } from '../../src/servers/data';
|
||||||
|
|
||||||
describe('<EditServer />', () => {
|
describe('<EditServer />', () => {
|
||||||
let wrapper;
|
let wrapper: ReactWrapper;
|
||||||
const ServerError = jest.fn();
|
const ServerError = jest.fn();
|
||||||
const editServerMock = jest.fn();
|
const editServerMock = jest.fn();
|
||||||
const historyMock = { push: jest.fn() };
|
const push = jest.fn();
|
||||||
const match = {
|
const historyMock = Mock.of<History>({ push });
|
||||||
|
const match = Mock.of<match<{ serverId: string }>>({
|
||||||
params: { serverId: 'abc123' },
|
params: { serverId: 'abc123' },
|
||||||
};
|
});
|
||||||
const selectedServer = {
|
const selectedServer = Mock.of<ReachableServer>({
|
||||||
id: 'abc123',
|
id: 'abc123',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
url: 'url',
|
url: 'url',
|
||||||
apiKey: 'apiKey',
|
apiKey: 'apiKey',
|
||||||
};
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const EditServer = editServerConstruct(ServerError);
|
const EditServer = editServerConstruct(ServerError);
|
||||||
|
@ -26,16 +31,15 @@ describe('<EditServer />', () => {
|
||||||
editServer={editServerMock}
|
editServer={editServerMock}
|
||||||
history={historyMock}
|
history={historyMock}
|
||||||
match={match}
|
match={match}
|
||||||
|
location={Mock.all<Location>()}
|
||||||
selectedServer={selectedServer}
|
selectedServer={selectedServer}
|
||||||
selectServer={jest.fn()}
|
selectServer={jest.fn()}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(jest.resetAllMocks);
|
||||||
jest.resetAllMocks();
|
afterEach(() => wrapper?.unmount());
|
||||||
wrapper && wrapper.unmount();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders components', () => {
|
it('renders components', () => {
|
||||||
expect(wrapper.find(ServerForm)).toHaveLength(1);
|
expect(wrapper.find(ServerForm)).toHaveLength(1);
|
||||||
|
@ -47,6 +51,6 @@ describe('<EditServer />', () => {
|
||||||
form.simulate('submit', {});
|
form.simulate('submit', {});
|
||||||
|
|
||||||
expect(editServerMock).toHaveBeenCalledTimes(1);
|
expect(editServerMock).toHaveBeenCalledTimes(1);
|
||||||
expect(historyMock.push).toHaveBeenCalledTimes(1);
|
expect(push).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -1,24 +1,24 @@
|
||||||
import { identity, values } from 'ramda';
|
import { values } from 'ramda';
|
||||||
import React from 'react';
|
import { Mock } from 'ts-mockery';
|
||||||
import { shallow } from 'enzyme';
|
import React, { FC } from 'react';
|
||||||
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { DropdownItem, DropdownToggle } from 'reactstrap';
|
import { DropdownItem, DropdownToggle } from 'reactstrap';
|
||||||
import serversDropdownCreator from '../../src/servers/ServersDropdown';
|
import serversDropdownCreator, { ServersDropdownProps } from '../../src/servers/ServersDropdown';
|
||||||
|
import { ServerWithId } from '../../src/servers/data';
|
||||||
|
import ServersExporter from '../../src/servers/services/ServersExporter';
|
||||||
|
|
||||||
describe('<ServersDropdown />', () => {
|
describe('<ServersDropdown />', () => {
|
||||||
let wrapped;
|
let wrapped: ShallowWrapper;
|
||||||
let ServersDropdown;
|
let ServersDropdown: FC<ServersDropdownProps>;
|
||||||
const servers = {
|
const servers = {
|
||||||
'1a': { name: 'foo', id: 1 },
|
'1a': Mock.of<ServerWithId>({ name: 'foo', id: '1a' }),
|
||||||
'2b': { name: 'bar', id: 2 },
|
'2b': Mock.of<ServerWithId>({ name: 'bar', id: '2b' }),
|
||||||
'3c': { name: 'baz', id: 3 },
|
'3c': Mock.of<ServerWithId>({ name: 'baz', id: '3c' }),
|
||||||
};
|
|
||||||
const history = {
|
|
||||||
push: jest.fn(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ServersDropdown = serversDropdownCreator({});
|
ServersDropdown = serversDropdownCreator(Mock.of<ServersExporter>());
|
||||||
wrapped = shallow(<ServersDropdown servers={servers} listServers={identity} history={history} />);
|
wrapped = shallow(<ServersDropdown servers={servers} selectedServer={null} />);
|
||||||
});
|
});
|
||||||
afterEach(() => wrapped.unmount());
|
afterEach(() => wrapped.unmount());
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ describe('<ServersDropdown />', () => {
|
||||||
|
|
||||||
it('shows a message when no servers exist yet', () => {
|
it('shows a message when no servers exist yet', () => {
|
||||||
wrapped = shallow(
|
wrapped = shallow(
|
||||||
<ServersDropdown servers={{}} listServers={identity} history={history} />,
|
<ServersDropdown servers={{}} selectedServer={null} />,
|
||||||
);
|
);
|
||||||
const item = wrapped.find(DropdownItem);
|
const item = wrapped.find(DropdownItem);
|
||||||
|
|
Loading…
Reference in a new issue