Refactored ServerError to infer error message based on provided server type guards

This commit is contained in:
Alejandro Celaya
2020-08-29 10:53:02 +02:00
parent f40ad91ea9
commit 8cc0695ee9
14 changed files with 177 additions and 186 deletions

View File

@@ -13,18 +13,18 @@ import { DeleteServerButtonProps } from '../servers/DeleteServerButton';
import { ServerWithId } from '../servers/data';
import './AsideMenu.scss';
interface AsideMenuProps {
export interface AsideMenuProps {
selectedServer: ServerWithId;
className?: string;
showOnMobile?: boolean;
}
interface AsideMenuItemItemProps extends NavLinkProps {
interface AsideMenuItemProps extends NavLinkProps {
to: string;
className?: string;
}
const AsideMenuItem: FC<AsideMenuItemItemProps> = ({ children, to, className, ...rest }) => (
const AsideMenuItem: FC<AsideMenuItemProps> = ({ children, to, className, ...rest }) => (
<NavLink
className={classNames('aside-menu__item', className)}
activeClassName="aside-menu__item--selected"

View File

@@ -1,30 +1,31 @@
import React from 'react';
import * as PropTypes from 'prop-types';
import './ErrorHandler.scss';
import React, { ReactNode } from 'react';
import { Button } from 'reactstrap';
import './ErrorHandler.scss';
// FIXME Replace with typescript: (window, console)
const ErrorHandler = ({ location }, { error }) => class ErrorHandler extends React.Component {
static propTypes = {
children: PropTypes.node.isRequired,
};
interface ErrorHandlerState {
hasError: boolean;
}
constructor(props) {
const ErrorHandler = (
{ location }: Window,
{ error }: Console,
) => class ErrorHandler extends React.Component<any, ErrorHandlerState> {
public constructor(props: object) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
public static getDerivedStateFromError(): ErrorHandlerState {
return { hasError: true };
}
componentDidCatch(e) {
public componentDidCatch(e: Error): void {
if (process.env.NODE_ENV !== 'development') {
error(e);
}
}
render() {
public render(): ReactNode | undefined {
if (this.state.hasError) {
return (
<div className="error-handler">

View File

@@ -2,8 +2,8 @@ import React, { useEffect } from 'react';
import { isEmpty, values } from 'ramda';
import { Link } from 'react-router-dom';
import ServersListGroup from '../servers/ServersListGroup';
import { ServersMap } from '../servers/reducers/servers';
import './Home.scss';
import { ServersMap } from '../servers/data';
export interface HomeProps {
resetSelectedServer: Function;

View File

@@ -1,98 +0,0 @@
import React, { useEffect } from 'react';
import { Route, Switch } from 'react-router-dom';
import { Swipeable } from 'react-swipeable';
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames';
import * as PropTypes from 'prop-types';
import { serverType } from '../servers/prop-types';
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
import { useToggle } from '../utils/helpers/hooks';
import { versionMatch } from '../utils/helpers/version';
import NotFound from './NotFound';
import './MenuLayout.scss';
const propTypes = {
match: PropTypes.object,
location: PropTypes.object,
selectedServer: serverType,
};
const MenuLayout = (
TagsList,
ShortUrls,
AsideMenu,
CreateShortUrl,
ShortUrlVisits,
TagVisits,
ShlinkVersions,
ServerError,
) => {
const MenuLayoutComp = ({ match, location, selectedServer }) => {
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
const { params: { serverId } } = match;
useEffect(() => hideSidebar(), [ location ]);
if (selectedServer.serverNotReachable) {
return <ServerError type="not-reachable" />;
}
const addTagsVisitsRoute = versionMatch(selectedServer.version, { minVersion: '2.2.0' });
const burgerClasses = classNames('menu-layout__burger-icon', {
'menu-layout__burger-icon--active': sidebarVisible,
});
const swipeMenuIfNoModalExists = (callback) => (e) => {
const swippedOnVisitsTable = e.event.path.some(
({ classList }) => classList && classList.contains('visits-table'),
);
if (swippedOnVisitsTable || document.querySelector('.modal')) {
return;
}
callback();
};
return (
<React.Fragment>
<FontAwesomeIcon icon={burgerIcon} className={burgerClasses} onClick={toggleSidebar} />
<Swipeable
delta={40}
className="menu-layout__swipeable"
onSwipedLeft={swipeMenuIfNoModalExists(hideSidebar)}
onSwipedRight={swipeMenuIfNoModalExists(showSidebar)}
>
<div className="row menu-layout__swipeable-inner">
<AsideMenu className="col-lg-2 col-md-3" selectedServer={selectedServer} showOnMobile={sidebarVisible} />
<div className="col-lg-10 offset-lg-2 col-md-9 offset-md-3" onClick={() => hideSidebar()}>
<div className="menu-layout__container">
<Switch>
<Route exact path="/server/:serverId/list-short-urls/:page" component={ShortUrls} />
<Route exact path="/server/:serverId/create-short-url" component={CreateShortUrl} />
<Route exact path="/server/:serverId/short-code/:shortCode/visits" component={ShortUrlVisits} />
{addTagsVisitsRoute && <Route exact path="/server/:serverId/tag/:tag/visits" component={TagVisits} />}
<Route exact path="/server/:serverId/manage-tags" component={TagsList} />
<Route
render={() => <NotFound to={`/server/${serverId}/list-short-urls/1`}>List short URLs</NotFound>}
/>
</Switch>
</div>
<div className="menu-layout__footer text-center text-md-right">
<ShlinkVersions />
</div>
</div>
</div>
</Swipeable>
</React.Fragment>
);
};
MenuLayoutComp.propTypes = propTypes;
return withSelectedServer(MenuLayoutComp, ServerError);
};
export default MenuLayout;

89
src/common/MenuLayout.tsx Normal file
View File

@@ -0,0 +1,89 @@
import React, { FC, useEffect } from 'react';
import { Route, RouteChildrenProps, Switch } from 'react-router-dom';
import { EventData, Swipeable } from 'react-swipeable';
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import classNames from 'classnames';
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
import { useToggle } from '../utils/helpers/hooks';
import { versionMatch } from '../utils/helpers/version';
import { isReachableServer, SelectedServer } from '../servers/data';
import NotFound from './NotFound';
import { AsideMenuProps } from './AsideMenu';
import './MenuLayout.scss';
interface MenuLayoutProps extends RouteChildrenProps {
selectedServer: SelectedServer;
}
const MenuLayout = (
TagsList: FC,
ShortUrls: FC,
AsideMenu: FC<AsideMenuProps>,
CreateShortUrl: FC,
ShortUrlVisits: FC,
TagVisits: FC,
ShlinkVersions: FC,
ServerError: FC,
) => withSelectedServer(({ location, selectedServer }: MenuLayoutProps) => {
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
useEffect(() => hideSidebar(), [ location ]);
if (!isReachableServer(selectedServer)) {
return <ServerError />;
}
const addTagsVisitsRoute = versionMatch(selectedServer.version, { minVersion: '2.2.0' });
const burgerClasses = classNames('menu-layout__burger-icon', {
'menu-layout__burger-icon--active': sidebarVisible,
});
const swipeMenuIfNoModalExists = (callback: () => void) => (e: EventData) => {
const swippedOnVisitsTable = (e.event.composedPath() as HTMLElement[]).some(
({ classList }) => classList?.contains('visits-table'),
);
if (swippedOnVisitsTable || document.querySelector('.modal')) {
return;
}
callback();
};
return (
<React.Fragment>
<FontAwesomeIcon icon={burgerIcon} className={burgerClasses} onClick={toggleSidebar} />
<Swipeable
delta={40}
className="menu-layout__swipeable"
onSwipedLeft={swipeMenuIfNoModalExists(hideSidebar)}
onSwipedRight={swipeMenuIfNoModalExists(showSidebar)}
>
<div className="row menu-layout__swipeable-inner">
<AsideMenu className="col-lg-2 col-md-3" selectedServer={selectedServer} showOnMobile={sidebarVisible} />
<div className="col-lg-10 offset-lg-2 col-md-9 offset-md-3" onClick={() => hideSidebar()}>
<div className="menu-layout__container">
<Switch>
<Route exact path="/server/:serverId/list-short-urls/:page" component={ShortUrls} />
<Route exact path="/server/:serverId/create-short-url" component={CreateShortUrl} />
<Route exact path="/server/:serverId/short-code/:shortCode/visits" component={ShortUrlVisits} />
{addTagsVisitsRoute && <Route exact path="/server/:serverId/tag/:tag/visits" component={TagVisits} />}
<Route exact path="/server/:serverId/manage-tags" component={TagsList} />
<Route
render={() => <NotFound to={`/server/${selectedServer.id}/list-short-urls/1`}>List short URLs</NotFound>}
/>
</Switch>
</div>
<div className="menu-layout__footer text-center text-md-right">
<ShlinkVersions />
</div>
</div>
</div>
</Swipeable>
</React.Fragment>
);
}, ServerError);
export default MenuLayout;