Merge pull request #82 from EETagent/form_nested

(frontend) nested form
This commit is contained in:
Vojtěch Jungmann 2022-12-06 19:23:56 +01:00 committed by GitHub
commit 2ca3991a15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 161 additions and 88 deletions

View file

@ -79,11 +79,22 @@ export const apiLogin = async (data: CandidateLogin): Promise<number> => {
}; };
export const apiFillDetails = async (data: CandidateData): Promise<CandidateData> => { export const apiFillDetails = async (data: CandidateData): Promise<CandidateData> => {
Object.keys(data).forEach((key) => { // Sanitize candidate data
Object.keys(data.candidate).forEach((key) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
data[key] = DOMPurify.sanitize(data[key]); data.candidate[key] = DOMPurify.sanitize(data.candidate[key]);
}); });
// Sanitize parents data
for (let index = 0; index < data.parents.length; index++) {
Object.keys(data.parents[index]).forEach((key) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
data.parents[index][key] = DOMPurify.sanitize(data.parents[index][key]);
});
}
console.log(data);
try { try {
const res = await axios.post(API_URL + '/candidate/details', data, { withCredentials: true }); const res = await axios.post(API_URL + '/candidate/details', data, { withCredentials: true });
return res.data; return res.data;

View file

@ -1,21 +1,25 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
export interface CandidateData { export interface CandidateData {
name?: string; candidate: {
surname?: string; name?: string;
birthplace?: string; surname?: string;
birthdate?: string; birthplace?: string;
address?: string; birthdate?: string;
telephone?: string; address?: string;
citizenship?: string; telephone?: string;
email?: string; citizenship?: string;
sex?: string; email?: string;
study?: string; sex?: string;
personalIdNumber?: string; study?: string;
parentName?: string; personalIdNumber?: string;
parentSurname?: string; };
parentTelephone?: string; parents: Array<{
parentEmail?: string; name?: string;
surname?: string;
telephone?: string;
email?: string;
}>;
} }
export interface CandidatePreview { export interface CandidatePreview {

View file

@ -34,8 +34,8 @@
<FullLayout> <FullLayout>
<div class="dashboard dashboardDesktop"> <div class="dashboard dashboardDesktop">
<div class="name col-span-3 <2xl:col-span-4"> <div class="name col-span-3 <2xl:col-span-4">
<DashboardInfoCard status={getUploadStatus($submissionProgress.status)} title={$candidateData.name + ' ' + $candidateData.surname ?? ''}> <DashboardInfoCard status={getUploadStatus($submissionProgress.status)} title={$candidateData.candidate.name + ' ' + $candidateData.candidate.surname ?? ''}>
<span class="text-sspsBlue mt-3 truncate">{$candidateData.email}</span> <span class="text-sspsBlue mt-3 truncate">{$candidateData.candidate.email}</span>
<span class="text-sspsGray mt-3 text-xs">Uchazeč na SSPŠ</span> <span class="text-sspsGray mt-3 text-xs">Uchazeč na SSPŠ</span>
</DashboardInfoCard> </DashboardInfoCard>
</div> </div>
@ -51,8 +51,8 @@
</div> </div>
<div class="dashboard dashboardMobile"> <div class="dashboard dashboardMobile">
<div class="name my-10 mx-auto w-[90%]"> <div class="name my-10 mx-auto w-[90%]">
<DashboardInfoCard status={getUploadStatus($submissionProgress.status)} title={$candidateData.name + ' ' + $candidateData.surname ?? ''}> <DashboardInfoCard status={getUploadStatus($submissionProgress.status)} title={$candidateData.candidate.name + ' ' + $candidateData.candidate.surname ?? ''}>
<span class="text-sspsBlue mt-3 truncate">{$candidateData.email}</span> <span class="text-sspsBlue mt-3 truncate">{$candidateData.candidate.email}</span>
<span class="text-sspsGray mt-3 text-xs">Uchazeč na SSPŠ</span> <span class="text-sspsGray mt-3 text-xs">Uchazeč na SSPŠ</span>
</DashboardInfoCard> </DashboardInfoCard>
</div> </div>

View file

@ -12,8 +12,10 @@
import NameField from '$lib/components/textfield/NameField.svelte'; import NameField from '$lib/components/textfield/NameField.svelte';
import TelephoneField from '$lib/components/textfield/TelephoneField.svelte'; import TelephoneField from '$lib/components/textfield/TelephoneField.svelte';
import TextField from '$lib/components/textfield/TextField.svelte'; import TextField from '$lib/components/textfield/TextField.svelte';
import type { CandidateData } from '$lib/stores/candidate';
import { createForm } from 'svelte-forms-lib'; import { createForm } from 'svelte-forms-lib';
import type { Writable } from 'svelte/store';
import * as yup from 'yup'; import * as yup from 'yup';
const pageCount = 3; const pageCount = 3;
@ -21,26 +23,31 @@
let pagesFilled = 0; let pagesFilled = 0;
const formInitialValues = { const formInitialValues = {
name: '', candidate: {
surname: '', name: '',
email: '', surname: '',
telephone: '', email: '',
birthplace: '', telephone: '',
birthdate: '', birthplace: '',
sex: '', birthdate: '',
address: '', sex: '',
citizenship: '', address: '',
personalIdNumber: '', citizenship: '',
study: '', personalIdNumber: '',
parentName: '', study: '',
parentSurname: '', },
parentTelephone: '', parents: [
parentEmail: '' {
name: '',
surname: '',
email: '',
telephone: ''
}
]
}; };
const { form, errors, handleSubmit, handleChange } = createForm({ const formValidationSchema = yup.object().shape({
initialValues: formInitialValues, candidate: yup.object().shape({
validationSchema: yup.object().shape({
name: yup.string().required(), name: yup.string().required(),
surname: yup.string(), surname: yup.string(),
email: yup.string().email().required(), email: yup.string().email().required(),
@ -49,28 +56,55 @@
.required() .required()
.matches(/^\+\d{1,3} \d{3} \d{3} \d{3}$/), .matches(/^\+\d{1,3} \d{3} \d{3} \d{3}$/),
birthplace: yup.string().required(), birthplace: yup.string().required(),
birthdate: yup.string().required(), birthdate: yup
.string()
.required()
.matches(/^([0-3]?[0-9])\.([1-9]|1[0-2])\.[0-9]{4}$/),
sex: yup.string(), sex: yup.string(),
address: yup.string().required(), address: yup.string().required(),
citizenship: yup.string().required(), citizenship: yup.string().required(),
personalIdNumber: yup.string().required(), personalIdNumber: yup.string().required(),
study: yup.string().required(), study: yup.string().required()
parentName: yup.string(),
parentSurname: yup.string(),
parentTelephone: yup
.string()
.required()
.matches(/^\+\d{1,3} \d{3} \d{3} \d{3}$/),
parentEmail: yup.string().email().required()
}), }),
parents: yup
.array()
.of(
yup.object().shape({
name: yup.string().required(),
surname: yup.string().required(),
email: yup.string().email().required(),
telephone: yup
.string()
.required()
.matches(/^\+\d{1,3} \d{3} \d{3} \d{3}$/)
})
)
.required()
});
onSubmit: async (values) => { const { form, errors, handleSubmit, handleChange } = createForm({
initialValues: formInitialValues,
validationSchema: formValidationSchema,
onSubmit: async (values: CandidateData) => {
console.log('page count: ' + pageIndex);
console.log(values.candidate);
console.log(values.parents);
console.log(values);
if (pageIndex === pageCount) { if (pageIndex === pageCount) {
try { try {
console.log('submit'); console.log('submit');
// @ts-ignore // love javascript // @ts-ignore // love javascript
delete values.undefined; delete values.undefined;
values.birthdate = '2000-01-01'; // TODO: reformat user typed date // convert birthdate from dd.mm.yyyy to yyyy-mm-dd
let birthdate_formttted = values.candidate
.birthdate!.split('.')
.map((x) => x.padStart(2, '0'))
.reverse()
.join('-');
values.candidate.birthdate = birthdate_formttted;
await apiFillDetails(values); await apiFillDetails(values);
goto('/dashboard'); goto('/dashboard');
} catch (e) { } catch (e) {
@ -80,35 +114,57 @@
} }
}); });
$: console.log($errors); type FormErrorType = {
[K in keyof typeof formInitialValues]: typeof formInitialValues[K] extends Record<
string,
unknown
>
? {
[K2 in keyof typeof formInitialValues[K]]: string;
}
: typeof formInitialValues[K] extends Array<Record<string, unknown>>
? Array<{ [K3 in keyof typeof formInitialValues[K][number]]: string }>
: string;
};
// TODO: https://github.com/tjinauyeung/svelte-forms-lib/issues/171!! (Zatím tenhle mega typ)
$: typedErrors = errors as unknown as Writable<FormErrorType>;
const isPageInvalid = (): boolean => { const isPageInvalid = (): boolean => {
switch (pageIndex) { switch (pageIndex) {
case 0: case 0:
if ($errors.name || $errors.email || $errors.telephone) { if (
$typedErrors['candidate']['name'] ||
$typedErrors['candidate']['email'] ||
$typedErrors['candidate']['telephone']
) {
return true; return true;
} }
break; break;
case 1: case 1:
if ( if (
/* $errors.birthdurname || */ $errors.birthplace || /* $typedErrors.birthdurname || */ $typedErrors['candidate']['birthplace'] ||
$errors.birthdate /* || $errors.sex */ $typedErrors['candidate']['birthdate'] /* || $typedErrors.sex */
) { ) {
return true; return true;
} }
break; break;
case 2: case 2:
if ($errors.address || $errors.parentEmail || $errors.parentTelephone) { if (
$typedErrors['candidate']['address'] ||
$typedErrors['parents'][0]['email'] ||
$typedErrors['parents'][0]['telephone']
) {
return true; return true;
} }
break; break;
case 3: case 3:
if ( if (
$errors.citizenship || $typedErrors['candidate']['citizenship'] ||
$errors.personalIdNumber || $typedErrors['candidate']['personalIdNumber'] ||
$errors.study //|| $typedErrors['candidate']['study'] //||
// $errors.applicationId // $typedErrors.applicationId
) { ) {
return true; return true;
} }
@ -135,27 +191,27 @@
<div class="flex w-full items-center justify-center md:flex-col"> <div class="flex w-full items-center justify-center md:flex-col">
<span class="mt-8 w-full"> <span class="mt-8 w-full">
<NameField <NameField
error={$errors.name} error={$typedErrors['candidate']['name']}
on:change={handleChange} on:change={handleChange}
bind:valueName={$form.name} bind:valueName={$form.candidate.name}
bind:valueSurname={$form.surname} bind:valueSurname={$form.candidate.surname}
placeholder="Jméno a příjmení" placeholder="Jméno a příjmení"
/> />
</span> </span>
<span class="mt-8 ml-2 w-full md:ml-0"> <span class="mt-8 ml-2 w-full md:ml-0">
<EmailField <EmailField
error={$errors.email} error={$typedErrors['candidate']['email']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.email} bind:value={$form.candidate.email}
placeholder="E-mail" placeholder="E-mail"
/> />
</span> </span>
</div> </div>
<div class="mt-8 w-full"> <div class="mt-8 w-full">
<TelephoneField <TelephoneField
error={$errors.telephone} error={$typedErrors['candidate']['telephone']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.telephone} bind:value={$form.candidate.telephone}
placeholder="Telefon" placeholder="Telefon"
/> />
</div> </div>
@ -169,23 +225,23 @@
<div class="flex w-full flex-row md:flex-col"> <div class="flex w-full flex-row md:flex-col">
<span class="mt-8 w-full"> <span class="mt-8 w-full">
<NameField <NameField
error={$errors.name} error={$typedErrors['parents'][0]['name'] || $typedErrors['parents'][0]['surname']}
on:change={handleChange} on:change={handleChange}
bind:valueName={$form.parentName} bind:valueName={$form.parents[0].name}
bind:valueSurname={$form.parentSurname} bind:valueSurname={$form.parents[0].surname}
placeholder="Jméno a příjmení zákonného zástupce" placeholder="Jméno a příjmení zákonného zástupce"
/> />
</span> </span>
<span class="mt-8 ml-2 w-full md:ml-0"> <span class="mt-8 ml-2 w-full md:ml-0">
<TextField <TextField
error={$errors.birthplace} error={$typedErrors['candidate']['birthplace']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.birthplace} bind:value={$form.candidate.birthplace}
type="text" type="text"
placeholder="Místo narození" placeholder="Místo narození"
icon icon
> >
<div slot="icon" class="flex items-center justify-center text-sspsBlue"> <div slot="icon" class="text-sspsBlue flex items-center justify-center">
<Home /> <Home />
</div> </div>
</TextField> </TextField>
@ -194,17 +250,17 @@
<div class="mt-8 flex w-full items-center"> <div class="mt-8 flex w-full items-center">
<TextField <TextField
error={$errors.birthdate} error={$typedErrors['candidate']['birthdate']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.birthdate} bind:value={$form.candidate.birthdate}
type="text" type="text"
placeholder="Datum narození" placeholder="Datum narození"
/> />
<div class="ml-2"> <div class="ml-2">
<SelectField <SelectField
error={$errors.sex} error={$typedErrors['candidate']['sex']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.sex} bind:value={$form.candidate.sex}
options={['Žena', 'Muž']} options={['Žena', 'Muž']}
placeholder="Pohlaví" placeholder="Pohlaví"
/> />
@ -219,9 +275,9 @@
<div class="flex w-full flex-col"> <div class="flex w-full flex-col">
<span class="mt-8 w-full"> <span class="mt-8 w-full">
<TextField <TextField
error={$errors.address} error={$typedErrors['candidate']['address']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.address} bind:value={$form.candidate.address}
type="text" type="text"
placeholder="Adresa trvalého bydliště" placeholder="Adresa trvalého bydliště"
/> />
@ -229,17 +285,17 @@
<div class="mt-8 flex flex-row items-center md:flex-col"> <div class="mt-8 flex flex-row items-center md:flex-col">
<span class="w-full"> <span class="w-full">
<EmailField <EmailField
error={$errors.parentEmail} error={$typedErrors['parents'][0]['email']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.parentEmail} bind:value={$form.parents[0].email}
placeholder="E-mail zákonného zástupce" placeholder="E-mail zákonného zástupce"
/> />
</span> </span>
<span class="ml-2 w-full md:ml-0 md:mt-8"> <span class="ml-2 w-full md:ml-0 md:mt-8">
<TelephoneField <TelephoneField
error={$errors.parentTelephone} error={$typedErrors['parents'][0]['telephone']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.parentTelephone} bind:value={$form.parents[0].telephone}
placeholder="Telefon zákonného zástupce" placeholder="Telefon zákonného zástupce"
/> />
</span> </span>
@ -254,9 +310,9 @@
<div class="flex w-full flex-row md:flex-col"> <div class="flex w-full flex-row md:flex-col">
<span class="mt-8 w-full"> <span class="mt-8 w-full">
<TextField <TextField
error={$errors.citizenship} error={$typedErrors['candidate']['citizenship']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.citizenship} bind:value={$form.candidate.citizenship}
type="text" type="text"
placeholder="Občanství" placeholder="Občanství"
/> />
@ -267,16 +323,16 @@
</div> </div>
<div class="mt-8 flex w-full items-center justify-center"> <div class="mt-8 flex w-full items-center justify-center">
<IdField <IdField
error={$errors.personalIdNumber} error={$typedErrors['candidate']['personalIdNumber']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.personalIdNumber} bind:value={$form.candidate.personalIdNumber}
placeholder="Rodné číslo" placeholder="Rodné číslo"
/> />
<span class="ml-2"> <span class="ml-2">
<SelectField <SelectField
error={$errors.study} error={$typedErrors['candidate']['study']}
on:change={handleChange} on:change={handleChange}
bind:value={$form.study} bind:value={$form.candidate.study}
placeholder="Obor" placeholder="Obor"
options={['KB', 'IT', 'G']} options={['KB', 'IT', 'G']}
/> />
@ -295,6 +351,7 @@
pagesFilled++; pagesFilled++;
pageIndex++; pageIndex++;
} }
// @ts-ignore
errors.set(formInitialValues); errors.set(formInitialValues);
}} }}
value={pageIndex === pageCount ? 'Odeslat' : 'Pokračovat'} value={pageIndex === pageCount ? 'Odeslat' : 'Pokračovat'}
@ -315,6 +372,7 @@
if (isPageInvalid()) return; if (isPageInvalid()) return;
pagesFilled++; pagesFilled++;
pageIndex++; pageIndex++;
// @ts-ignore
errors.set(formInitialValues); errors.set(formInitialValues);
} }
}} }}