aggiunta libreria per stile e validazione

This commit is contained in:
Dmitri 2025-11-19 00:06:55 +01:00
parent 77758f2c29
commit fbd90e1649
Signed by: kanopo
GPG Key ID: 759ADD40E3132AC7
3 changed files with 1274 additions and 139 deletions

1103
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,12 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@fontsource/roboto": "^5.2.8",
"@hookform/resolvers": "^5.2.2",
"@mui/material": "^7.3.5",
"@mui/x-date-pickers": "^8.18.0",
"@tanstack/react-query": "^5.90.9", "@tanstack/react-query": "^5.90.9",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
@ -11,13 +17,16 @@
"@types/node": "^16.18.3", "@types/node": "^16.18.3",
"@types/react": "^18.0.25", "@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9", "@types/react-dom": "^18.0.9",
"date-fns": "^4.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hook-form": "^7.66.1",
"react-router": "^6.4.3", "react-router": "^6.4.3",
"react-router-dom": "^6.4.3", "react-router-dom": "^6.4.3",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"typescript": "^4.9.3", "typescript": "^4.9.3",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4",
"yup": "^1.7.1"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -1,48 +1,102 @@
import { useMutation, useQuery } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { useState } from "react"; import { Controller, SubmitHandler, useForm, useWatch } from "react-hook-form";
import { BaseResponse, FormState } from "../interfaces"; import { Button, Checkbox, FormControlLabel, Stack, TextField } from "@mui/material";
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import * as yup from "yup";
import { differenceInYears, isValid } from "date-fns";
import { yupResolver } from "@hookform/resolvers/yup";
import { useEffect } from "react";
const schema = yup.object({
name: yup.string().required("Name is required"),
age: yup.number()
.typeError("Age must be a number")
.positive()
.integer()
.required("Age is required"),
married: yup.boolean()
.required(),
dateOfBirth: yup.date()
.typeError("Devi inserire una data valida")
.required("DOB is required")
.test("dob-match", "Date of Birth does not match the Age provided", function(value) {
const { age } = this.parent;
if (!value || typeof age !== 'number') return true;
if (!isValid(value)) return false;
const calculatedAge = differenceInYears(new Date(), value);
return calculatedAge === age;
}),
}).required();
type FormSchemaType = yup.InferType<typeof schema>;
interface BaseResponse {
success: boolean;
}
const CheckAgeForm = () => { const CheckAgeForm = () => {
const [form, setForm] = useState<FormState>({ const {
name: "", control,
age: 0, handleSubmit,
dateOfBirth: new Date() reset,
}) trigger,
const validate = useMutation({ setValue,
mutationFn: async (data: FormState): Promise<BaseResponse> => { formState: { errors, isValid, isDirty }
const res = await fetch('http://localhost:3001/info/validate-form', { } = useForm<FormSchemaType>({
method: 'POST', mode: "onChange",
headers: { resolver: yupResolver(schema),
'Content-Type': 'application/json' defaultValues: {
},
body: JSON.stringify({
...data
})
})
// TODO: usare axios o altre librerie per avere un minimo di typesafety, qui sto pregando che il risultato sia quello che mi aspetto e sinceramente non so se dia errore fino a quando non prova a chiamare un oggetto annidato
const resData: BaseResponse = await res.json()
console.log(resData)
return resData
}
})
const handleSand = async () => {
await validate.mutateAsync({
...form
})
}
const handleReset = () => {
validate.reset()
setForm({
name: "", name: "",
age: 0, age: 0,
married: false,
dateOfBirth: new Date() dateOfBirth: new Date()
}) },
} });
const watchedAge = useWatch({ control, name: "age" });
const isAbove18 = (watchedAge || 0) >= 18;
useEffect(() => {
if (!isAbove18) {
setValue("married", false, { shouldValidate: true });
}
if (control._fields['dateOfBirth']) {
trigger("dateOfBirth");
}
}, [watchedAge, isAbove18, control]);
const validate = useMutation({
mutationFn: async (data: FormSchemaType): Promise<BaseResponse> => {
const res = await fetch('http://localhost:3001/info/validate-form', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const resData: BaseResponse = await res.json();
return resData;
}
});
const onSubmit: SubmitHandler<FormSchemaType> = async (data) => {
await validate.mutateAsync(data);
};
const handleReset = () => {
validate.reset();
reset({
name: "",
age: 0,
married: false,
dateOfBirth: new Date()
});
};
const isAbove18 = form.age >= 18
if (validate.isError) { if (validate.isError) {
return ( return (
@ -55,96 +109,115 @@ const CheckAgeForm = () => {
if (validate.isPending) { if (validate.isPending) {
return ( return (
<div> <div><h1>INVIO IN CORSO...</h1></div>
<h1>INVIO IN CORSO</h1>
<button onClick={handleReset}>ANNULLA</button>
</div>
); );
} }
if (validate.isSuccess) { if (validate.isSuccess) {
return ( return (
<div> <div>
{validate.data?.success === true && <h1>DATI INVIATI VALIDI</h1>} {validate.data?.success ? <div>
{validate.data?.success === false && <h1>DATI INVIATI NON VALIDI</h1>} <h1>DATI VALIDI</h1>
<button onClick={handleReset}>INVIA UN ALTRO VALORE</button> <p>
{JSON.stringify(validate.data)}
</p>
</div>
: <h1>DATI NON VALIDI</h1>}
<button onClick={handleReset}>RESET</button>
</div> </div>
); );
} }
return ( return (
<div> <LocalizationProvider dateAdapter={AdapterDateFns}>
<input <Stack sx={{ width: "90%", margin: "auto", gap: 2, mt: 5 }}>
type="text"
placeholder="name"
value={form.name}
onChange={(e) => {
setForm((prev) => {
return {
...prev,
name: e.target.value
}
});
}}
/>
<input <Controller
type="number" name="name"
placeholder="age" control={control}
value={form.age} render={({ field }) => (
onChange={(e) => { <TextField
setForm((prev) => { {...field}
const age = Number.parseInt(e.target.value) ?? 0 label="Name"
return { error={!!errors.name}
...prev, helperText={errors.name?.message}
age />
} )}
}); />
}}
/>
{ <Controller
(isAbove18) && ( name="age"
<input control={control}
type="checkbox" render={({ field }) => (
placeholder="married?" <TextField
checked={form.married} {...field}
onChange={() => { label="Age"
setForm((prev) => { type="number"
return { error={!!errors.age}
...prev, helperText={errors.age?.message}
married: !(prev.married ?? false) />
)}
/>
<Controller
name="dateOfBirth"
control={control}
render={({ field }) => (
<DatePicker
{...field}
label="Date of birth"
slotProps={{
textField: {
error: !!errors.dateOfBirth,
helperText: errors.dateOfBirth?.message
} }
}); }}
}} />
/> )}
) />
}
<input
type="date"
placeholder="date of birth"
value={form.dateOfBirth.toISOString().split("T")[0]}
onChange={(e) => {
setForm((prev) => {
const newDate = new Date(e.target.value);
return {
...prev,
dateOfBirth: newDate,
};
});
}}
/>
<button <FormControlLabel
onClick={handleSand} control={
> <Controller
VALIDA name="married"
</button> control={control}
render={({ field }) => (
<Checkbox
checked={!!field.value}
onChange={(e) => field.onChange(e.target.checked)}
disabled={!isAbove18}
/>
)}
/>
}
label={"Married?"}
/>
</div> <Stack
) sx={{
width: "100%",
gap: 2,
flexDirection: "row",
justifyContent: "flex-end"
}}
>
<Button
disabled={!isDirty}
variant="outlined"
onClick={handleReset}
>
UNDO
</Button>
<Button
disabled={!isValid}
variant="contained"
onClick={handleSubmit(onSubmit)}>
VALIDA
</Button>
</Stack>
</Stack>
</LocalizationProvider>
);
} }
export default CheckAgeForm export default CheckAgeForm;