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" />