Skip to content

Commit

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

React Router upgrade: main app (part 1)
  • Loading branch information
sergei-maertens authored Jan 14, 2025
2 parents d4bceb8 + 5b5ad8b commit 9768bdd
Show file tree
Hide file tree
Showing 20 changed files with 562 additions and 433 deletions.
3 changes: 2 additions & 1 deletion src/api-mocks/forms.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {produce} from 'immer';
import {HttpResponse, http} from 'msw';

import {PRIVACY_POLICY_ACCEPTED} from 'components/SummaryConfirmation/mocks';
import {SUBMISSION_ALLOWED} from 'components/constants';

import {BASE_URL, getDefaultFactory} from './base';

Expand All @@ -18,7 +19,7 @@ export const FORM_DEFAULTS = {
showSummaryProgress: false,
maintenanceMode: false,
active: true,
submissionAllowed: 'yes',
submissionAllowed: SUBMISSION_ALLOWED.yes,
suspensionAllowed: true,
sendConfirmationEmail: true,
submissionStatementsConfiguration: [PRIVACY_POLICY_ACCEPTED],
Expand Down
2 changes: 2 additions & 0 deletions src/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
appointmentRoutes,
manageAppointmentRoutes,
} from 'components/appointments';
import formRoutes from 'components/formRoutes';
import useFormContext from 'hooks/useFormContext';
import useQuery from 'hooks/useQuery';
import useZodErrorMap from 'hooks/useZodErrorMap';
Expand Down Expand Up @@ -44,6 +45,7 @@ export const routes = [
<Form />
</ErrorBoundary>
),
children: formRoutes,
},
];

Expand Down
13 changes: 10 additions & 3 deletions src/components/AppDebug.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,16 @@ const AppDebug = () => {
<div className="debug-info-container" title="Debug information (only available in dev)">
<DebugInfo label="Current locale" value={locale} />
<DebugInfo label="Session expires at">
<FormattedDate value={expiry} hour="numeric" minute="numeric" second="numeric" />
&nbsp;(
<FormattedRelativeTime value={expiryDelta} numeric="auto" updateIntervalInSeconds={1} />)
{expiry ? (
<>
<FormattedDate value={expiry} hour="numeric" minute="numeric" second="numeric" />
&nbsp;(
<FormattedRelativeTime value={expiryDelta} numeric="auto" updateIntervalInSeconds={1} />
)
</>
) : (
'-'
)}
</DebugInfo>
<DebugInfo label="SDK version">{getVersion()}</DebugInfo>
</div>
Expand Down
21 changes: 19 additions & 2 deletions src/components/CoSign/test.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {render, screen} from '@testing-library/react';
import messagesNL from 'i18n/compiled/nl.json';
import {IntlProvider} from 'react-intl';

import {testLoginForm} from 'components/FormStart/fixtures';
import {buildForm} from 'api-mocks';

import {CoSignAuthentication} from './CoSignOld';

Expand All @@ -14,10 +14,27 @@ it('CoSign component constructs the right auth URL', () => {
href: 'https://openforms.nl/form-name/step/step-name',
};

const form = buildForm({
loginOptions: [
{
identifier: 'digid',
label: 'DigiD',
url: 'https://openforms.nl/auth/form-name/digid/start',
logo: {
title: 'DigiD',
imageSrc: 'https://openforms.nl/static/img/digid-46x46.71ea68346bbb.png',
href: 'https://www.digid.nl/',
appearance: 'dark',
},
isForGemachtigde: false,
},
],
});

render(
<IntlProvider locale="nl" messages={messagesNL}>
<CoSignAuthentication
form={testLoginForm}
form={form}
submissionUuid="111-222-333"
authPlugin="digid"
saveStepData={() => {}}
Expand Down
128 changes: 50 additions & 78 deletions src/components/Form.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import {useContext, useEffect} from 'react';
import PropTypes from 'prop-types';
import React, {useContext, useEffect} from 'react';
import {useIntl} from 'react-intl';
import {Navigate, Route, Routes, useLocation, useMatch, useNavigate} from 'react-router-dom';
import {
Navigate,
Outlet,
Route,
Routes,
useLocation,
useMatch,
useNavigate,
} from 'react-router-dom';
import {useAsync, usePrevious} from 'react-use';
import {useImmerReducer} from 'use-immer';

import {AnalyticsToolsConfigContext, ConfigContext} from 'Context';
import {destroy, get} from 'api';
import ErrorBoundary from 'components/Errors/ErrorBoundary';
import FormStart from 'components/FormStart';
import FormStep from 'components/FormStep';
import IntroductionPage from 'components/IntroductionPage';
import Loader from 'components/Loader';
import {ConfirmationView, StartPaymentView} from 'components/PostCompletionViews';
import ProgressIndicator from 'components/ProgressIndicator';
Expand All @@ -23,13 +30,14 @@ import {
SUBMISSION_ALLOWED,
} from 'components/constants';
import {findNextApplicableStep} from 'components/utils';
import {createSubmission, flagActiveSubmission, flagNoActiveSubmission} from 'data/submissions';
import {flagActiveSubmission, flagNoActiveSubmission} from 'data/submissions';
import useAutomaticRedirect from 'hooks/useAutomaticRedirect';
import useFormContext from 'hooks/useFormContext';
import usePageViews from 'hooks/usePageViews';
import useQuery from 'hooks/useQuery';
import useRecycleSubmission from 'hooks/useRecycleSubmission';
import useSessionTimeout from 'hooks/useSessionTimeout';
import Types from 'types';

import FormDisplay from './FormDisplay';
import {addFixedSteps, getStepsInfo} from './ProgressIndicator/utils';
Expand Down Expand Up @@ -119,13 +127,9 @@ const Form = () => {
const confirmationMatch = useMatch('/bevestiging');

// extract the declared properties and configuration
const {steps, introductionPageContent = ''} = form;
const {steps} = form;
const config = useContext(ConfigContext);

// This has to do with a data reference if it is provided by the external party
// It will be used in the backend for retrieving additional information from the API
const initialDataReference = queryParams?.get('initial_data_reference');

// load the state management/reducer
const initialStateFromProps = {...initialState, step: steps[0]};
const [state, dispatch] = useImmerReducer(reducer, initialStateFromProps);
Expand All @@ -148,7 +152,7 @@ const Form = () => {
onSubmissionLoaded
);

const [, expiryDate, resetSession] = useSessionTimeout();
const [, expiryDate] = useSessionTimeout();

const {value: analyticsToolsConfigInfo, loading: loadingAnalyticsConfig} = useAsync(async () => {
return await get(`${config.baseUrl}analytics/analytics-tools-config-info`);
Expand All @@ -167,48 +171,13 @@ const Form = () => {
[intl.locale, prevLocale, removeSubmissionId, state.submission] // eslint-disable-line react-hooks/exhaustive-deps
);

/**
* When the form is started, create a submission and add it to the state.
*
* @param {Event} event The DOM event, could be a button click or a custom event.
* @return {Void}
*/
const onFormStart = async (event, anonymous = false) => {
if (event) event.preventDefault();

// required to get rid of the error message saying the session is expired - once
// you start a new submission, any previous call history should be discarded.
resetSession();

if (state.submission != null) {
onSubmissionLoaded(state.submission);
return;
}

let submission;
try {
submission = await createSubmission(
config.baseUrl,
form,
config.clientBaseUrl,
null,
initialDataReference,
anonymous
);
} catch (exc) {
dispatch({type: 'STARTING_ERROR', payload: exc});
return;
}

const onSubmissionObtained = submission => {
dispatch({
type: 'SUBMISSION_LOADED',
payload: submission,
});
flagActiveSubmission();
setSubmissionId(submission.id);
// navigate to the first step
const firstStepRoute = `/stap/${form.steps[0].slug}`;
navigate(firstStepRoute);
};

const onStepSubmitted = async formStep => {
Expand Down Expand Up @@ -354,39 +323,9 @@ const Form = () => {

if (state.startingError) throw state.startingError;

let startPageUrl = introductionPageContent ? 'introductie' : 'startpagina';
const extraStartUrlParams = {};
if (initialDataReference) {
extraStartUrlParams.initial_data_reference = initialDataReference;
startPageUrl = `${startPageUrl}?${new URLSearchParams(extraStartUrlParams).toString()}`;
}

// Route the correct page based on URL
const router = (
<Routes>
<Route path="" element={<Navigate replace to={startPageUrl} />} />

<Route
path="introductie"
// Ensure the initialDataReference is preserved when continuing to the Form start
element={<IntroductionPage extraParams={extraStartUrlParams} />}
/>

<Route
path="startpagina"
element={
<ErrorBoundary useCard>
<FormStart
form={form}
submission={state.submission}
onFormStart={onFormStart}
onDestroySession={onDestroySession}
initialDataReference={initialDataReference}
/>
</ErrorBoundary>
}
/>

<Route
path="overzicht"
element={
Expand Down Expand Up @@ -462,12 +401,45 @@ const Form = () => {
return (
<FormDisplay progressIndicator={progressIndicator}>
<AnalyticsToolsConfigContext.Provider value={analyticsToolsConfigInfo}>
{router}
<SubmissionProvider
submission={state.submission}
onSubmissionObtained={onSubmissionObtained}
onDestroySession={onDestroySession}
>
<Outlet />
{router}
</SubmissionProvider>
</AnalyticsToolsConfigContext.Provider>
</FormDisplay>
);
};

Form.propTypes = {};

const SubmissionContext = React.createContext({
submission: null,
onSubmissionObtained: () => {},
onDestroySession: () => {},
});

const SubmissionProvider = ({
submission = null,
onSubmissionObtained,
onDestroySession,
children,
}) => (
<SubmissionContext.Provider value={{submission, onSubmissionObtained, onDestroySession}}>
{children}
</SubmissionContext.Provider>
);

SubmissionProvider.propTypes = {
submission: Types.Submission,
onSubmissionObtained: PropTypes.func.isRequired,
onDestroySession: PropTypes.func.isRequired,
};

const useSubmissionContext = () => useContext(SubmissionContext);

export default Form;
export {useSubmissionContext, SubmissionProvider};
Loading

0 comments on commit 9768bdd

Please sign in to comment.