-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gg/form validation cherrypick #65
Merged
Merged
Changes from 7 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
dda916b
wip: react form hook
ca09d81
add the api call
d24b4b1
Added form validation with zod and refactored the components
2c6cd50
update calculation schema
7b7b3ce
add data type
0b65c42
update calculation input
7237d4a
update spinners package
30f14df
fixed typo
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,167 +1,140 @@ | ||
"use client"; | ||
import React, { FormEvent, useState } from "react"; | ||
import React, { useState } from "react"; | ||
import { useForm } from "react-hook-form"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
|
||
import calculateFairhold from "@/app/models/testClasses"; | ||
import { Household } from "@/app/models/Household"; | ||
import Dashboard from "./Dashboard"; | ||
import { | ||
calculationSchema, | ||
Calculation, | ||
} from "@/app/schemas/calculationSchema"; | ||
|
||
import RadioButton from "./RadioButton"; | ||
import InputField from "./InputField"; | ||
|
||
import { ClipLoader } from "react-spinners"; | ||
|
||
const houseTypes = { | ||
Detached: "D", | ||
Semidetached: "S", | ||
Terrace: "T", | ||
Flat: "F", | ||
}; // variables associated to the house types | ||
|
||
const CalculatorInput = () => { | ||
const { | ||
register, | ||
handleSubmit, | ||
formState: { errors }, | ||
} = useForm<Calculation>({ | ||
resolver: zodResolver(calculationSchema), | ||
defaultValues: { | ||
houseType: "D", // Default value for house type | ||
}, | ||
}); | ||
|
||
// create different view states: one for form and one for graph dashboard | ||
const [view, setView] = useState("form"); | ||
const [data, setData] = useState<Household | null>(null); | ||
const [housePostcode, sethousePostcode] = useState(""); // variable associated to the postcode | ||
const houseTypes = { | ||
Detached: "D", | ||
Semidetached: "S", | ||
Terrace: "T", | ||
Flat: "F", | ||
}; // variables associated to the house types | ||
const [houseType, setHouseType] = useState(houseTypes.Detached); // variables associated to the house type | ||
const [houseBedrooms, setHouseBedrooms] = useState(""); // variables associated to the number of bedrooms in the house | ||
const [howSize, setHouseSize] = useState(""); // variables associated to the house size | ||
const [houseAge, setHouseAge] = useState(""); // variables associated to the house age | ||
|
||
// fucntion that defines what happens after submitting the form | ||
async function handleSubmit(e: FormEvent<HTMLFormElement>) { | ||
e.preventDefault(); // pr event the default of the form | ||
const formData = new FormData(e.currentTarget); // get the data in the form, e.g postcode, house size etc | ||
const data = Object.fromEntries(formData.entries()); | ||
|
||
//fetch the data: call the api and attach the form data | ||
const onSubmit = async (data: Calculation) => { | ||
setView("loading"); | ||
const response = await fetch("/api", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify(data), // pass the form data to the API | ||
}); | ||
|
||
const jsonData = await response.json(); | ||
console.log("handleSubmit jsonData: ", jsonData); | ||
const processedData = calculateFairhold(jsonData); | ||
console.log("handleSubmit processedData: ", processedData); | ||
|
||
// saved processedData & switch to dashboard view | ||
setData(processedData); | ||
setView("dashboard"); | ||
} | ||
}; | ||
|
||
return view === "form" ? ( | ||
<div className="flex -centeitemsr justify-center text-black mt-5"> | ||
<div className=" w-1/2 border-black border-2 rounded-lg "> | ||
<div className="bg-black text-white h-48 flex items-center justify-center"> | ||
<h1 className="text-6xl">Fairhold Calculator</h1> | ||
</div> | ||
<form onSubmit={handleSubmit} className=" flex flex-col m-5"> | ||
<h2 className="mb-1 font-bold">House postcode</h2> | ||
<input | ||
className="mb-3 focus:outline-none focus:border-sky-500 focus:ring-1 focus:ring-sky-500 | ||
disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none | ||
rounded-md " | ||
type="text" | ||
placeholder="e.g. SE17 1PE" | ||
value={housePostcode} | ||
onChange={(e) => sethousePostcode(e.target.value)} | ||
name="housePostcode" | ||
/> | ||
<h2 className="mb-1 font-bold">House size</h2> | ||
<input | ||
className="mb-3 focus:outline-none focus:border-sky-500 focus:ring-1 focus:ring-sky-500 | ||
disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none | ||
rounded-md " | ||
type="number" | ||
placeholder="Provide the house size in m square, e.g. 66" | ||
value={howSize} | ||
onChange={(e) => setHouseSize(e.target.value)} | ||
name="houseSize" | ||
/> | ||
<h2 className="mb-1 font-bold">House typology</h2> | ||
<div className="flex"> | ||
<label className="mx-2"> | ||
<input | ||
className="accent-black" | ||
type="radio" | ||
id="Detached" | ||
name="houseType" | ||
value={houseTypes.Detached} | ||
checked={houseType === houseTypes.Detached} | ||
onChange={() => setHouseType(houseTypes.Detached)} | ||
/> | ||
Detached | ||
</label> | ||
|
||
<label className="mx-2"> | ||
<input | ||
className="accent-black" | ||
type="radio" | ||
id="Semidetached" | ||
name="houseType" | ||
value={houseTypes.Semidetached} | ||
checked={houseType === houseTypes.Semidetached} | ||
onChange={() => setHouseType(houseTypes.Semidetached)} | ||
/> | ||
Semi-Detached | ||
</label> | ||
if (view === "form") { | ||
return ( | ||
<div className="flex -centeitemsr justify-center text-black mt-5"> | ||
<div className=" w-1/2 border-black border-2 rounded-lg "> | ||
<div className="bg-black text-white h-48 flex items-center justify-center"> | ||
<h1 className="text-6xl">Fairhold Calculator</h1> | ||
</div> | ||
<form | ||
onSubmit={handleSubmit(onSubmit)} | ||
className=" flex flex-col m-5" | ||
> | ||
<h2 className="mb-1 font-bold">House postcode</h2> | ||
<InputField | ||
id="housePostcode" | ||
type="text" | ||
placeholder="set the postcode, e.g. SE17 1PE" | ||
register={register} | ||
error={errors.housePostcode?.message} | ||
/> | ||
|
||
<label className="mx-2"> | ||
<input | ||
className="accent-black" | ||
type="radio" | ||
id="Terrace" | ||
name="houseType" | ||
value={houseTypes.Terrace} | ||
checked={houseType === houseTypes.Terrace} | ||
onChange={() => setHouseType(houseTypes.Terrace)} | ||
/> | ||
Terrace | ||
</label> | ||
<h2 className="mb-1 font-bold">House size</h2> | ||
<InputField | ||
type="number" | ||
placeholder="Provide the house size in m square, e.g. 66" | ||
id="houseSize" | ||
register={register} | ||
error={errors.houseSize?.message} | ||
/> | ||
|
||
<label className="mx-2"> | ||
<input | ||
className="accent-black" | ||
type="radio" | ||
id="Flat" | ||
name="houseType" | ||
value={houseTypes.Flat} | ||
checked={houseType === houseTypes.Flat} | ||
onChange={() => setHouseType(houseTypes.Flat)} | ||
/> | ||
Flat | ||
</label> | ||
</div> | ||
<h2 className="mb-1 font-bold">House type</h2> | ||
<div className="flex"> | ||
{Object.entries(houseTypes).map(([label, value]) => ( | ||
<RadioButton | ||
key={label} | ||
label={label} | ||
id={label} | ||
value={value} | ||
register={register} | ||
error={errors.houseType?.message} | ||
/> | ||
))} | ||
</div> | ||
|
||
<h2 className="mb-1 font-bold">House age</h2> | ||
<input | ||
className="mb-3 focus:outline-none focus:border-sky-500 focus:ring-1 focus:ring-sky-500 | ||
disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none | ||
rounded-md " | ||
type="number" | ||
placeholder="Provide the house age in years. For a new build, insert age 1" | ||
value={houseAge} | ||
onChange={(e) => setHouseAge(e.target.value)} | ||
name="houseAge" | ||
/> | ||
<h2 className="mb-1 font-bold">Number of bedrooms</h2> | ||
<input | ||
className="mb-3 focus:outline-none focus:border-sky-500 focus:ring-1 focus:ring-sky-500 | ||
disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none | ||
rounded-md " | ||
type="number" | ||
placeholder="Provide the number of bedrooms e.g. 2" | ||
value={houseBedrooms} | ||
onChange={(e) => setHouseBedrooms(e.target.value)} | ||
name="houseBedrooms" | ||
/> | ||
<button | ||
className="text-white bg-black w-1/3 rounded-xl" | ||
type="submit" | ||
> | ||
Calculate | ||
</button> | ||
</form> | ||
<h2 className="mb-1 font-bold">House age</h2> | ||
<InputField | ||
type="number" | ||
placeholder="Provide the house age in years. For a new build, insert age 1" | ||
id="houseAge" | ||
register={register} | ||
error={errors.houseAge?.message} | ||
/> | ||
<h2 className="mb-1 font-bold">Number of bedrooms</h2> | ||
<InputField | ||
type="number" | ||
placeholder="Provide the number of bedrooms e.g. 2" | ||
id="houseBedrooms" | ||
register={register} | ||
error={errors.houseBedrooms?.message} | ||
/> | ||
<button | ||
className="text-white bg-black w-1/3 rounded-xl" | ||
type="submit" | ||
> | ||
Calculate | ||
</button> | ||
</form> | ||
</div> | ||
</div> | ||
</div> | ||
) : ( | ||
<Dashboard data={data as Household} /> | ||
); | ||
); | ||
} else if (view === "loading") { | ||
return ( | ||
<div className="flex items-center justify-center h-screen text-black mt-5"> | ||
<ClipLoader color="black" size={50} /> | ||
</div> | ||
); | ||
} else if (view === "dashboard") { | ||
return <Dashboard data={data as Household} />; | ||
} | ||
}; | ||
|
||
export default CalculatorInput; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import React from "react"; | ||
import { UseFormRegister } from "react-hook-form"; | ||
import { Calculation } from "@/app/schemas/calculationSchema"; | ||
|
||
interface InputFieldProps { | ||
id: keyof Calculation; | ||
placeholder: string; | ||
type: string; | ||
register: UseFormRegister<Calculation>; | ||
error?: string; | ||
} | ||
|
||
const InputField: React.FC<InputFieldProps> = ({ | ||
id, | ||
placeholder, | ||
register, | ||
type, | ||
error, | ||
}) => { | ||
return ( | ||
<> | ||
<input | ||
className="mb-3 focus:outline-none focus:border-sky-500 focus:ring-1 focus:ring-sky-500 | ||
disabled:bg-slate-50 disabled:text-slate-500 disabled:border-slate-200 disabled:shadow-none | ||
rounded-md " | ||
type={type} | ||
placeholder={placeholder} | ||
{...register(id)} | ||
id={id} | ||
key={id} | ||
/> | ||
{error && <p className="text-red-500">{error}</p>} | ||
</> | ||
); | ||
}; | ||
export default InputField; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import React from "react"; | ||
import { UseFormRegister } from "react-hook-form"; | ||
import { Calculation } from "@/app/schemas/calculationSchema"; | ||
|
||
interface radioButtonProps { | ||
id: string; | ||
value: string; | ||
label: string; | ||
register: UseFormRegister<Calculation>; | ||
error?: string; | ||
} | ||
|
||
const RadioButton: React.FC<radioButtonProps> = ({ | ||
id, | ||
value, | ||
label, | ||
register, | ||
error, | ||
}) => { | ||
return ( | ||
<> | ||
<label className="mx-2"> | ||
<input | ||
className="accent-black" | ||
type="radio" | ||
id={id} | ||
{...register("houseType")} | ||
value={value} | ||
/> | ||
{label} | ||
</label> | ||
{error && <p className="text-red-500">{error}</p>} | ||
</> | ||
); | ||
}; | ||
|
||
export default RadioButton; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Think this is a typo, maybe
centeritems
or something was meant?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!