From cfc96cc671f58d4038dbac8f5cfe827e804302ad Mon Sep 17 00:00:00 2001 From: bahaa Date: Tue, 14 Jan 2025 12:36:56 +0200 Subject: [PATCH 1/6] 1804: adding hint for birth date input --- .../components/CustomDatePicker.tsx | 60 +++++++++++-------- .../cards/extensions/BirthdayExtension.tsx | 32 +++++++++- 2 files changed, 65 insertions(+), 27 deletions(-) diff --git a/administration/src/bp-modules/components/CustomDatePicker.tsx b/administration/src/bp-modules/components/CustomDatePicker.tsx index 1ffff281e..13bdb7905 100644 --- a/administration/src/bp-modules/components/CustomDatePicker.tsx +++ b/administration/src/bp-modules/components/CustomDatePicker.tsx @@ -1,9 +1,14 @@ import { DesktopDatePicker } from '@mui/x-date-pickers' import { deDE } from '@mui/x-date-pickers/locales' import React, { ReactElement } from 'react' +import styled from 'styled-components' import useWindowDimensions from '../../hooks/useWindowDimensions' +const StyledDiv = styled.div` + display: flex; + align-items: center; +` type CustomDatePickerProps = { date: Date | null onBlur: () => void @@ -13,6 +18,7 @@ type CustomDatePickerProps = { minDate?: Date maxDate?: Date disableFuture: boolean + sideComponent?: ReactElement } const CustomDatePicker = ({ @@ -24,38 +30,42 @@ const CustomDatePicker = ({ minDate, maxDate, disableFuture, + sideComponent, }: CustomDatePickerProps): ReactElement => { const { viewportSmall } = useWindowDimensions() const formStyle = viewportSmall ? { fontSize: 16, padding: '9px 10px' } : { fontSize: 14, padding: '6px 10px' } const textFieldBoxShadow = '0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0), inset 0 0 0 1px #cd4246, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3)' return ( - + + }} + localeText={deDE.components.MuiLocalizationProvider.defaultProps.localeText} + disableFuture={disableFuture} + minDate={minDate} + maxDate={maxDate} + onChange={onChange} + /> + {sideComponent} + ) } diff --git a/administration/src/cards/extensions/BirthdayExtension.tsx b/administration/src/cards/extensions/BirthdayExtension.tsx index cf3bddbb6..19f530444 100644 --- a/administration/src/cards/extensions/BirthdayExtension.tsx +++ b/administration/src/cards/extensions/BirthdayExtension.tsx @@ -1,11 +1,26 @@ -import { FormGroup } from '@blueprintjs/core' -import React, { ReactElement, useState } from 'react' +import { Classes, FormGroup } from '@blueprintjs/core' +import { Tooltip } from '@blueprintjs/core' +import HelpOutlineIcon from '@mui/icons-material/HelpOutline' +import React, { ReactElement, useContext, useState } from 'react' +import styled from 'styled-components' import CustomDatePicker from '../../bp-modules/components/CustomDatePicker' import FormErrorMessage from '../../bp-modules/self-service/components/FormErrorMessage' +import { ProjectConfigContext } from '../../project-configs/ProjectConfigContext' import PlainDate from '../../util/PlainDate' import { Extension, ExtensionComponentProps } from './extensions' +const StyledToolTip = styled(Tooltip)` + border: 0; +` + +const StyledHelpOutlineIcon = styled(HelpOutlineIcon)` + width: 20px; + height: auto; + margin-left: 16px; + color: #595959; +` + export const BIRTHDAY_EXTENSION_NAME = 'birthday' export type BirthdayExtensionState = { [BIRTHDAY_EXTENSION_NAME]: PlainDate | null } @@ -21,6 +36,8 @@ const BirthdayForm = ({ const [touched, setTouched] = useState(false) const { birthday } = value const showErrorMessage = touched || showRequired + const projectConfig = useContext(ProjectConfigContext) + const getErrorMessage = (): string | null => { if (!birthday) { return 'Bitte geben Sie ein gültiges Geburtsdatum an.' @@ -45,6 +62,17 @@ const BirthdayForm = ({ isValid={isValid || !showErrorMessage} maxDate={new Date()} disableFuture + sideComponent={ + <> + {projectConfig.projectId.includes('koblenz') && ( + + + + )} + + } /> {showErrorMessage && } From cb02c2e17e83d8182b1d8b7c2372df4aa115ba73 Mon Sep 17 00:00:00 2001 From: bahaa Date: Tue, 14 Jan 2025 14:04:50 +0200 Subject: [PATCH 2/6] 1804: blueprintjs fix multiple imports --- administration/src/cards/extensions/BirthdayExtension.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/administration/src/cards/extensions/BirthdayExtension.tsx b/administration/src/cards/extensions/BirthdayExtension.tsx index 19f530444..aca142a9c 100644 --- a/administration/src/cards/extensions/BirthdayExtension.tsx +++ b/administration/src/cards/extensions/BirthdayExtension.tsx @@ -1,5 +1,4 @@ -import { Classes, FormGroup } from '@blueprintjs/core' -import { Tooltip } from '@blueprintjs/core' +import { Classes, FormGroup, Tooltip } from '@blueprintjs/core' import HelpOutlineIcon from '@mui/icons-material/HelpOutline' import React, { ReactElement, useContext, useState } from 'react' import styled from 'styled-components' From c8c16200420dcb22f1255e97cd5e1ac67533d27b Mon Sep 17 00:00:00 2001 From: bahaa Date: Tue, 14 Jan 2025 18:00:48 +0200 Subject: [PATCH 3/6] 1804: Adding showBirthdayMinorHint flag to each project config --- administration/src/cards/extensions/BirthdayExtension.tsx | 7 +++---- administration/src/project-configs/bayern/config.ts | 1 + administration/src/project-configs/getProjectConfig.ts | 1 + administration/src/project-configs/koblenz/config.ts | 1 + administration/src/project-configs/nuernberg/config.ts | 1 + administration/src/project-configs/showcase/config.ts | 1 + 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/administration/src/cards/extensions/BirthdayExtension.tsx b/administration/src/cards/extensions/BirthdayExtension.tsx index aca142a9c..49c13ff3c 100644 --- a/administration/src/cards/extensions/BirthdayExtension.tsx +++ b/administration/src/cards/extensions/BirthdayExtension.tsx @@ -11,12 +11,11 @@ import { Extension, ExtensionComponentProps } from './extensions' const StyledToolTip = styled(Tooltip)` border: 0; + width: 20px; + margin-left: 16px; ` const StyledHelpOutlineIcon = styled(HelpOutlineIcon)` - width: 20px; - height: auto; - margin-left: 16px; color: #595959; ` @@ -63,7 +62,7 @@ const BirthdayForm = ({ disableFuture sideComponent={ <> - {projectConfig.projectId.includes('koblenz') && ( + {projectConfig.showBirthdayMinorHint && ( diff --git a/administration/src/project-configs/bayern/config.ts b/administration/src/project-configs/bayern/config.ts index c51997050..f7fd0b8b6 100644 --- a/administration/src/project-configs/bayern/config.ts +++ b/administration/src/project-configs/bayern/config.ts @@ -93,6 +93,7 @@ const config: ProjectConfig = { enabled: false, }, userImportApiEnabled: false, + showBirthdayMinorHint: false, } export default config diff --git a/administration/src/project-configs/getProjectConfig.ts b/administration/src/project-configs/getProjectConfig.ts index 51263a6d1..d28ef9f05 100644 --- a/administration/src/project-configs/getProjectConfig.ts +++ b/administration/src/project-configs/getProjectConfig.ts @@ -119,6 +119,7 @@ export type ProjectConfig = { selfServiceEnabled: boolean storesManagement: StoresManagementConfig userImportApiEnabled: boolean + showBirthdayMinorHint: boolean } export const setProjectConfigOverride = (hostname: string): void => { diff --git a/administration/src/project-configs/koblenz/config.ts b/administration/src/project-configs/koblenz/config.ts index d2084e629..3c86ae833 100644 --- a/administration/src/project-configs/koblenz/config.ts +++ b/administration/src/project-configs/koblenz/config.ts @@ -36,6 +36,7 @@ const config: ProjectConfig = { selfServiceEnabled: true, storesManagement: storesManagementConfig, userImportApiEnabled: true, + showBirthdayMinorHint: true, } export default config diff --git a/administration/src/project-configs/nuernberg/config.ts b/administration/src/project-configs/nuernberg/config.ts index d493a758d..2a09f584d 100644 --- a/administration/src/project-configs/nuernberg/config.ts +++ b/administration/src/project-configs/nuernberg/config.ts @@ -61,6 +61,7 @@ const config: ProjectConfig = { selfServiceEnabled: false, storesManagement: storesManagementConfig, userImportApiEnabled: false, + showBirthdayMinorHint: false, } export default config diff --git a/administration/src/project-configs/showcase/config.ts b/administration/src/project-configs/showcase/config.ts index 86bccee46..86de8eca1 100644 --- a/administration/src/project-configs/showcase/config.ts +++ b/administration/src/project-configs/showcase/config.ts @@ -36,6 +36,7 @@ const config: ProjectConfig = { enabled: false, }, userImportApiEnabled: false, + showBirthdayMinorHint: false, } export default config From ff37657a15df359383088f7bf550778aa73c84c1 Mon Sep 17 00:00:00 2001 From: bahaa Date: Wed, 15 Jan 2025 18:25:57 +0200 Subject: [PATCH 4/6] 1804: Fixing the mock issue for csvExport test and removing a duplicate Extension --- administration/src/cards/extensions/extensions.tsx | 1 - .../project-configs/nuernberg/__tests__/csvExport.test.tsx | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/administration/src/cards/extensions/extensions.tsx b/administration/src/cards/extensions/extensions.tsx index 2f85404c2..fabd05e79 100644 --- a/administration/src/cards/extensions/extensions.tsx +++ b/administration/src/cards/extensions/extensions.tsx @@ -53,7 +53,6 @@ const Extensions = [ AddressPlzExtension, AddressLocationExtension, NuernbergPassIdExtension, - NuernbergPassIdExtension, EMailNotificationExtension, KoblenzReferenceNumberExtension, ] as const diff --git a/administration/src/project-configs/nuernberg/__tests__/csvExport.test.tsx b/administration/src/project-configs/nuernberg/__tests__/csvExport.test.tsx index 58e71057f..335e47296 100644 --- a/administration/src/project-configs/nuernberg/__tests__/csvExport.test.tsx +++ b/administration/src/project-configs/nuernberg/__tests__/csvExport.test.tsx @@ -9,7 +9,10 @@ jest.mock('csv-stringify/browser/esm/sync', () => ({ stringify: (input: string[][]) => input[0].join(','), })) -jest.mock('../../getProjectConfig') +jest.mock('../../getProjectConfig', () => ({ + __esModule: true, + default: jest.fn(), +})) describe('csvExport', () => { it('header should have same length as line', () => { From 01950e7b68dcfddff6eb4cce20baac074ac0a6fd Mon Sep 17 00:00:00 2001 From: bahaa Date: Tue, 4 Feb 2025 20:55:46 +0200 Subject: [PATCH 5/6] 1804: Requested changes part one for birthday validation --- .../components/CustomDatePicker.tsx | 60 ++++++++----------- .../cards/extensions/BirthdayExtension.tsx | 42 +++++-------- .../src/project-configs/bayern/config.ts | 2 +- .../src/project-configs/getProjectConfig.ts | 2 +- .../src/project-configs/koblenz/config.ts | 2 +- .../src/project-configs/nuernberg/config.ts | 2 +- .../src/project-configs/showcase/config.ts | 2 +- administration/src/util/PlainDate.ts | 11 +++- administration/src/util/translations/de.json | 5 +- 9 files changed, 60 insertions(+), 68 deletions(-) diff --git a/administration/src/bp-modules/components/CustomDatePicker.tsx b/administration/src/bp-modules/components/CustomDatePicker.tsx index 13bdb7905..1ffff281e 100644 --- a/administration/src/bp-modules/components/CustomDatePicker.tsx +++ b/administration/src/bp-modules/components/CustomDatePicker.tsx @@ -1,14 +1,9 @@ import { DesktopDatePicker } from '@mui/x-date-pickers' import { deDE } from '@mui/x-date-pickers/locales' import React, { ReactElement } from 'react' -import styled from 'styled-components' import useWindowDimensions from '../../hooks/useWindowDimensions' -const StyledDiv = styled.div` - display: flex; - align-items: center; -` type CustomDatePickerProps = { date: Date | null onBlur: () => void @@ -18,7 +13,6 @@ type CustomDatePickerProps = { minDate?: Date maxDate?: Date disableFuture: boolean - sideComponent?: ReactElement } const CustomDatePicker = ({ @@ -30,42 +24,38 @@ const CustomDatePicker = ({ minDate, maxDate, disableFuture, - sideComponent, }: CustomDatePickerProps): ReactElement => { const { viewportSmall } = useWindowDimensions() const formStyle = viewportSmall ? { fontSize: 16, padding: '9px 10px' } : { fontSize: 14, padding: '6px 10px' } const textFieldBoxShadow = '0 0 0 0 rgba(205, 66, 70, 0), 0 0 0 0 rgba(205, 66, 70, 0), inset 0 0 0 1px #cd4246, inset 0 0 0 1px rgba(17, 20, 24, 0.2), inset 0 1px 1px rgba(17, 20, 24, 0.3)' return ( - - - {sideComponent} - + }, + }} + localeText={deDE.components.MuiLocalizationProvider.defaultProps.localeText} + disableFuture={disableFuture} + minDate={minDate} + maxDate={maxDate} + onChange={onChange} + /> ) } diff --git a/administration/src/cards/extensions/BirthdayExtension.tsx b/administration/src/cards/extensions/BirthdayExtension.tsx index 49c13ff3c..77faee184 100644 --- a/administration/src/cards/extensions/BirthdayExtension.tsx +++ b/administration/src/cards/extensions/BirthdayExtension.tsx @@ -1,7 +1,6 @@ -import { Classes, FormGroup, Tooltip } from '@blueprintjs/core' -import HelpOutlineIcon from '@mui/icons-material/HelpOutline' +import { FormGroup } from '@blueprintjs/core' import React, { ReactElement, useContext, useState } from 'react' -import styled from 'styled-components' +import { useTranslation } from 'react-i18next' import CustomDatePicker from '../../bp-modules/components/CustomDatePicker' import FormErrorMessage from '../../bp-modules/self-service/components/FormErrorMessage' @@ -9,21 +8,12 @@ import { ProjectConfigContext } from '../../project-configs/ProjectConfigContext import PlainDate from '../../util/PlainDate' import { Extension, ExtensionComponentProps } from './extensions' -const StyledToolTip = styled(Tooltip)` - border: 0; - width: 20px; - margin-left: 16px; -` - -const StyledHelpOutlineIcon = styled(HelpOutlineIcon)` - color: #595959; -` - export const BIRTHDAY_EXTENSION_NAME = 'birthday' export type BirthdayExtensionState = { [BIRTHDAY_EXTENSION_NAME]: PlainDate | null } const minBirthday = new PlainDate(1900, 1, 1) const getInitialState = (): BirthdayExtensionState => ({ birthday: null }) +let validateUnderAge: boolean const BirthdayForm = ({ value, @@ -35,14 +25,23 @@ const BirthdayForm = ({ const { birthday } = value const showErrorMessage = touched || showRequired const projectConfig = useContext(ProjectConfigContext) + validateUnderAge = projectConfig.showBirthdayExtensionHint + const { t } = useTranslation('application') const getErrorMessage = (): string | null => { + const today = PlainDate.fromLocalDate(new Date()) + const underAge = today.subtract({ years: 16 }) + if (!birthday) { return 'Bitte geben Sie ein gültiges Geburtsdatum an.' } - if (birthday.isAfter(PlainDate.fromLocalDate(new Date()))) { + if (birthday.isAfter(today)) { return 'Das Geburtsdatum darf nicht in der Zukunft liegen.' } + if (birthday.isAfter(underAge) && projectConfig.showBirthdayExtensionHint) { + return t('extensions.birthdayHint') + } + return null } @@ -60,17 +59,6 @@ const BirthdayForm = ({ isValid={isValid || !showErrorMessage} maxDate={new Date()} disableFuture - sideComponent={ - <> - {projectConfig.showBirthdayMinorHint && ( - - - - )} - - } /> {showErrorMessage && } @@ -92,7 +80,9 @@ const BirthdayExtension: Extension = { return false } const today = PlainDate.fromLocalDate(new Date()) - return !birthday.isBefore(minBirthday) && !birthday.isAfter(today) + const underAge = today.subtract({ years: 16 }) + const underAgeCheck = birthday.isAfter(underAge) && validateUnderAge + return !birthday.isBefore(minBirthday) && !birthday.isAfter(today) && !underAgeCheck }, fromString: (value: string) => { const birthday = PlainDate.safeFromCustomFormat(value) diff --git a/administration/src/project-configs/bayern/config.ts b/administration/src/project-configs/bayern/config.ts index f7fd0b8b6..3f8323699 100644 --- a/administration/src/project-configs/bayern/config.ts +++ b/administration/src/project-configs/bayern/config.ts @@ -93,7 +93,7 @@ const config: ProjectConfig = { enabled: false, }, userImportApiEnabled: false, - showBirthdayMinorHint: false, + showBirthdayExtensionHint: false, } export default config diff --git a/administration/src/project-configs/getProjectConfig.ts b/administration/src/project-configs/getProjectConfig.ts index d28ef9f05..beccd6754 100644 --- a/administration/src/project-configs/getProjectConfig.ts +++ b/administration/src/project-configs/getProjectConfig.ts @@ -119,7 +119,7 @@ export type ProjectConfig = { selfServiceEnabled: boolean storesManagement: StoresManagementConfig userImportApiEnabled: boolean - showBirthdayMinorHint: boolean + showBirthdayExtensionHint: boolean } export const setProjectConfigOverride = (hostname: string): void => { diff --git a/administration/src/project-configs/koblenz/config.ts b/administration/src/project-configs/koblenz/config.ts index 3c86ae833..be3d21114 100644 --- a/administration/src/project-configs/koblenz/config.ts +++ b/administration/src/project-configs/koblenz/config.ts @@ -36,7 +36,7 @@ const config: ProjectConfig = { selfServiceEnabled: true, storesManagement: storesManagementConfig, userImportApiEnabled: true, - showBirthdayMinorHint: true, + showBirthdayExtensionHint: true, } export default config diff --git a/administration/src/project-configs/nuernberg/config.ts b/administration/src/project-configs/nuernberg/config.ts index 2a09f584d..e6ed409e9 100644 --- a/administration/src/project-configs/nuernberg/config.ts +++ b/administration/src/project-configs/nuernberg/config.ts @@ -61,7 +61,7 @@ const config: ProjectConfig = { selfServiceEnabled: false, storesManagement: storesManagementConfig, userImportApiEnabled: false, - showBirthdayMinorHint: false, + showBirthdayExtensionHint: false, } export default config diff --git a/administration/src/project-configs/showcase/config.ts b/administration/src/project-configs/showcase/config.ts index 86de8eca1..afe035146 100644 --- a/administration/src/project-configs/showcase/config.ts +++ b/administration/src/project-configs/showcase/config.ts @@ -36,7 +36,7 @@ const config: ProjectConfig = { enabled: false, }, userImportApiEnabled: false, - showBirthdayMinorHint: false, + showBirthdayExtensionHint: false, } export default config diff --git a/administration/src/util/PlainDate.ts b/administration/src/util/PlainDate.ts index 723906eb7..48c1dd002 100644 --- a/administration/src/util/PlainDate.ts +++ b/administration/src/util/PlainDate.ts @@ -1,4 +1,4 @@ -import { add, differenceInCalendarDays, format, isValid, parse } from 'date-fns' +import { add, differenceInCalendarDays, format, isValid, parse, sub } from 'date-fns' type DateDuration = { years?: number; months?: number; days?: number } @@ -125,6 +125,15 @@ class PlainDate { return PlainDate.fromLocalDate(add(this.toLocalDate(), duration)) } + /** + * Returns a new PlainDate corresponding to `this` minus `duration`. + * @param duration + */ + subtract(duration: DateDuration): PlainDate { + // Mirror the add() method but use date-fns/sub + return PlainDate.fromLocalDate(sub(this.toLocalDate(), duration)) + } + /** * Returns a new PlainDate corresponding to the result of adding `days` days to 1970-01-01 * @param days diff --git a/administration/src/util/translations/de.json b/administration/src/util/translations/de.json index 02232248f..b4ff67ad7 100644 --- a/administration/src/util/translations/de.json +++ b/administration/src/util/translations/de.json @@ -63,6 +63,9 @@ "givenInformationIsCorrectAndComplete": "Ich versichere, dass alle angegebenen Informationen korrekt und vollständig sind", "title": "Bayerische Ehrenamtskarte beantragen", "sentSuccessfully": "Erfolgreich gesendet", - "sentError": "Fehler beim Senden" + "sentError": "Fehler beim Senden", + "extensions": { + "birthdayHint": "Bei Minderjährigen unter 16 Jahren darf der KoblenzPass nur mit Einverständnis der Erziehungsberechtigten abgerufen werden." + } } } From e94603d6086e8699424463c87a8166c56020cbbf Mon Sep 17 00:00:00 2001 From: bahaa Date: Tue, 4 Feb 2025 21:03:39 +0200 Subject: [PATCH 6/6] 1804: Added birthdayHint translation --- administration/src/util/translations/de.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/administration/src/util/translations/de.json b/administration/src/util/translations/de.json index 54559b8ba..5a558e4b9 100644 --- a/administration/src/util/translations/de.json +++ b/administration/src/util/translations/de.json @@ -65,7 +65,10 @@ "sentSuccessfully": "Erfolgreich gesendet", "sentError": "Fehler beim Senden", "positiveAnswer": "Ja", - "negativeAnswer": "Nein" + "negativeAnswer": "Nein", + "extensions": { + "birthdayHint": "Bei Minderjährigen unter 16 Jahren darf der KoblenzPass nur mit Einverständnis der Erziehungsberechtigten abgerufen werden." + } }, "applications": { "allApplications": "Alle Anträge",