Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cosign improvements #729

Merged
merged 8 commits into from
Nov 18, 2024
1 change: 1 addition & 0 deletions src/Context.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const FormContext = React.createContext({
introductionPageContent: '',
loginRequired: false,
loginOptions: [],
cosignLoginOptions: [],
maintenanceMode: false,
showProgressIndicator: true,
showSummaryProgress: false,
Expand Down
28 changes: 10 additions & 18 deletions src/components/Button/OFButton.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,25 @@ aria attribute instead.
More information on the Utrecht buttons can be found
[here](https://nl-design-system.github.io/themes/?path=/docs/button--gemeente-utrecht).

<Canvas>
<Story of={OFButtonStories.UtrechtDefault} />
<Story of={OFButtonStories.UtrechtPrimary} />
<Story of={OFButtonStories.UtrechtSecondary} />
<Story of={OFButtonStories.UtrechtDanger} />
</Canvas>
<Canvas of={OFButtonStories.UtrechtDefault} />
<Canvas of={OFButtonStories.UtrechtPrimary} />
<Canvas of={OFButtonStories.UtrechtSecondary} />
<Canvas of={OFButtonStories.UtrechtDanger} />

### Links that look like buttons

<Canvas>
<Story of={OFButtonStories.UtrechtLinkLooksLikeDefaultButton} />
<Story of={OFButtonStories.UtrechtLinkLooksLikePrimaryButton} />
<Story of={OFButtonStories.UtrechtLinkLooksLikeSecondaryButton} />
</Canvas>
<Canvas of={OFButtonStories.UtrechtLinkLooksLikeDefaultButton} />
<Canvas of={OFButtonStories.UtrechtLinkLooksLikePrimaryButton} />
<Canvas of={OFButtonStories.UtrechtLinkLooksLikeSecondaryButton} />

### Button that looks like a link

<Canvas>
<Story of={OFButtonStories.UtrechtButtonLooksLikeLink} />
</Canvas>
<Canvas of={OFButtonStories.UtrechtButtonLooksLikeLink} />

## Icon buttons

<Canvas>
<Story of={OFButtonStories.UtrechtIconButton} />
<Story of={OFButtonStories.UtrechtIconButtonDanger} />
</Canvas>
<Canvas of={OFButtonStories.UtrechtIconButton} />
<Canvas of={OFButtonStories.UtrechtIconButtonDanger} />

## Disabled state

Expand Down
4 changes: 2 additions & 2 deletions src/components/CoSign/CoSign.stories.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import CosignDone from './CosignDone';

export default {
title: 'Views / Co-sign done',
title: 'Views / Cosign / Done',
component: CosignDone,
args: {
reportDownloadUrl: '#',
},
};

export const CoSignDone = {
name: 'Co-sign done',
name: 'CosignDone',
};
2 changes: 2 additions & 0 deletions src/components/CoSign/Cosign.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {CosignSummary} from 'components/Summary';
import useFormContext from 'hooks/useFormContext';

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

const initialState = {
submission: null,
Expand Down Expand Up @@ -60,6 +61,7 @@ const Cosign = () => {
return (
<ErrorBoundary useCard>
<Routes>
<Route path="start" element={<CosignStart />} />
<Route
path="check"
element={
Expand Down
82 changes: 82 additions & 0 deletions src/components/CoSign/CosignStart.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {FormattedMessage} from 'react-intl';

import Body from 'components/Body';
import Card from 'components/Card';
import {LiteralsProvider} from 'components/Literal';
import LoginOptions from 'components/LoginOptions';
import MaintenanceMode from 'components/MaintenanceMode';
import {
AuthenticationErrors,
useDetectAuthErrorMessages,
} from 'components/auth/AuthenticationErrors';
import AuthenticationOutage, {
useDetectAuthenticationOutage,
} from 'components/auth/AuthenticationOutage';
import {UnprocessableEntity} from 'errors';
import {IsFormDesigner} from 'headers';
import useFormContext from 'hooks/useFormContext';

const CosignStart = () => {
const form = useFormContext();

const outagePluginId = useDetectAuthenticationOutage();
const authErrors = useDetectAuthErrorMessages();

if (!form.active) {
throw new UnprocessableEntity('Unprocessable Entity', 422, 'Form not active', 'form-inactive');

Check warning on line 26 in src/components/CoSign/CosignStart.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/CoSign/CosignStart.jsx#L26

Added line #L26 was not covered by tests
}

const userIsFormDesigner = IsFormDesigner.getValue();
if (!userIsFormDesigner && form.maintenanceMode) {
return <MaintenanceMode title={form.name} />;

Check warning on line 31 in src/components/CoSign/CosignStart.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/CoSign/CosignStart.jsx#L31

Added line #L31 was not covered by tests
}

if (outagePluginId) {
const loginOption = form.cosignLoginOptions.find(
option => option.identifier === outagePluginId

Check warning on line 36 in src/components/CoSign/CosignStart.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/CoSign/CosignStart.jsx#L35-L36

Added lines #L35 - L36 were not covered by tests
);
if (!loginOption) throw new Error('Unknown login plugin identifier');
return (

Check warning on line 39 in src/components/CoSign/CosignStart.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/CoSign/CosignStart.jsx#L39

Added line #L39 was not covered by tests
<Card
title={
<FormattedMessage
description="Form start outage title"
defaultMessage="Problem - {formName}"
values={{formName: form.name}}
/>
}
>
<AuthenticationOutage loginOption={loginOption} />
</Card>
);
}

return (
<LiteralsProvider literals={form.literals}>
<Card title={form.name}>
{userIsFormDesigner && form.maintenanceMode && <MaintenanceMode asToast />}

{!!authErrors ? <AuthenticationErrors parameters={authErrors} /> : null}

<Body>
<FormattedMessage
description="Cosign start explanation message"
defaultMessage={`Did you receive an email with a request to cosign?
Start the cosigning by logging in.`}
/>
</Body>

<LoginOptions
// hide the normal login options, and only display the cosign login options
form={{...form, loginOptions: []}}
// dummy - we don't actually start a new submission, but the next URL is baked
// into the login URLs.
onFormStart={() => {}}

Check warning on line 74 in src/components/CoSign/CosignStart.jsx

View check run for this annotation

Codecov / codecov/patch

src/components/CoSign/CosignStart.jsx#L74

Added line #L74 was not covered by tests
isolateCosignOptions={false}
/>
</Card>
</LiteralsProvider>
);
};

export default CosignStart;
51 changes: 51 additions & 0 deletions src/components/CoSign/CosignStart.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {withRouter} from 'storybook-addon-remix-react-router';

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

import CosignStart from './CosignStart';

export default {
title: 'Views / Cosign / Start',
component: CosignStart,
decorators: [withForm, withRouter],
parameters: {
formContext: {
form: buildForm({
loginRequired: true,
loginOptions: [
{
identifier: 'digid',
label: 'DigiD',
url: '#',
logo: {
title: 'DigiD simulatie',
imageSrc: './digid.png',
href: 'https://www.digid.nl/',
appearance: 'dark',
},
isForGemachtigde: false,
},
],
cosignLoginOptions: [
{
identifier: 'digid',
label: 'DigiD',
url: 'http://localhost:8000/auth/digid/?next=http://localhost:8000/cosign&amp;code=123',
logo: {
title: 'DigiD simulatie',
imageSrc: './digid.png',
href: 'https://www.digid.nl/',
appearance: 'dark',
},
isForGemachtigde: false,
},
],
}),
},
},
};

export const Default = {
name: 'CosignStart',
};
8 changes: 7 additions & 1 deletion src/components/FormStart/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,13 @@ const FormStart = ({form, submission, onFormStart, onDestroySession, initialData
isAuthenticated={isAuthenticated}
/>
) : (
<LoginOptions form={form} onFormStart={onFormStart} extraNextParams={extraNextParams} />
<LoginOptions
// if cosign allows links in emails, we don't need to display the cosign
// login options, so strip them out
form={form.cosignHasLinkInEmail ? {...form, cosignLoginOptions: []} : form}
onFormStart={onFormStart}
extraNextParams={extraNextParams}
/>
)}
</Card>
</LiteralsProvider>
Expand Down
8 changes: 6 additions & 2 deletions src/components/LoginOptions/LoginOptions.stories.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {expect, fn, userEvent, waitFor, within} from '@storybook/test';
import {withRouter} from 'storybook-addon-remix-react-router';

import {buildForm} from 'api-mocks';
import {LiteralDecorator} from 'story-utils/decorators';
Expand All @@ -9,7 +10,10 @@ import LoginOptionsDisplay from './LoginOptionsDisplay';
export default {
title: 'Composites / Login Options',
component: LoginOptions,
decorators: [LiteralDecorator],
decorators: [LiteralDecorator, withRouter],
args: {
onFormStart: fn(),
},
argTypes: {
form: {table: {disable: true}},
},
Expand Down Expand Up @@ -239,7 +243,7 @@ export const WithCoSignOption = {
{
identifier: 'digid',
label: 'DigiD',
url: '#',
url: 'http://localhost:8000/auth/digid/?next=http://localhost:3000/form?_start=1',
logo: {
title: 'DigiD simulatie',
imageSrc: './digid.png',
Expand Down
30 changes: 23 additions & 7 deletions src/components/LoginOptions/LoginOptionsDisplay.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage} from 'react-intl';

import Body from 'components/Body';
import LoginButton from 'components/LoginButton';
import FormattedLoginOption from 'types/FormattedLoginOption';
import {getBEMClassName} from 'utils';
Expand All @@ -10,6 +11,7 @@ const LoginOptionsDisplay = ({
loginAsYourselfOptions,
loginAsGemachtigdeOptions,
cosignLoginOptions,
isolateCosignOptions = true,
}) => {
return (
<div className={getBEMClassName('login-options')}>
Expand Down Expand Up @@ -37,13 +39,26 @@ const LoginOptionsDisplay = ({
)}

{cosignLoginOptions?.length > 0 && (
<div className={getBEMClassName('login-options__cosign')}>
<h2 className={getBEMClassName('login-options__caption')}>
<FormattedMessage
description="Log in to co-sign the form title"
defaultMessage="Log in to co-sign the form"
/>
</h2>
<div
className={isolateCosignOptions ? getBEMClassName('login-options__cosign') : undefined}
>
{isolateCosignOptions && (
<>
<h2 className={getBEMClassName('login-options__caption')}>
<FormattedMessage
description="Log in to co-sign the form title"
defaultMessage="Log in to co-sign the form"
/>
</h2>
<Body>
<FormattedMessage
description="Cosign start explanation message"
defaultMessage={`Did you receive an email with a request to cosign?
Start the cosigning by logging in.`}
/>
</Body>
</>
)}

<div className={getBEMClassName('login-options__list')}>
{cosignLoginOptions.map(option => (
Expand All @@ -60,6 +75,7 @@ LoginOptionsDisplay.propTypes = {
loginAsYourselfOptions: PropTypes.arrayOf(FormattedLoginOption).isRequired,
loginAsGemachtigdeOptions: PropTypes.arrayOf(FormattedLoginOption).isRequired,
cosignLoginOptions: PropTypes.arrayOf(FormattedLoginOption),
isolateCosignOptions: PropTypes.bool,
};

export default LoginOptionsDisplay;
12 changes: 10 additions & 2 deletions src/components/LoginOptions/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import {FormattedMessage} from 'react-intl';

import {ConfigContext} from 'Context';
import Literal from 'components/Literal';
import {getLoginUrl} from 'components/utils';
import {getCosignLoginUrl, getLoginUrl} from 'components/utils';
import useQuery from 'hooks/useQuery';
import Types from 'types';

import LoginOptionsDisplay from './LoginOptionsDisplay';

const LoginOptions = ({form, onFormStart, extraNextParams = {}}) => {
const LoginOptions = ({form, onFormStart, extraNextParams = {}, isolateCosignOptions = true}) => {
const config = useContext(ConfigContext);
const queryParams = useQuery();

const loginAsYourselfOptions = [];
const loginAsGemachtigdeOptions = [];
Expand All @@ -35,9 +37,12 @@ const LoginOptions = ({form, onFormStart, extraNextParams = {}}) => {
});

if (form.cosignLoginOptions) {
const cosignCode = queryParams.get('code');
form.cosignLoginOptions.forEach(option => {
const loginUrl = getCosignLoginUrl(option, cosignCode ? {code: cosignCode} : undefined);
cosignLoginOptions.push({
...option,
url: loginUrl,
label: (
<FormattedMessage
description="Login button label"
Expand Down Expand Up @@ -65,6 +70,7 @@ const LoginOptions = ({form, onFormStart, extraNextParams = {}}) => {
e.preventDefault();
onFormStart(e, true);
},
'data-testid': 'start-form',
};

return (
Expand All @@ -73,6 +79,7 @@ const LoginOptions = ({form, onFormStart, extraNextParams = {}}) => {
loginAsYourselfOptions={loginAsYourselfOptions}
loginAsGemachtigdeOptions={loginAsGemachtigdeOptions}
cosignLoginOptions={cosignLoginOptions}
isolateCosignOptions={isolateCosignOptions}
/>
</Container>
);
Expand All @@ -83,6 +90,7 @@ LoginOptions.propTypes = {
onFormStart: PropTypes.func.isRequired,
extraParams: PropTypes.object,
extraNextParams: PropTypes.object,
isolateCosignOptions: PropTypes.bool,
};

export default LoginOptions;
Loading