Skip to content

Commit 2f6ba6e

Browse files
feat: add "Is part of cultural route" selection to enrolment form
also: - with the request of the product owner don't restore any previous answer to "Is part of cultural route" question from local storage persisted Formik form's data, but instead always reset the question to its initial value i.e. no data given at all refs PT-453
1 parent a0bc759 commit 2f6ba6e

14 files changed

Lines changed: 189 additions & 3 deletions

File tree

public/locales/en/enrolment.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
"labelIsSharingDataAccepted": "I agree to share my information with the event organizer",
1818
"labelLanguage": "Language",
1919
"labelNotificationLanguage": "Language used in notifications",
20+
"culturalRoute": {
21+
"labelIsPartOf": "The visit is part of the cultural route",
22+
"labelNoOrUnknown": "No / I don't know",
23+
"labelYes": "Yes",
24+
"helperText": "Cultural routes are implementation proposals for the City of Helsinki's cultural education plan from early childhood education to upper secondary education. <a>Read more</a>",
25+
"readMoreUrl": "/en/cms-page/cultural-education/cultural-routes"
26+
},
2027
"person": {
2128
"labelEmailAddress": "Email address",
2229
"labelName": "Name",

public/locales/fi/enrolment.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
"labelIsSharingDataAccepted": "Hyväksyn tietojeni jakamisen tapahtuman järjestäjän kanssa",
1818
"labelLanguage": "Kieli",
1919
"labelNotificationLanguage": "Ilmoitusten kieli",
20+
"culturalRoute": {
21+
"labelIsPartOf": "Käynti sisältyy kulttuuripolkuun",
22+
"labelNoOrUnknown": "Ei / en tiedä",
23+
"labelYes": "Kyllä",
24+
"helperText": "Kulttuuripolut ovat Helsingin kaupungin kulttuurikasvatussuunnitelman toteutusehdotuksia varhaiskasvatuksesta toiselle asteelle. <a>Lue lisää kulttuuripoluista</a>",
25+
"readMoreUrl": "/fi/cms-page/kulttuurikasvatus/kulttuuripolulla-kulttuuria-kaikille"
26+
},
2027
"person": {
2128
"labelEmailAddress": "Sähköpostiosoite",
2229
"labelName": "Nimi",

public/locales/sv/enrolment.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
"labelIsSharingDataAccepted": "Jag samtycker till att dela mina uppgifter med arrangören av evenemanget.",
1818
"labelLanguage": "Språk",
1919
"labelNotificationLanguage": "Språket för meddelanden",
20+
"culturalRoute": {
21+
"labelIsPartOf": "Besöket ingår i kulturstigen",
22+
"labelNoOrUnknown": "Nej / jag vet inte",
23+
"labelYes": "Ja",
24+
"helperText": "Kulturstigarna är förslag på genomförande av Helsingfors stads kulturfostransplan från småbarnspedagogik till gymnasieutbildning. <a>Läs mer om kulturstigarna</a>",
25+
"readMoreUrl": "/sv/cms-page/kulturpedagogik/kulturstigen-kultur-for-alla"
26+
},
2027
"person": {
2128
"labelEmailAddress": "E-post",
2229
"labelName": "Namn",

src/common/components/formikPersist/FormikPersist.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { FormikProps, useFormikContext } from 'formik';
2+
import cloneDeep from 'lodash/cloneDeep';
3+
import get from 'lodash/get';
24
import isEqual from 'lodash/isEqual';
5+
import set from 'lodash/set';
36
import * as React from 'react';
47

58
import useIsMounted from '../../../hooks/useIsMounted';
@@ -26,13 +29,16 @@ export interface PersistProps {
2629
debounceTime?: number;
2730
isSessionStorage?: boolean;
2831
initialValues: Record<string, unknown>;
32+
// Dot-notation paths of fields that are restored using initial values:
33+
alwaysFreshFields?: string[];
2934
}
3035

3136
const FormikPersist = ({
3237
debounceTime = 300,
3338
isSessionStorage = false,
3439
name,
3540
initialValues,
41+
alwaysFreshFields = [],
3642
}: PersistProps): null => {
3743
const isMounted = useIsMounted();
3844
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -78,7 +84,12 @@ const FormikPersist = ({
7884
storedFormikStateObject &&
7985
objectStructureMatches(initialValues, storedFormikStateObject.values)
8086
) {
81-
formik.setValues(storedFormikStateObject.values, true);
87+
const valuesToRestore = cloneDeep(storedFormikStateObject.values);
88+
// Restore fresh fields with their initial values instead of stored ones:
89+
for (const path of alwaysFreshFields) {
90+
set(valuesToRestore, path, get(initialValues, path));
91+
}
92+
formik.setValues(valuesToRestore, true);
8293
}
8394
// eslint-disable-next-line react-hooks/exhaustive-deps
8495
}, []);

src/domain/enrolment/enrolmentForm/EnrolmentForm.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
nameToLabelPath,
1818
} from './constants';
1919
import styles from './enrolmentForm.module.scss';
20+
import IsPartOfCulturalRouteField from './IsPartOfCulturalRouteField';
2021
import getValidationSchema from './ValidationSchema';
2122
import ErrorMessage from '../../../common/components/form/ErrorMessage';
2223
import CheckboxField from '../../../common/components/form/fields/CheckboxField';
@@ -103,6 +104,7 @@ const EnrolmentForm: React.FC<Props> = ({
103104
<FormikPersist
104105
name={FORM_NAMES.ENROLMENT_FORM}
105106
initialValues={initialValues}
107+
alwaysFreshFields={['isPartOfCulturalRoute']}
106108
/>
107109
<h2>{t('enrolment:enrolmentForm.studyGroup.titleNotifier')}</h2>
108110
<FormErrorNotification
@@ -206,6 +208,10 @@ const EnrolmentForm: React.FC<Props> = ({
206208
</FormGroup>
207209
</div>
208210

211+
<FormGroup>
212+
<IsPartOfCulturalRouteField formikFieldName="isPartOfCulturalRoute" />
213+
</FormGroup>
214+
209215
<div className={styles.rowWith2Columns}>
210216
<div>
211217
<h2>{t('enrolment:enrolmentForm.studyGroup.titleGroup')}</h2>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Link } from '@city-of-helsinki/react-helsinki-headless-cms';
2+
import { useField } from 'formik';
3+
import { RadioButton, SelectionGroup } from 'hds-react';
4+
import { Trans, useTranslation } from 'next-i18next';
5+
import React from 'react';
6+
7+
import { IsPartOfCulturalRoute, nameToLabelPath } from './constants';
8+
import type { I18nNamespace } from '../../../types';
9+
10+
type Props = {
11+
formikFieldName: string;
12+
};
13+
14+
/**
15+
* React component for "Is part of cultural route?" field in the enrolment form,
16+
* with choices of "No / I don't know" and "Yes".
17+
*/
18+
const IsPartOfCulturalRouteField: React.FC<Props> = ({ formikFieldName }) => {
19+
const { t } = useTranslation<I18nNamespace>();
20+
const [field, meta] = useField(formikFieldName);
21+
const errorText = meta.touched && meta.error ? t(meta.error) : undefined;
22+
const readMoreUrl = t('enrolment:enrolmentForm.culturalRoute.readMoreUrl');
23+
24+
return (
25+
<SelectionGroup
26+
label={t(nameToLabelPath['isPartOfCulturalRoute'])}
27+
direction="horizontal"
28+
required
29+
aria-required
30+
errorText={errorText}
31+
/* @ts-expect-error TS2322 SelectionGroup's types allow only string, but Trans works */
32+
helperText={
33+
<Trans
34+
t={t}
35+
i18nKey={'enrolment:enrolmentForm.culturalRoute.helperText'}
36+
components={{ a: <Link href={readMoreUrl} /> }}
37+
/>
38+
}
39+
>
40+
<RadioButton
41+
id="isPartOfCulturalRoute-noOrUnknown"
42+
name={field.name}
43+
value={IsPartOfCulturalRoute.NO_OR_UNKNOWN}
44+
label={t('enrolment:enrolmentForm.culturalRoute.labelNoOrUnknown')}
45+
checked={field.value === IsPartOfCulturalRoute.NO_OR_UNKNOWN}
46+
onChange={field.onChange}
47+
onBlur={field.onBlur}
48+
/>
49+
<RadioButton
50+
id="isPartOfCulturalRoute-yes"
51+
name={field.name}
52+
value={IsPartOfCulturalRoute.YES}
53+
label={t('enrolment:enrolmentForm.culturalRoute.labelYes')}
54+
checked={field.value === IsPartOfCulturalRoute.YES}
55+
onChange={field.onChange}
56+
onBlur={field.onBlur}
57+
/>
58+
</SelectionGroup>
59+
);
60+
};
61+
62+
export default IsPartOfCulturalRouteField;

src/domain/enrolment/enrolmentForm/ValidationSchema.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as Yup from 'yup';
22

3+
import { IsPartOfCulturalRoute } from './constants';
34
import { VALIDATION_MESSAGE_KEYS } from '../../app/forms/constants';
45

56
type EnrolmentFormValidationSchemaProps = {
@@ -14,6 +15,10 @@ export default function getValidationSchema({
1415
isQueueEnrolment,
1516
}: EnrolmentFormValidationSchemaProps) {
1617
return Yup.object().shape({
18+
isPartOfCulturalRoute: Yup.string().oneOf(
19+
[IsPartOfCulturalRoute.NO_OR_UNKNOWN, IsPartOfCulturalRoute.YES],
20+
VALIDATION_MESSAGE_KEYS.STRING_REQUIRED
21+
),
1722
hasEmailNotification: Yup.bool().oneOf(
1823
[true],
1924
'enrolment:enrolmentForm.validation.hasEmailNotification'

src/domain/enrolment/enrolmentForm/__tests__/EnrolmentForm.test.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import {
2323
within,
2424
sleep,
2525
} from '../../../../utils/testUtils';
26-
import { defaultEnrolmentInitialValues } from '../constants';
26+
import {
27+
defaultEnrolmentInitialValues,
28+
IsPartOfCulturalRoute,
29+
} from '../constants';
2730
import EnrolmentForm, { Props } from '../EnrolmentForm';
2831

2932
configure({ defaultHidden: true });
@@ -113,6 +116,14 @@ test('renders form and user can fill it and submit and form is saved to local st
113116

114117
// // close dropdown
115118

119+
// Answer "Is part of cultural route?" question
120+
const culturalRouteGroup = await screen.findByRole('group', {
121+
name: /käynti sisältyy kulttuuripolkuun/i,
122+
});
123+
await userEvent.click(
124+
within(culturalRouteGroup).getByRole('radio', { name: /kyllä/i })
125+
);
126+
116127
await userEvent.click(screen.getByRole('combobox', { name: /luokka-aste/i }));
117128

118129
await userEvent.type(screen.getByLabelText(/lapsia/i), '10');
@@ -411,6 +422,7 @@ if (isFeatureEnabled('FORMIK_PERSIST')) {
411422
isSameResponsiblePerson: true,
412423
isSharingDataAccepted: false,
413424
isMandatoryAdditionalInformationRequired: false,
425+
isPartOfCulturalRoute: IsPartOfCulturalRoute.NO_OR_UNKNOWN,
414426
language: '',
415427
studyGroup: {
416428
person: {
@@ -504,6 +516,23 @@ if (isFeatureEnabled('FORMIK_PERSIST')) {
504516
expect(
505517
screen.getByRole('combobox', { name: /luokka-aste/i })
506518
).toHaveTextContent('1. luokka');
519+
520+
// Test that "Is part of cultural route?" question is not answered,
521+
// because this field should not be restored from local storage:
522+
const culturalRouteGroup = await screen.findByRole('group', {
523+
name: /käynti sisältyy kulttuuripolkuun/i,
524+
});
525+
const isNotPartOfCulturalRoute = within(culturalRouteGroup).getByRole(
526+
'radio',
527+
{ name: /ei \/ en tiedä/i }
528+
);
529+
const isPartOfCulturalRoute = within(culturalRouteGroup).getByRole(
530+
'radio',
531+
{ name: /kyllä/i }
532+
);
533+
expect(isNotPartOfCulturalRoute).not.toBeChecked();
534+
expect(isPartOfCulturalRoute).not.toBeChecked();
535+
507536
const childrenCountInput = screen.getByRole('spinbutton', {
508537
name: /lapsia \*/i,
509538
});

src/domain/enrolment/enrolmentForm/__tests__/__snapshots__/EnrolmentForm.test.tsx.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ exports[`renders form and user can fill it and submit and form is saved to local
1010
"hasEmailNotification": true,
1111
"hasSmsNotification": false,
1212
"isMandatoryAdditionalInformationRequired": false,
13+
"isPartOfCulturalRoute": "",
1314
"isSameResponsiblePerson": true,
1415
"isSharingDataAccepted": false,
1516
"language": "",
@@ -42,6 +43,7 @@ exports[`renders form and user can fill it and submit and form is saved to local
4243
"hasEmailNotification": true,
4344
"hasSmsNotification": true,
4445
"isMandatoryAdditionalInformationRequired": true,
46+
"isPartOfCulturalRoute": true,
4547
"isSameResponsiblePerson": true,
4648
"isSharingDataAccepted": true,
4749
"language": true,
@@ -76,6 +78,7 @@ exports[`renders form and user can fill it and submit and form is saved to local
7678
"hasEmailNotification": true,
7779
"hasSmsNotification": true,
7880
"isMandatoryAdditionalInformationRequired": false,
81+
"isPartOfCulturalRoute": "YES",
7982
"isSameResponsiblePerson": true,
8083
"isSharingDataAccepted": true,
8184
"language": "FI",
@@ -112,6 +115,7 @@ exports[`renders form and user can fill it and submit and form is saved to local
112115
"hasEmailNotification": true,
113116
"hasSmsNotification": true,
114117
"isMandatoryAdditionalInformationRequired": false,
118+
"isPartOfCulturalRoute": "YES",
115119
"isSameResponsiblePerson": true,
116120
"isSharingDataAccepted": true,
117121
"language": "FI",

src/domain/enrolment/enrolmentForm/constants.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1+
export const enum IsPartOfCulturalRoute {
2+
NO_OR_UNKNOWN = 'NO_OR_UNKNOWN',
3+
YES = 'YES',
4+
}
5+
16
export type EnrolmentFormFields = {
27
hasEmailNotification: boolean;
38
hasSmsNotification: boolean;
49
isSameResponsiblePerson: boolean;
510
isSharingDataAccepted: boolean;
611
isMandatoryAdditionalInformationRequired: boolean;
712
language: string;
13+
isPartOfCulturalRoute: IsPartOfCulturalRoute | '';
814
person: {
915
name: string;
1016
phoneNumber: string;
@@ -34,6 +40,7 @@ export const defaultEnrolmentInitialValues: EnrolmentFormFields = {
3440
isSharingDataAccepted: false,
3541
isMandatoryAdditionalInformationRequired: false,
3642
language: '',
43+
isPartOfCulturalRoute: '',
3744
person: {
3845
name: '',
3946
phoneNumber: '',
@@ -85,4 +92,5 @@ export const nameToLabelPath: Record<string, string> = {
8592
'enrolment:enrolmentForm.studyGroup.person.labelPhoneNumber',
8693
'person.emailAddress':
8794
'enrolment:enrolmentForm.studyGroup.person.labelEmailAddress',
95+
isPartOfCulturalRoute: 'enrolment:enrolmentForm.culturalRoute.labelIsPartOf',
8896
};

0 commit comments

Comments
 (0)