Skip to content

Commit

Permalink
Merge pull request #769 from open-formulieren/issue/4929-react-router…
Browse files Browse the repository at this point in the history
…-upgrade

Convert cosign v5 react-router routes to data router format
  • Loading branch information
sergei-maertens authored Jan 13, 2025
2 parents c298814 + 69651d2 commit d4bceb8
Show file tree
Hide file tree
Showing 12 changed files with 386 additions and 224 deletions.
20 changes: 20 additions & 0 deletions src/api-mocks/submissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,26 @@ export const mockSubmissionCheckLogicPost = () =>
return HttpResponse.json(responseData, {status: 200});
});

export const mockSubmissionSummaryGet = () =>
http.get(`${BASE_URL}submissions/:uuid/summary`, () =>
HttpResponse.json(
[
{
slug: SUBMISSION_STEP_DETAILS.slug,
name: SUBMISSION_DETAILS.steps[0].name,
data: [
{
name: SUBMISSION_STEP_DETAILS.formStep.configuration.components[0].label,
value: 'Compnent 1 value',
component: SUBMISSION_STEP_DETAILS.formStep.configuration.components[0],
},
],
},
],
{status: 200}
)
);

/**
* Simulate a successful backend processing status without payment.
*/
Expand Down
3 changes: 2 additions & 1 deletion src/components/App.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Navigate, Outlet, useMatch} from 'react-router-dom';

import {Cosign} from 'components/CoSign';
import {Cosign, cosignRoutes} from 'components/CoSign';
import ErrorBoundary from 'components/Errors/ErrorBoundary';
import Form from 'components/Form';
import SessionExpired from 'components/Sessions/SessionExpired';
Expand All @@ -26,6 +26,7 @@ export const routes = [
{
path: 'cosign/*',
element: <Cosign />,
children: cosignRoutes,
},
{
path: 'sessie-verlopen',
Expand Down
12 changes: 11 additions & 1 deletion src/components/CoSign/CoSignDone.stories.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import {fn} from '@storybook/test';
import {withRouter} from 'storybook-addon-remix-react-router';

import {buildForm} from 'api-mocks';
import {withForm} from 'story-utils/decorators';

import {CosignProvider} from './Context';
import CosignDone from './CosignDone';

export default {
title: 'Views / Cosign / Done',
component: CosignDone,
decorators: [withForm, withRouter],
decorators: [
(Story, {args}) => (
<CosignProvider reportDownloadUrl={args.reportDownloadUrl} onCosignComplete={fn()}>
<Story />
</CosignProvider>
),
withForm,
withRouter,
],
args: {
reportDownloadUrl: '#',
},
Expand Down
141 changes: 141 additions & 0 deletions src/components/CoSign/CoSignOld.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import PropTypes from 'prop-types';
import {useContext} from 'react';
import {FormattedMessage} from 'react-intl';
import {useAsync} from 'react-use';

import {ConfigContext, SubmissionContext} from 'Context';
import {get} from 'api';
import Body from 'components/Body';
import ErrorMessage from 'components/Errors/ErrorMessage';
import Loader from 'components/Loader';
import LoginOptionsDisplay from 'components/LoginOptions/LoginOptionsDisplay';
import {getLoginUrl} from 'components/utils';
import Types from 'types';
import {getBEMClassName} from 'utils';

const getCosignStatus = async (baseUrl, submissionUuid) => {
const endpoint = `${baseUrl}submissions/${submissionUuid}/co-sign`;
return await get(endpoint);
};

const CoSignAuthentication = ({form, submissionUuid, authPlugin}) => {
const loginOption = form.loginOptions.find(opt => opt.identifier === authPlugin);
if (!loginOption) {
return (
<ErrorMessage>
<FormattedMessage
description="Co-sign auth option not available on form"
defaultMessage="Something went wrong while presenting the login option. Please contact the municipality."
/>
</ErrorMessage>
);
}

// add the co-sign submission parameter to the login URL
const loginUrl = getLoginUrl(loginOption, {coSignSubmission: submissionUuid});
const modifiedLoginOption = {
...loginOption,
url: loginUrl,
label: (
<FormattedMessage
description="Login button label"
defaultMessage="Login with {provider}"
values={{provider: loginOption.label}}
/>
),
};

return (
<LoginOptionsDisplay
loginAsYourselfOptions={[modifiedLoginOption]}
loginAsGemachtigdeOptions={[]}
/>
);
};

CoSignAuthentication.propTypes = {
form: Types.Form.isRequired,
submissionUuid: PropTypes.string.isRequired,
authPlugin: PropTypes.string.isRequired,
};

const CoSignOld = ({
submissionUuid,
interactive = true,
form = null,
saveStepData,
authPlugin = 'digid-mock',
}) => {
const {baseUrl} = useContext(ConfigContext);
const {submission} = useContext(SubmissionContext);

if (!submissionUuid) {
submissionUuid = submission.id;
}

const {
loading,
value: coSignState,
error,
} = useAsync(
async () => await getCosignStatus(baseUrl, submissionUuid),
[baseUrl, submissionUuid]
);

// log errors to the console if any
if (error) console.error(error);

// while loading, display spinner
if (loading) {
return <Loader modifiers={['small']} />;
}
const {coSigned, representation} = coSignState;

if (!coSigned) {
if (!interactive) {
return (
<FormattedMessage
description="Not co-signed (summary) message"
defaultMessage="Not co-signed"
/>
);
}

if (!form || !saveStepData) {
throw new Error('Interactive co-sign components require the "form" and "saveStepData" props');
}

return (
<CoSignAuthentication
form={form}
submissionUuid={submissionUuid}
saveStepData={saveStepData}
authPlugin={authPlugin}
/>
);
}

return (
<Body component="div">
<div className={getBEMClassName('co-sign__representation')}>
{representation ?? (
<FormattedMessage
description="Co-signed without representation fallback message"
defaultMessage="Something went wrong while processing the co-sign authentication. Please contact the municipality."
/>
)}
</div>
</Body>
);
};

CoSignOld.propTypes = {
interactive: PropTypes.bool,
form: Types.Form,
submissionUuid: PropTypes.string, // fall back to context if not provided
saveStepData: PropTypes.func,
authPlugin: PropTypes.string,
};

export default CoSignOld;
export {CoSignAuthentication};
29 changes: 29 additions & 0 deletions src/components/CoSign/Context.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import PropTypes from 'prop-types';
import React, {useContext} from 'react';

const CosignContext = React.createContext({
reportDownloadUrl: '',
onCosignComplete: () => {},
});

CosignContext.displayName = 'CosignContext';

const CosignProvider = ({reportDownloadUrl, onCosignComplete, children}) => (
<CosignContext.Provider
value={{
reportDownloadUrl,
onCosignComplete,
}}
>
{children}
</CosignContext.Provider>
);

CosignProvider.propTypes = {
reportDownloadUrl: PropTypes.string.isRequired,
onCosignComplete: PropTypes.func.isRequired,
};

const useCosignContext = () => useContext(CosignContext);

export {CosignProvider, useCosignContext};
82 changes: 8 additions & 74 deletions src/components/CoSign/Cosign.jsx
Original file line number Diff line number Diff line change
@@ -1,90 +1,24 @@
import {useContext} from 'react';
import {Route, Routes, useNavigate} from 'react-router-dom';
import {useImmerReducer} from 'use-immer';
import {useState} from 'react';
import {Outlet, useNavigate} from 'react-router-dom';

import {ConfigContext} from 'Context';
import {destroy} from 'api';
import ErrorBoundary from 'components/Errors/ErrorBoundary';
import {CosignSummary} from 'components/Summary';
import useFormContext from 'hooks/useFormContext';

import CosignDone from './CosignDone';
import CosignStart from './CosignStart';

const initialState = {
submission: null,
summaryData: [],
reportUrl: '',
cosignedSubmission: null,
};

const reducer = (draft, action) => {
switch (action.type) {
case 'SUBMISSION_LOADED': {
draft.submission = action.payload;
break;
}
case 'LOADED_SUMMARY_DATA': {
draft.summaryData = action.payload;
break;
}
case 'COSIGN_COMPLETE': {
return {...initialState, reportUrl: action.payload};
}
case 'RESET': {
return initialState;
}
default: {
throw new Error(`Unknown action ${action.type}`);
}
}
};
import {CosignProvider} from './Context';

const Cosign = () => {
const form = useFormContext();
const [state, dispatch] = useImmerReducer(reducer, initialState);
const navigate = useNavigate();
const config = useContext(ConfigContext);
const [reportDownloadUrl, setReportDownloadUrl] = useState('');

const onCosignComplete = reportUrl => {
dispatch({type: 'COSIGN_COMPLETE', payload: reportUrl});
setReportDownloadUrl(reportUrl);
navigate('/cosign/done');
};

const onDestroySession = async () => {
await destroy(`${config.baseUrl}authentication/${state.submission.id}/session`);

dispatch({type: 'RESET'});
navigate('/');
};

return (
<ErrorBoundary useCard>
<Routes>
<Route path="start" element={<CosignStart />} />
<Route
path="check"
element={
<CosignSummary
form={form}
submission={state.submission}
summaryData={state.summaryData}
onSubmissionLoaded={submission =>
dispatch({
type: 'SUBMISSION_LOADED',
payload: submission,
})
}
onDataLoaded={({summaryData}) =>
dispatch({type: 'LOADED_SUMMARY_DATA', payload: summaryData})
}
onCosignComplete={onCosignComplete}
onDestroySession={onDestroySession}
/>
}
/>
<Route path="done" element={<CosignDone reportDownloadUrl={state.reportUrl} />} />
</Routes>
<CosignProvider reportDownloadUrl={reportDownloadUrl} onCosignComplete={onCosignComplete}>
<Outlet />
</CosignProvider>
</ErrorBoundary>
);
};
Expand Down
Loading

0 comments on commit d4bceb8

Please sign in to comment.