Skip to content

Commit

Permalink
feat: add PageRedirectHandler HOC
Browse files Browse the repository at this point in the history
Page redirection must be handled above the QueryRenderer
  • Loading branch information
matthieu-foucault committed Jul 15, 2020
1 parent 555957e commit 9154469
Show file tree
Hide file tree
Showing 52 changed files with 210 additions and 297 deletions.
102 changes: 102 additions & 0 deletions app/components/PageRedirectHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import React, {useState, useEffect} from 'react';
import {useRouter} from 'next/router';
import {fetchQuery, graphql} from 'relay-runtime';
import LoadingSpinner from 'components/LoadingSpinner';
import {RelayModernEnvironment} from 'relay-runtime/lib/store/RelayModernEnvironment';
import {CiipPageComponent} from 'next-env';
import {getUserGroupLandingRoute} from 'lib/user-groups';
import {PageRedirectHandlerQuery} from '__generated__/PageRedirectHandlerQuery.graphql';

interface Props {
environment: RelayModernEnvironment;
pageComponent: CiipPageComponent;
}

const PageRedirectHandler: React.FunctionComponent<Props> = ({
children,
environment,
pageComponent
}) => {
const sessionQuery = graphql`
query PageRedirectHandlerQuery {
session {
userGroups
ciipUserBySub {
__typename
}
}
}
`;

const [shouldRender, setShouldRender] = useState(false);

const router = useRouter();

useEffect(() => {
if (!shouldRender) {
checkSessionAndGroups();
}
});

const checkSessionAndGroups = async () => {
const {isAccessProtected, allowedGroups = []} = pageComponent;
const response = await fetchQuery<PageRedirectHandlerQuery>(
environment,
sessionQuery,
{}
);
if (isAccessProtected && !response?.session) {
router.push({
pathname: '/login-redirect',
query: {
redirectTo: router.asPath
}
});
return null;
}

const userGroups = response?.session?.userGroups || ['Guest'];

const canAccess =
allowedGroups.length === 0 ||
userGroups.some((g) => allowedGroups.includes(g));

// Redirect users attempting to access a page that their group doesn't allow
// to their landing route. This needs to happen before redirecting to the registration
// to ensure that a pending analyst doesn't get redirect to the registration page
if (!canAccess) {
router.push({
pathname: getUserGroupLandingRoute(userGroups)
});
return null;
}

if (isAccessProtected && !response?.session?.ciipUserBySub) {
router.push({
pathname: '/registration',
query: {
redirectTo: router.asPath
}
});
return null;
}

setShouldRender(true);
};

// eslint-disable-next-line react/jsx-no-useless-fragment
if (shouldRender) return <>{children}</>;

return (
<div>
<LoadingSpinner />
<style jsx>{`
div {
height: 100vh;
}
`}</style>
</div>
);
};

export default PageRedirectHandler;
48 changes: 0 additions & 48 deletions app/layouts/default-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import React from 'react';
import {Container} from 'react-bootstrap';
import {graphql, createFragmentContainer} from 'react-relay';
import {defaultLayout_session} from '__generated__/defaultLayout_session.graphql';
import {useRouter} from 'next/router';
import getConfig from 'next/config';
import Header from 'components/Layout/Header';
import Footer from 'components/Layout/Footer';
import Subheader from 'components/Layout/Subheader';
import {getUserGroupLandingRoute} from 'lib/user-groups';
import Help from 'components/helpers/Help';
import SiteNoticeBanner from 'components/Layout/SiteNoticeBanner';

Expand All @@ -17,10 +15,7 @@ interface Props {
title?: string | JSX.Element;
showSubheader?: boolean;
session: defaultLayout_session;
needsUser?: boolean;
needsSession?: boolean;
width?: 'narrow' | 'wide';
allowedGroups?: string[];
help?: {
title: string;
helpMessage: string;
Expand All @@ -33,49 +28,8 @@ const DefaultLayout: React.FunctionComponent<Props> = ({
showSubheader,
session,
width = 'narrow',
needsUser = true,
needsSession = true,
allowedGroups = [],
help
}) => {
const router = useRouter();

const userGroups = session?.userGroups || ['Guest'];

const canAccess =
allowedGroups.length === 0 ||
userGroups.some((g) => allowedGroups.includes(g));

if ((needsSession || needsUser) && !session) {
router.push({
pathname: '/login-redirect',
query: {
redirectTo: router.asPath
}
});
return null;
}

// Redirect users attempting to access a page that their group doesn't allow
// to their landing route. This needs to happen before redirecting to the registration
// to ensure that a pending analyst doesn't get redirect to the registration page
if (!canAccess) {
router.push({
pathname: getUserGroupLandingRoute(userGroups)
});
return null;
}

if (needsUser && !session.ciipUserBySub) {
router.push({
pathname: '/registration',
query: {
redirectTo: router.asPath
}
});
return null;
}

return (
<div className="page-wrap">
<Header
Expand Down Expand Up @@ -181,8 +135,6 @@ export {DefaultLayout as DefaultLayoutComponent};
export default createFragmentContainer(DefaultLayout, {
session: graphql`
fragment defaultLayout_session on JwtToken {
userGroups
priorityGroup
ciipUserBySub {
__typename
}
Expand Down
6 changes: 1 addition & 5 deletions app/lib/error-boundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@ class ErrorBoundary extends Component {
const feedbackUrl = getConfig()?.publicRuntimeConfig.FEEDBACK_SITE_URL;
// You can render any custom fallback UI
return (
<DefaultLayoutComponent
needsSession={false}
needsUser={false}
session={null}
>
<DefaultLayoutComponent session={null}>
<Alert variant="danger">
<Alert.Heading>An unexpected error has occured</Alert.Heading>
<p>
Expand Down
2 changes: 2 additions & 0 deletions app/next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export type CiipPageComponent = NextComponentType<
> &
ComponentClass<CiipPageComponentProps> & {
static query: GraphQLTaggedNode;
static isAccessProtected: boolean;
static allowedGroups: string[];
};

// This is overriding the form props defined in @types/react-jsonschema-form as they
Expand Down
44 changes: 25 additions & 19 deletions app/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ErrorBoundary from 'lib/error-boundary';
import LoadingSpinner from 'components/LoadingSpinner';
import ToasterHelper from 'components/helpers/Toaster';
import 'react-toastify/dist/ReactToastify.min.css';
import PageRedirectHandler from 'components/PageRedirectHandler';

interface AppProps {
pageProps: {
Expand Down Expand Up @@ -49,26 +50,31 @@ export default class App extends NextApp<AppProps> {

return (
<ErrorBoundary>
<QueryRenderer
<PageRedirectHandler
environment={environment}
query={Component.query}
variables={{...variables, ...router.query}}
render={({error, props}: {error: any; props: any}) => {
if (error !== null) throw error; // Let the ErrorBoundary above render the error nicely
if (props)
return <Component {...props} router={this.props.router} />;
return (
<div>
<LoadingSpinner />
<style jsx>{`
div {
height: 100vh;
}
`}</style>
</div>
);
}}
/>
pageComponent={Component}
>
<QueryRenderer
environment={environment}
query={Component.query}
variables={{...variables, ...router.query}}
render={({error, props}: {error: any; props: any}) => {
if (error !== null) throw error; // Let the ErrorBoundary above render the error nicely
if (props)
return <Component {...props} router={this.props.router} />;
return (
<div>
<LoadingSpinner />
<style jsx>{`
div {
height: 100vh;
}
`}</style>
</div>
);
}}
/>
</PageRedirectHandler>
<ToasterHelper />
</ErrorBoundary>
);
Expand Down
8 changes: 3 additions & 5 deletions app/pages/admin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ interface Props extends CiipPageComponentProps {
query: adminQueryResponse['query'];
}
class Admin extends Component<Props> {
static allowedGroups = ALLOWED_GROUPS;
static isAccessProtected = true;
static query = graphql`
query adminQuery {
query {
Expand All @@ -31,11 +33,7 @@ class Admin extends Component<Props> {
query: {session}
} = this.props;
return (
<DefaultLayout
session={session}
allowedGroups={ALLOWED_GROUPS}
title="Administrator Dashboard"
>
<DefaultLayout session={session} title="Administrator Dashboard">
<div>
<Row>
<UserManagement />
Expand Down
2 changes: 2 additions & 0 deletions app/pages/admin/products-benchmarks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ interface Props {
}

class ProductsBenchmarks extends Component<Props> {
static allowedGroups = ALLOWED_GROUPS;
static isAccessProtected = true;
static query = graphql`
query productsBenchmarksQuery(
$orderByField: String
Expand Down
8 changes: 3 additions & 5 deletions app/pages/admin/reporting-years.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interface Props {
}

class ReportingYears extends Component<Props> {
static allowedGroups = ALLOWED_GROUPS;
static isAccessProtected = true;
static query = graphql`
query reportingYearsQuery {
query {
Expand All @@ -26,11 +28,7 @@ class ReportingYears extends Component<Props> {
render() {
const {query} = this.props;
return (
<DefaultLayout
session={query.session}
title="Reporting Years"
allowedGroups={ALLOWED_GROUPS}
>
<DefaultLayout session={query.session} title="Reporting Years">
<ReportingYearTable query={query} />
</DefaultLayout>
);
Expand Down
8 changes: 3 additions & 5 deletions app/pages/admin/users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interface Props {
}

class Users extends Component<Props> {
static allowedGroups = ALLOWED_GROUPS;
static isAccessProtected = true;
static query = graphql`
query usersQuery {
query {
Expand All @@ -26,11 +28,7 @@ class Users extends Component<Props> {
render() {
const {query} = this.props;
return (
<DefaultLayout
session={query.session}
title="User List"
allowedGroups={ALLOWED_GROUPS}
>
<DefaultLayout session={query.session} title="User List">
<UserTable query={query} />
</DefaultLayout>
);
Expand Down
8 changes: 3 additions & 5 deletions app/pages/analyst/add-facility.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interface Props {
query: addFacilityQueryResponse['query'];
}
class AddFacilityPage extends Component<Props> {
static allowedGroups = ALLOWED_GROUPS;
static isAccessProtected = true;
static query = graphql`
query addFacilityQuery {
query {
Expand Down Expand Up @@ -39,11 +41,7 @@ class AddFacilityPage extends Component<Props> {
render() {
const {query} = this.props;
return (
<DefaultLayout
session={query.session}
title="Add Facility"
allowedGroups={ALLOWED_GROUPS}
>
<DefaultLayout session={query.session} title="Add Facility">
<Card>
<Card.Header as="h5">Attention</Card.Header>
<Card.Body>
Expand Down
8 changes: 3 additions & 5 deletions app/pages/analyst/add-organisation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ interface Props {
query: addOrganisationQueryResponse['query'];
}
class AddOrganisationPage extends Component<Props> {
static allowedGroups = ALLOWED_GROUPS;
static isAccessProtected = true;
static query = graphql`
query addOrganisationQuery {
query {
Expand Down Expand Up @@ -39,11 +41,7 @@ class AddOrganisationPage extends Component<Props> {
render() {
const {query} = this.props;
return (
<DefaultLayout
session={query.session}
title="Add Organisation"
allowedGroups={ALLOWED_GROUPS}
>
<DefaultLayout session={query.session} title="Add Organisation">
<Card>
<Card.Header as="h5">Attention</Card.Header>
<Card.Body>
Expand Down
Loading

0 comments on commit 9154469

Please sign in to comment.