From a2cb8c06970ebb5403ba37db92d4e36e019acbc9 Mon Sep 17 00:00:00 2001 From: Claas Date: Wed, 17 Jun 2026 00:04:21 +0000 Subject: [PATCH] Add validation --- client/src/index.css | 3 +++ client/src/routes/times/new.tsx | 35 +++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/client/src/index.css b/client/src/index.css index cac22d3..c01be8e 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -13,6 +13,9 @@ /* @apply in components is afaik not best practice but the only way to get color variables from plugins as Tailwind CSS v4 does not create variables for those and has the same behavior as in v3 */ .text-field { @apply border-outline rounded-extra-small focus-visible:outline-primary text-body-lg border px-4 py-4 leading-6 focus-visible:outline-3; + &:user-invalid { + @apply outline-error outline-3; + } } /* TODO button states */ diff --git a/client/src/routes/times/new.tsx b/client/src/routes/times/new.tsx index 7e33502..75b6484 100644 --- a/client/src/routes/times/new.tsx +++ b/client/src/routes/times/new.tsx @@ -21,10 +21,7 @@ function RouteComponent() { const navigate = useNavigate(); - async function handleSubmit(event: SubmitEvent) { - event.preventDefault(); - - const form = event.currentTarget as HTMLFormElement; + function extractValues(form: HTMLFormElement) { const dateInput = form.elements.namedItem("date") as HTMLInputElement; const date = Temporal.PlainDate.from(dateInput.value); console.debug("Date", date); @@ -43,6 +40,14 @@ function RouteComponent() { timeZone, }); + return { start, end, startInput, endInput }; + } + async function handleSubmit(event: SubmitEvent) { + event.preventDefault(); + + const form = event.currentTarget as HTMLFormElement; + + const { start, end } = extractValues(form); const projectSelect = form.elements.namedItem("project") as HTMLSelectElement; const projectId = projectSelect.value; @@ -69,6 +74,26 @@ function RouteComponent() { } const selectableProjects = () => projects.data?.filter(isProject); + + function handleInput(event: InputEvent) { + const input = event.currentTarget as HTMLInputElement; + const isStart = input.name === "start"; + const { start, end, startInput, endInput } = extractValues(input.form!); + + if (isStart && Temporal.ZonedDateTime.compare(start, end) >= 0) { + startInput.setCustomValidity("Start time must be before end time."); + return; + } else { + startInput.setCustomValidity(""); + } + + if (!isStart && Temporal.ZonedDateTime.compare(end, start) >= 0) { + endInput.setCustomValidity("Start time must be before end time."); + return; + } else { + endInput.setCustomValidity(""); + } + } return ( <> @@ -103,6 +128,7 @@ function RouteComponent() { id="start" name="start" required + onInput={handleInput} class="text-field row-start-4 mt-1 w-full" /> @@ -114,6 +140,7 @@ function RouteComponent() { id="end" name="end" required + onInput={handleInput} value={Temporal.Now.plainTimeISO().toString().substring(0, 5)} class="text-field row-start-4 mt-1 w-full" /> -- 2.51.2