use children in routes instead of component prop

This commit is contained in:
Jakob Schrettenbrunner 2021-01-01 17:21:13 +00:00
parent 5f284dad1d
commit 0f9b3a5722
6 changed files with 80 additions and 88 deletions

View file

@ -1,10 +1,9 @@
import React, { useRef } from 'react'; import React from 'react';
import { Route } from 'react-router'; import { Route } from 'react-router';
import { SwitchTransition } from 'react-transition-group'; import { SwitchTransition } from 'react-transition-group';
import Fade from '@/components/elements/Fade'; import Fade from '@/components/elements/Fade';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import tw from 'twin.macro'; import tw from 'twin.macro';
import v4 from 'uuid/v4';
const StyledSwitchTransition = styled(SwitchTransition)` const StyledSwitchTransition = styled(SwitchTransition)`
${tw`relative`}; ${tw`relative`};
@ -15,13 +14,11 @@ const StyledSwitchTransition = styled(SwitchTransition)`
`; `;
const TransitionRouter: React.FC = ({ children }) => { const TransitionRouter: React.FC = ({ children }) => {
const uuid = useRef(v4()).current;
return ( return (
<Route <Route
render={({ location }) => ( render={({ location }) => (
<StyledSwitchTransition> <StyledSwitchTransition>
<Fade timeout={150} key={location.key || uuid} in appear unmountOnExit> <Fade timeout={150} key={location.pathname + location.search} in appear unmountOnExit>
<section> <section>
{children} {children}
</section> </section>

View file

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import getServerSchedules from '@/api/server/schedules/getServerSchedules'; import getServerSchedules from '@/api/server/schedules/getServerSchedules';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import Spinner from '@/components/elements/Spinner'; import Spinner from '@/components/elements/Spinner';
import { RouteComponentProps } from 'react-router-dom'; import { useHistory, useRouteMatch } from 'react-router-dom';
import FlashMessageRender from '@/components/FlashMessageRender'; import FlashMessageRender from '@/components/FlashMessageRender';
import ScheduleRow from '@/components/server/schedules/ScheduleRow'; import ScheduleRow from '@/components/server/schedules/ScheduleRow';
import { httpErrorToHuman } from '@/api/http'; import { httpErrorToHuman } from '@/api/http';
@ -14,7 +14,10 @@ import GreyRowBox from '@/components/elements/GreyRowBox';
import Button from '@/components/elements/Button'; import Button from '@/components/elements/Button';
import ServerContentBlock from '@/components/elements/ServerContentBlock'; import ServerContentBlock from '@/components/elements/ServerContentBlock';
export default ({ match, history }: RouteComponentProps) => { export default () => {
const match = useRouteMatch();
const history = useHistory();
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
const { clearFlashes, addError } = useFlash(); const { clearFlashes, addError } = useFlash();
const [ loading, setLoading ] = useState(true); const [ loading, setLoading ] = useState(true);

View file

@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { RouteComponentProps } from 'react-router-dom'; import { useHistory, useLocation, useParams } from 'react-router-dom';
import { Schedule } from '@/api/server/schedules/getServerSchedules'; import { Schedule } from '@/api/server/schedules/getServerSchedules';
import getServerSchedule from '@/api/server/schedules/getServerSchedule'; import getServerSchedule from '@/api/server/schedules/getServerSchedule';
import Spinner from '@/components/elements/Spinner'; import Spinner from '@/components/elements/Spinner';
@ -45,7 +45,11 @@ const ActivePill = ({ active }: { active: boolean }) => (
</span> </span>
); );
export default ({ match, history, location: { state } }: RouteComponentProps<Params, Record<string, unknown>, State>) => { export default () => {
const params = useParams() as Params;
const history = useHistory();
const state: State = useLocation().state;
const id = ServerContext.useStoreState(state => state.server.data!.id); const id = ServerContext.useStoreState(state => state.server.data!.id);
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
@ -57,20 +61,20 @@ export default ({ match, history, location: { state } }: RouteComponentProps<Par
const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule); const appendSchedule = ServerContext.useStoreActions(actions => actions.schedules.appendSchedule);
useEffect(() => { useEffect(() => {
if (schedule?.id === Number(match.params.id)) { if (schedule?.id === Number(params.id)) {
setIsLoading(false); setIsLoading(false);
return; return;
} }
clearFlashes('schedules'); clearFlashes('schedules');
getServerSchedule(uuid, Number(match.params.id)) getServerSchedule(uuid, Number(params.id))
.then(schedule => appendSchedule(schedule)) .then(schedule => appendSchedule(schedule))
.catch(error => { .catch(error => {
console.error(error); console.error(error);
clearAndAddHttpError({ error, key: 'schedules' }); clearAndAddHttpError({ error, key: 'schedules' });
}) })
.then(() => setIsLoading(false)); .then(() => setIsLoading(false));
}, [ match ]); }, [ params ]);
const toggleEditModal = useCallback(() => { const toggleEditModal = useCallback(() => {
setShowEditModal(s => !s); setShowEditModal(s => !s);

View file

@ -0,0 +1,25 @@
import React from 'react';
import Can from '@/components/elements/Can';
import ScreenBlock from '@/components/screens/ScreenBlock';
export interface RequireServerPermissionProps {
permissions: string | string[]
}
const RequireServerPermission: React.FC<RequireServerPermissionProps> = ({ children, permissions }) => {
return (
<Can
action={permissions}
renderOnError={
<ScreenBlock
image={'/assets/svgs/server_error.svg'}
title={'Access Denied'}
message={'You do not have permission to access this page.'}
/>
}
>
{children}
</Can>
);
};
export default RequireServerPermission;

View file

@ -1,31 +0,0 @@
import React from 'react';
import Can from '@/components/elements/Can';
import ScreenBlock from '@/components/screens/ScreenBlock';
import isEqual from 'react-fast-compare';
const requireServerPermission = (Component: React.ComponentType<any>, permissions: string | string[]) => {
return class extends React.Component<any, any> {
shouldComponentUpdate (nextProps: Readonly<any>) {
return !isEqual(nextProps, this.props);
}
render () {
return (
<Can
action={permissions}
renderOnError={
<ScreenBlock
image={'/assets/svgs/server_error.svg'}
title={'Access Denied'}
message={'You do not have permission to access this page.'}
/>
}
>
<Component {...this.props}/>
</Can>
);
}
};
};
export default requireServerPermission;

View file

@ -27,10 +27,10 @@ import SubNavigation from '@/components/elements/SubNavigation';
import NetworkContainer from '@/components/server/network/NetworkContainer'; import NetworkContainer from '@/components/server/network/NetworkContainer';
import InstallListener from '@/components/server/InstallListener'; import InstallListener from '@/components/server/InstallListener';
import StartupContainer from '@/components/server/startup/StartupContainer'; import StartupContainer from '@/components/server/startup/StartupContainer';
import requireServerPermission from '@/hoc/requireServerPermission';
import ErrorBoundary from '@/components/elements/ErrorBoundary'; import ErrorBoundary from '@/components/elements/ErrorBoundary';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'; import { faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
import RequireServerPermission from '@/hoc/RequireServerPermission';
const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => { const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => {
const rootAdmin = useStoreState(state => state.user.data!.rootAdmin); const rootAdmin = useStoreState(state => state.user.data!.rootAdmin);
@ -142,50 +142,44 @@ const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>)
<TransitionRouter> <TransitionRouter>
<Switch location={location}> <Switch location={location}>
<Route path={`${match.path}`} component={ServerConsole} exact/> <Route path={`${match.path}`} component={ServerConsole} exact/>
<Route <Route path={`${match.path}/files`} exact>
path={`${match.path}/files`} <RequireServerPermission permissions={'file.*'}>
component={requireServerPermission(FileManagerContainer, 'file.*')} <FileManagerContainer />
exact </RequireServerPermission>
/> </Route>
<Route <Route path={`${match.path}/files/:action(edit|new)`} exact>
path={`${match.path}/files/:action(edit|new)`} <SuspenseSpinner>
render={props => ( <FileEditContainer />
<SuspenseSpinner> </SuspenseSpinner>
<FileEditContainer {...props as any}/> </Route>
</SuspenseSpinner> <Route path={`${match.path}/databases`} exact>
)} <RequireServerPermission permissions={'database.*'}>
exact <DatabasesContainer />
/> </RequireServerPermission>
<Route </Route>
path={`${match.path}/databases`} <Route path={`${match.path}/schedules`} exact>
component={requireServerPermission(DatabasesContainer, 'database.*')} <RequireServerPermission permissions={'schedule.*'}>
exact <ScheduleContainer />
/> </RequireServerPermission>
<Route </Route>
path={`${match.path}/schedules`} <Route path={`${match.path}/schedules/:id`} exact>
component={requireServerPermission(ScheduleContainer, 'schedule.*')} <ScheduleEditContainer/>
exact </Route>
/> <Route path={`${match.path}/users`} exact>
<Route <RequireServerPermission permissions={'user.*'}>
path={`${match.path}/schedules/:id`} <UsersContainer />
component={ScheduleEditContainer} </RequireServerPermission>
exact </Route>
/> <Route path={`${match.path}/backups`} exact>
<Route <RequireServerPermission permissions={'backup.*'}>
path={`${match.path}/users`} <BackupContainer />
component={requireServerPermission(UsersContainer, 'user.*')} </RequireServerPermission>
exact </Route>
/> <Route path={`${match.path}/network`} exact>
<Route <RequireServerPermission permissions={'allocation.*'}>
path={`${match.path}/backups`} <NetworkContainer />
component={requireServerPermission(BackupContainer, 'backup.*')} </RequireServerPermission>
exact </Route>
/>
<Route
path={`${match.path}/network`}
component={requireServerPermission(NetworkContainer, 'allocation.*')}
exact
/>
<Route path={`${match.path}/startup`} component={StartupContainer} exact/> <Route path={`${match.path}/startup`} component={StartupContainer} exact/>
<Route path={`${match.path}/settings`} component={SettingsContainer} exact/> <Route path={`${match.path}/settings`} component={SettingsContainer} exact/>
<Route path={'*'} component={NotFound}/> <Route path={'*'} component={NotFound}/>