diff --git a/frontend/package.json b/frontend/package.json index 73a5d99..a5527a2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,7 +43,9 @@ "fuse.js": "^6.6.2", "isomorphic-dompurify": "^0.26.0", "just-debounce-it": "^3.2.0", + "libphonenumber-js": "^1.10.19", "svelte-forms-lib": "^2.0.1", + "svelte-tel-input": "^1.1.2", "svelte-tippy": "^1.3.2", "swiper": "^8.4.6", "tippy.js": "^6.3.7", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 9cfed88..310a2ec 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -22,9 +22,15 @@ dependencies: just-debounce-it: specifier: ^3.2.0 version: 3.2.0 + libphonenumber-js: + specifier: ^1.10.19 + version: 1.10.19 svelte-forms-lib: specifier: ^2.0.1 version: 2.0.1 + svelte-tel-input: + specifier: ^1.1.2 + version: 1.1.2 svelte-tippy: specifier: ^1.3.2 version: 1.3.2 @@ -1677,6 +1683,10 @@ packages: type-check: 0.4.0 dev: true + /libphonenumber-js@1.10.19: + resolution: {integrity: sha512-MDZ1zLIkfSDZV5xBta3nuvbEOlsnKCPe4z5r3hyup/AXveevkl9A1eSWmLhd2FX4k7pJDe4MrLeQsux0HI/VWg==} + dev: false + /locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -2307,6 +2317,14 @@ packages: typescript: 4.9.4 dev: true + /svelte-tel-input@1.1.2: + resolution: {integrity: sha512-KJxV/4h2JJ6b2Gh6WfXwfukXcW02SHai65jZAfV6DuT3Mu0zhfZ7Lu4iyi9PAOaLQzEVQ3UpwBEBXH3B7YCIog==} + engines: {node: '>= 16', npm: '>= 7', pnpm: '>= 7', yarn: '>=2'} + dependencies: + libphonenumber-js: 1.10.19 + svelte: 3.55.1 + dev: false + /svelte-tippy@1.3.2: resolution: {integrity: sha512-41f+85hwhKBRqX0UNYrgFsi34Kk/KDvUkIZXYANxkWoA2NTVTCZbUC2J8hRNZ4TRVxObTshoZRjK2co5+i6LMw==} dependencies: @@ -2326,7 +2344,6 @@ packages: /svelte@3.55.1: resolution: {integrity: sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==} engines: {node: '>= 8'} - dev: true /swiper@8.4.6: resolution: {integrity: sha512-HACW035vBz2T6Kfut23EAzXhcDpgR8doX+wjq0ZUvJgS5SQApGrV885DAPLBFnmPUISsAhNSVxPKDxqroFvXvQ==} diff --git a/frontend/src/lib/components/textfield/TelephoneField.svelte b/frontend/src/lib/components/textfield/TelephoneField.svelte index b8eed07..ed5c28c 100644 --- a/frontend/src/lib/components/textfield/TelephoneField.svelte +++ b/frontend/src/lib/components/textfield/TelephoneField.svelte @@ -1,39 +1,119 @@ - - - + + Země + {#each normalizedCountries as country (country.id)} + + {country.name.split('(').length > 1 + ? country.name.split('(')[1].replace(')', '') + : country.name} (+{country.dialCode}) + + {/each} + + + + + + - + diff --git a/frontend/src/routes/(candidate)/(authenticated)/register/+page.svelte b/frontend/src/routes/(candidate)/(authenticated)/register/+page.svelte index 7e0382d..b37a10f 100644 --- a/frontend/src/routes/(candidate)/(authenticated)/register/+page.svelte +++ b/frontend/src/routes/(candidate)/(authenticated)/register/+page.svelte @@ -5,7 +5,6 @@ import { apiFillDetails } from '$lib/@api/candidate'; import Submit from '$lib/components/button/Submit.svelte'; import GdprCheckBox from '$lib/components/checkbox/GdprCheckBox.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'; @@ -16,9 +15,8 @@ import TextField from '$lib/components/textfield/TextField.svelte'; import type { PageData } from './$types'; import { SvelteToast, toast } from '@zerodevx/svelte-toast'; - + import parsePhoneNumber from 'libphonenumber-js'; import { createForm } from 'svelte-forms-lib'; - import type { Writable } from 'svelte/store'; import * as yup from 'yup'; import type { CandidateData } from '$lib/stores/candidate'; import AccountLinkCheckBox from '$lib/components/checkbox/AccountLinkCheckBox.svelte'; @@ -28,11 +26,13 @@ import { isPersonalIdNumberWithBirthdateValid } from '$lib/utils/personalIdFormat'; import PersonalIdErrorModal from '$lib/components/modal/PersonalIdErrorModal.svelte'; import LinkErrorModal from '$lib/components/modal/LinkErrorModal.svelte'; + import type { Writable } from 'svelte/store'; let pageIndex = 0; let pagesFilled = [false, false, false, false, false, false, false, false]; - let editModePageIndex = 3; + const editModePageIndex = 3; const pageCount = pagesFilled.length; + let pageTexts = [ $LL.candidate.register.second.title(), $LL.candidate.register.third.title(), @@ -47,7 +47,6 @@ let details = data.candidate; let baseCandidateDetails = data.whoami; - let personalIdBirthdateMatch = true; const formInitialValues = { gdpr: false, personalIdOk: false, @@ -107,7 +106,12 @@ telephone: yup .string() .required() - .matches(/^\+\d{1,3} \d{3} \d{3} \d{3}$/), + .test((_val) => { + if (!_val) return false; + const number = parsePhoneNumber(_val); + if (!number) return false; + return number.isValid(); + }), // already validated by the 'TelephoneField' component birthplace: yup.string().required(), birthdate: yup .string() @@ -175,10 +179,13 @@ return _val !== ''; }), telephone: yup.string().test((_val, context) => { - if (context.path.includes('parents[1]') && _val === '') { + if (context.path.includes('parents[1]')) { return true; } - return _val?.match(/^\+\d{1,3} \d{3} \d{3} \d{3}$/) !== null; + if (!_val) return false; + const number = parsePhoneNumber(_val); + if (!number) return false; + return number.isValid(); }) }) ) @@ -209,29 +216,30 @@ personalIdModal: false, linkErrorModal: false }; + const validatePersonalId = () => { + if ($form.candidate.citizenship === 'Česká republika') { + if ( + !isPersonalIdNumberWithBirthdateValid( + $form.candidate.personalIdNumber, + $form.candidate.birthdate + ) + ) { + toast.push('Rodné číslo neodpovídá oficiální specifikaci či datumu narození', { + theme: { + '--toastColor': 'mintcream', + '--toastBackground': '#b91c1c', + '--toastBarBackground': '#7f1d1d' + } + }); + throw new Error('Rodné číslo neodpovídá datumu narození'); + } + } + }; const onSubmit = async (values: CandidateData) => { - if (pageIndex === 3) { - if (values.candidate.citizenship === 'Česká republika') { - if ( - !isPersonalIdNumberWithBirthdateValid( - values.candidate.personalIdNumber, - values.candidate.birthdate - ) - ) { - toast.push('Rodné číslo neodpovídá oficiální specifikaci či datumu narození', { - theme: { - '--toastColor': 'mintcream', - '--toastBackground': '#b91c1c', - '--toastBarBackground': '#7f1d1d' - } - }); - personalIdBirthdateMatch = false; - throw new Error('Rodné číslo neodpovídá datumu narození'); - } - } - personalIdBirthdateMatch = true; - } + console.log('submit button clicked'); + console.log(pagesFilled.map((_, i) => !isPageInvalid(i))); + if (pageIndex === pageCount) { console.log('submitting'); // clone values to oldValues @@ -286,7 +294,6 @@ onSubmit: async (values: CandidateData) => onSubmit(values) }); - const isPageInvalid = (index: number): boolean => { switch (index) { case 0: @@ -330,8 +337,7 @@ $typedErrors['candidate']['birthdate'] || $typedErrors['candidate']['birthplace'] || $typedErrors['candidate']['personalIdNumber'] || - $typedErrors['candidate']['testLanguage'] || - !personalIdBirthdateMatch + $typedErrors['candidate']['testLanguage'] ) { return true; } @@ -511,31 +517,33 @@ /> - - - - - - - - - - + + + + + + + + + + + @@ -675,7 +683,7 @@ @@ -704,7 +712,7 @@ @@ -746,10 +754,14 @@ { + if (pageIndex === 4) { + console.log('validating personal id'); + validatePersonalId(); + } await handleSubmit(e); + console.log(pagesFilled.map((_, i) => !isPageInvalid(i))); if (isPageInvalid(pageIndex)) return; - if (pageIndex === pageCount) { - } else { + if (pageIndex !== pageCount) { pagesFilled[pageIndex] = true; pageIndex++; } @@ -765,6 +777,9 @@ { + if (pageIndex === 4 && i > pageIndex) { + validatePersonalId(); + } pageIndex -= pageIndex === pageCount ? 1 : 0; await handleSubmit(e); pagesFilled = pagesFilled.map((_, i) => !isPageInvalid(i)); diff --git a/frontend/src/routes/(candidate)/auth/login/+page.svelte b/frontend/src/routes/(candidate)/auth/login/+page.svelte index 6920fa8..19d9be2 100644 --- a/frontend/src/routes/(candidate)/auth/login/+page.svelte +++ b/frontend/src/routes/(candidate)/auth/login/+page.svelte @@ -21,7 +21,7 @@ {$LL.candidate.auth.login.title()} - + {$LL.candidate.auth.login.description()}
+
{$LL.candidate.auth.login.description()}