mirror of
https://github.com/danbulant/Portfolio
synced 2026-06-09 01:30:18 +00:00
feat: personalIdNumber confirmation
This commit is contained in:
parent
6da19fe492
commit
09ce40ec51
6 changed files with 254 additions and 75 deletions
|
|
@ -4,10 +4,9 @@
|
|||
export let linkOk: boolean = false;
|
||||
export let linkError: boolean = false;
|
||||
export let applications: Array<number>;
|
||||
|
||||
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) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
<script lang="ts">
|
||||
import LL from '$i18n/i18n-svelte';
|
||||
|
||||
export let personalIdOk: boolean = false;
|
||||
export let personalIdErr: boolean = false;
|
||||
export let personalIdNumber: string;
|
||||
let titleOk = $LL.components.checkbox.personalIdConfirmCheckBox.titleOk({
|
||||
personalId: personalIdNumber,
|
||||
});
|
||||
let titleErr = $LL.components.checkbox.personalIdConfirmCheckBox.titleErr({
|
||||
personalId: personalIdNumber,
|
||||
});
|
||||
|
||||
export let error: string = '';
|
||||
|
||||
const switchSelection = (id: number) => {
|
||||
if (id === 0) {
|
||||
personalIdOk = true;
|
||||
personalIdErr = false;
|
||||
} else {
|
||||
personalIdOk = false;
|
||||
personalIdErr = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<input
|
||||
on:click={(_) => switchSelection(0)}
|
||||
class:error
|
||||
on:change
|
||||
type="checkbox"
|
||||
id="linkOk"
|
||||
checked={personalIdOk}
|
||||
class="peer hidden"
|
||||
/>
|
||||
<label for="linkOk" class="peer-checked:border-sspsBlue peer-checked:text-gray-600" class:error>
|
||||
<div class="block">
|
||||
<span class="text-2xl">📜</span>
|
||||
|
||||
<div class="w-full text-lg font-semibold">
|
||||
{titleOk}
|
||||
</div>
|
||||
<div class="w-full text-sm">{$LL.components.checkbox.personalIdConfirmCheckBox.ok()}</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<input
|
||||
on:click={(_) => switchSelection(1)}
|
||||
on:change
|
||||
type="checkbox"
|
||||
id="linkError"
|
||||
checked={personalIdErr}
|
||||
class="peer hidden"
|
||||
/>
|
||||
<label for="linkError" class="peer-checked:border-sspsBlue peer-checked:text-gray-600">
|
||||
<div class="block">
|
||||
<span class="text-2xl">📜</span>
|
||||
|
||||
<div class="w-full text-lg font-semibold">
|
||||
{titleErr}
|
||||
</div>
|
||||
<div class="w-full text-sm">{$LL.components.checkbox.personalIdConfirmCheckBox.whatHappened()}</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
label {
|
||||
@apply inline-flex w-full items-center justify-between;
|
||||
@apply cursor-pointer;
|
||||
@apply bg-white p-5 text-gray-500;
|
||||
@apply hover:bg-gray-50 hover:text-gray-600;
|
||||
@apply rounded-lg border-2 border-gray-200;
|
||||
}
|
||||
.error {
|
||||
@apply border-red-700;
|
||||
}
|
||||
</style>
|
||||
74
frontend/src/lib/utils/personalIdFormat.ts
Normal file
74
frontend/src/lib/utils/personalIdFormat.ts
Normal file
|
|
@ -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];
|
||||
}
|
||||
|
|
@ -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<FormErrorType>;
|
||||
|
||||
// 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();
|
||||
}
|
||||
</script>
|
||||
|
|
@ -453,6 +422,21 @@
|
|||
{/if}
|
||||
<form on:submit={handleSubmit} id="triggerForm" class="invisible hidden" />
|
||||
{#if pageIndex === 0}
|
||||
<form on:submit={handleSubmit}>
|
||||
<h1 class="title mt-8">{$LL.candidate.register.first.title()}</h1>
|
||||
<p class="description mt-8 block text-center">
|
||||
{$LL.candidate.register.first.description()}
|
||||
</p>
|
||||
<div class="field">
|
||||
<PersonalIdConfirmCheckBox
|
||||
personalIdNumber={baseCandidateDetails.personalIdNumber}
|
||||
bind:personalIdOk={$form.personalIdOk}
|
||||
bind:personalIdErr={$form.personalIdErr}
|
||||
error={$typedErrors['personalIdOk']}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
{:else if pageIndex === 1}
|
||||
<form on:submit={handleSubmit}>
|
||||
<h1 class="title mt-8">{$LL.candidate.register.first.title()}</h1>
|
||||
<p class="description mt-8 block text-center">
|
||||
|
|
@ -467,7 +451,7 @@
|
|||
/>
|
||||
</div>
|
||||
</form>
|
||||
{:else if pageIndex === 1}
|
||||
{:else if pageIndex === 2}
|
||||
<form on:submit={handleSubmit}>
|
||||
<h1 class="title mt-8">{pageTexts[0]}</h1>
|
||||
<p class="description mt-8 block text-center">
|
||||
|
|
@ -478,7 +462,7 @@
|
|||
<GdprCheckBox bind:value={$form.gdpr} error={$typedErrors['gdpr']} />
|
||||
</div>
|
||||
</form>
|
||||
{:else if pageIndex === 2}
|
||||
{:else if pageIndex === 3}
|
||||
<form on:submit={handleSubmit}>
|
||||
<h1 class="title mt-8">{pageTexts[1]}</h1>
|
||||
<p class="description mt-8 block text-center">
|
||||
|
|
@ -552,7 +536,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{:else if pageIndex === 3}
|
||||
{:else if pageIndex === 4}
|
||||
<h1 class="title mt-8">{pageTexts[2]}</h1>
|
||||
<p class="description mt-8 block text-center">
|
||||
{$LL.candidate.register.fourth.description()}
|
||||
|
|
@ -643,7 +627,7 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
{:else if pageIndex === 4}
|
||||
{:else if pageIndex === 5}
|
||||
<h1 class="title mt-8">{pageTexts[3]}</h1>
|
||||
<p class="description mt-8 block text-center">
|
||||
{$LL.candidate.register.fifth.description()}
|
||||
|
|
@ -672,7 +656,7 @@
|
|||
/>
|
||||
</span>
|
||||
</div>
|
||||
{:else if pageIndex === 5}
|
||||
{:else if pageIndex === 6}
|
||||
<h1 class="title mt-8">{pageTexts[4]}</h1>
|
||||
<p class="description mt-8 block text-center">
|
||||
{$LL.candidate.register.sixth.description()}
|
||||
|
|
@ -701,7 +685,7 @@
|
|||
/>
|
||||
</span>
|
||||
</div>
|
||||
{:else if pageIndex === 6}
|
||||
{:else if pageIndex === 7}
|
||||
<h1 class="title mt-8">Přihlášky na školy</h1>
|
||||
<div class="flex flex-col justify-between h-full">
|
||||
<span>
|
||||
|
|
@ -711,8 +695,8 @@
|
|||
<SchoolSelect bind:selectedSchool={$form.candidate.secondSchool}></SchoolSelect>
|
||||
</span>
|
||||
</div>
|
||||
{:else if pageIndex === 7}
|
||||
<h1 class="title mt-8">{pageTexts[5]}</h1>
|
||||
{:else if pageIndex === 8}
|
||||
<h1 class="title mt-8">{pageTexts[6]}</h1>
|
||||
<p class="description mt-8 block text-center">
|
||||
{$LL.candidate.register.eighth.description()}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -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ů',
|
||||
|
|
|
|||
|
|
@ -272,6 +272,26 @@ type RootTranslation = {
|
|||
title2: string
|
||||
}
|
||||
}
|
||||
personalIdConfirmCheckBox: {
|
||||
/**
|
||||
* Vše je v pořádku
|
||||
*/
|
||||
ok: string
|
||||
/**
|
||||
* Co se děje?
|
||||
*/
|
||||
whatHappened: string
|
||||
/**
|
||||
* Potvrzuji, že moje rodné číslo je {personalId}
|
||||
* @param {unknown} personalId
|
||||
*/
|
||||
titleOk: RequiredParams<'personalId'>
|
||||
/**
|
||||
* Ne, moje rodné číslo není {personalId}
|
||||
* @param {unknown} personalId
|
||||
*/
|
||||
titleErr: RequiredParams<'personalId'>
|
||||
}
|
||||
gdprCheckBox: {
|
||||
/**
|
||||
* Souhlasím se zpracováním osobních údajů
|
||||
|
|
@ -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ů
|
||||
|
|
|
|||
Loading…
Reference in a new issue