From 09ce40ec515fff6d13a54a3f55e4d50ece27579d Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Wed, 25 Jan 2023 10:42:25 +0100 Subject: [PATCH] feat: personalIdNumber confirmation --- .../checkbox/AccountLinkCheckBox.svelte | 7 +- .../checkbox/PersonalIdConfirmCheckBox.svelte | 80 +++++++++++ frontend/src/lib/utils/personalIdFormat.ts | 74 +++++++++++ .../(authenticated)/register/+page.svelte | 124 ++++++++---------- frontend/src/translations/cs/index.ts | 6 + frontend/src/translations/i18n-types.ts | 38 ++++++ 6 files changed, 254 insertions(+), 75 deletions(-) create mode 100644 frontend/src/lib/components/checkbox/PersonalIdConfirmCheckBox.svelte create mode 100644 frontend/src/lib/utils/personalIdFormat.ts diff --git a/frontend/src/lib/components/checkbox/AccountLinkCheckBox.svelte b/frontend/src/lib/components/checkbox/AccountLinkCheckBox.svelte index 55f1438..2009579 100644 --- a/frontend/src/lib/components/checkbox/AccountLinkCheckBox.svelte +++ b/frontend/src/lib/components/checkbox/AccountLinkCheckBox.svelte @@ -4,10 +4,9 @@ export let linkOk: boolean = false; export let linkError: boolean = false; export let applications: Array; - let title1 = $LL.components.checkbox.accountLinkCheckBox.multiple.title({ first: applications[0], - second: applications[1] + second: applications[1], }); let title2 = $LL.components.checkbox.accountLinkCheckBox.multiple.title2({ first: applications[0] @@ -15,13 +14,11 @@ if (applications.length === 1) { title1 = $LL.components.checkbox.accountLinkCheckBox.single.title({ - first: applications[0] + first: applications[0], }); title2 = $LL.components.checkbox.accountLinkCheckBox.single.title2(); } - $: console.log(linkOk, linkError); - export let error: string = ''; const switchSelection = (id: number) => { diff --git a/frontend/src/lib/components/checkbox/PersonalIdConfirmCheckBox.svelte b/frontend/src/lib/components/checkbox/PersonalIdConfirmCheckBox.svelte new file mode 100644 index 0000000..d0efed5 --- /dev/null +++ b/frontend/src/lib/components/checkbox/PersonalIdConfirmCheckBox.svelte @@ -0,0 +1,80 @@ + + +
+ switchSelection(0)} + class:error + on:change + type="checkbox" + id="linkOk" + checked={personalIdOk} + class="peer hidden" + /> + +
+
+ switchSelection(1)} + on:change + type="checkbox" + id="linkError" + checked={personalIdErr} + class="peer hidden" + /> + +
+ + diff --git a/frontend/src/lib/utils/personalIdFormat.ts b/frontend/src/lib/utils/personalIdFormat.ts new file mode 100644 index 0000000..2cb6282 --- /dev/null +++ b/frontend/src/lib/utils/personalIdFormat.ts @@ -0,0 +1,74 @@ +// TODO: nefunguje pro lidi nar. pred 1.1.1954 :D +export const isPersonalIdNumberValid = (personalIdNumber: string): boolean => { + const idFmt = personalIdNumber.split('/').join(''); + + const lastDigitCheck = + Number(idFmt.slice(0, 9)) % 11 === Number(idFmt.at(-1)) || + Number(idFmt.slice(0, 9)) % 11 === 10; // an edge case that could occur + const divisibleBy11 = Number(idFmt) % 11 === 0; + + if (lastDigitCheck && divisibleBy11) { + return true; + } else { + return false; + } +}; + +export const isPersonalIdNumberWithBirthdateValid = ( + personalIdNumber: string, + birthdate: string +): boolean => { + const dateFmt = birthdate + .split('.') + .map((x) => x.padStart(2, '0')) + .reverse() + .join('') + .slice(2); + const idFmt = personalIdNumber.split('/').join(''); + + const divisionValid = isPersonalIdNumberValid(personalIdNumber); + + const idMonth = Number(idFmt.slice(2, 4)); + const dateMonth = Number(dateFmt.slice(2, 4)); + const monthValid = + idMonth === dateMonth || + idMonth === dateMonth + 50 || + idMonth === dateMonth + 20 || + idMonth === dateMonth + 70; + + if ( + idFmt.slice(0, 2) === dateFmt.slice(0, 2) && + monthValid && + idFmt.slice(4, 6) === dateFmt.slice(4, 6) && + divisionValid + ) { + return true; + } else { + return false; + } +}; + +export const deriveBirthdateFromPersonalId = (personalIdNumber: string): + [birthdate: string, sex: 'MUŽ' | 'ŽENA'] => { + const year = Number(personalIdNumber.slice(0, 2)); + const idMonth = Number(personalIdNumber.slice(2, 4)); + let month; + let sex: 'MUŽ' | 'ŽENA'; + if (idMonth > 12 && idMonth <= 32) { + month = idMonth - 20; + sex = 'MUŽ'; + } else if (idMonth > 50 && idMonth <= 52) { + month = idMonth - 50; + sex = 'ŽENA'; + } else if (idMonth > 70 && idMonth <= 82) { + month = idMonth - 70; + sex = 'ŽENA'; + } else { + month = idMonth; + sex = 'MUŽ'; + }; + const day = Number(personalIdNumber.slice(4, 6)); + + const birthdate = `${day}.${month}.${year}`; + return [birthdate, sex]; +} \ No newline at end of file diff --git a/frontend/src/routes/(candidate)/(authenticated)/register/+page.svelte b/frontend/src/routes/(candidate)/(authenticated)/register/+page.svelte index 0de206f..4f01808 100644 --- a/frontend/src/routes/(candidate)/(authenticated)/register/+page.svelte +++ b/frontend/src/routes/(candidate)/(authenticated)/register/+page.svelte @@ -6,7 +6,6 @@ import Submit from '$lib/components/button/Submit.svelte'; import GdprCheckBox from '$lib/components/checkbox/GdprCheckBox.svelte'; - import Home from '$lib/components/icons/Home.svelte'; import SchoolBadge from '$lib/components/icons/SchoolBadge.svelte'; import SplitLayout from '$lib/components/layout/SplitLayout.svelte'; import SelectField from '$lib/components/select/SelectField.svelte'; @@ -25,9 +24,12 @@ import AccountLinkCheckBox from '$lib/components/checkbox/AccountLinkCheckBox.svelte'; import GradesTable from '$lib/components/grades/GradesTable.svelte'; import SchoolSelect from '$lib/components/select/SchoolSelect.svelte'; + import PersonalIdConfirmCheckBox from '$lib/components/checkbox/PersonalIdConfirmCheckBox.svelte'; + import { deriveBirthdateFromPersonalId, isPersonalIdNumberWithBirthdateValid } from '$lib/utils/personalIdFormat'; let pageIndex = 0; - let pagesFilled = [false, false, false, false, false, false, false]; + let pagesFilled = [false, false, false, false, false, false, false, false]; + let editModePageIndex = 3; const pageCount = pagesFilled.length; let pageTexts = [ $LL.candidate.register.second.title(), @@ -46,6 +48,8 @@ let personalIdBirthdateMatch = true; const formInitialValues = { gdpr: false, + personalIdOk: false, + personalIdErr: false, linkOk: false, linkError: false, candidate: { @@ -90,6 +94,8 @@ const formValidationSchema = yup.object().shape({ gdpr: yup.boolean().oneOf([true]), + personalIdOk: yup.boolean().oneOf([true]), + personalIdErr: yup.boolean().oneOf([false]), linkOk: yup.boolean().oneOf([true]), linkError: yup.boolean().oneOf([false]), candidate: yup.object().shape({ @@ -190,56 +196,6 @@ // TODO: https://github.com/tjinauyeung/svelte-forms-lib/issues/171!! (Zatím tenhle mega typ) $: typedErrors = errors as unknown as Writable; - // TODO: validate on admin dashboard, move somewhere - // TODO: nefunguje pro lidi nar. pred 1.1.1954 :D - const isPersonalIdNumberValid = (personalIdNumber: string): boolean => { - const idFmt = personalIdNumber.split('/').join(''); - - const lastDigitCheck = - Number(idFmt.slice(0, 9)) % 11 === Number(idFmt.at(-1)) || - Number(idFmt.slice(0, 9)) % 11 === 10; // an edge case that could occur - const divisibleBy11 = Number(idFmt) % 11 === 0; - - if (lastDigitCheck && divisibleBy11) { - return true; - } else { - return false; - } - }; - - const isPersonalIdNumberWithBirthdateValid = ( - personalIdNumber: string, - birthdate: string - ): boolean => { - const dateFmt = birthdate - .split('.') - .map((x) => x.padStart(2, '0')) - .reverse() - .join('') - .slice(2); - const idFmt = personalIdNumber.split('/').join(''); - - const divisionValid = isPersonalIdNumberValid(personalIdNumber); - - const idMonth = Number(idFmt.slice(2, 4)); - const dateMonth = Number(dateFmt.slice(2, 4)); - const monthValid = - idMonth === dateMonth || - idMonth === dateMonth + 50 || - idMonth === dateMonth + 20 || - idMonth === dateMonth + 70; - - if ( - idFmt.slice(0, 2) === dateFmt.slice(0, 2) && - monthValid && - idFmt.slice(4, 6) === dateFmt.slice(4, 6) && - divisionValid - ) { - return true; - } else { - return false; - } - }; $: console.log($typedErrors); const onSubmit = async (values: CandidateData) => { if (pageIndex === 3) { @@ -320,17 +276,22 @@ const isPageInvalid = (index: number): boolean => { switch (index) { - case 0: - if ($typedErrors['linkOk'] || $typedErrors['linkError']) { + case 0: + if ($typedErrors['personalIdOk'] || $typedErrors['personalIdErr']) { return true; } break; case 1: - if ($typedErrors['gdpr']) { + if ($typedErrors['linkOk'] || $typedErrors['linkError']) { return true; } break; case 2: + if ($typedErrors['gdpr']) { + return true; + } + break; + case 3: if ( $typedErrors['candidate']['name'] || $typedErrors['candidate']['surname'] || @@ -345,7 +306,7 @@ } break; - case 3: + case 4: if ( $typedErrors['candidate']['citizenship'] || $typedErrors['candidate']['personalIdNumber'] || @@ -360,7 +321,7 @@ return true; } break; - case 4: + case 5: if ( $typedErrors['parents'][0]['name'] || $typedErrors['parents'][0]['surname'] || @@ -370,7 +331,7 @@ return true; } break; - case 5: + case 6: if ( $typedErrors['parents'][1]['name'] || $typedErrors['parents'][1]['surname'] || @@ -380,7 +341,7 @@ return true; } break; - case 6: + case 7: // @ts-ignore if ($typedErrors["candidate"]["firstSchool"].name || $typedErrors["candidate"]["firstSchool"].field || // @ts-ignore @@ -389,8 +350,8 @@ return true; } break; - case 7: - if ($typedErrors["candidate"]["grades"].length > 0) return true; + case 8: + if ($typedErrors['candidate']['grades'].length > 0) return true; break; default: return false; @@ -402,6 +363,12 @@ return '+' + telephone.match(/[0-9]{1,3}/g)!.join(' '); }; + + /* $form.candidate.personalIdNumber = data.whoami.personalIdNumber; + const [birthdate, sex] = deriveBirthdateFromPersonalId(data.whoami.personalIdNumber); + $form.candidate.birthdate = birthdate; + $form.candidate.sex = sex; */ + if (details !== undefined) { details.candidate.birthdate = details.candidate.birthdate.split('-').reverse().join('.'); @@ -413,6 +380,8 @@ gdpr: true, linkOk: true, linkError: false, + personalIdOk: true, + personalIdErr: false, candidate: { ...details.candidate, street: details.candidate.address.split(',')[0].split(' ')[0], @@ -436,7 +405,7 @@ } ] }); - pageIndex = 2; // skip gdpr page + pageIndex = editModePageIndex; // skip gdpr page pageTexts[2] = $LL.candidate.register.fourth.titleEdit(); } @@ -453,6 +422,21 @@ {/if} + {:else if pageIndex === 1}

{$LL.candidate.register.first.title()}

@@ -467,7 +451,7 @@ />

- {:else if pageIndex === 1} + {:else if pageIndex === 2}

{pageTexts[0]}

@@ -478,7 +462,7 @@ - {:else if pageIndex === 2} + {:else if pageIndex === 3}

{pageTexts[1]}

@@ -552,7 +536,7 @@

- {:else if pageIndex === 3} + {:else if pageIndex === 4}

{pageTexts[2]}

{$LL.candidate.register.fourth.description()} @@ -643,7 +627,7 @@ - {:else if pageIndex === 4} + {:else if pageIndex === 5}

{pageTexts[3]}

{$LL.candidate.register.fifth.description()} @@ -672,7 +656,7 @@ /> - {:else if pageIndex === 5} + {:else if pageIndex === 6}

{pageTexts[4]}

{$LL.candidate.register.sixth.description()} @@ -701,7 +685,7 @@ /> - {:else if pageIndex === 6} + {:else if pageIndex === 7}

Přihlášky na školy

@@ -711,8 +695,8 @@
- {:else if pageIndex === 7} -

{pageTexts[5]}

+ {:else if pageIndex === 8} +

{pageTexts[6]}

{$LL.candidate.register.eighth.description()}

diff --git a/frontend/src/translations/cs/index.ts b/frontend/src/translations/cs/index.ts index cc35d28..7e2e7c2 100644 --- a/frontend/src/translations/cs/index.ts +++ b/frontend/src/translations/cs/index.ts @@ -118,6 +118,12 @@ const cs: BaseTranslation = { title2: 'Ne, přihlášku na SSPŠaG jsem podával více přihlášek' } }, + personalIdConfirmCheckBox: { + ok: 'Vše je v pořádku', + whatHappened: 'Co se děje?', + titleOk: 'Potvrzuji, že moje rodné číslo je {personalId}', + titleErr: 'Ne, moje rodné číslo není {personalId}', + }, gdprCheckBox: { title: 'Souhlasím se zpracováním osobních údajů', description: 'Kliknutím vyjaďřujete souhlas se zpracováním osobních údajů', diff --git a/frontend/src/translations/i18n-types.ts b/frontend/src/translations/i18n-types.ts index b03dcbb..6af7424 100644 --- a/frontend/src/translations/i18n-types.ts +++ b/frontend/src/translations/i18n-types.ts @@ -272,6 +272,26 @@ type RootTranslation = { title2: string } } + personalIdConfirmCheckBox: { + /** + * V​š​e​ ​j​e​ ​v​ ​p​o​ř​á​d​k​u + */ + ok: string + /** + * C​o​ ​s​e​ ​d​ě​j​e​? + */ + whatHappened: string + /** + * P​o​t​v​r​z​u​j​i​,​ ​ž​e​ ​m​o​j​e​ ​r​o​d​n​é​ ​č​í​s​l​o​ ​j​e​ ​{​p​e​r​s​o​n​a​l​I​d​} + * @param {unknown} personalId + */ + titleOk: RequiredParams<'personalId'> + /** + * N​e​,​ ​m​o​j​e​ ​r​o​d​n​é​ ​č​í​s​l​o​ ​n​e​n​í​ ​{​p​e​r​s​o​n​a​l​I​d​} + * @param {unknown} personalId + */ + titleErr: RequiredParams<'personalId'> + } gdprCheckBox: { /** * S​o​u​h​l​a​s​í​m​ ​s​e​ ​z​p​r​a​c​o​v​á​n​í​m​ ​o​s​o​b​n​í​c​h​ ​ú​d​a​j​ů @@ -645,6 +665,24 @@ export type TranslationFunctions = { title2: () => LocalizedString } } + personalIdConfirmCheckBox: { + /** + * Vše je v pořádku + */ + ok: () => LocalizedString + /** + * Co se děje? + */ + whatHappened: () => LocalizedString + /** + * Potvrzuji, že moje rodné číslo je {personalId} + */ + titleOk: (arg: { personalId: unknown }) => LocalizedString + /** + * Ne, moje rodné číslo není {personalId} + */ + titleErr: (arg: { personalId: unknown }) => LocalizedString + } gdprCheckBox: { /** * Souhlasím se zpracováním osobních údajů