From 363f491de12ca8b9f9274804f2e2801f9d6bc9c2 Mon Sep 17 00:00:00 2001 From: Daniel Shi <144500568+danielshid@users.noreply.github.com> Date: Fri, 31 Jul 2026 19:32:36 +0000 Subject: [PATCH 01/19] stricter form submission logic (reset password not included) --- .../src/app/(auth)/forgot-password/page.tsx | 14 ++++- frontend/src/app/(auth)/login/page.tsx | 18 ++++++- frontend/src/app/(auth)/register/page.tsx | 26 ++++++++-- .../[event-code]/painting/page-client.tsx | 34 ++++++++++-- frontend/src/features/event/editor/editor.tsx | 52 +++++++++++++++++-- 5 files changed, 131 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/(auth)/forgot-password/page.tsx b/frontend/src/app/(auth)/forgot-password/page.tsx index 4575adf9..f2cd7370 100644 --- a/frontend/src/app/(auth)/forgot-password/page.tsx +++ b/frontend/src/app/(auth)/forgot-password/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import Link from "next/link"; @@ -10,6 +10,7 @@ import LinkText from "@/components/link-text"; import TextInputField from "@/components/text-input-field"; import ActionButton from "@/features/button/components/action"; import LinkButton from "@/features/button/components/link"; +import useCheckMobile from "@/lib/hooks/use-check-mobile"; import { useFormErrors } from "@/lib/hooks/use-form-errors"; import { MESSAGES } from "@/lib/messages"; import { clientPost } from "@/lib/utils/api/client-fetch"; @@ -19,10 +20,20 @@ import { ApiErrorResponse } from "@/lib/utils/api/fetch-wrapper"; export default function Page() { const [email, setEmail] = useState(""); const [emailSent, setEmailSent] = useState(false); + const isMobile = useCheckMobile(); // TOASTS AND ERROR STATES const { errors, handleError, clearAllErrors } = useFormErrors(); + // FORM VALIDATION + const isFormValid = useMemo(() => { + if (!email || !email.trim()) { + return false; + } + + return true; + }, [email]); + const handleEmailChange = (value: string) => { handleError("email", ""); handleError("api", ""); @@ -91,6 +102,7 @@ export default function Page() { buttonStyle="primary" label="Send Link" onClick={handleSubmit} + disabled={!isMobile && !isFormValid} loadOnSuccess /> diff --git a/frontend/src/app/(auth)/login/page.tsx b/frontend/src/app/(auth)/login/page.tsx index d9535069..7bdc0166 100644 --- a/frontend/src/app/(auth)/login/page.tsx +++ b/frontend/src/app/(auth)/login/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import Link from "next/link"; import { useRouter, useSearchParams } from "next/navigation"; @@ -10,6 +10,7 @@ import AuthPageLayout from "@/components/layout/auth-page"; import LinkText from "@/components/link-text"; import TextInputField from "@/components/text-input-field"; import ActionButton from "@/features/button/components/action"; +import useCheckMobile from "@/lib/hooks/use-check-mobile"; import { useFormErrors } from "@/lib/hooks/use-form-errors"; import { MESSAGES } from "@/lib/messages"; import { clientPost } from "@/lib/utils/api/client-fetch"; @@ -22,6 +23,7 @@ export default function Page() { const [password, setPassword] = useState(""); const [rememberMe, setRememberMe] = useState(false); const router = useRouter(); + const isMobile = useCheckMobile(); const searchParams = useSearchParams(); const callbackUrl = getSafeRedirectUrl(searchParams.get("callbackUrl")); @@ -29,6 +31,19 @@ export default function Page() { // TOASTS AND ERROR STATES const { errors, handleError, clearAllErrors } = useFormErrors(); + // FORM VALIDATION + const isFormValid = useMemo(() => { + if (!email || !email.trim()) { + return false; + } + + if (!password) { + return false; + } + + return true; + }, [email, password]); + const handleEmailChange = (value: string) => { handleError("email", ""); handleError("api", ""); @@ -123,6 +138,7 @@ export default function Page() { buttonStyle="primary" label="Login" onClick={handleSubmit} + disabled={!isMobile && !isFormValid} loadOnSuccess /> diff --git a/frontend/src/app/(auth)/register/page.tsx b/frontend/src/app/(auth)/register/page.tsx index 79c214c7..e6d767d7 100644 --- a/frontend/src/app/(auth)/register/page.tsx +++ b/frontend/src/app/(auth)/register/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; @@ -10,6 +10,7 @@ import LinkText from "@/components/link-text"; import TextInputField from "@/components/text-input-field"; import PasswordValidation from "@/features/auth/components/password-validation"; import ActionButton from "@/features/button/components/action"; +import useCheckMobile from "@/lib/hooks/use-check-mobile"; import { useFormErrors } from "@/lib/hooks/use-form-errors"; import { MESSAGES } from "@/lib/messages"; import { clientPost } from "@/lib/utils/api/client-fetch"; @@ -23,13 +24,31 @@ export default function Page() { const [passwordCriteria, setPasswordCriteria] = useState({}); const [showPasswordCriteria, setShowPasswordCriteria] = useState(false); const router = useRouter(); + const isMobile = useCheckMobile(); // TOASTS AND ERROR STATES const { errors, handleError, clearAllErrors } = useFormErrors(); - function passwordIsStrong() { + const passwordIsStrong = useCallback(() => { return Object.values(passwordCriteria).every((value) => value === true); - } + }, [passwordCriteria]); + + // FORM VALIDATION + const isFormValid = useMemo(() => { + if (!email || !email.trim()) { + return false; + } + + if (!passwordIsStrong()) { + return false; + } + + if (!confirmPassword || confirmPassword !== password) { + return false; + } + + return true; + }, [email, password, confirmPassword, passwordIsStrong]); const handleEmailChange = (value: string) => { handleError("email", ""); @@ -148,6 +167,7 @@ export default function Page() { buttonStyle="primary" label="Register" onClick={handleSubmit} + disabled={!isMobile && !isFormValid} loadOnSuccess /> diff --git a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx index 8fb4afac..ffd70422 100644 --- a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { parseISO } from "date-fns"; import { useRouter } from "next/navigation"; @@ -89,6 +89,19 @@ export default function ClientPage({ // return () => removeToast(toastId); // }, [addToast, removeToast]); + // FORM VALIDATION + const isFormValid = useMemo(() => { + if (!displayName || !displayName.trim() || !!errors.displayName) { + return false; + } + + if (!userAvailability || userAvailability.size === 0) { + return false; + } + + return true; + }, [displayName, errors.displayName, userAvailability]); + const checkNameAvailability = useDebouncedCallback(async (displayName) => { try { await clientPost(ROUTES.availability.checkDisplayName, { @@ -236,7 +249,20 @@ export default function ClientPage({ href={`/${eventCode}`} /> ); - const submitButton = ( + const desktopSubmitButton = ( + + ); + const mobileSubmitButton = ( {eventName}
{cancelButton} - {submitButton} + {desktopSubmitButton}
@@ -320,7 +346,7 @@ export default function ClientPage({
("details"); + // FORM VALIDATION + const isFormValid = useMemo(() => { + if (!title || !title.trim() || title.length > MAX_TITLE_LENGTH) { + return false; + } + + if (eventRange.type === "specific") { + if (!eventRange.dateRange.from || !eventRange.dateRange.to) { + return false; + } + } else if (eventRange.type === "weekday") { + if (!eventRange.weekdays || eventRange.weekdays.length === 0) { + return false; + } + } + + if (!eventRange.timeRange.from || !eventRange.timeRange.to) { + return false; + } else if (eventRange.timeRange.from >= eventRange.timeRange.to) { + return false; + } + + if (type === "edit" && initialData) { + const isTitleUnchanged = title.trim() === initialData.title.trim(); + const isRangeUnchanged = + JSON.stringify(eventRange) === JSON.stringify(initialData.eventRange); + + if (isTitleUnchanged && isRangeUnchanged) { + return false; + } + } + + return true; + }, [title, eventRange, type, initialData]); + // SUBMIT EVENT INFO const submitEventInfo = async () => { clearAllErrors(); @@ -96,7 +131,16 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { href={`/${initialData?.customCode}`} /> ); - const submitButton = ( + const desktopSubmitButton = ( + + ); + const mobileSubmitButton = (
{type === "edit" && cancelButton} - {submitButton} + {desktopSubmitButton}
@@ -210,7 +254,7 @@ function EventEditorContent({ type, initialData }: EventEditorProps) {
Date: Fri, 7 Aug 2026 00:22:11 +0000 Subject: [PATCH 02/19] added tooltips (painting wip) --- .../src/app/(auth)/forgot-password/page.tsx | 9 +++++--- frontend/src/app/(auth)/login/page.tsx | 7 +++--- frontend/src/app/(auth)/register/page.tsx | 7 +++--- frontend/src/features/event/editor/editor.tsx | 22 ++++++++++++++----- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/(auth)/forgot-password/page.tsx b/frontend/src/app/(auth)/forgot-password/page.tsx index f2cd7370..892fa5d3 100644 --- a/frontend/src/app/(auth)/forgot-password/page.tsx +++ b/frontend/src/app/(auth)/forgot-password/page.tsx @@ -25,8 +25,8 @@ export default function Page() { // TOASTS AND ERROR STATES const { errors, handleError, clearAllErrors } = useFormErrors(); - // FORM VALIDATION - const isFormValid = useMemo(() => { + // CHECK FIELDS + const fieldsFilled = useMemo(() => { if (!email || !email.trim()) { return false; } @@ -101,8 +101,11 @@ export default function Page() {
diff --git a/frontend/src/app/(auth)/login/page.tsx b/frontend/src/app/(auth)/login/page.tsx index 7bdc0166..e9f08474 100644 --- a/frontend/src/app/(auth)/login/page.tsx +++ b/frontend/src/app/(auth)/login/page.tsx @@ -31,8 +31,8 @@ export default function Page() { // TOASTS AND ERROR STATES const { errors, handleError, clearAllErrors } = useFormErrors(); - // FORM VALIDATION - const isFormValid = useMemo(() => { + // CHECK FIELDS + const fieldsFilled = useMemo(() => { if (!email || !email.trim()) { return false; } @@ -137,8 +137,9 @@ export default function Page() {
diff --git a/frontend/src/app/(auth)/register/page.tsx b/frontend/src/app/(auth)/register/page.tsx index e6d767d7..62b2995a 100644 --- a/frontend/src/app/(auth)/register/page.tsx +++ b/frontend/src/app/(auth)/register/page.tsx @@ -33,8 +33,8 @@ export default function Page() { return Object.values(passwordCriteria).every((value) => value === true); }, [passwordCriteria]); - // FORM VALIDATION - const isFormValid = useMemo(() => { + // CHECK FIELDS + const fieldsFilled = useMemo(() => { if (!email || !email.trim()) { return false; } @@ -166,8 +166,9 @@ export default function Page() { diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index 084ddf83..2a047b5a 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -61,8 +61,8 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { const [mobileTab, setMobileTab] = useState("details"); - // FORM VALIDATION - const isFormValid = useMemo(() => { + // CHECK FIELDS + const fieldsFilled = useMemo(() => { if (!title || !title.trim() || title.length > MAX_TITLE_LENGTH) { return false; } @@ -85,9 +85,7 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { if (type === "edit" && initialData) { const isTitleUnchanged = title.trim() === initialData.title.trim(); - const isRangeUnchanged = - JSON.stringify(eventRange) === JSON.stringify(initialData.eventRange); - + const isRangeUnchanged = eventRange === initialData.originalEventRange; if (isTitleUnchanged && isRangeUnchanged) { return false; } @@ -131,12 +129,24 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { href={`/${initialData?.customCode}`} /> ); + + const desktopSubmitTooltip = useMemo(() => { + if (fieldsFilled) return undefined; + + if (type === "edit") { + return "Make changes to update the event."; + } + + return "Please fill out all fields."; + }, [fieldsFilled, type]); + const desktopSubmitButton = ( ); From 3af397366758219a6982ff00376530347e29b212 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Tue, 11 Aug 2026 22:25:36 -0400 Subject: [PATCH 03/19] Add useCallback to password criteria --- frontend/src/app/(auth)/reset-password/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/(auth)/reset-password/page.tsx b/frontend/src/app/(auth)/reset-password/page.tsx index 2d7c05e2..f224198e 100644 --- a/frontend/src/app/(auth)/reset-password/page.tsx +++ b/frontend/src/app/(auth)/reset-password/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { notFound, useRouter, useSearchParams } from "next/navigation"; @@ -27,9 +27,9 @@ export default function Page() { notFound(); // If no token is provided, show 404 page } - function passwordIsStrong() { + const passwordIsStrong = useCallback(() => { return Object.values(passwordCriteria).every((value) => value === true); - } + }, [passwordCriteria]); // TOASTS AND ERROR STATES const { errors, handleError, clearAllErrors } = useFormErrors(); From 7bc4c9cf36126e521cda3fbc06f3c7194bf78148 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Tue, 11 Aug 2026 22:28:30 -0400 Subject: [PATCH 04/19] Remove new password match requirement --- frontend/src/app/(auth)/register/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/(auth)/register/page.tsx b/frontend/src/app/(auth)/register/page.tsx index 62b2995a..be56d358 100644 --- a/frontend/src/app/(auth)/register/page.tsx +++ b/frontend/src/app/(auth)/register/page.tsx @@ -43,12 +43,12 @@ export default function Page() { return false; } - if (!confirmPassword || confirmPassword !== password) { + if (!confirmPassword) { return false; } return true; - }, [email, password, confirmPassword, passwordIsStrong]); + }, [email, confirmPassword, passwordIsStrong]); const handleEmailChange = (value: string) => { handleError("email", ""); From f573a194ce35a63db969d76ce11ac15fca98e47f Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Tue, 11 Aug 2026 22:36:43 -0400 Subject: [PATCH 05/19] Add disabled submission to reset password --- .../src/app/(auth)/reset-password/page.tsx | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/(auth)/reset-password/page.tsx b/frontend/src/app/(auth)/reset-password/page.tsx index f224198e..ddf703af 100644 --- a/frontend/src/app/(auth)/reset-password/page.tsx +++ b/frontend/src/app/(auth)/reset-password/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { notFound, useRouter, useSearchParams } from "next/navigation"; @@ -8,6 +8,7 @@ import AuthPageLayout from "@/components/layout/auth-page"; import TextInputField from "@/components/text-input-field"; import PasswordValidation from "@/features/auth/components/password-validation"; import ActionButton from "@/features/button/components/action"; +import useCheckMobile from "@/lib/hooks/use-check-mobile"; import { useFormErrors } from "@/lib/hooks/use-form-errors"; import { MESSAGES } from "@/lib/messages"; import { clientPost } from "@/lib/utils/api/client-fetch"; @@ -20,6 +21,7 @@ export default function Page() { const [passwordCriteria, setPasswordCriteria] = useState({}); const [showPasswordCriteria, setShowPasswordCriteria] = useState(false); const router = useRouter(); + const isMobile = useCheckMobile(); const searchParams = useSearchParams(); const pwdResetToken = searchParams.get("token"); @@ -31,6 +33,23 @@ export default function Page() { return Object.values(passwordCriteria).every((value) => value === true); }, [passwordCriteria]); + // CHECK FIELDS + const fieldsFilled = useMemo(() => { + if (!newPassword) { + return false; + } + + if (!passwordIsStrong()) { + return false; + } + + if (!confirmPassword) { + return false; + } + + return true; + }, [newPassword, passwordIsStrong, confirmPassword]); + // TOASTS AND ERROR STATES const { errors, handleError, clearAllErrors } = useFormErrors(); @@ -125,7 +144,9 @@ export default function Page() { From 8afe5d4ef2426ecd359a1249dcd161ff5f151127 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Tue, 11 Aug 2026 22:46:58 -0400 Subject: [PATCH 06/19] Consolidate painting submit buttons --- .../[event-code]/painting/page-client.tsx | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx index ffd70422..2142be82 100644 --- a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx @@ -249,20 +249,7 @@ export default function ClientPage({ href={`/${eventCode}`} /> ); - const desktopSubmitButton = ( - - ); - const mobileSubmitButton = ( + const submitButton = (desktop: boolean) => ( ); @@ -289,7 +277,7 @@ export default function ClientPage({

{eventName}

{cancelButton} - {desktopSubmitButton} + {submitButton(true)}
@@ -346,7 +334,7 @@ export default function ClientPage({
Date: Tue, 11 Aug 2026 22:47:15 -0400 Subject: [PATCH 07/19] Add tooltip to painting submit button --- .../src/app/(event)/[event-code]/painting/page-client.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx index 2142be82..45a937c6 100644 --- a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx @@ -257,6 +257,11 @@ export default function ClientPage({ ? "Update Availability" : "Submit Availability" } + tooltip={ + desktop && !isFormValid + ? "Please fill out your name and availability." + : undefined + } onClick={handleSubmitAvailability} disabled={desktop && !isFormValid} loadOnSuccess From 96a7ad413aa9d8bc1dd47a3393dce0fc3d682dd6 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 12 Aug 2026 11:02:54 -0400 Subject: [PATCH 08/19] Update editor submit tooltip message --- frontend/src/features/event/editor/editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index 2a047b5a..14007f7b 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -137,7 +137,7 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { return "Make changes to update the event."; } - return "Please fill out all fields."; + return "Please fill out all the fields."; }, [fieldsFilled, type]); const desktopSubmitButton = ( From c89295ecb7a05f5d2401f62e388e02332ef01011 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 12 Aug 2026 13:56:09 -0400 Subject: [PATCH 09/19] Strengthen auth page form checks --- .../src/app/(auth)/forgot-password/page.tsx | 20 +++++------ frontend/src/app/(auth)/login/page.tsx | 22 +++++------- frontend/src/app/(auth)/register/page.tsx | 28 ++++++--------- .../src/app/(auth)/reset-password/page.tsx | 34 ++++++++----------- frontend/src/lib/messages.ts | 4 +++ 5 files changed, 48 insertions(+), 60 deletions(-) diff --git a/frontend/src/app/(auth)/forgot-password/page.tsx b/frontend/src/app/(auth)/forgot-password/page.tsx index 892fa5d3..0ea2fb38 100644 --- a/frontend/src/app/(auth)/forgot-password/page.tsx +++ b/frontend/src/app/(auth)/forgot-password/page.tsx @@ -26,13 +26,13 @@ export default function Page() { const { errors, handleError, clearAllErrors } = useFormErrors(); // CHECK FIELDS - const fieldsFilled = useMemo(() => { - if (!email || !email.trim()) { - return false; - } - - return true; - }, [email]); + const invalidForm = useMemo(() => { + return !email || !email.trim() + ? "Please enter an email address." + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; + }, [email, errors]); const handleEmailChange = (value: string) => { handleError("email", ""); @@ -101,11 +101,9 @@ export default function Page() {
diff --git a/frontend/src/app/(auth)/login/page.tsx b/frontend/src/app/(auth)/login/page.tsx index e9f08474..d054c668 100644 --- a/frontend/src/app/(auth)/login/page.tsx +++ b/frontend/src/app/(auth)/login/page.tsx @@ -32,17 +32,13 @@ export default function Page() { const { errors, handleError, clearAllErrors } = useFormErrors(); // CHECK FIELDS - const fieldsFilled = useMemo(() => { - if (!email || !email.trim()) { - return false; - } - - if (!password) { - return false; - } - - return true; - }, [email, password]); + const invalidForm = useMemo(() => { + return !email || !email.trim() || !password + ? MESSAGES.FORM_NOT_FILLED + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; + }, [email, password, errors]); const handleEmailChange = (value: string) => { handleError("email", ""); @@ -137,9 +133,9 @@ export default function Page() {
diff --git a/frontend/src/app/(auth)/register/page.tsx b/frontend/src/app/(auth)/register/page.tsx index be56d358..9faddb5e 100644 --- a/frontend/src/app/(auth)/register/page.tsx +++ b/frontend/src/app/(auth)/register/page.tsx @@ -34,21 +34,15 @@ export default function Page() { }, [passwordCriteria]); // CHECK FIELDS - const fieldsFilled = useMemo(() => { - if (!email || !email.trim()) { - return false; - } - - if (!passwordIsStrong()) { - return false; - } - - if (!confirmPassword) { - return false; - } - - return true; - }, [email, confirmPassword, passwordIsStrong]); + const invalidForm = useMemo(() => { + return !email || !email.trim() || !password + ? MESSAGES.FORM_NOT_FILLED + : !passwordIsStrong() + ? MESSAGES.ERROR_PASSWORD_WEAK + : !confirmPassword + ? MESSAGES.FORM_NOT_FILLED + : undefined; + }, [email, password, confirmPassword, passwordIsStrong]); const handleEmailChange = (value: string) => { handleError("email", ""); @@ -166,9 +160,9 @@ export default function Page() { diff --git a/frontend/src/app/(auth)/reset-password/page.tsx b/frontend/src/app/(auth)/reset-password/page.tsx index ddf703af..db74ba76 100644 --- a/frontend/src/app/(auth)/reset-password/page.tsx +++ b/frontend/src/app/(auth)/reset-password/page.tsx @@ -33,26 +33,22 @@ export default function Page() { return Object.values(passwordCriteria).every((value) => value === true); }, [passwordCriteria]); - // CHECK FIELDS - const fieldsFilled = useMemo(() => { - if (!newPassword) { - return false; - } - - if (!passwordIsStrong()) { - return false; - } - - if (!confirmPassword) { - return false; - } - - return true; - }, [newPassword, passwordIsStrong, confirmPassword]); - // TOASTS AND ERROR STATES const { errors, handleError, clearAllErrors } = useFormErrors(); + // CHECK FIELDS + const invalidForm = useMemo(() => { + return !newPassword + ? MESSAGES.FORM_NOT_FILLED + : !passwordIsStrong() + ? MESSAGES.ERROR_PASSWORD_WEAK + : !confirmPassword + ? MESSAGES.FORM_NOT_FILLED + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; + }, [newPassword, passwordIsStrong, confirmPassword, errors]); + const handleConfirmPasswordChange = (value: string) => { handleError("confirmPassword", ""); handleError("api", ""); @@ -144,9 +140,9 @@ export default function Page() { diff --git a/frontend/src/lib/messages.ts b/frontend/src/lib/messages.ts index e298bfb1..40a7a7db 100644 --- a/frontend/src/lib/messages.ts +++ b/frontend/src/lib/messages.ts @@ -74,4 +74,8 @@ export const MESSAGES = { INFO_NO_IDEAL_TIMES: "There are no times where everyone is available.", INFO_NO_IDEAL_TIMES_BANNER: "There are no times where everyone is available. Times with an indicator are the best options.", + + // form error messages + FORM_NOT_FILLED: "Please fill out all fields.", + FORM_HAS_ERRORS: "Please fix the displayed errors.", }; From 09cd68529f22edcb09bbfc297023b0c42e6af996 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 12 Aug 2026 19:27:46 -0400 Subject: [PATCH 10/19] Rework error handling on painting page --- .../[event-code]/painting/page-client.tsx | 71 ++++++++----------- 1 file changed, 31 insertions(+), 40 deletions(-) diff --git a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx index 24111d54..cd128ca7 100644 --- a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx @@ -23,6 +23,7 @@ import { RateLimitBanner, useToast, } from "@/features/system-feedback"; +import { useFormErrors } from "@/lib/hooks/use-form-errors"; import { MESSAGES } from "@/lib/messages"; import { clientPost } from "@/lib/utils/api/client-fetch"; import { ROUTES } from "@/lib/utils/api/endpoints"; @@ -57,7 +58,7 @@ export default function ClientPage({ // TOASTS AND ERROR STATES const { addToast } = useToast(); - const [errors, setErrors] = useState>({}); + const { errors, handleError, clearAllErrors } = useFormErrors(); // VISITED LAST PAGE STATE const [maxVisitedPage, setMaxVisitedPage] = useState(0); @@ -90,17 +91,20 @@ export default function ClientPage({ // }, [addToast, removeToast]); // FORM VALIDATION - const isFormValid = useMemo(() => { - if (!displayName || !displayName.trim() || !!errors.displayName) { - return false; - } - - if (!userAvailability || userAvailability.size === 0) { - return false; - } - - return true; - }, [displayName, errors.displayName, userAvailability]); + const invalidForm = useMemo(() => { + const hasName = displayName && displayName.trim(); + const hasAvailability = userAvailability && userAvailability.size > 0; + + return !hasName + ? !hasAvailability + ? "Please fill out your name and availability." + : "Please fill out your name." + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : !hasAvailability + ? "Please select your availability on the grid." + : undefined; + }, [displayName, userAvailability, errors]); const checkNameAvailability = useDebouncedCallback(async (displayName) => { try { @@ -111,10 +115,7 @@ export default function ClientPage({ } catch (e) { const error = e as ApiErrorResponse; if (error.badRequest) { - setErrors((prev) => ({ - ...prev, - displayName: MESSAGES.ERROR_NAME_TAKEN, - })); + handleError("displayName", MESSAGES.ERROR_NAME_TAKEN); } else { addToast("error", error.formattedMessage); } @@ -125,18 +126,12 @@ export default function ClientPage({ setDisplayName(value); if (value === "") { checkNameAvailability.cancel(); - setErrors((prev) => ({ - ...prev, - displayName: MESSAGES.ERROR_NAME_MISSING, - })); + handleError("displayName", MESSAGES.ERROR_NAME_MISSING); } else if (value.length > MAX_DISPLAY_NAME_LENGTH) { checkNameAvailability.cancel(); - setErrors((prev) => ({ - ...prev, - displayName: MESSAGES.ERROR_NAME_LENGTH, - })); + handleError("displayName", MESSAGES.ERROR_NAME_LENGTH); } else { - setErrors((prev) => ({ ...prev, displayName: "" })); + handleError("displayName", ""); checkNameAvailability(value); } }; @@ -171,14 +166,14 @@ export default function ClientPage({ // SUBMIT AVAILABILITY const handleSubmitAvailability = async () => { - setErrors({}); // reset errors + clearAllErrors(); // reset errors const validationErrors = await validateAvailabilityData(state); if (Object.keys(validationErrors).length > 0) { - setErrors(validationErrors); - Object.values(validationErrors).forEach((error) => - addToast("error", error), - ); + for (const [field, message] of Object.entries(validationErrors)) { + handleError(field, message); + addToast("error", message); + } return false; } @@ -232,10 +227,10 @@ export default function ClientPage({ } catch (e) { const error = e as ApiErrorResponse; if (error.rateLimited) { - setErrors((prev) => ({ - ...prev, - rate_limit: error.formattedMessage || MESSAGES.ERROR_RATE_LIMIT, - })); + handleError( + "rate_limit", + error.formattedMessage || MESSAGES.ERROR_RATE_LIMIT, + ); } else { addToast("error", error.formattedMessage); } @@ -259,13 +254,9 @@ export default function ClientPage({ ? "Update Availability" : "Submit Availability" } - tooltip={ - desktop && !isFormValid - ? "Please fill out your name and availability." - : undefined - } + tooltip={desktop && invalidForm ? invalidForm : undefined} onClick={handleSubmitAvailability} - disabled={desktop && !isFormValid} + disabled={desktop && !!invalidForm} loadOnSuccess /> ); From d09cc1ceae641bee23f83ec668d01a7f1a99763e Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 12 Aug 2026 19:34:48 -0400 Subject: [PATCH 11/19] Update event editor submission criteria --- frontend/src/features/event/editor/editor.tsx | 81 +++++++------------ 1 file changed, 27 insertions(+), 54 deletions(-) diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index 14007f7b..89e37248 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -62,37 +62,28 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { const [mobileTab, setMobileTab] = useState("details"); // CHECK FIELDS - const fieldsFilled = useMemo(() => { - if (!title || !title.trim() || title.length > MAX_TITLE_LENGTH) { - return false; - } - - if (eventRange.type === "specific") { - if (!eventRange.dateRange.from || !eventRange.dateRange.to) { - return false; - } - } else if (eventRange.type === "weekday") { - if (!eventRange.weekdays || eventRange.weekdays.length === 0) { - return false; - } - } - - if (!eventRange.timeRange.from || !eventRange.timeRange.to) { - return false; - } else if (eventRange.timeRange.from >= eventRange.timeRange.to) { - return false; - } - - if (type === "edit" && initialData) { - const isTitleUnchanged = title.trim() === initialData.title.trim(); - const isRangeUnchanged = eventRange === initialData.originalEventRange; - if (isTitleUnchanged && isRangeUnchanged) { - return false; - } - } - - return true; - }, [title, eventRange, type, initialData]); + const invalidForm = useMemo(() => { + const eventNotEdited = + type === "edit" && + initialData && + title.trim() === initialData.title.trim() && + eventRange === initialData.originalEventRange; + + return !title || + !title.trim() || + (eventRange.type === "specific" && + (!eventRange.dateRange.from || !eventRange.dateRange.to)) || + (eventRange.type === "weekday" && + (!eventRange.weekdays || eventRange.weekdays.length === 0)) || + !eventRange.timeRange.from || + !eventRange.timeRange.to + ? MESSAGES.FORM_NOT_FILLED + : eventNotEdited + ? "Please make changes to update the event." + : Object.keys(errors).length || title.length > MAX_TITLE_LENGTH + ? MESSAGES.FORM_HAS_ERRORS + : undefined; + }, [title, eventRange, type, initialData, errors]); // SUBMIT EVENT INFO const submitEventInfo = async () => { @@ -130,31 +121,13 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { /> ); - const desktopSubmitTooltip = useMemo(() => { - if (fieldsFilled) return undefined; - - if (type === "edit") { - return "Make changes to update the event."; - } - - return "Please fill out all the fields."; - }, [fieldsFilled, type]); - - const desktopSubmitButton = ( - - ); - const mobileSubmitButton = ( + const submitButton = (desktop: boolean) => ( ); @@ -195,7 +168,7 @@ function EventEditorContent({ type, initialData }: EventEditorProps) {
{type === "edit" && cancelButton} - {desktopSubmitButton} + {submitButton(true)}
@@ -264,7 +237,7 @@ function EventEditorContent({ type, initialData }: EventEditorProps) {
Date: Wed, 12 Aug 2026 19:46:16 -0400 Subject: [PATCH 12/19] Add disabled submit button on form dialog --- .../features/system-feedback/dialog/components/form.tsx | 4 ++++ frontend/src/features/system-feedback/dialog/props.ts | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/frontend/src/features/system-feedback/dialog/components/form.tsx b/frontend/src/features/system-feedback/dialog/components/form.tsx index babb8bf5..48ba6b08 100644 --- a/frontend/src/features/system-feedback/dialog/components/form.tsx +++ b/frontend/src/features/system-feedback/dialog/components/form.tsx @@ -13,6 +13,8 @@ export default function FormDialog({ description, onSubmit, submitLabel = "Save", + submitDisabled = false, + submitTooltip, cancelLabel = "Cancel", children, trigger, @@ -79,6 +81,8 @@ export default function FormDialog({ type="submit" buttonStyle={config.buttonStyle} label={submitLabel} + tooltip={submitTooltip} + disabled={submitDisabled} />
diff --git a/frontend/src/features/system-feedback/dialog/props.ts b/frontend/src/features/system-feedback/dialog/props.ts index 08349db8..b05f5c0e 100644 --- a/frontend/src/features/system-feedback/dialog/props.ts +++ b/frontend/src/features/system-feedback/dialog/props.ts @@ -1,3 +1,5 @@ +import { ReactNode } from "react"; + import type { DialogType } from "@/features/system-feedback"; type CommonDialogProps = { @@ -104,6 +106,12 @@ export type FormDialogProps = CommonDialogProps & { /** Text for the submit button (Defaults to "Save" or "Submit") */ submitLabel?: string; + /** Whether to disable the submit button */ + submitDisabled?: boolean; + + /** Optional tooltip for the submit button */ + submitTooltip?: ReactNode; + /** Text for the cancel button (Defaults to "Cancel") */ cancelLabel?: string; }; From 85d1f2b3c4dbf66f2ff84de6701ac2df4d267601 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 12 Aug 2026 19:47:43 -0400 Subject: [PATCH 13/19] Add strict password change/reset flow --- .../change-password/main-dialog.tsx | 2 ++ .../change-password/use-change-password.ts | 36 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/frontend/src/features/account/setting-dialogs/change-password/main-dialog.tsx b/frontend/src/features/account/setting-dialogs/change-password/main-dialog.tsx index 63826de1..a4efa4a6 100644 --- a/frontend/src/features/account/setting-dialogs/change-password/main-dialog.tsx +++ b/frontend/src/features/account/setting-dialogs/change-password/main-dialog.tsx @@ -64,6 +64,8 @@ export default function ChangePasswordDialog() { open={flow.open} onOpenChange={flow.handleOpenChange} onSubmit={onConfirmHandler} + submitTooltip={flow.invalidForm} + submitDisabled={!isMobile && !!flow.invalidForm} > {dialogContent} diff --git a/frontend/src/features/account/setting-dialogs/change-password/use-change-password.ts b/frontend/src/features/account/setting-dialogs/change-password/use-change-password.ts index da59aeb5..9f2dc954 100644 --- a/frontend/src/features/account/setting-dialogs/change-password/use-change-password.ts +++ b/frontend/src/features/account/setting-dialogs/change-password/use-change-password.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useEffect, useMemo, useState } from "react"; import PasswordValidation from "@/features/auth/components/password-validation"; import { useToast } from "@/features/system-feedback"; @@ -62,6 +62,39 @@ export function useChangePasswordFlow() { } }; + const invalidForm = useMemo(() => { + if (step === "CHANGE") { + return !form.currentPassword || !form.newPassword + ? MESSAGES.FORM_NOT_FILLED + : !passwordIsStrong + ? MESSAGES.ERROR_PASSWORD_WEAK + : !form.confirmPassword + ? MESSAGES.FORM_NOT_FILLED + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; + } + if (step === "OTP") { + return !form.resetCode + ? "Please enter the reset code." + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; + } + if (step === "RESET") { + return !form.newPassword + ? MESSAGES.FORM_NOT_FILLED + : !passwordIsStrong + ? MESSAGES.ERROR_PASSWORD_WEAK + : !form.confirmPassword + ? MESSAGES.FORM_NOT_FILLED + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; + } + return undefined; + }, [step, form, passwordIsStrong, errors]); + // --- API FUNCTIONS --- const handleForgotPassword = async () => { clearAllErrors(); @@ -175,6 +208,7 @@ export function useChangePasswordFlow() { setStep, form, updateForm, + invalidForm, errors, criteria, showCriteria, From 62aeff1d9cf75505427720c76bb853301365e043 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 12 Aug 2026 19:51:29 -0400 Subject: [PATCH 14/19] Add form validation to account deletion --- .../account/setting-dialogs/delete-account.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/features/account/setting-dialogs/delete-account.tsx b/frontend/src/features/account/setting-dialogs/delete-account.tsx index f90b400c..c3f5c6a8 100644 --- a/frontend/src/features/account/setting-dialogs/delete-account.tsx +++ b/frontend/src/features/account/setting-dialogs/delete-account.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { useRouter } from "next/navigation"; @@ -27,6 +27,15 @@ export default function DeleteAccountDialog() { const [confirmationOpen, setConfirmationOpen] = useState(false); + // CHECK FIELDS + const invalidForm = useMemo(() => { + return !currentPassword + ? MESSAGES.FORM_NOT_FILLED + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; + }, [currentPassword, errors]); + const handleOpenChange = (open: boolean) => { setConfirmationOpen(open); if (!open) { @@ -78,6 +87,8 @@ export default function DeleteAccountDialog() { onOpenChange={handleOpenChange} onSubmit={handleDeleteAccount} submitLabel="Delete Account" + submitDisabled={!isMobile && !!invalidForm} + submitTooltip={invalidForm} >

@@ -97,6 +108,7 @@ export default function DeleteAccountDialog() { value={currentPassword} onChange={(value) => { setCurrentPassword(value); + handleError("currentPassword", ""); }} style="outlined" error={errors.currentPassword || errors.api} From dd1b1818e394af8c1427672f304de29f41c07bd0 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Wed, 12 Aug 2026 19:58:30 -0400 Subject: [PATCH 15/19] Add error checking on register --- frontend/src/app/(auth)/register/page.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/(auth)/register/page.tsx b/frontend/src/app/(auth)/register/page.tsx index 9faddb5e..10ae0e0d 100644 --- a/frontend/src/app/(auth)/register/page.tsx +++ b/frontend/src/app/(auth)/register/page.tsx @@ -41,8 +41,10 @@ export default function Page() { ? MESSAGES.ERROR_PASSWORD_WEAK : !confirmPassword ? MESSAGES.FORM_NOT_FILLED - : undefined; - }, [email, password, confirmPassword, passwordIsStrong]); + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; + }, [email, password, confirmPassword, passwordIsStrong, errors]); const handleEmailChange = (value: string) => { handleError("email", ""); From 6aa4a014d65d59181c941d8b5237f65aa00bd8f2 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Thu, 13 Aug 2026 14:27:07 -0400 Subject: [PATCH 16/19] Add trim to display name missing check --- frontend/src/app/(event)/[event-code]/painting/page-client.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx index cd128ca7..76fba6be 100644 --- a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx @@ -124,7 +124,7 @@ export default function ClientPage({ const handleNameChange = (value: string) => { setDisplayName(value); - if (value === "") { + if (value.trim() === "") { checkNameAvailability.cancel(); handleError("displayName", MESSAGES.ERROR_NAME_MISSING); } else if (value.length > MAX_DISPLAY_NAME_LENGTH) { From a48813dda102864181a50f2f7f412c01f5c8ce61 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Thu, 13 Aug 2026 14:36:41 -0400 Subject: [PATCH 17/19] Add error clearing on password change --- frontend/src/app/(auth)/register/page.tsx | 8 +++++++- frontend/src/app/(auth)/reset-password/page.tsx | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/(auth)/register/page.tsx b/frontend/src/app/(auth)/register/page.tsx index 10ae0e0d..8e59474f 100644 --- a/frontend/src/app/(auth)/register/page.tsx +++ b/frontend/src/app/(auth)/register/page.tsx @@ -52,6 +52,12 @@ export default function Page() { setEmail(value); }; + const handlePasswordChange = (value: string) => { + handleError("password", ""); + handleError("api", ""); + setPassword(value); + }; + const handleConfirmPasswordChange = (value: string) => { handleError("confirmPassword", ""); handleError("api", ""); @@ -131,7 +137,7 @@ export default function Page() { label="Password*" value={password} onChange={(value) => { - setPassword(value); + handlePasswordChange(value); }} onFocus={() => setShowPasswordCriteria(true)} onBlur={() => { diff --git a/frontend/src/app/(auth)/reset-password/page.tsx b/frontend/src/app/(auth)/reset-password/page.tsx index db74ba76..1a312017 100644 --- a/frontend/src/app/(auth)/reset-password/page.tsx +++ b/frontend/src/app/(auth)/reset-password/page.tsx @@ -49,6 +49,12 @@ export default function Page() { : undefined; }, [newPassword, passwordIsStrong, confirmPassword, errors]); + const handleNewPasswordChange = (value: string) => { + handleError("password", ""); + handleError("api", ""); + setNewPassword(value); + }; + const handleConfirmPasswordChange = (value: string) => { handleError("confirmPassword", ""); handleError("api", ""); @@ -108,7 +114,7 @@ export default function Page() { label="New Password*" value={newPassword} onChange={(value) => { - setNewPassword(value); + handleNewPasswordChange(value); }} onFocus={() => setShowPasswordCriteria(true)} onBlur={() => { From 71f32ef9c9c1b27a37a33d8325dbb96b73d49afb Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Thu, 13 Aug 2026 14:38:38 -0400 Subject: [PATCH 18/19] Add confirm password match check --- frontend/src/app/(auth)/register/page.tsx | 8 +++++--- frontend/src/app/(auth)/reset-password/page.tsx | 8 +++++--- .../change-password/use-change-password.ts | 16 ++++++++++------ 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/(auth)/register/page.tsx b/frontend/src/app/(auth)/register/page.tsx index 8e59474f..87886c3d 100644 --- a/frontend/src/app/(auth)/register/page.tsx +++ b/frontend/src/app/(auth)/register/page.tsx @@ -41,9 +41,11 @@ export default function Page() { ? MESSAGES.ERROR_PASSWORD_WEAK : !confirmPassword ? MESSAGES.FORM_NOT_FILLED - : Object.keys(errors).length - ? MESSAGES.FORM_HAS_ERRORS - : undefined; + : password !== confirmPassword + ? MESSAGES.ERROR_PASSWORD_MISMATCH + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; }, [email, password, confirmPassword, passwordIsStrong, errors]); const handleEmailChange = (value: string) => { diff --git a/frontend/src/app/(auth)/reset-password/page.tsx b/frontend/src/app/(auth)/reset-password/page.tsx index 1a312017..26d10ac2 100644 --- a/frontend/src/app/(auth)/reset-password/page.tsx +++ b/frontend/src/app/(auth)/reset-password/page.tsx @@ -44,9 +44,11 @@ export default function Page() { ? MESSAGES.ERROR_PASSWORD_WEAK : !confirmPassword ? MESSAGES.FORM_NOT_FILLED - : Object.keys(errors).length - ? MESSAGES.FORM_HAS_ERRORS - : undefined; + : newPassword !== confirmPassword + ? MESSAGES.ERROR_PASSWORD_MISMATCH + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; }, [newPassword, passwordIsStrong, confirmPassword, errors]); const handleNewPasswordChange = (value: string) => { diff --git a/frontend/src/features/account/setting-dialogs/change-password/use-change-password.ts b/frontend/src/features/account/setting-dialogs/change-password/use-change-password.ts index 9f2dc954..817ad4bb 100644 --- a/frontend/src/features/account/setting-dialogs/change-password/use-change-password.ts +++ b/frontend/src/features/account/setting-dialogs/change-password/use-change-password.ts @@ -70,9 +70,11 @@ export function useChangePasswordFlow() { ? MESSAGES.ERROR_PASSWORD_WEAK : !form.confirmPassword ? MESSAGES.FORM_NOT_FILLED - : Object.keys(errors).length - ? MESSAGES.FORM_HAS_ERRORS - : undefined; + : form.newPassword !== form.confirmPassword + ? MESSAGES.ERROR_PASSWORD_MISMATCH + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; } if (step === "OTP") { return !form.resetCode @@ -88,9 +90,11 @@ export function useChangePasswordFlow() { ? MESSAGES.ERROR_PASSWORD_WEAK : !form.confirmPassword ? MESSAGES.FORM_NOT_FILLED - : Object.keys(errors).length - ? MESSAGES.FORM_HAS_ERRORS - : undefined; + : form.newPassword !== form.confirmPassword + ? MESSAGES.ERROR_PASSWORD_MISMATCH + : Object.keys(errors).length + ? MESSAGES.FORM_HAS_ERRORS + : undefined; } return undefined; }, [step, form, passwordIsStrong, errors]); From 2f0be0dc556436af40ffff658b50255783619f2a Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Thu, 13 Aug 2026 14:51:20 -0400 Subject: [PATCH 19/19] Fix event changed check --- frontend/src/features/event/editor/editor.tsx | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index 89e37248..838709d4 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -63,11 +63,33 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { // CHECK FIELDS const invalidForm = useMemo(() => { - const eventNotEdited = - type === "edit" && - initialData && - title.trim() === initialData.title.trim() && - eventRange === initialData.originalEventRange; + let eventNotEdited = false; + if (type === "edit" && initialData && initialData.originalEventRange) { + const sameTitle = title.trim() === initialData.title.trim(); + const sameType = eventRange.type === initialData.originalEventRange.type; + const sameTimeRange = + eventRange.timeRange.from === + initialData.originalEventRange.timeRange.from && + eventRange.timeRange.to === + initialData.originalEventRange.timeRange.to && + eventRange.timezone === initialData.originalEventRange.timezone; + + const sameDate = + eventRange.type === "specific" && + initialData.originalEventRange.type === "specific" && + eventRange.dateRange.from === + initialData.originalEventRange.dateRange.from && + eventRange.dateRange.to === initialData.originalEventRange.dateRange.to; + + const sameWeekdays = + eventRange.type === "weekday" && + initialData.originalEventRange.type === "weekday" && + JSON.stringify(eventRange.weekdays) === + JSON.stringify(initialData.originalEventRange.weekdays); + + eventNotEdited = + sameTitle && sameType && sameTimeRange && (sameDate || sameWeekdays); + } return !title || !title.trim() ||