From 6e40bee199c8cb62579cef2bb665f7eb77ecbbeb Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 6 Nov 2024 20:42:55 +0100 Subject: [PATCH 1/6] 1670: add hints for name data privacy and referenceNr, adjust error message --- .../self-service/CardSelfServiceForm.tsx | 67 +++++++++++-------- .../self-service/CardSelfServiceView.tsx | 14 +++- .../components/FormErrorMessage.tsx | 31 +++++++++ ...ice.ts => useCardGeneratorSelfService.tsx} | 7 +- administration/src/cards/Card.test.ts | 23 +++++++ administration/src/cards/Card.ts | 28 +++++++- .../KoblenzReferenceNumberExtension.tsx | 24 ++++--- administration/src/errors/DefaultErrorMap.tsx | 3 +- 8 files changed, 152 insertions(+), 45 deletions(-) create mode 100644 administration/src/bp-modules/self-service/components/FormErrorMessage.tsx rename administration/src/bp-modules/self-service/hooks/{useCardGeneratorSelfService.ts => useCardGeneratorSelfService.tsx} (95%) diff --git a/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx b/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx index d7a193387..7deb97cfc 100644 --- a/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx +++ b/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx @@ -1,15 +1,18 @@ import { Checkbox, FormGroup, InputGroup, Intent } from '@blueprintjs/core' import InfoOutlined from '@mui/icons-material/InfoOutlined' -import { Alert, styled } from '@mui/material' +import { styled } from '@mui/material' import React, { ReactElement, useContext, useState } from 'react' -import { Card, isFullNameValid, isValid } from '../../cards/Card' +import { Card, getFullNameValidationErrorMessage, isFullNameValid, isValid } from '../../cards/Card' import ClearInputButton from '../../cards/extensions/components/ClearInputButton' import useWindowDimensions from '../../hooks/useWindowDimensions' import BasicDialog from '../../mui-modules/application/BasicDialog' import { ProjectConfigContext } from '../../project-configs/ProjectConfigContext' +import { useAppToaster } from '../AppToaster' import ExtensionForms from '../cards/ExtensionForms' +import { DataPrivacyAcceptingStatus } from './CardSelfServiceView' import { ActionButton } from './components/ActionButton' +import FormErrorMessage from './components/FormErrorMessage' import { IconTextButton } from './components/IconTextButton' import { UnderlineTextButton } from './components/UnderlineTextButton' @@ -19,11 +22,6 @@ const StyledCheckbox = styled(Checkbox)` margin-left: 4px; ` -const StyledAlert = styled(Alert)` - margin-bottom: 24px; - white-space: pre-line; -` - const Container = styled('div')` margin-bottom: 24px; ` @@ -31,22 +29,11 @@ const Container = styled('div')` type CardSelfServiceFormProps = { card: Card updateCard: (card: Partial) => void - dataPrivacyAccepted: boolean - setDataPrivacyAccepted: (value: boolean) => void + dataPrivacyAccepted: DataPrivacyAcceptingStatus + setDataPrivacyAccepted: (status: DataPrivacyAcceptingStatus) => void generateCards: () => Promise } -const getTooltipMessage = (cardsValid: boolean, dataPrivacyAccepted: boolean): string => { - const tooltipMessages: string[] = [] - if (!cardsValid) { - tooltipMessages.push('Mindestens eine Ihrer Angaben ist ungültig.') - } - if (!dataPrivacyAccepted) { - tooltipMessages.push('Bitte akzeptieren Sie die Datenschutzerklärung.') - } - - return tooltipMessages.join('\n') -} const CardSelfServiceForm = ({ card, updateCard, @@ -59,7 +46,24 @@ const CardSelfServiceForm = ({ const [openDataPrivacy, setOpenDataPrivacy] = useState(false) const [openReferenceInformation, setOpenReferenceInformation] = useState(false) const cardValid = isValid(card, { expirationDateNullable: true }) - const cardCreationDisabled = !cardValid || !dataPrivacyAccepted + const appToaster = useAppToaster() + + const createKoblenzPass = async () => { + if (dataPrivacyAccepted === DataPrivacyAcceptingStatus.untouched) { + setDataPrivacyAccepted(DataPrivacyAcceptingStatus.denied) + } + if (!cardValid || dataPrivacyAccepted !== DataPrivacyAcceptingStatus.accepted) { + appToaster?.show({ + message: ( + + ), + timeout: 0, + intent: 'danger', + }) + return + } + await generateCards() + } return ( <> @@ -80,23 +84,30 @@ const CardSelfServiceForm = ({ value={card.fullName} onChange={event => updateCard({ fullName: event.target.value })} /> + setOpenReferenceInformation(true)}> Informationen zur Referenznummer - setDataPrivacyAccepted(!dataPrivacyAccepted)}> + + setDataPrivacyAccepted( + dataPrivacyAccepted === DataPrivacyAcceptingStatus.accepted + ? DataPrivacyAcceptingStatus.denied + : DataPrivacyAcceptingStatus.accepted + ) + }> Ich akzeptiere die{' '} setOpenDataPrivacy(true)}>Datenschutzerklärung. + {dataPrivacyAccepted === DataPrivacyAcceptingStatus.denied && ( + + )} - {cardCreationDisabled && ( - - {getTooltipMessage(cardValid, dataPrivacyAccepted)} - - )} - + Pass erstellen { const projectConfig = useContext(ProjectConfigContext) - const [dataPrivacyAccepted, setDataPrivacyAccepted] = useState(false) + const [dataPrivacyCheckbox, setDataPrivacyCheckbox] = useState( + DataPrivacyAcceptingStatus.untouched + ) const { selfServiceState, setSelfServiceState, @@ -113,8 +121,8 @@ const CardSelfServiceView = (): ReactElement => { {selfServiceState === CardSelfServiceStep.form && ( setSelfServiceCard(updateCard(selfServiceCard, updatedCard))} generateCards={generateCards} /> diff --git a/administration/src/bp-modules/self-service/components/FormErrorMessage.tsx b/administration/src/bp-modules/self-service/components/FormErrorMessage.tsx new file mode 100644 index 000000000..70b6519b3 --- /dev/null +++ b/administration/src/bp-modules/self-service/components/FormErrorMessage.tsx @@ -0,0 +1,31 @@ +import InfoOutlined from '@mui/icons-material/InfoOutlined' +import { styled } from '@mui/material' +import React, { CSSProperties, ReactElement } from 'react' + +const Container = styled('div')` + margin: 6px 0; + color: #ba1a1a; + display: flex; + gap: 8px; + align-items: center; + font-size: 14px; +` + +type FormErrorMessageProps = { + errorMessage: string | null + style?: CSSProperties +} + +const FormErrorMessage = ({ errorMessage, style }: FormErrorMessageProps): ReactElement | null => { + if (!errorMessage) { + return null + } + return ( + + + {errorMessage} + + ) +} + +export default FormErrorMessage diff --git a/administration/src/bp-modules/self-service/hooks/useCardGeneratorSelfService.ts b/administration/src/bp-modules/self-service/hooks/useCardGeneratorSelfService.tsx similarity index 95% rename from administration/src/bp-modules/self-service/hooks/useCardGeneratorSelfService.ts rename to administration/src/bp-modules/self-service/hooks/useCardGeneratorSelfService.tsx index 041edde93..a7469dbed 100644 --- a/administration/src/bp-modules/self-service/hooks/useCardGeneratorSelfService.ts +++ b/administration/src/bp-modules/self-service/hooks/useCardGeneratorSelfService.tsx @@ -1,5 +1,5 @@ import { ApolloError } from '@apollo/client' -import { useCallback, useContext, useState } from 'react' +import React, { useCallback, useContext, useState } from 'react' import { Card, generateCardInfo, initializeCard } from '../../../cards/Card' import { generatePdf } from '../../../cards/PdfFactory' @@ -12,6 +12,7 @@ import { base64ToUint8Array, uint8ArrayToBase64 } from '../../../util/base64' import downloadDataUri from '../../../util/downloadDataUri' import getCustomDeepLinkFromQrCode from '../../../util/getCustomDeepLinkFromQrCode' import { useAppToaster } from '../../AppToaster' +import FormErrorMessage from '../components/FormErrorMessage' export enum CardSelfServiceStep { form, @@ -54,9 +55,9 @@ const useCardGeneratorSelfService = (): UseCardGeneratorSelfServiceReturn => { if (error instanceof ApolloError) { const { title } = getMessageFromApolloError(error) appToaster?.show({ - message: title, - intent: 'danger', + message: , timeout: 0, + intent: 'danger', }) } else { appToaster?.show({ diff --git a/administration/src/cards/Card.test.ts b/administration/src/cards/Card.test.ts index 0ed056912..98ad4c84c 100644 --- a/administration/src/cards/Card.test.ts +++ b/administration/src/cards/Card.test.ts @@ -2,6 +2,7 @@ import { BavariaCardType } from '../generated/card_pb' import { Region } from '../generated/graphql' import PlainDate from '../util/PlainDate' import { + MAX_NAME_LENGTH, generateCardInfo, getValueByCSVHeader, initializeCard, @@ -139,6 +140,28 @@ describe('Card', () => { }) }) + it.each(['$tefan Mayer', 'Karla K.', 'Karla 1234 '])( + 'should correctly identify invalid special characters in fullname', + fullName => { + const card = initializeCard(cardConfig, region, { fullName }) + expect(card.fullName).toBe(fullName) + expect(isValueValid(card, cardConfig, 'Name')).toBeFalsy() + expect(isValid(card)).toBeFalsy() + } + ) + + it.each(['Karla', 'Karl L'])('should correctly identify invalid fullname that is incomplete', fullName => { + const card = initializeCard(cardConfig, region, { fullName }) + expect(isValueValid(card, cardConfig, 'Name')).toBeFalsy() + expect(isValid(card)).toBeFalsy() + }) + + it(`should correctly identify invalid fullname that exceeds max length (${MAX_NAME_LENGTH} characters)`, () => { + const card = initializeCard(cardConfig, region, { fullName: 'Karl LauterLauterLauterLauterLauterLauterLauterbach' }) + expect(isValueValid(card, cardConfig, 'Name')).toBeFalsy() + expect(isValid(card)).toBeFalsy() + }) + describe('self service', () => { const cardConfig = { defaultValidity: { years: 3 }, diff --git a/administration/src/cards/Card.ts b/administration/src/cards/Card.ts index 873eac29a..8969bfb67 100644 --- a/administration/src/cards/Card.ts +++ b/administration/src/cards/Card.ts @@ -9,7 +9,7 @@ import { REGION_EXTENSION_NAME } from './extensions/RegionExtension' import Extensions, { Extension, ExtensionKey, ExtensionState, InferExtensionStateType } from './extensions/extensions' // Due to limited space on the cards -const MAX_NAME_LENGTH = 30 +export const MAX_NAME_LENGTH = 40 // Due to limited space on the qr code const MAX_ENCODED_NAME_LENGTH = 50 @@ -65,11 +65,22 @@ export const getExtensions = ({ extensions }: Card): ExtensionWithState[] => { export const hasInfiniteLifetime = (card: Card): boolean => getExtensions(card).some(({ extension, state }) => extension.causesInfiniteLifetime(state)) -export const isFullNameValid = ({ fullName }: Card): boolean => { +const containsNameSpecialCharacters = (fullName: string): boolean => + /[`!@#$%^&*()_+\-=\]{};':"\\|,.<>?~0123456789]/.test(fullName) + +const hasValidNameLength = (fullName: string): boolean => { const encodedName = new TextEncoder().encode(fullName) return fullName.length > 0 && encodedName.length <= MAX_ENCODED_NAME_LENGTH && fullName.length <= MAX_NAME_LENGTH } +const hasNameAndForename = (fullName: string): boolean => { + const names = fullName.split(' ') + return names.length > 1 && names.every(name => name.length > 1) +} + +export const isFullNameValid = ({ fullName }: Card): boolean => + hasValidNameLength(fullName) && hasNameAndForename(fullName) && !containsNameSpecialCharacters(fullName) + export const isExpirationDateValid = (card: Card, { nullable } = { nullable: false }): boolean => { const today = PlainDate.fromLocalDate(new Date()) const startDay = card.extensions.startDay @@ -180,3 +191,16 @@ export const updateCard = (oldCard: Card, updatedCard: Partial): Card => ( ...(updatedCard.extensions ?? {}), }, }) + +export const getFullNameValidationErrorMessage = (name: string): string | null => { + if (containsNameSpecialCharacters(name)) { + return 'Der Name darf keine Sonderzeichen oder Zahlen enthalten.' + } + if (!hasNameAndForename(name)) { + return 'Bitte geben Sie Ihren vollständigen Namen ein.' + } + if (!hasValidNameLength(name)) { + return `Der Name darf nicht länger als ${MAX_NAME_LENGTH} Zeichen sein` + } + return null +} diff --git a/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx b/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx index 39413ecd2..77694b17c 100644 --- a/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx +++ b/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx @@ -1,6 +1,7 @@ import { FormGroup, InputGroup, Intent } from '@blueprintjs/core' import React, { ReactElement } from 'react' +import FormErrorMessage from '../../bp-modules/self-service/components/FormErrorMessage' import useWindowDimensions from '../../hooks/useWindowDimensions' import ClearInputButton from './components/ClearInputButton' import { Extension, ExtensionComponentProps } from './extensions' @@ -11,6 +12,9 @@ type KoblenzReferenceNumberExtensionState = { [KOBLENZ_REFERENCE_NUMBER_EXTENSIO const KoblenzReferenceNumberMinLength = 4 const KoblenzReferenceNumberMaxLength = 15 +const hasSpecialChars = (referenceNr: string): boolean => /[`!@#$%^&*()_+\-=\]{};':"\\|,<>?~]/.test(referenceNr) +const hasInvalidLength = (referenceNumberLength: number): boolean => + referenceNumberLength < KoblenzReferenceNumberMinLength || referenceNumberLength > KoblenzReferenceNumberMaxLength const KoblenzReferenceNumberExtensionForm = ({ value, @@ -20,6 +24,16 @@ const KoblenzReferenceNumberExtensionForm = ({ const { viewportSmall } = useWindowDimensions() const clearInput = () => setValue({ koblenzReferenceNumber: '' }) + const getErrorMessage = (): string | null => { + if (hasSpecialChars(value.koblenzReferenceNumber)) { + return 'Das Aktenzeichen enthält ungültige Sonderzeichen.' + } + if (hasInvalidLength(value.koblenzReferenceNumber.length)) { + return `Das Aktenzeichen muss eine Länge zwischen ${KoblenzReferenceNumberMinLength} und ${KoblenzReferenceNumberMaxLength} haben.` + } + return null + } + return ( } - onChange={event => { - const value = event.target.value - if (value.length <= KoblenzReferenceNumberMaxLength) { - setValue({ koblenzReferenceNumber: value }) - } - }} + onChange={event => setValue({ koblenzReferenceNumber: event.target.value })} /> + ) } diff --git a/administration/src/errors/DefaultErrorMap.tsx b/administration/src/errors/DefaultErrorMap.tsx index 180a78c46..a9043f9f5 100644 --- a/administration/src/errors/DefaultErrorMap.tsx +++ b/administration/src/errors/DefaultErrorMap.tsx @@ -99,7 +99,8 @@ const defaultErrorMap = (extensions?: ErrorExtensions): GraphQLErrorMessage => { } case GraphQlExceptionCode.UserEntitlementExpired: return { - title: 'Sie sind nicht mehr berechtigt einen KoblenzPass zu erstellen.', + title: + 'Sie sind nicht länger berechtigt, einen KoblenzPass zu erstellen. Bitte kontaktieren Sie koblenzpass@stadt.koblenz.de für weitere Informationen.', } case GraphQlExceptionCode.MailNotSent: return { From 4a8ec1f54a66e5d655810783d90e6e22f86ce46e Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 12 Nov 2024 16:15:32 +0100 Subject: [PATCH 2/6] 1650: added line breaks to help texts --- .../self-service/CardSelfServiceForm.tsx | 22 +++++++++---------- administration/src/cards/Card.ts | 22 ++++++++++++++----- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx b/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx index 7deb97cfc..d05bcad8d 100644 --- a/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx +++ b/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx @@ -49,20 +49,20 @@ const CardSelfServiceForm = ({ const appToaster = useAppToaster() const createKoblenzPass = async () => { + if (cardValid && dataPrivacyAccepted === DataPrivacyAcceptingStatus.accepted) { + await generateCards() + return + } if (dataPrivacyAccepted === DataPrivacyAcceptingStatus.untouched) { setDataPrivacyAccepted(DataPrivacyAcceptingStatus.denied) } - if (!cardValid || dataPrivacyAccepted !== DataPrivacyAcceptingStatus.accepted) { - appToaster?.show({ - message: ( - - ), - timeout: 0, - intent: 'danger', - }) - return - } - await generateCards() + appToaster?.show({ + message: ( + + ), + timeout: 0, + intent: 'danger', + }) } return ( diff --git a/administration/src/cards/Card.ts b/administration/src/cards/Card.ts index 8969bfb67..1ea8914f4 100644 --- a/administration/src/cards/Card.ts +++ b/administration/src/cards/Card.ts @@ -73,13 +73,19 @@ const hasValidNameLength = (fullName: string): boolean => { return fullName.length > 0 && encodedName.length <= MAX_ENCODED_NAME_LENGTH && fullName.length <= MAX_NAME_LENGTH } +const multipleSpacePattern = /\s\s+/g +const containsMultipleSpaces = (fullName: string): boolean => multipleSpacePattern.test(fullName) + const hasNameAndForename = (fullName: string): boolean => { - const names = fullName.split(' ') + const names = fullName.replace(multipleSpacePattern, ' ').split(' ') return names.length > 1 && names.every(name => name.length > 1) } export const isFullNameValid = ({ fullName }: Card): boolean => - hasValidNameLength(fullName) && hasNameAndForename(fullName) && !containsNameSpecialCharacters(fullName) + hasValidNameLength(fullName) && + hasNameAndForename(fullName) && + !containsNameSpecialCharacters(fullName) && + !containsMultipleSpaces(fullName) export const isExpirationDateValid = (card: Card, { nullable } = { nullable: false }): boolean => { const today = PlainDate.fromLocalDate(new Date()) @@ -193,14 +199,18 @@ export const updateCard = (oldCard: Card, updatedCard: Partial): Card => ( }) export const getFullNameValidationErrorMessage = (name: string): string | null => { + const errors: string[] = [] if (containsNameSpecialCharacters(name)) { - return 'Der Name darf keine Sonderzeichen oder Zahlen enthalten.' + errors.push('Der Name darf keine Sonderzeichen oder Zahlen enthalten.') + } + if (containsMultipleSpaces(name)) { + errors.push('Der Name darf nicht mehrere aufeinanderfolge Leerzeichen enthalten.') } if (!hasNameAndForename(name)) { - return 'Bitte geben Sie Ihren vollständigen Namen ein.' + errors.push('Bitte geben Sie Ihren vollständigen Namen ein.') } if (!hasValidNameLength(name)) { - return `Der Name darf nicht länger als ${MAX_NAME_LENGTH} Zeichen sein` + errors.push(`Der Name darf nicht länger als ${MAX_NAME_LENGTH} Zeichen sein`) } - return null + return errors.join(' ') } From cd7d8dba63eb107e193f356e915e821f2ea8d43a Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 13 Nov 2024 12:17:49 +0100 Subject: [PATCH 3/6] 1670: disallow multi spaces. move replace multi spaces to utils, fix small issues --- .../self-service/CardSelfServiceForm.tsx | 3 ++- administration/src/cards/Card.ts | 16 ++++------------ administration/src/util/helper.ts | 3 +++ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx b/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx index bae404cf8..95eb0376c 100644 --- a/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx +++ b/administration/src/bp-modules/self-service/CardSelfServiceForm.tsx @@ -8,6 +8,7 @@ import ClearInputButton from '../../cards/extensions/components/ClearInputButton import useWindowDimensions from '../../hooks/useWindowDimensions' import BasicDialog from '../../mui-modules/application/BasicDialog' import { ProjectConfigContext } from '../../project-configs/ProjectConfigContext' +import { removeMultipleSpaces } from '../../util/helper' import { useAppToaster } from '../AppToaster' import ExtensionForms from '../cards/ExtensionForms' import { DataPrivacyAcceptingStatus } from './CardSelfServiceView' @@ -82,7 +83,7 @@ const CardSelfServiceForm = ({ } intent={isFullNameValid(card) ? undefined : Intent.DANGER} value={card.fullName} - onChange={event => updateCard({ fullName: event.target.value })} + onChange={event => updateCard({ fullName: removeMultipleSpaces(event.target.value) })} /> diff --git a/administration/src/cards/Card.ts b/administration/src/cards/Card.ts index 36f7c3603..63dbe7c28 100644 --- a/administration/src/cards/Card.ts +++ b/administration/src/cards/Card.ts @@ -5,11 +5,12 @@ import { CardExtensions, CardInfo } from '../generated/card_pb' import { Region } from '../generated/graphql' import { CardConfig } from '../project-configs/getProjectConfig' import PlainDate from '../util/PlainDate' +import { removeMultipleSpaces } from '../util/helper' import { REGION_EXTENSION_NAME } from './extensions/RegionExtension' import Extensions, { Extension, ExtensionKey, ExtensionState, InferExtensionStateType } from './extensions/extensions' // Due to limited space on the cards -export const MAX_NAME_LENGTH = 40 +export const MAX_NAME_LENGTH = 30 // Due to limited space on the qr code const MAX_ENCODED_NAME_LENGTH = 50 @@ -75,19 +76,13 @@ const hasValidNameLength = (fullName: string): boolean => { return fullName.length > 0 && encodedName.length <= MAX_ENCODED_NAME_LENGTH && fullName.length <= MAX_NAME_LENGTH } -const multipleSpacePattern = /\s\s+/g -const containsMultipleSpaces = (fullName: string): boolean => multipleSpacePattern.test(fullName) - const hasNameAndForename = (fullName: string): boolean => { - const names = fullName.replace(multipleSpacePattern, ' ').split(' ') + const names = removeMultipleSpaces(fullName).split(' ') return names.length > 1 && names.every(name => name.length > 1) } export const isFullNameValid = ({ fullName }: Card): boolean => - hasValidNameLength(fullName) && - hasNameAndForename(fullName) && - !containsNameSpecialCharacters(fullName) && - !containsMultipleSpaces(fullName) + hasValidNameLength(fullName) && hasNameAndForename(fullName) && !containsNameSpecialCharacters(fullName) export const isExpirationDateValid = (card: Card, { nullable } = { nullable: false }): boolean => { const today = PlainDate.fromLocalDate(new Date()) @@ -205,9 +200,6 @@ export const getFullNameValidationErrorMessage = (name: string): string | null = if (containsNameSpecialCharacters(name)) { errors.push('Der Name darf keine Sonderzeichen oder Zahlen enthalten.') } - if (containsMultipleSpaces(name)) { - errors.push('Der Name darf nicht mehrere aufeinanderfolge Leerzeichen enthalten.') - } if (!hasNameAndForename(name)) { errors.push('Bitte geben Sie Ihren vollständigen Namen ein.') } diff --git a/administration/src/util/helper.ts b/administration/src/util/helper.ts index 535b442d9..25eaa4ee5 100644 --- a/administration/src/util/helper.ts +++ b/administration/src/util/helper.ts @@ -10,3 +10,6 @@ export const updateArrayItem = (array: T[], updatedItem: T, index: number): T // eslint-disable-next-line @typescript-eslint/no-explicit-any export type UnionToIntersection = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never + +const multipleSpacePattern = /\s\s+/g +export const removeMultipleSpaces = (value: string): string => value.replace(multipleSpacePattern, ' ') From ca0080a1053cb1e3505fae579d2f434bee2870f0 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 13 Nov 2024 20:18:22 +0100 Subject: [PATCH 4/6] 1670: add check for emojis to name and referenceNr, allow empty space at the beginning and end of the name but trim it in generateCardInfo --- .../components/FormErrorMessage.tsx | 10 +++------- administration/src/cards/Card.test.ts | 12 +++++++++++- administration/src/cards/Card.ts | 17 ++++++++--------- .../KoblenzReferenceNumberExtension.tsx | 7 +++++-- administration/src/util/helper.ts | 7 +++++++ 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/administration/src/bp-modules/self-service/components/FormErrorMessage.tsx b/administration/src/bp-modules/self-service/components/FormErrorMessage.tsx index 70b6519b3..1e9cea2eb 100644 --- a/administration/src/bp-modules/self-service/components/FormErrorMessage.tsx +++ b/administration/src/bp-modules/self-service/components/FormErrorMessage.tsx @@ -16,16 +16,12 @@ type FormErrorMessageProps = { style?: CSSProperties } -const FormErrorMessage = ({ errorMessage, style }: FormErrorMessageProps): ReactElement | null => { - if (!errorMessage) { - return null - } - return ( +const FormErrorMessage = ({ errorMessage, style }: FormErrorMessageProps): ReactElement | null => + errorMessage ? ( {errorMessage} - ) -} + ) : null export default FormErrorMessage diff --git a/administration/src/cards/Card.test.ts b/administration/src/cards/Card.test.ts index 98ad4c84c..27f3b9b7b 100644 --- a/administration/src/cards/Card.test.ts +++ b/administration/src/cards/Card.test.ts @@ -140,7 +140,7 @@ describe('Card', () => { }) }) - it.each(['$tefan Mayer', 'Karla K.', 'Karla 1234 '])( + it.each(['$tefan Mayer', 'Karla K.', 'Karla Karls😀'])( 'should correctly identify invalid special characters in fullname', fullName => { const card = initializeCard(cardConfig, region, { fullName }) @@ -150,6 +150,16 @@ describe('Card', () => { } ) + it.each([' Karla Koblenz', ' Karla Karl', ' Karla Karls '])( + 'should correctly create a card even with whitespace in the beginning and end', + fullName => { + const card = initializeCard(cardConfig, region, { fullName }) + expect(card.fullName).toBe(fullName) + expect(isValueValid(card, cardConfig, 'Name')).toBeTruthy() + expect(isValid(card)).toBeTruthy() + } + ) + it.each(['Karla', 'Karl L'])('should correctly identify invalid fullname that is incomplete', fullName => { const card = initializeCard(cardConfig, region, { fullName }) expect(isValueValid(card, cardConfig, 'Name')).toBeFalsy() diff --git a/administration/src/cards/Card.ts b/administration/src/cards/Card.ts index 63dbe7c28..f328b4322 100644 --- a/administration/src/cards/Card.ts +++ b/administration/src/cards/Card.ts @@ -5,7 +5,7 @@ import { CardExtensions, CardInfo } from '../generated/card_pb' import { Region } from '../generated/graphql' import { CardConfig } from '../project-configs/getProjectConfig' import PlainDate from '../util/PlainDate' -import { removeMultipleSpaces } from '../util/helper' +import { containsNoEmojis, containsSpecialCharacters, removeMultipleSpaces } from '../util/helper' import { REGION_EXTENSION_NAME } from './extensions/RegionExtension' import Extensions, { Extension, ExtensionKey, ExtensionState, InferExtensionStateType } from './extensions/extensions' @@ -67,22 +67,21 @@ export const getExtensions = ({ extensions }: Card): ExtensionWithState[] => { export const hasInfiniteLifetime = (card: Card): boolean => getExtensions(card).some(({ extension, state }) => extension.causesInfiniteLifetime(state)) - -const containsNameSpecialCharacters = (fullName: string): boolean => - /[`!@#$%^&*()_+\-=\]{};':"\\|,.<>?~0123456789]/.test(fullName) - const hasValidNameLength = (fullName: string): boolean => { const encodedName = new TextEncoder().encode(fullName) return fullName.length > 0 && encodedName.length <= MAX_ENCODED_NAME_LENGTH && fullName.length <= MAX_NAME_LENGTH } const hasNameAndForename = (fullName: string): boolean => { - const names = removeMultipleSpaces(fullName).split(' ') + const names = removeMultipleSpaces(fullName).trim().split(' ') return names.length > 1 && names.every(name => name.length > 1) } export const isFullNameValid = ({ fullName }: Card): boolean => - hasValidNameLength(fullName) && hasNameAndForename(fullName) && !containsNameSpecialCharacters(fullName) + hasValidNameLength(fullName) && + hasNameAndForename(fullName) && + containsNoEmojis(fullName) && + !containsSpecialCharacters(fullName) export const isExpirationDateValid = (card: Card, { nullable } = { nullable: false }): boolean => { const today = PlainDate.fromLocalDate(new Date()) @@ -115,7 +114,7 @@ export const generateCardInfo = (card: Card): CardInfo => { expirationDate !== null && !hasInfiniteLifetime(card) ? Math.max(expirationDate.toDaysSinceEpoch(), 0) : undefined return new CardInfo({ - fullName: card.fullName, + fullName: card.fullName.trim(), expirationDay, extensions: new CardExtensions(extensionsMessage), }) @@ -197,7 +196,7 @@ export const updateCard = (oldCard: Card, updatedCard: Partial): Card => ( export const getFullNameValidationErrorMessage = (name: string): string | null => { const errors: string[] = [] - if (containsNameSpecialCharacters(name)) { + if (!containsNoEmojis(name) || containsSpecialCharacters(name)) { errors.push('Der Name darf keine Sonderzeichen oder Zahlen enthalten.') } if (!hasNameAndForename(name)) { diff --git a/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx b/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx index caa31e7ef..c1cce32fb 100644 --- a/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx +++ b/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx @@ -3,6 +3,7 @@ import React, { ReactElement } from 'react' import FormErrorMessage from '../../bp-modules/self-service/components/FormErrorMessage' import useWindowDimensions from '../../hooks/useWindowDimensions' +import { containsNoEmojis } from '../../util/helper' import ClearInputButton from './components/ClearInputButton' import { Extension, ExtensionComponentProps } from './extensions' @@ -25,7 +26,7 @@ const KoblenzReferenceNumberExtensionForm = ({ const clearInput = () => setValue({ koblenzReferenceNumber: '' }) const getErrorMessage = (): string | null => { - if (hasSpecialChars(value.koblenzReferenceNumber)) { + if (hasSpecialChars(value.koblenzReferenceNumber) || !containsNoEmojis(value.koblenzReferenceNumber)) { return 'Das Aktenzeichen enthält ungültige Sonderzeichen.' } if (hasInvalidLength(value.koblenzReferenceNumber.length)) { @@ -71,7 +72,9 @@ const KoblenzReferenceNumberExtension: Extension= KoblenzReferenceNumberMinLength && - koblenzReferenceNumber.length <= KoblenzReferenceNumberMaxLength + koblenzReferenceNumber.length <= KoblenzReferenceNumberMaxLength && + !hasSpecialChars(koblenzReferenceNumber) && + containsNoEmojis(koblenzReferenceNumber) ) }, fromString: value => ({ koblenzReferenceNumber: value }), diff --git a/administration/src/util/helper.ts b/administration/src/util/helper.ts index 25eaa4ee5..61ffb73ad 100644 --- a/administration/src/util/helper.ts +++ b/administration/src/util/helper.ts @@ -13,3 +13,10 @@ export type UnionToIntersection = (U extends any ? (x: U) => void : never) ex const multipleSpacePattern = /\s\s+/g export const removeMultipleSpaces = (value: string): string => value.replace(multipleSpacePattern, ' ') +export const containsSpecialCharacters = (value: string): boolean => + /[`!@#$%^&*()_+\-=\]{};':"\\|,.<>?~0123456789]/.test(value) + +export const containsNoEmojis = (value: string): boolean => + /^(?!.*(?:\uD83D(?:[\uDC76\uDC66\uDC67](?:\uD83C[\uDFFB-\uDFFF])?|\uDC68(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92]|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]))?)|\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D(?:\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC68\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92])|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]|\u2764(?:\uFE0F\u200D\uD83D(?:\uDC8B\u200D\uD83D\uDC68|\uDC68)|\u200D\uD83D(?:\uDC8B\u200D\uD83D\uDC68|\uDC68)))))?|\uDC69(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92]|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]))?)|\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D(?:\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92])|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]|\u2764(?:\uFE0F\u200D\uD83D(?:\uDC8B\u200D\uD83D[\uDC68\uDC69]|[\uDC68\uDC69])|\u200D\uD83D(?:\uDC8B\u200D\uD83D[\uDC68\uDC69]|[\uDC68\uDC69])))))?|[\uDC74\uDC75](?:\uD83C[\uDFFB-\uDFFF])?|\uDC6E(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDD75(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC82\uDC77](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDC78(?:\uD83C[\uDFFB-\uDFFF])?|\uDC73(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDC72(?:\uD83C[\uDFFB-\uDFFF])?|\uDC71(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC70\uDC7C](?:\uD83C[\uDFFB-\uDFFF])?|[\uDE4D\uDE4E\uDE45\uDE46\uDC81\uDE4B\uDE47\uDC86\uDC87\uDEB6](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC83\uDD7A](?:\uD83C[\uDFFB-\uDFFF])?|\uDC6F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|[\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|\uDD74(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|\uDDE3\uFE0F?|[\uDEA3\uDEB4\uDEB5](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDCAA\uDC48\uDC49\uDC46\uDD95\uDC47\uDD96](?:\uD83C[\uDFFB-\uDFFF])?|\uDD90(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\uDC4C-\uDC4E\uDC4A\uDC4B\uDC4F\uDC50\uDE4C\uDE4F\uDC85\uDC42\uDC43](?:\uD83C[\uDFFB-\uDFFF])?|\uDC41(?:(?:\uFE0F(?:\u200D\uD83D\uDDE8\uFE0F?)?|\u200D\uD83D\uDDE8\uFE0F?))?|[\uDDE8\uDDEF\uDD73\uDD76\uDECD\uDC3F\uDD4A\uDD77\uDD78\uDDFA\uDEE3\uDEE4\uDEE2\uDEF3\uDEE5\uDEE9\uDEF0\uDECE\uDD70\uDD79\uDDBC\uDDA5\uDDA8\uDDB1\uDDB2\uDCFD\uDD6F\uDDDE\uDDF3\uDD8B\uDD8A\uDD8C\uDD8D\uDDC2\uDDD2\uDDD3\uDD87\uDDC3\uDDC4\uDDD1\uDDDD\uDEE0\uDDE1\uDEE1\uDDDC\uDECF\uDECB\uDD49]\uFE0F?|[\uDE00-\uDE06\uDE09-\uDE0B\uDE0E\uDE0D\uDE18\uDE17\uDE19\uDE1A\uDE42\uDE10\uDE11\uDE36\uDE44\uDE0F\uDE23\uDE25\uDE2E\uDE2F\uDE2A\uDE2B\uDE34\uDE0C\uDE1B-\uDE1D\uDE12-\uDE15\uDE43\uDE32\uDE41\uDE16\uDE1E\uDE1F\uDE24\uDE22\uDE2D\uDE26-\uDE29\uDE2C\uDE30\uDE31\uDE33\uDE35\uDE21\uDE20\uDE37\uDE07\uDE08\uDC7F\uDC79\uDC7A\uDC80\uDC7B\uDC7D\uDC7E\uDCA9\uDE3A\uDE38\uDE39\uDE3B-\uDE3D\uDE40\uDE3F\uDE3E\uDE48-\uDE4A\uDC64\uDC65\uDC6B-\uDC6D\uDC8F\uDC91\uDC6A\uDC63\uDC40\uDC45\uDC44\uDC8B\uDC98\uDC93-\uDC97\uDC99-\uDC9C\uDDA4\uDC9D-\uDC9F\uDC8C\uDCA4\uDCA2\uDCA3\uDCA5\uDCA6\uDCA8\uDCAB-\uDCAD\uDC53-\uDC62\uDC51\uDC52\uDCFF\uDC84\uDC8D\uDC8E\uDC35\uDC12\uDC36\uDC15\uDC29\uDC3A\uDC31\uDC08\uDC2F\uDC05\uDC06\uDC34\uDC0E\uDC2E\uDC02-\uDC04\uDC37\uDC16\uDC17\uDC3D\uDC0F\uDC11\uDC10\uDC2A\uDC2B\uDC18\uDC2D\uDC01\uDC00\uDC39\uDC30\uDC07\uDC3B\uDC28\uDC3C\uDC3E\uDC14\uDC13\uDC23-\uDC27\uDC38\uDC0A\uDC22\uDC0D\uDC32\uDC09\uDC33\uDC0B\uDC2C\uDC1F-\uDC21\uDC19\uDC1A\uDC0C\uDC1B-\uDC1E\uDC90\uDCAE\uDD2A\uDDFE\uDDFB\uDC92\uDDFC\uDDFD\uDD4C\uDD4D\uDD4B\uDC88\uDE82-\uDE8A\uDE9D\uDE9E\uDE8B-\uDE8E\uDE90-\uDE9C\uDEB2\uDEF4\uDEF9\uDEF5\uDE8F\uDEA8\uDEA5\uDEA6\uDED1\uDEA7\uDEF6\uDEA4\uDEA2\uDEEB\uDEEC\uDCBA\uDE81\uDE9F-\uDEA1\uDE80\uDEF8\uDD5B\uDD67\uDD50\uDD5C\uDD51\uDD5D\uDD52\uDD5E\uDD53\uDD5F\uDD54\uDD60\uDD55\uDD61\uDD56\uDD62\uDD57\uDD63\uDD58\uDD64\uDD59\uDD65\uDD5A\uDD66\uDD25\uDCA7\uDEF7\uDD2E\uDD07-\uDD0A\uDCE2\uDCE3\uDCEF\uDD14\uDD15\uDCFB\uDCF1\uDCF2\uDCDE-\uDCE0\uDD0B\uDD0C\uDCBB\uDCBD-\uDCC0\uDCFA\uDCF7-\uDCF9\uDCFC\uDD0D\uDD0E\uDCA1\uDD26\uDCD4-\uDCDA\uDCD3\uDCD2\uDCC3\uDCDC\uDCC4\uDCF0\uDCD1\uDD16\uDCB0\uDCB4-\uDCB8\uDCB3\uDCB9\uDCB1\uDCB2\uDCE7-\uDCE9\uDCE4-\uDCE6\uDCEB\uDCEA\uDCEC-\uDCEE\uDCDD\uDCBC\uDCC1\uDCC2\uDCC5-\uDCD0\uDD12\uDD13\uDD0F-\uDD11\uDD28\uDD2B\uDD27\uDD29\uDD17\uDD2C\uDD2D\uDCE1\uDC89\uDC8A\uDEAA\uDEBD\uDEBF\uDEC1\uDED2\uDEAC\uDDFF\uDEAE\uDEB0\uDEB9-\uDEBC\uDEBE\uDEC2-\uDEC5\uDEB8\uDEAB\uDEB3\uDEAD\uDEAF\uDEB1\uDEB7\uDCF5\uDD1E\uDD03\uDD04\uDD19-\uDD1D\uDED0\uDD4E\uDD2F\uDD00-\uDD02\uDD3C\uDD3D\uDD05\uDD06\uDCF6\uDCF3\uDCF4\uDD31\uDCDB\uDD30\uDD1F\uDCAF\uDD20-\uDD24\uDD36-\uDD3B\uDCA0\uDD18\uDD32-\uDD35\uDEA9])|\uD83E(?:[\uDDD2\uDDD1\uDDD3](?:\uD83C[\uDFFB-\uDFFF])?|[\uDDB8\uDDB9](?:\u200D(?:[\u2640\u2642]\uFE0F?))?|[\uDD34\uDDD5\uDDD4\uDD35\uDD30\uDD31\uDD36](?:\uD83C[\uDFFB-\uDFFF])?|[\uDDD9-\uDDDD](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?)|\u200D(?:[\u2640\u2642]\uFE0F?)))?|[\uDDDE\uDDDF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?|[\uDD26\uDD37](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDDD6-\uDDD8](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?)|\u200D(?:[\u2640\u2642]\uFE0F?)))?|\uDD38(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDD3C(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|[\uDD3D\uDD3E\uDD39](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDD33\uDDB5\uDDB6\uDD1E\uDD18\uDD19\uDD1B\uDD1C\uDD1A\uDD1F\uDD32](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD23\uDD70\uDD17\uDD29\uDD14\uDD28\uDD10\uDD24\uDD11\uDD2F\uDD75\uDD76\uDD2A\uDD2C\uDD12\uDD15\uDD22\uDD2E\uDD27\uDD20\uDD21\uDD73\uDD74\uDD7A\uDD25\uDD2B\uDD2D\uDDD0\uDD13\uDD16\uDD3A\uDD1D\uDDB0-\uDDB3\uDDE0\uDDB4\uDDB7\uDDE1\uDD7D\uDD7C\uDDE3-\uDDE6\uDD7E\uDD7F\uDDE2\uDD8D\uDD8A\uDD9D\uDD81\uDD84\uDD93\uDD8C\uDD99\uDD92\uDD8F\uDD9B\uDD94\uDD87\uDD98\uDDA1\uDD83\uDD85\uDD86\uDDA2\uDD89\uDD9A\uDD9C\uDD8E\uDD95\uDD96\uDD88\uDD80\uDD9E\uDD90\uDD91\uDD8B\uDD97\uDD82\uDD9F\uDDA0\uDD40\uDD6D\uDD5D\uDD65\uDD51\uDD54\uDD55\uDD52\uDD6C\uDD66\uDD5C\uDD50\uDD56\uDD68\uDD6F\uDD5E\uDDC0\uDD69\uDD53\uDD6A\uDD59\uDD5A\uDD58\uDD63\uDD57\uDDC2\uDD6B\uDD6E\uDD5F-\uDD61\uDDC1\uDD67\uDD5B\uDD42\uDD43\uDD64\uDD62\uDD44\uDDED\uDDF1\uDDF3\uDDE8\uDDE7\uDD47-\uDD49\uDD4E\uDD4F\uDD4D\uDD4A\uDD4B\uDD45\uDD4C\uDDFF\uDDE9\uDDF8\uDD41\uDDEE\uDDFE\uDDF0\uDDF2\uDDEA-\uDDEC\uDDEF\uDDF4-\uDDF7\uDDF9-\uDDFD])|[\u263A\u2639\u2620]\uFE0F?|\uD83C(?:\uDF85(?:\uD83C[\uDFFB-\uDFFF])?|\uDFC3(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFC7\uDFC2](?:\uD83C[\uDFFB-\uDFFF])?|\uDFCC(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFC4\uDFCA](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDFCB(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFCE\uDFCD\uDFF5\uDF36\uDF7D\uDFD4-\uDFD6\uDFDC-\uDFDF\uDFDB\uDFD7\uDFD8\uDFDA\uDFD9\uDF21\uDF24-\uDF2C\uDF97\uDF9F\uDF96\uDF99-\uDF9B\uDF9E\uDFF7\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37]\uFE0F?|\uDFF4(?:(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F|\uDC73\uDB40\uDC63\uDB40\uDC74\uDB40\uDC7F|\uDC77\uDB40\uDC6C\uDB40\uDC73\uDB40\uDC7F)))?|\uDFF3(?:(?:\uFE0F(?:\u200D\uD83C\uDF08)?|\u200D\uD83C\uDF08))?|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|[\uDFFB-\uDFFF\uDF92\uDFA9\uDF93\uDF38-\uDF3C\uDF37\uDF31-\uDF35\uDF3E-\uDF43\uDF47-\uDF53\uDF45\uDF46\uDF3D\uDF44\uDF30\uDF5E\uDF56\uDF57\uDF54\uDF5F\uDF55\uDF2D-\uDF2F\uDF73\uDF72\uDF7F\uDF71\uDF58-\uDF5D\uDF60\uDF62-\uDF65\uDF61\uDF66-\uDF6A\uDF82\uDF70\uDF6B-\uDF6F\uDF7C\uDF75\uDF76\uDF7E\uDF77-\uDF7B\uDF74\uDFFA\uDF0D-\uDF10\uDF0B\uDFE0-\uDFE6\uDFE8-\uDFED\uDFEF\uDFF0\uDF01\uDF03-\uDF07\uDF09\uDF0C\uDFA0-\uDFA2\uDFAA\uDF11-\uDF20\uDF00\uDF08\uDF02\uDF0A\uDF83\uDF84\uDF86-\uDF8B\uDF8D-\uDF91\uDF80\uDF81\uDFAB\uDFC6\uDFC5\uDFC0\uDFD0\uDFC8\uDFC9\uDFBE\uDFB3\uDFCF\uDFD1-\uDFD3\uDFF8\uDFA3\uDFBD\uDFBF\uDFAF\uDFB1\uDFAE\uDFB0\uDFB2\uDCCF\uDC04\uDFB4\uDFAD\uDFA8\uDFBC\uDFB5\uDFB6\uDFA4\uDFA7\uDFB7-\uDFBB\uDFA5\uDFAC\uDFEE\uDFF9\uDFE7\uDFA6\uDD8E\uDD91-\uDD9A\uDE01\uDE36\uDE2F\uDE50\uDE39\uDE1A\uDE32\uDE51\uDE38\uDE34\uDE33\uDE3A\uDE35\uDFC1\uDF8C])|\u26F7\uFE0F?|\u26F9(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\u261D\u270C](?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\u270B\u270A](?:\uD83C[\uDFFB-\uDFFF])?|\u270D(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\u2764\u2763\u26D1\u2618\u26F0\u26E9\u2668\u26F4\u2708\u23F1\u23F2\u2600\u2601\u26C8\u2602\u26F1\u2744\u2603\u2604\u26F8\u2660\u2665\u2666\u2663\u260E\u2328\u2709\u270F\u2712\u2702\u26CF\u2692\u2694\u2699\u2696\u26D3\u2697\u26B0\u26B1\u26A0\u2622\u2623\u2B06\u2197\u27A1\u2198\u2B07\u2199\u2B05\u2196\u2195\u2194\u21A9\u21AA\u2934\u2935\u269B\u267E\u2721\u2638\u262F\u271D\u2626\u262A\u262E\u25B6\u23ED\u23EF\u25C0\u23EE\u23F8-\u23FA\u23CF\u2640\u2642\u2695\u267B\u269C\u2611\u2714\u2716\u303D\u2733\u2734\u2747\u203C\u2049\u3030\u00A9\u00AE\u2122]\uFE0F?|[\u0023\u002A\u0030-\u0039](?:\uFE0F\u20E3|\u20E3)|[\u2139\u24C2\u3297\u3299\u25AA\u25AB\u25FB\u25FC]\uFE0F?|[\u2615\u26EA\u26F2\u26FA\u26FD\u2693\u26F5\u231B\u23F3\u231A\u23F0\u2B50\u26C5\u2614\u26A1\u26C4\u2728\u26BD\u26BE\u26F3\u267F\u26D4\u2648-\u2653\u26CE\u23E9-\u23EC\u2B55\u2705\u274C\u274E\u2795-\u2797\u27B0\u27BF\u2753-\u2755\u2757\u25FD\u25FE\u2B1B\u2B1C\u26AA\u26AB])).*/.test( + value + ) From b4c271a6a76c4880e6cdb7b1017673e1a52c770f Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 15 Nov 2024 14:32:15 +0100 Subject: [PATCH 5/6] 1670: remove emoji check, add xregexp check for common and latin charset --- administration/package.json | 7 +++--- administration/src/cards/Card.test.ts | 2 +- administration/src/cards/Card.ts | 6 ++--- .../KoblenzReferenceNumberExtension.tsx | 6 ++--- administration/src/util/helper.ts | 12 ++++++---- package-lock.json | 24 +++++++++++++++++-- 6 files changed, 40 insertions(+), 17 deletions(-) diff --git a/administration/package.json b/administration/package.json index c15d5a761..4c0001733 100644 --- a/administration/package.json +++ b/administration/package.json @@ -27,15 +27,16 @@ "date-fns": "^2.30.0", "graphql": "^16.8.1", "localforage": "^1.10.0", - "normalize.css": "^8.0.1", "normalize-strings": "^1.1.1", + "normalize.css": "^8.0.1", "notistack": "^3.0.1", "pdf-lib": "^1.17.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-flip-move": "^3.0.5", "react-router-dom": "^6.14.0", - "styled-components": "^5.3.11" + "styled-components": "^5.3.11", + "xregexp": "^5.1.1" }, "devDependencies": { "@babel/core": "^7.22.5", @@ -73,8 +74,8 @@ "dotenv": "^16.3.1", "dotenv-expand": "^10.0.0", "eslint": "^8.48.0", - "eslint-config-prettier": "^8.8.0", "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^28.2.0", "eslint-plugin-jsx-a11y": "^6.8.0", diff --git a/administration/src/cards/Card.test.ts b/administration/src/cards/Card.test.ts index 27f3b9b7b..4cfc1d0db 100644 --- a/administration/src/cards/Card.test.ts +++ b/administration/src/cards/Card.test.ts @@ -140,7 +140,7 @@ describe('Card', () => { }) }) - it.each(['$tefan Mayer', 'Karla K.', 'Karla Karls😀'])( + it.each(['$tefan Mayer', 'Karla K.', 'Karla Karls😀', 'إئبآء؟ؤئحجرزش'])( 'should correctly identify invalid special characters in fullname', fullName => { const card = initializeCard(cardConfig, region, { fullName }) diff --git a/administration/src/cards/Card.ts b/administration/src/cards/Card.ts index f328b4322..b821b3af2 100644 --- a/administration/src/cards/Card.ts +++ b/administration/src/cards/Card.ts @@ -5,7 +5,7 @@ import { CardExtensions, CardInfo } from '../generated/card_pb' import { Region } from '../generated/graphql' import { CardConfig } from '../project-configs/getProjectConfig' import PlainDate from '../util/PlainDate' -import { containsNoEmojis, containsSpecialCharacters, removeMultipleSpaces } from '../util/helper' +import { containsOnlyLatinAndCommonCharset, containsSpecialCharacters, removeMultipleSpaces } from '../util/helper' import { REGION_EXTENSION_NAME } from './extensions/RegionExtension' import Extensions, { Extension, ExtensionKey, ExtensionState, InferExtensionStateType } from './extensions/extensions' @@ -80,7 +80,7 @@ const hasNameAndForename = (fullName: string): boolean => { export const isFullNameValid = ({ fullName }: Card): boolean => hasValidNameLength(fullName) && hasNameAndForename(fullName) && - containsNoEmojis(fullName) && + containsOnlyLatinAndCommonCharset(fullName) && !containsSpecialCharacters(fullName) export const isExpirationDateValid = (card: Card, { nullable } = { nullable: false }): boolean => { @@ -196,7 +196,7 @@ export const updateCard = (oldCard: Card, updatedCard: Partial): Card => ( export const getFullNameValidationErrorMessage = (name: string): string | null => { const errors: string[] = [] - if (!containsNoEmojis(name) || containsSpecialCharacters(name)) { + if (!containsOnlyLatinAndCommonCharset(name) || containsSpecialCharacters(name)) { errors.push('Der Name darf keine Sonderzeichen oder Zahlen enthalten.') } if (!hasNameAndForename(name)) { diff --git a/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx b/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx index c1cce32fb..041b139e5 100644 --- a/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx +++ b/administration/src/cards/extensions/KoblenzReferenceNumberExtension.tsx @@ -3,7 +3,6 @@ import React, { ReactElement } from 'react' import FormErrorMessage from '../../bp-modules/self-service/components/FormErrorMessage' import useWindowDimensions from '../../hooks/useWindowDimensions' -import { containsNoEmojis } from '../../util/helper' import ClearInputButton from './components/ClearInputButton' import { Extension, ExtensionComponentProps } from './extensions' @@ -26,7 +25,7 @@ const KoblenzReferenceNumberExtensionForm = ({ const clearInput = () => setValue({ koblenzReferenceNumber: '' }) const getErrorMessage = (): string | null => { - if (hasSpecialChars(value.koblenzReferenceNumber) || !containsNoEmojis(value.koblenzReferenceNumber)) { + if (hasSpecialChars(value.koblenzReferenceNumber)) { return 'Das Aktenzeichen enthält ungültige Sonderzeichen.' } if (hasInvalidLength(value.koblenzReferenceNumber.length)) { @@ -73,8 +72,7 @@ const KoblenzReferenceNumberExtension: Extension= KoblenzReferenceNumberMinLength && koblenzReferenceNumber.length <= KoblenzReferenceNumberMaxLength && - !hasSpecialChars(koblenzReferenceNumber) && - containsNoEmojis(koblenzReferenceNumber) + !hasSpecialChars(koblenzReferenceNumber) ) }, fromString: value => ({ koblenzReferenceNumber: value }), diff --git a/administration/src/util/helper.ts b/administration/src/util/helper.ts index 61ffb73ad..6e289b871 100644 --- a/administration/src/util/helper.ts +++ b/administration/src/util/helper.ts @@ -1,3 +1,5 @@ +import XRegExp from 'xregexp' + export const isDevMode = (): boolean => window.location.hostname === 'localhost' export const isStagingMode = (): boolean => !!window.location.hostname.match(/staging./) @@ -16,7 +18,9 @@ export const removeMultipleSpaces = (value: string): string => value.replace(mul export const containsSpecialCharacters = (value: string): boolean => /[`!@#$%^&*()_+\-=\]{};':"\\|,.<>?~0123456789]/.test(value) -export const containsNoEmojis = (value: string): boolean => - /^(?!.*(?:\uD83D(?:[\uDC76\uDC66\uDC67](?:\uD83C[\uDFFB-\uDFFF])?|\uDC68(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92]|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]))?)|\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D(?:\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC68\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92])|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]|\u2764(?:\uFE0F\u200D\uD83D(?:\uDC8B\u200D\uD83D\uDC68|\uDC68)|\u200D\uD83D(?:\uDC8B\u200D\uD83D\uDC68|\uDC68)))))?|\uDC69(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92]|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]))?)|\u200D(?:\u2695\uFE0F?|\uD83C[\uDF93\uDFEB\uDF3E\uDF73\uDFED\uDFA4\uDFA8]|\u2696\uFE0F?|\uD83D(?:\uDC69\u200D\uD83D(?:\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?)|\uDC66(?:\u200D\uD83D\uDC66)?|\uDC67(?:\u200D\uD83D[\uDC66\uDC67])?|[\uDD27\uDCBC\uDD2C\uDCBB\uDE80\uDE92])|\u2708\uFE0F?|\uD83E[\uDDB0-\uDDB3]|\u2764(?:\uFE0F\u200D\uD83D(?:\uDC8B\u200D\uD83D[\uDC68\uDC69]|[\uDC68\uDC69])|\u200D\uD83D(?:\uDC8B\u200D\uD83D[\uDC68\uDC69]|[\uDC68\uDC69])))))?|[\uDC74\uDC75](?:\uD83C[\uDFFB-\uDFFF])?|\uDC6E(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDD75(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC82\uDC77](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDC78(?:\uD83C[\uDFFB-\uDFFF])?|\uDC73(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDC72(?:\uD83C[\uDFFB-\uDFFF])?|\uDC71(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC70\uDC7C](?:\uD83C[\uDFFB-\uDFFF])?|[\uDE4D\uDE4E\uDE45\uDE46\uDC81\uDE4B\uDE47\uDC86\uDC87\uDEB6](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDC83\uDD7A](?:\uD83C[\uDFFB-\uDFFF])?|\uDC6F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|[\uDEC0\uDECC](?:\uD83C[\uDFFB-\uDFFF])?|\uDD74(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|\uDDE3\uFE0F?|[\uDEA3\uDEB4\uDEB5](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDCAA\uDC48\uDC49\uDC46\uDD95\uDC47\uDD96](?:\uD83C[\uDFFB-\uDFFF])?|\uDD90(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\uDC4C-\uDC4E\uDC4A\uDC4B\uDC4F\uDC50\uDE4C\uDE4F\uDC85\uDC42\uDC43](?:\uD83C[\uDFFB-\uDFFF])?|\uDC41(?:(?:\uFE0F(?:\u200D\uD83D\uDDE8\uFE0F?)?|\u200D\uD83D\uDDE8\uFE0F?))?|[\uDDE8\uDDEF\uDD73\uDD76\uDECD\uDC3F\uDD4A\uDD77\uDD78\uDDFA\uDEE3\uDEE4\uDEE2\uDEF3\uDEE5\uDEE9\uDEF0\uDECE\uDD70\uDD79\uDDBC\uDDA5\uDDA8\uDDB1\uDDB2\uDCFD\uDD6F\uDDDE\uDDF3\uDD8B\uDD8A\uDD8C\uDD8D\uDDC2\uDDD2\uDDD3\uDD87\uDDC3\uDDC4\uDDD1\uDDDD\uDEE0\uDDE1\uDEE1\uDDDC\uDECF\uDECB\uDD49]\uFE0F?|[\uDE00-\uDE06\uDE09-\uDE0B\uDE0E\uDE0D\uDE18\uDE17\uDE19\uDE1A\uDE42\uDE10\uDE11\uDE36\uDE44\uDE0F\uDE23\uDE25\uDE2E\uDE2F\uDE2A\uDE2B\uDE34\uDE0C\uDE1B-\uDE1D\uDE12-\uDE15\uDE43\uDE32\uDE41\uDE16\uDE1E\uDE1F\uDE24\uDE22\uDE2D\uDE26-\uDE29\uDE2C\uDE30\uDE31\uDE33\uDE35\uDE21\uDE20\uDE37\uDE07\uDE08\uDC7F\uDC79\uDC7A\uDC80\uDC7B\uDC7D\uDC7E\uDCA9\uDE3A\uDE38\uDE39\uDE3B-\uDE3D\uDE40\uDE3F\uDE3E\uDE48-\uDE4A\uDC64\uDC65\uDC6B-\uDC6D\uDC8F\uDC91\uDC6A\uDC63\uDC40\uDC45\uDC44\uDC8B\uDC98\uDC93-\uDC97\uDC99-\uDC9C\uDDA4\uDC9D-\uDC9F\uDC8C\uDCA4\uDCA2\uDCA3\uDCA5\uDCA6\uDCA8\uDCAB-\uDCAD\uDC53-\uDC62\uDC51\uDC52\uDCFF\uDC84\uDC8D\uDC8E\uDC35\uDC12\uDC36\uDC15\uDC29\uDC3A\uDC31\uDC08\uDC2F\uDC05\uDC06\uDC34\uDC0E\uDC2E\uDC02-\uDC04\uDC37\uDC16\uDC17\uDC3D\uDC0F\uDC11\uDC10\uDC2A\uDC2B\uDC18\uDC2D\uDC01\uDC00\uDC39\uDC30\uDC07\uDC3B\uDC28\uDC3C\uDC3E\uDC14\uDC13\uDC23-\uDC27\uDC38\uDC0A\uDC22\uDC0D\uDC32\uDC09\uDC33\uDC0B\uDC2C\uDC1F-\uDC21\uDC19\uDC1A\uDC0C\uDC1B-\uDC1E\uDC90\uDCAE\uDD2A\uDDFE\uDDFB\uDC92\uDDFC\uDDFD\uDD4C\uDD4D\uDD4B\uDC88\uDE82-\uDE8A\uDE9D\uDE9E\uDE8B-\uDE8E\uDE90-\uDE9C\uDEB2\uDEF4\uDEF9\uDEF5\uDE8F\uDEA8\uDEA5\uDEA6\uDED1\uDEA7\uDEF6\uDEA4\uDEA2\uDEEB\uDEEC\uDCBA\uDE81\uDE9F-\uDEA1\uDE80\uDEF8\uDD5B\uDD67\uDD50\uDD5C\uDD51\uDD5D\uDD52\uDD5E\uDD53\uDD5F\uDD54\uDD60\uDD55\uDD61\uDD56\uDD62\uDD57\uDD63\uDD58\uDD64\uDD59\uDD65\uDD5A\uDD66\uDD25\uDCA7\uDEF7\uDD2E\uDD07-\uDD0A\uDCE2\uDCE3\uDCEF\uDD14\uDD15\uDCFB\uDCF1\uDCF2\uDCDE-\uDCE0\uDD0B\uDD0C\uDCBB\uDCBD-\uDCC0\uDCFA\uDCF7-\uDCF9\uDCFC\uDD0D\uDD0E\uDCA1\uDD26\uDCD4-\uDCDA\uDCD3\uDCD2\uDCC3\uDCDC\uDCC4\uDCF0\uDCD1\uDD16\uDCB0\uDCB4-\uDCB8\uDCB3\uDCB9\uDCB1\uDCB2\uDCE7-\uDCE9\uDCE4-\uDCE6\uDCEB\uDCEA\uDCEC-\uDCEE\uDCDD\uDCBC\uDCC1\uDCC2\uDCC5-\uDCD0\uDD12\uDD13\uDD0F-\uDD11\uDD28\uDD2B\uDD27\uDD29\uDD17\uDD2C\uDD2D\uDCE1\uDC89\uDC8A\uDEAA\uDEBD\uDEBF\uDEC1\uDED2\uDEAC\uDDFF\uDEAE\uDEB0\uDEB9-\uDEBC\uDEBE\uDEC2-\uDEC5\uDEB8\uDEAB\uDEB3\uDEAD\uDEAF\uDEB1\uDEB7\uDCF5\uDD1E\uDD03\uDD04\uDD19-\uDD1D\uDED0\uDD4E\uDD2F\uDD00-\uDD02\uDD3C\uDD3D\uDD05\uDD06\uDCF6\uDCF3\uDCF4\uDD31\uDCDB\uDD30\uDD1F\uDCAF\uDD20-\uDD24\uDD36-\uDD3B\uDCA0\uDD18\uDD32-\uDD35\uDEA9])|\uD83E(?:[\uDDD2\uDDD1\uDDD3](?:\uD83C[\uDFFB-\uDFFF])?|[\uDDB8\uDDB9](?:\u200D(?:[\u2640\u2642]\uFE0F?))?|[\uDD34\uDDD5\uDDD4\uDD35\uDD30\uDD31\uDD36](?:\uD83C[\uDFFB-\uDFFF])?|[\uDDD9-\uDDDD](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?)|\u200D(?:[\u2640\u2642]\uFE0F?)))?|[\uDDDE\uDDDF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?|[\uDD26\uDD37](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDDD6-\uDDD8](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2640\u2642]\uFE0F?))?)|\u200D(?:[\u2640\u2642]\uFE0F?)))?|\uDD38(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDD3C(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|[\uDD3D\uDD3E\uDD39](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDD33\uDDB5\uDDB6\uDD1E\uDD18\uDD19\uDD1B\uDD1C\uDD1A\uDD1F\uDD32](?:\uD83C[\uDFFB-\uDFFF])?|[\uDD23\uDD70\uDD17\uDD29\uDD14\uDD28\uDD10\uDD24\uDD11\uDD2F\uDD75\uDD76\uDD2A\uDD2C\uDD12\uDD15\uDD22\uDD2E\uDD27\uDD20\uDD21\uDD73\uDD74\uDD7A\uDD25\uDD2B\uDD2D\uDDD0\uDD13\uDD16\uDD3A\uDD1D\uDDB0-\uDDB3\uDDE0\uDDB4\uDDB7\uDDE1\uDD7D\uDD7C\uDDE3-\uDDE6\uDD7E\uDD7F\uDDE2\uDD8D\uDD8A\uDD9D\uDD81\uDD84\uDD93\uDD8C\uDD99\uDD92\uDD8F\uDD9B\uDD94\uDD87\uDD98\uDDA1\uDD83\uDD85\uDD86\uDDA2\uDD89\uDD9A\uDD9C\uDD8E\uDD95\uDD96\uDD88\uDD80\uDD9E\uDD90\uDD91\uDD8B\uDD97\uDD82\uDD9F\uDDA0\uDD40\uDD6D\uDD5D\uDD65\uDD51\uDD54\uDD55\uDD52\uDD6C\uDD66\uDD5C\uDD50\uDD56\uDD68\uDD6F\uDD5E\uDDC0\uDD69\uDD53\uDD6A\uDD59\uDD5A\uDD58\uDD63\uDD57\uDDC2\uDD6B\uDD6E\uDD5F-\uDD61\uDDC1\uDD67\uDD5B\uDD42\uDD43\uDD64\uDD62\uDD44\uDDED\uDDF1\uDDF3\uDDE8\uDDE7\uDD47-\uDD49\uDD4E\uDD4F\uDD4D\uDD4A\uDD4B\uDD45\uDD4C\uDDFF\uDDE9\uDDF8\uDD41\uDDEE\uDDFE\uDDF0\uDDF2\uDDEA-\uDDEC\uDDEF\uDDF4-\uDDF7\uDDF9-\uDDFD])|[\u263A\u2639\u2620]\uFE0F?|\uD83C(?:\uDF85(?:\uD83C[\uDFFB-\uDFFF])?|\uDFC3(?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFC7\uDFC2](?:\uD83C[\uDFFB-\uDFFF])?|\uDFCC(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFC4\uDFCA](?:(?:\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|\uDFCB(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\uDFCE\uDFCD\uDFF5\uDF36\uDF7D\uDFD4-\uDFD6\uDFDC-\uDFDF\uDFDB\uDFD7\uDFD8\uDFDA\uDFD9\uDF21\uDF24-\uDF2C\uDF97\uDF9F\uDF96\uDF99-\uDF9B\uDF9E\uDFF7\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37]\uFE0F?|\uDFF4(?:(?:\u200D\u2620\uFE0F?|\uDB40\uDC67\uDB40\uDC62\uDB40(?:\uDC65\uDB40\uDC6E\uDB40\uDC67\uDB40\uDC7F|\uDC73\uDB40\uDC63\uDB40\uDC74\uDB40\uDC7F|\uDC77\uDB40\uDC6C\uDB40\uDC73\uDB40\uDC7F)))?|\uDFF3(?:(?:\uFE0F(?:\u200D\uD83C\uDF08)?|\u200D\uD83C\uDF08))?|\uDDE6\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF]|\uDDE7\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF]|\uDDE8\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF]|\uDDE9\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF]|\uDDEA\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA]|\uDDEB\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7]|\uDDEC\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE]|\uDDED\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA]|\uDDEE\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9]|\uDDEF\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5]|\uDDF0\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF]|\uDDF1\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE]|\uDDF2\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF]|\uDDF3\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF]|\uDDF4\uD83C\uDDF2|\uDDF5\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE]|\uDDF6\uD83C\uDDE6|\uDDF7\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC]|\uDDF8\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF]|\uDDF9\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF]|\uDDFA\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF]|\uDDFB\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA]|\uDDFC\uD83C[\uDDEB\uDDF8]|\uDDFD\uD83C\uDDF0|\uDDFE\uD83C[\uDDEA\uDDF9]|\uDDFF\uD83C[\uDDE6\uDDF2\uDDFC]|[\uDFFB-\uDFFF\uDF92\uDFA9\uDF93\uDF38-\uDF3C\uDF37\uDF31-\uDF35\uDF3E-\uDF43\uDF47-\uDF53\uDF45\uDF46\uDF3D\uDF44\uDF30\uDF5E\uDF56\uDF57\uDF54\uDF5F\uDF55\uDF2D-\uDF2F\uDF73\uDF72\uDF7F\uDF71\uDF58-\uDF5D\uDF60\uDF62-\uDF65\uDF61\uDF66-\uDF6A\uDF82\uDF70\uDF6B-\uDF6F\uDF7C\uDF75\uDF76\uDF7E\uDF77-\uDF7B\uDF74\uDFFA\uDF0D-\uDF10\uDF0B\uDFE0-\uDFE6\uDFE8-\uDFED\uDFEF\uDFF0\uDF01\uDF03-\uDF07\uDF09\uDF0C\uDFA0-\uDFA2\uDFAA\uDF11-\uDF20\uDF00\uDF08\uDF02\uDF0A\uDF83\uDF84\uDF86-\uDF8B\uDF8D-\uDF91\uDF80\uDF81\uDFAB\uDFC6\uDFC5\uDFC0\uDFD0\uDFC8\uDFC9\uDFBE\uDFB3\uDFCF\uDFD1-\uDFD3\uDFF8\uDFA3\uDFBD\uDFBF\uDFAF\uDFB1\uDFAE\uDFB0\uDFB2\uDCCF\uDC04\uDFB4\uDFAD\uDFA8\uDFBC\uDFB5\uDFB6\uDFA4\uDFA7\uDFB7-\uDFBB\uDFA5\uDFAC\uDFEE\uDFF9\uDFE7\uDFA6\uDD8E\uDD91-\uDD9A\uDE01\uDE36\uDE2F\uDE50\uDE39\uDE1A\uDE32\uDE51\uDE38\uDE34\uDE33\uDE3A\uDE35\uDFC1\uDF8C])|\u26F7\uFE0F?|\u26F9(?:(?:\uFE0F(?:\u200D(?:[\u2642\u2640]\uFE0F?))?|\uD83C(?:[\uDFFB-\uDFFF](?:\u200D(?:[\u2642\u2640]\uFE0F?))?)|\u200D(?:[\u2642\u2640]\uFE0F?)))?|[\u261D\u270C](?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\u270B\u270A](?:\uD83C[\uDFFB-\uDFFF])?|\u270D(?:(?:\uD83C[\uDFFB-\uDFFF]|\uFE0F))?|[\u2764\u2763\u26D1\u2618\u26F0\u26E9\u2668\u26F4\u2708\u23F1\u23F2\u2600\u2601\u26C8\u2602\u26F1\u2744\u2603\u2604\u26F8\u2660\u2665\u2666\u2663\u260E\u2328\u2709\u270F\u2712\u2702\u26CF\u2692\u2694\u2699\u2696\u26D3\u2697\u26B0\u26B1\u26A0\u2622\u2623\u2B06\u2197\u27A1\u2198\u2B07\u2199\u2B05\u2196\u2195\u2194\u21A9\u21AA\u2934\u2935\u269B\u267E\u2721\u2638\u262F\u271D\u2626\u262A\u262E\u25B6\u23ED\u23EF\u25C0\u23EE\u23F8-\u23FA\u23CF\u2640\u2642\u2695\u267B\u269C\u2611\u2714\u2716\u303D\u2733\u2734\u2747\u203C\u2049\u3030\u00A9\u00AE\u2122]\uFE0F?|[\u0023\u002A\u0030-\u0039](?:\uFE0F\u20E3|\u20E3)|[\u2139\u24C2\u3297\u3299\u25AA\u25AB\u25FB\u25FC]\uFE0F?|[\u2615\u26EA\u26F2\u26FA\u26FD\u2693\u26F5\u231B\u23F3\u231A\u23F0\u2B50\u26C5\u2614\u26A1\u26C4\u2728\u26BD\u26BE\u26F3\u267F\u26D4\u2648-\u2653\u26CE\u23E9-\u23EC\u2B55\u2705\u274C\u274E\u2795-\u2797\u27B0\u27BF\u2753-\u2755\u2757\u25FD\u25FE\u2B1B\u2B1C\u26AA\u26AB])).*/.test( - value - ) +/** This regEx is needed to avoid breaking pdf creation due to incompatible charsets in form fields + * Common charset includes common pattern f.e. empty spaces. + * Checking for latin charset should be fine because all fonts we're going to use for pdf generation should be latin based. + * */ +export const containsOnlyLatinAndCommonCharset = (value: string): boolean => + XRegExp('^[\\p{Latin}\\p{Common}]+$').test(value) diff --git a/package-lock.json b/package-lock.json index 6cc98efce..78710a7d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,8 @@ "react-dom": "^18.2.0", "react-flip-move": "^3.0.5", "react-router-dom": "^6.14.0", - "styled-components": "^5.3.11" + "styled-components": "^5.3.11", + "xregexp": "^5.1.1" }, "devDependencies": { "@babel/core": "^7.22.5", @@ -2715,6 +2716,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz", + "integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", @@ -10269,7 +10282,6 @@ "version": "3.38.1", "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.38.1.tgz", "integrity": "sha512-BY8Etc1FZqdw1glX0XNOq2FDwfrg/VGqoZOZCdaL+UmdaqDwQwYXkMJT4t6In+zfEfOJDcM9T0KdbBeJg8KKCQ==", - "dev": true, "hasInstallScript": true, "license": "MIT", "funding": { @@ -24673,6 +24685,14 @@ "dev": true, "license": "MIT" }, + "node_modules/xregexp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.1.tgz", + "integrity": "sha512-fKXeVorD+CzWvFs7VBuKTYIW63YD1e1osxwQ8caZ6o1jg6pDAbABDG54LCIq0j5cy7PjRvGIq6sef9DYPXpncg==", + "dependencies": { + "@babel/runtime-corejs3": "^7.16.5" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", From d297d63eb980eecd73c14d0423782a42ab3c861a Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 19 Nov 2024 17:39:29 +0100 Subject: [PATCH 6/6] 1670: fix package json lock --- package-lock.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/package-lock.json b/package-lock.json index 66268cc93..81ee1c072 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "graphql": "^16.8.1", "i18next": "^23.16.4", "localforage": "^1.10.0", + "normalize-strings": "^1.1.1", "normalize.css": "^8.0.1", "notistack": "^3.0.1", "pdf-lib": "^1.17.1", @@ -18990,6 +18991,11 @@ "node": ">=0.10.0" } }, + "node_modules/normalize-strings": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/normalize-strings/-/normalize-strings-1.1.1.tgz", + "integrity": "sha512-fARPRdTwmrQDLYhmeh7j/eZwrCP6WzxD6uKOdK/hT/uKACAE9AG2Bc2dgqOZLkfmmctHpfcJ9w3AQnfLgg3GYg==" + }, "node_modules/normalize.css": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz",