From 7d31488d6f888da89d6b0944399479eb764304fa Mon Sep 17 00:00:00 2001 From: Daniel Shi <144500568+danielshid@users.noreply.github.com> Date: Sat, 27 Jun 2026 01:37:01 +0000 Subject: [PATCH 01/21] added limits and inline style to TextInputField component --- .../src/app/(auth)/forgot-password/page.tsx | 2 +- frontend/src/app/(auth)/login/page.tsx | 4 +- frontend/src/app/(auth)/register/page.tsx | 6 +- .../src/app/(auth)/reset-password/page.tsx | 4 +- .../[event-code]/painting/page-client.tsx | 22 +-- frontend/src/app/settings/(submenus)/page.tsx | 2 +- frontend/src/components/text-input-field.tsx | 157 ++++++++++++------ .../change-password/steps/change.tsx | 6 +- .../change-password/steps/reset.tsx | 4 +- .../setting-dialogs/delete-account.tsx | 2 +- .../features/event/availability/constants.ts | 1 + .../event/availability/validate-data.ts | 3 + frontend/src/lib/messages.ts | 6 +- 13 files changed, 136 insertions(+), 83 deletions(-) create mode 100644 frontend/src/features/event/availability/constants.ts diff --git a/frontend/src/app/(auth)/forgot-password/page.tsx b/frontend/src/app/(auth)/forgot-password/page.tsx index e52f7923f..4575adf91 100644 --- a/frontend/src/app/(auth)/forgot-password/page.tsx +++ b/frontend/src/app/(auth)/forgot-password/page.tsx @@ -80,7 +80,7 @@ export default function Page() { label="Email*" value={email} onChange={handleEmailChange} - outlined + style="outlined" error={errors.email || errors.api} />, ]} diff --git a/frontend/src/app/(auth)/login/page.tsx b/frontend/src/app/(auth)/login/page.tsx index 0d90179a9..d95350698 100644 --- a/frontend/src/app/(auth)/login/page.tsx +++ b/frontend/src/app/(auth)/login/page.tsx @@ -93,7 +93,7 @@ export default function Page() { label="Email*" value={email} onChange={handleEmailChange} - outlined + style="outlined" error={errors.email || errors.api} />, @@ -105,7 +105,7 @@ export default function Page() { label="Password*" value={password} onChange={handlePasswordChange} - outlined + style="outlined" error={errors.password || errors.api} />, ]} diff --git a/frontend/src/app/(auth)/register/page.tsx b/frontend/src/app/(auth)/register/page.tsx index 6e911c4fc..79c214c77 100644 --- a/frontend/src/app/(auth)/register/page.tsx +++ b/frontend/src/app/(auth)/register/page.tsx @@ -104,7 +104,7 @@ export default function Page() { label="Email*" value={email} onChange={handleEmailChange} - outlined + style="outlined" error={errors.email || errors.api} />, @@ -124,7 +124,7 @@ export default function Page() { setShowPasswordCriteria(false); } }} - outlined + style="outlined" error={errors.password || errors.api} showPasswordCriteria={showPasswordCriteria} passwordCriteria={passwordCriteria} @@ -138,7 +138,7 @@ export default function Page() { label="Retype Password*" value={confirmPassword} onChange={handleConfirmPasswordChange} - outlined + style="outlined" error={errors.confirmPassword || errors.api} />, ]} diff --git a/frontend/src/app/(auth)/reset-password/page.tsx b/frontend/src/app/(auth)/reset-password/page.tsx index 600c3768c..2d7c05e20 100644 --- a/frontend/src/app/(auth)/reset-password/page.tsx +++ b/frontend/src/app/(auth)/reset-password/page.tsx @@ -101,7 +101,7 @@ export default function Page() { setShowPasswordCriteria(false); } }} - outlined + style="outlined" error={errors.password || errors.api} showPasswordCriteria={showPasswordCriteria} passwordCriteria={passwordCriteria} @@ -115,7 +115,7 @@ export default function Page() { label="Retype Password*" value={confirmPassword} onChange={handleConfirmPasswordChange} - outlined + style="outlined" error={errors.confirmPassword || errors.api} />, ]} 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 ffda22256..7dd5085a1 100644 --- a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx @@ -8,6 +8,7 @@ import { useDebouncedCallback } from "use-debounce"; import Checkbox from "@/components/checkbox"; import MobileFooterIsland from "@/components/mobile-footer-island"; +import TextInputField from "@/components/text-input-field"; import { useAvailability } from "@/core/availability/use-availability"; import { EventRange } from "@/core/event/types"; import ActionButton from "@/features/button/components/action"; @@ -379,26 +380,19 @@ function DisplayNameInput({
-

- {errors.displayName ? errors.displayName : "Error Placeholder"} -

Hi,{" "} - { - setDisplayName(e.target.value); - handleNameChange(e.target.value); + setDisplayName(e); + handleNameChange(e); }} placeholder="add your name" - className={`inline-block w-auto border-b bg-transparent px-1 focus:outline-none ${ - errors.displayName - ? "border-error placeholder:text-error" - : "border-gray-400" - }`} + error={errors.displayName} />
add your availabilities here diff --git a/frontend/src/app/settings/(submenus)/page.tsx b/frontend/src/app/settings/(submenus)/page.tsx index c03811864..ef1ff1234 100644 --- a/frontend/src/app/settings/(submenus)/page.tsx +++ b/frontend/src/app/settings/(submenus)/page.tsx @@ -100,7 +100,7 @@ export default function Page() { type="text" onChange={handleDefaultNameChange} error={defaultNameError} - outlined + style="outlined" />
void; onFocus?: () => void; onBlur?: () => void; - outlined?: boolean; + style?: FieldStyle; error?: string; className?: string; showPasswordCriteria?: boolean; passwordCriteria?: { [key: string]: boolean }; + placeholder?: string; }; export default function TextInputField(props: TextInputFieldProps) { @@ -32,10 +38,11 @@ export default function TextInputField(props: TextInputFieldProps) { onFocus, onBlur, error, - outlined, + style, className, showPasswordCriteria = false, passwordCriteria = {}, + placeholder, } = props; const [showPassword, setShowPassword] = useState(false); @@ -43,10 +50,33 @@ export default function TextInputField(props: TextInputFieldProps) { const isPassword = type === "password"; const inputType = isPassword ? (showPassword ? "text" : "password") : type; + const isOutlined = style === "outlined"; + const isInline = style === "inline"; + + let maxLength: number | undefined = undefined; + let maxLengthError: string | undefined = undefined; + + if (id === "event-name") { + maxLength = MAX_TITLE_LENGTH; + maxLengthError = MESSAGES.ERROR_EVENT_NAME_LENGTH; + } else if (id === "displayName") { + maxLength = MAX_DISPLAY_NAME_LENGTH; + maxLengthError = MESSAGES.ERROR_NAME_LENGTH; + } else if (id === "defaultName") { + maxLength = MAX_DEFAULT_NAME_LENGTH; + maxLengthError = MESSAGES.ERROR_DEFAULT_NAME_LENGTH; + } + + // Character limit checking + const isOverLimit = maxLength ? value.length > maxLength : false; + const activeError = isOverLimit ? maxLengthError : error; + // ref for placeholder size const labelRef = useRef(null); const [labelWidth, setLabelWidth] = useState(0); useEffect(() => { + if (!isOutlined) return; + const measureLabel = () => { if (labelRef.current) { // This accounts for the label being scaled down when floated @@ -63,19 +93,36 @@ export default function TextInputField(props: TextInputFieldProps) { return () => { window.removeEventListener("resize", measureLabel); }; - }, [label, error]); + }, [label, activeError, isOutlined]); const labelStartPos = "1rem"; // Equal to left-4 // border element classes const borderClasses = cn( "pointer-events-none absolute inset-0 rounded-full", "border peer-focus:border-2", - error ? "border-error" : "border-foreground", + activeError ? "border-error" : "border-foreground", ); return ( -
-
+
+ {/* --- inline error layout --- */} + {isInline && ( +

+ {activeError ? activeError : "Error Placeholder"} +

+ )} + +
{/* --- input field --- */} onChange(e.target.value)} onFocus={onFocus} onBlur={onBlur} - placeholder=" " // triggers placeholder-shown state for floating label + placeholder={isInline ? placeholder || label : " "} // triggers placeholder-shown state for floating label className={cn( - "peer w-full bg-transparent py-2", + "peer bg-transparent", "focus:outline-none", - outlined - ? // Transparent border for proper spacing, actual border handled separately - "rounded-full border border-transparent px-4" - : "border-b-1 px-2", + isInline ? "inline-block w-auto border-b px-1" : "w-full py-2", + isInline + ? activeError + ? "border-error placeholder:text-error" + : "border-gray-400" + : isOutlined + ? // Transparent border for proper spacing, actual border handled separately + "rounded-full border border-transparent px-4" + : "border-b-1 px-2", isPassword && "pr-10", )} /> @@ -104,7 +156,7 @@ export default function TextInputField(props: TextInputFieldProps) { * * The size of the cutout is determined by the label width, measured with a ref. */} - {outlined && ( + {isOutlined && ( <>
- {error ? ( - - - ) : ( - label - )} - + {!isInline && ( + + )} {/* --- trailing icon --- */} {isPassword && ( diff --git a/frontend/src/features/account/setting-dialogs/change-password/steps/change.tsx b/frontend/src/features/account/setting-dialogs/change-password/steps/change.tsx index 7227174a7..7be8d96ae 100644 --- a/frontend/src/features/account/setting-dialogs/change-password/steps/change.tsx +++ b/frontend/src/features/account/setting-dialogs/change-password/steps/change.tsx @@ -20,7 +20,7 @@ export default function ChangeStep({ flow }: ChangePasswordStepProps) { onChange={(value) => { flow.updateForm("currentPassword", value); }} - outlined + style="outlined" error={flow.errors.currentPassword || flow.errors.api} /> @@ -41,7 +41,7 @@ export default function ChangeStep({ flow }: ChangePasswordStepProps) { } }, 0); }} - outlined + style="outlined" error={flow.errors.newPassword || flow.errors.api} showPasswordCriteria={flow.showCriteria} passwordCriteria={flow.criteria} @@ -54,7 +54,7 @@ export default function ChangeStep({ flow }: ChangePasswordStepProps) { label="Retype Password*" value={flow.form.confirmPassword} onChange={(value) => flow.updateForm("confirmPassword", value)} - outlined + style="outlined" error={flow.errors.confirmPassword || flow.errors.api} />
diff --git a/frontend/src/features/account/setting-dialogs/change-password/steps/reset.tsx b/frontend/src/features/account/setting-dialogs/change-password/steps/reset.tsx index 175f16cc5..ac85c19ca 100644 --- a/frontend/src/features/account/setting-dialogs/change-password/steps/reset.tsx +++ b/frontend/src/features/account/setting-dialogs/change-password/steps/reset.tsx @@ -24,7 +24,7 @@ export default function ResetStep({ flow }: ChangePasswordStepProps) { } }, 0); }} - outlined + style="outlined" error={flow.errors.newPassword || flow.errors.api} showPasswordCriteria={flow.showCriteria} passwordCriteria={flow.criteria} @@ -37,7 +37,7 @@ export default function ResetStep({ flow }: ChangePasswordStepProps) { label="Retype Password*" value={flow.form.confirmPassword} onChange={(value) => flow.updateForm("confirmPassword", value)} - outlined + style="outlined" error={flow.errors.confirmPassword || flow.errors.api} />
diff --git a/frontend/src/features/account/setting-dialogs/delete-account.tsx b/frontend/src/features/account/setting-dialogs/delete-account.tsx index a4630eb3d..76ea81e50 100644 --- a/frontend/src/features/account/setting-dialogs/delete-account.tsx +++ b/frontend/src/features/account/setting-dialogs/delete-account.tsx @@ -92,7 +92,7 @@ export default function DeleteAccountDialog() { onChange={(value) => { setCurrentPassword(value); }} - outlined + style="outlined" error={errors.currentPassword || errors.api} /> diff --git a/frontend/src/features/event/availability/constants.ts b/frontend/src/features/event/availability/constants.ts new file mode 100644 index 000000000..cf623a7c6 --- /dev/null +++ b/frontend/src/features/event/availability/constants.ts @@ -0,0 +1 @@ +export const MAX_DISPLAY_NAME_LENGTH = 25; diff --git a/frontend/src/features/event/availability/validate-data.ts b/frontend/src/features/event/availability/validate-data.ts index d0009a0de..443d7e993 100644 --- a/frontend/src/features/event/availability/validate-data.ts +++ b/frontend/src/features/event/availability/validate-data.ts @@ -1,4 +1,5 @@ import { AvailabilityState } from "@/core/availability/reducers/reducer"; +import { MAX_DISPLAY_NAME_LENGTH } from "@/features/event/availability/constants"; import { MESSAGES } from "@/lib/messages"; export async function validateAvailabilityData( @@ -9,6 +10,8 @@ export async function validateAvailabilityData( if (!displayName?.trim()) { errors.displayName = MESSAGES.ERROR_NAME_MISSING; + } else if (displayName.length > MAX_DISPLAY_NAME_LENGTH) { + errors.displayName = MESSAGES.ERROR_NAME_LENGTH; } if (!userAvailability || userAvailability.size === 0) { diff --git a/frontend/src/lib/messages.ts b/frontend/src/lib/messages.ts index f7f4c9b84..e298bfb1d 100644 --- a/frontend/src/lib/messages.ts +++ b/frontend/src/lib/messages.ts @@ -1,4 +1,5 @@ import { MAX_DEFAULT_NAME_LENGTH } from "@/features/account/constants"; +import { MAX_DISPLAY_NAME_LENGTH } from "@/features/event/availability/constants"; import { MAX_DURATION, MAX_TITLE_LENGTH, @@ -19,13 +20,14 @@ export const MESSAGES = { ERROR_RESET_TOKEN_INVALID: "Invalid or expired reset token.", // availability errors + ERROR_NAME_LENGTH: `Name must be ${MAX_DISPLAY_NAME_LENGTH} characters or less.`, ERROR_NAME_MISSING: "Missing name.", - ERROR_NAME_TAKEN: "This name is unavailable. Please choose another.", + ERROR_NAME_TAKEN: "This name is unavailable.", ERROR_AVAILABILITY_MISSING: "Please select your availability on the grid.", // event errors ERROR_EVENT_NAME_MISSING: "Missing event name.", - ERROR_EVENT_NAME_LENGTH: `Event name must be under ${MAX_TITLE_LENGTH} characters.`, + ERROR_EVENT_NAME_LENGTH: `Event name must be ${MAX_TITLE_LENGTH} characters or less.`, ERROR_EVENT_CODE_TAKEN: "This code is unavailable. Please choose another.", ERROR_EVENT_DATES_MISSING: "Please select possible dates for this event.", ERROR_EVENT_WEEKDAYS_MISSING: "Please select possible days for this event.", From e3cf8663948b046118e3e02d3f80fdaeb2e0b7ad Mon Sep 17 00:00:00 2001 From: Daniel Shi <144500568+danielshid@users.noreply.github.com> Date: Sat, 27 Jun 2026 06:39:20 +0000 Subject: [PATCH 02/21] label/error association in inline mode --- frontend/src/components/text-input-field.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frontend/src/components/text-input-field.tsx b/frontend/src/components/text-input-field.tsx index 6c2d4f202..8bfb491fb 100644 --- a/frontend/src/components/text-input-field.tsx +++ b/frontend/src/components/text-input-field.tsx @@ -71,6 +71,8 @@ export default function TextInputField(props: TextInputFieldProps) { const isOverLimit = maxLength ? value.length > maxLength : false; const activeError = isOverLimit ? maxLengthError : error; + const errorId = `${id}-error`; + // ref for placeholder size const labelRef = useRef(null); const [labelWidth, setLabelWidth] = useState(0); @@ -108,15 +110,23 @@ export default function TextInputField(props: TextInputFieldProps) { {/* --- inline error layout --- */} {isInline && (

{activeError ? activeError : "Error Placeholder"}

)} + {isInline && ( + + )} +
Date: Wed, 1 Jul 2026 22:09:41 +0000 Subject: [PATCH 03/21] removed hard coded limits and adjusted debounce logic for long display names --- .../[event-code]/painting/page-client.tsx | 11 ++++++++++ frontend/src/app/settings/(submenus)/page.tsx | 2 ++ frontend/src/components/text-input-field.tsx | 22 ++++--------------- frontend/src/features/event/editor/editor.tsx | 4 ++++ 4 files changed, 21 insertions(+), 18 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 7dd5085a1..cfcc0df8a 100644 --- a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx @@ -13,6 +13,7 @@ import { useAvailability } from "@/core/availability/use-availability"; import { EventRange } from "@/core/event/types"; import ActionButton from "@/features/button/components/action"; import LinkButton from "@/features/button/components/link"; +import { MAX_DISPLAY_NAME_LENGTH } from "@/features/event/availability/constants"; import { validateAvailabilityData } from "@/features/event/availability/validate-data"; import TimeZoneSelector from "@/features/event/components/selectors/timezone"; import { ScheduleGrid } from "@/features/event/grid"; @@ -99,6 +100,14 @@ export default function ClientPage({ return; } + if (displayName.length > MAX_DISPLAY_NAME_LENGTH) { + setErrors((prev) => ({ + ...prev, + displayName: MESSAGES.ERROR_NAME_LENGTH, + })); + return; + } + try { await clientPost(ROUTES.availability.checkDisplayName, { event_code: eventCode, @@ -393,6 +402,8 @@ function DisplayNameInput({ }} placeholder="add your name" error={errors.displayName} + maxLength={MAX_DISPLAY_NAME_LENGTH} + maxLengthError={MESSAGES.ERROR_NAME_LENGTH} />
add your availabilities here diff --git a/frontend/src/app/settings/(submenus)/page.tsx b/frontend/src/app/settings/(submenus)/page.tsx index ef1ff1234..2c7dfd08e 100644 --- a/frontend/src/app/settings/(submenus)/page.tsx +++ b/frontend/src/app/settings/(submenus)/page.tsx @@ -101,6 +101,8 @@ export default function Page() { onChange={handleDefaultNameChange} error={defaultNameError} style="outlined" + maxLength={MAX_DEFAULT_NAME_LENGTH} + maxLengthError={MESSAGES.ERROR_DEFAULT_NAME_LENGTH} />
maxLength : false; const activeError = isOverLimit ? maxLengthError : error; diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index e75d31af4..d3e768f20 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -15,6 +15,7 @@ import ActionButton from "@/features/button/components/action"; import LinkButton from "@/features/button/components/link"; import TimeSelector from "@/features/event/components/selectors/time"; import AdvancedOptions from "@/features/event/editor/advanced-options"; +import { MAX_TITLE_LENGTH } from "@/features/event/editor/constants"; import DateRangeSelection from "@/features/event/editor/date-range/selector"; import { EventEditorType } from "@/features/event/editor/types"; import { validateEventData } from "@/features/event/editor/validate-data"; @@ -22,6 +23,7 @@ import { GridPreviewDialog, ScheduleGrid } from "@/features/event/grid"; import HeaderSpacer from "@/features/header/components/header-spacer"; import FormSelectorField from "@/features/selector/components/selector-field"; import { RateLimitBanner } from "@/features/system-feedback"; +import { MESSAGES } from "@/lib/messages"; import submitEvent from "@/lib/utils/api/submit-event"; import { cn } from "@/lib/utils/classname"; @@ -123,6 +125,8 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { onChange={setTitle} error={errors.title || errors.api} className="text-2xl font-semibold" + maxLength={MAX_TITLE_LENGTH} + maxLengthError={MESSAGES.ERROR_EVENT_NAME_LENGTH} />
From 9dbe56234182e758bea002fae13521c7000c53da Mon Sep 17 00:00:00 2001 From: Daniel Shi <144500568+danielshid@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:45:08 +0000 Subject: [PATCH 04/21] maxLength prop now has both limit and prompt --- .../app/(event)/[event-code]/painting/page-client.tsx | 6 ++++-- frontend/src/app/settings/(submenus)/page.tsx | 6 ++++-- frontend/src/components/text-input-field.tsx | 11 ++++++----- frontend/src/features/event/editor/editor.tsx | 6 ++++-- 4 files changed, 18 insertions(+), 11 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 cfcc0df8a..a009fb465 100644 --- a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx @@ -402,8 +402,10 @@ function DisplayNameInput({ }} placeholder="add your name" error={errors.displayName} - maxLength={MAX_DISPLAY_NAME_LENGTH} - maxLengthError={MESSAGES.ERROR_NAME_LENGTH} + maxLength={{ + length: MAX_DISPLAY_NAME_LENGTH, + error: MESSAGES.ERROR_NAME_LENGTH, + }} />
add your availabilities here diff --git a/frontend/src/app/settings/(submenus)/page.tsx b/frontend/src/app/settings/(submenus)/page.tsx index 2c7dfd08e..54604a399 100644 --- a/frontend/src/app/settings/(submenus)/page.tsx +++ b/frontend/src/app/settings/(submenus)/page.tsx @@ -101,8 +101,10 @@ export default function Page() { onChange={handleDefaultNameChange} error={defaultNameError} style="outlined" - maxLength={MAX_DEFAULT_NAME_LENGTH} - maxLengthError={MESSAGES.ERROR_DEFAULT_NAME_LENGTH} + maxLength={{ + length: MAX_DEFAULT_NAME_LENGTH, + error: MESSAGES.ERROR_DEFAULT_NAME_LENGTH, + }} />
maxLength : false; - const activeError = isOverLimit ? maxLengthError : error; + const isOverLimit = maxLength ? value.length > maxLength.length : false; + const activeError = isOverLimit ? maxLength?.error : error; const errorId = `${id}-error`; diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index d3e768f20..80dd0fd38 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -125,8 +125,10 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { onChange={setTitle} error={errors.title || errors.api} className="text-2xl font-semibold" - maxLength={MAX_TITLE_LENGTH} - maxLengthError={MESSAGES.ERROR_EVENT_NAME_LENGTH} + maxLength={{ + length: MAX_TITLE_LENGTH, + error: MESSAGES.ERROR_EVENT_NAME_LENGTH, + }} />
From 547e8abaf528ca1c1a01dc0b61c9088232737a6a Mon Sep 17 00:00:00 2001 From: Daniel Shi <144500568+danielshid@users.noreply.github.com> Date: Fri, 10 Jul 2026 21:23:58 +0000 Subject: [PATCH 05/21] fixed max length error msg lingering + missing name error msg disappearing --- .../[event-code]/painting/page-client.tsx | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 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 a009fb465..8fb4afac0 100644 --- a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx @@ -89,31 +89,12 @@ export default function ClientPage({ // return () => removeToast(toastId); // }, [addToast, removeToast]); - const handleNameChange = useDebouncedCallback(async (displayName) => { - if (errors.displayName) setErrors((prev) => ({ ...prev, displayName: "" })); - - if (displayName === "") { - setErrors((prev) => ({ - ...prev, - displayName: MESSAGES.ERROR_NAME_MISSING, - })); - return; - } - - if (displayName.length > MAX_DISPLAY_NAME_LENGTH) { - setErrors((prev) => ({ - ...prev, - displayName: MESSAGES.ERROR_NAME_LENGTH, - })); - return; - } - + const checkNameAvailability = useDebouncedCallback(async (displayName) => { try { await clientPost(ROUTES.availability.checkDisplayName, { event_code: eventCode, display_name: displayName, }); - setErrors((prev) => ({ ...prev, displayName: "" })); } catch (e) { const error = e as ApiErrorResponse; if (error.badRequest) { @@ -127,6 +108,24 @@ export default function ClientPage({ } }, 300); + const handleNameChange = (value: string) => { + setDisplayName(value); + if (value === "") { + setErrors((prev) => ({ + ...prev, + displayName: MESSAGES.ERROR_NAME_MISSING, + })); + } else if (value.length > MAX_DISPLAY_NAME_LENGTH) { + setErrors((prev) => ({ + ...prev, + displayName: MESSAGES.ERROR_NAME_LENGTH, + })); + } else { + setErrors((prev) => ({ ...prev, displayName: "" })); + checkNameAvailability(value); + } + }; + // DEFAULT NAME SETTING const [saveDefaultName, setSaveDefaultName] = useState(false); @@ -148,12 +147,12 @@ export default function ClientPage({ // If the user has a default name, use it to autofill the name field const newName = session.user.defaultName; setDisplayName(newName); - handleNameChange(newName); + checkNameAvailability(newName); addToast("success", MESSAGES.INFO_NAME_AUTOFILLED, { title: "NAME AUTOFILLED", }); nameInitialized.current = true; - }, [session, setDisplayName, addToast, handleNameChange]); + }, [session, setDisplayName, addToast, checkNameAvailability]); // SUBMIT AVAILABILITY const handleSubmitAvailability = async () => { @@ -276,7 +275,6 @@ export default function ClientPage({ errors={errors} session={session} displayName={displayName} - setDisplayName={setDisplayName} handleNameChange={handleNameChange} saveDefaultName={saveDefaultName} setSaveDefaultName={setSaveDefaultName} @@ -329,7 +327,6 @@ export default function ClientPage({ errors={errors} session={session} displayName={displayName} - setDisplayName={setDisplayName} handleNameChange={handleNameChange} saveDefaultName={saveDefaultName} setSaveDefaultName={setSaveDefaultName} @@ -372,7 +369,6 @@ function DisplayNameInput({ errors, session, displayName, - setDisplayName, handleNameChange, saveDefaultName, setSaveDefaultName, @@ -380,7 +376,6 @@ function DisplayNameInput({ errors: Record; session: Session; displayName: string; - setDisplayName: (name: string) => void; handleNameChange: (name: string) => void; saveDefaultName: boolean; setSaveDefaultName: (save: boolean) => void; @@ -396,10 +391,7 @@ function DisplayNameInput({ label="Display name" style="inline" value={displayName} - onChange={(e) => { - setDisplayName(e); - handleNameChange(e); - }} + onChange={handleNameChange} placeholder="add your name" error={errors.displayName} maxLength={{ From 7c39f3fdd49d811521656c10f59ed262a55f3480 Mon Sep 17 00:00:00 2001 From: Daniel Shi <144500568+danielshid@users.noreply.github.com> Date: Fri, 10 Jul 2026 21:53:22 +0000 Subject: [PATCH 06/21] removed hard cap on nickname --- frontend/src/app/settings/(submenus)/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/settings/(submenus)/page.tsx b/frontend/src/app/settings/(submenus)/page.tsx index 54604a399..77849e6c9 100644 --- a/frontend/src/app/settings/(submenus)/page.tsx +++ b/frontend/src/app/settings/(submenus)/page.tsx @@ -62,11 +62,11 @@ export default function Page() { }; const handleDefaultNameChange = (value: string) => { + setDefaultName(value); if (value.length > MAX_DEFAULT_NAME_LENGTH) { setDefaultNameError(MESSAGES.ERROR_DEFAULT_NAME_LENGTH); } else { setDefaultNameError(""); - setDefaultName(value); } }; From 55ddf573c2c25a4b28503ac4226bd5ff7bc05f78 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 10 Jul 2026 20:58:58 -0400 Subject: [PATCH 07/21] Shrink date selector trigger area --- frontend/src/features/event/editor/date-range/selector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/event/editor/date-range/selector.tsx b/frontend/src/features/event/editor/date-range/selector.tsx index 9b758edbf..cdabb40b6 100644 --- a/frontend/src/features/event/editor/date-range/selector.tsx +++ b/frontend/src/features/event/editor/date-range/selector.tsx @@ -26,7 +26,7 @@ export default function DateRangeSelection({
-
+

From 79ec82d88d4190cbd6b2b2a1e114f21eb5495a87 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 10 Jul 2026 21:17:51 -0400 Subject: [PATCH 08/21] Remove grid preview dialog --- frontend/src/features/event/editor/editor.tsx | 14 +- frontend/src/features/event/grid/index.ts | 1 - .../features/event/grid/preview-dialog.tsx | 153 ------------------ 3 files changed, 11 insertions(+), 157 deletions(-) delete mode 100644 frontend/src/features/event/grid/preview-dialog.tsx diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index 80dd0fd38..1b6b76202 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -19,7 +19,7 @@ import { MAX_TITLE_LENGTH } from "@/features/event/editor/constants"; import DateRangeSelection from "@/features/event/editor/date-range/selector"; import { EventEditorType } from "@/features/event/editor/types"; import { validateEventData } from "@/features/event/editor/validate-data"; -import { GridPreviewDialog, ScheduleGrid } from "@/features/event/grid"; +import { ScheduleGrid } from "@/features/event/grid"; import HeaderSpacer from "@/features/header/components/header-spacer"; import FormSelectorField from "@/features/selector/components/selector-field"; import { RateLimitBanner } from "@/features/system-feedback"; @@ -34,7 +34,6 @@ type EventEditorProps = { type SegmentedControlOption = "details" | "preview"; -const MemoizedGridPreview = memo(GridPreviewDialog); const MemoizedScheduleGrid = memo(ScheduleGrid); export default function EventEditor({ type, initialData }: EventEditorProps) { @@ -179,7 +178,16 @@ function EventEditorContent({ type, initialData }: EventEditorProps) {

- +
+ +
diff --git a/frontend/src/features/event/grid/index.ts b/frontend/src/features/event/grid/index.ts index e1aa4984e..ab458d44b 100644 --- a/frontend/src/features/event/grid/index.ts +++ b/frontend/src/features/event/grid/index.ts @@ -1,3 +1,2 @@ // export components export { default as ScheduleGrid } from "@/features/event/grid/grid"; -export { default as GridPreviewDialog } from "@/features/event/grid/preview-dialog"; diff --git a/frontend/src/features/event/grid/preview-dialog.tsx b/frontend/src/features/event/grid/preview-dialog.tsx deleted file mode 100644 index 3a05412fc..000000000 --- a/frontend/src/features/event/grid/preview-dialog.tsx +++ /dev/null @@ -1,153 +0,0 @@ -"use client"; - -import { useCallback, useEffect, useState } from "react"; - -import { motion } from "framer-motion"; -import { MaximizeIcon, XIcon } from "lucide-react"; - -import checkUnselectedRange from "@/core/event/lib/unselected-range"; -import { EventRange } from "@/core/event/types"; -import ActionButton from "@/features/button/components/action"; -import TimeZoneSelector from "@/features/event/components/selectors/timezone"; -import ScheduleGrid from "@/features/event/grid/grid"; -import { cn } from "@/lib/utils/classname"; -import { findTimezoneLabel } from "@/lib/utils/date-time-format"; - -interface GridPreviewDialogProps { - eventRange: EventRange; - timeslots: Date[]; -} - -export default function GridPreviewDialog({ - eventRange, - timeslots, -}: GridPreviewDialogProps) { - const [isOpen, setIsOpen] = useState(false); - const [timezone, setTimezone] = useState(eventRange.timezone); - - useEffect(() => { - setTimezone(eventRange.timezone); - }, [eventRange.timezone]); - - const handleTZChange = (newTZ: string | number) => { - setTimezone(newTZ.toString()); - }; - - // Close dialog on Escape key - const closeDialog = useCallback(() => { - setIsOpen(false); - setTimezone(eventRange.timezone); - }, [eventRange.timezone]); - - useEffect(() => { - const handleEscape = (e: KeyboardEvent) => { - if (e.key === "Escape" && isOpen) { - closeDialog(); - } - }; - - window.addEventListener("keydown", handleEscape); - return () => window.removeEventListener("keydown", handleEscape); - }, [isOpen, eventRange.timezone, closeDialog]); - - const unselectedRange = checkUnselectedRange(eventRange); - - return ( -
- {isOpen && ( -
{ - setIsOpen(false); - setTimezone(eventRange.timezone); - }} - /> - )} - - {timeslots.length > 0 && ( - -

Grid Preview

- {isOpen ? ( -
- } - onClick={closeDialog} - className="bg-transparent p-1.5" - aria-label="Close Preview" - /> -
- ) : ( -
- } - onClick={() => { - setIsOpen(!isOpen); - }} - className="bg-transparent p-1.5" - aria-label="Open Preview" - /> -
- )} -
- )} - {isOpen ? ( - -
- -
-
- - -
-
- ) : ( - - - - )} -
-
- ); -} From f676c5e9449fd1730c92597bdb7337b341a94282 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 10 Jul 2026 21:19:25 -0400 Subject: [PATCH 09/21] Consolidate repeated grid instances --- frontend/src/features/event/editor/editor.tsx | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index 1b6b76202..e69c53ab1 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -88,7 +88,7 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { } }; - // BUTTONS + // REUSED COMPONENTS const cancelButton = ( ); + const grid = ( + + ); return (
@@ -179,14 +189,7 @@ function EventEditorContent({ type, initialData }: EventEditorProps) {
- + {grid}
@@ -197,14 +200,7 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { mobileTab === "details" ? "hidden" : "block", )} > - + {grid}
{/* This z-index is necessary to avoid the time column overlapping */} From f3ba02f286d48953433688ecf6f71a0763a53465 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Fri, 10 Jul 2026 21:23:04 -0400 Subject: [PATCH 10/21] Remove unnecessary justify --- frontend/src/features/event/editor/date-range/selector.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/event/editor/date-range/selector.tsx b/frontend/src/features/event/editor/date-range/selector.tsx index cdabb40b6..c0f5cdb26 100644 --- a/frontend/src/features/event/editor/date-range/selector.tsx +++ b/frontend/src/features/event/editor/date-range/selector.tsx @@ -26,7 +26,7 @@ export default function DateRangeSelection({
-
+

From 7fbaea717292380cf9090fc85e6d652fc84e80f8 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Sun, 12 Jul 2026 14:59:47 -0400 Subject: [PATCH 11/21] Create centralized share menu component --- .../app/(event)/[event-code]/page-client.tsx | 14 ++---- .../event/results/attendees/mobile-drawer.tsx | 21 +++----- .../share-menu.tsx => share-menu/content.tsx} | 2 +- frontend/src/features/share-menu/menu.tsx | 48 +++++++++++++++++++ 4 files changed, 61 insertions(+), 24 deletions(-) rename frontend/src/features/{event/results/share-menu.tsx => share-menu/content.tsx} (98%) create mode 100644 frontend/src/features/share-menu/menu.tsx diff --git a/frontend/src/app/(event)/[event-code]/page-client.tsx b/frontend/src/app/(event)/[event-code]/page-client.tsx index 15b4ef6d0..a9bbf1362 100644 --- a/frontend/src/app/(event)/[event-code]/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/page-client.tsx @@ -18,9 +18,8 @@ import { useResultsContext, } from "@/features/event/results/context"; import { ResultsInformation } from "@/features/event/results/lib/types"; -import ShareMenu from "@/features/event/results/share-menu"; import HeaderSpacer from "@/features/header/components/header-spacer"; -import BaseDialog from "@/features/system-feedback/dialog/components/base"; +import ShareMenu from "@/features/share-menu/menu"; import { cn } from "@/lib/utils/classname"; export default function ClientPage({ @@ -107,9 +106,9 @@ function EventResults({ eventData }: { eventData: EventInformation }) { ); const shareButton = ( - } - showCloseButton - > - - + /> ); return ( diff --git a/frontend/src/features/event/results/attendees/mobile-drawer.tsx b/frontend/src/features/event/results/attendees/mobile-drawer.tsx index e294bf98f..beb337dc3 100644 --- a/frontend/src/features/event/results/attendees/mobile-drawer.tsx +++ b/frontend/src/features/event/results/attendees/mobile-drawer.tsx @@ -4,14 +4,14 @@ import { ShareIcon, SquarePenIcon } from "lucide-react"; import EmptyButton from "@/features/button/components/empty"; import LinkButton from "@/features/button/components/link"; -import { FloatingDrawer, MorphingDrawer } from "@/features/drawer"; +import { MorphingDrawer } from "@/features/drawer"; import PanelHeader from "@/features/event/results/attendees/panel-header"; import ParticipantList from "@/features/event/results/attendees/participant-list"; import { RemoveParticipantDialog, useParticipantRemoval, } from "@/features/event/results/attendees/remove-participant"; -import ShareMenu from "@/features/event/results/share-menu"; +import ShareMenu from "@/features/share-menu/menu"; export default function AttendeesDrawer({ onSnapChange, @@ -61,9 +61,6 @@ export default function AttendeesDrawer({ /> ); - /* SHARE MENU */ - const [shareMenuOpen, setShareMenuOpen] = useState(false); - return ( - } - nested={true} - > - - + eventTitle={eventTitle} + eventCode={eventCode} + isNested + /> {paintingButton}

} diff --git a/frontend/src/features/event/results/share-menu.tsx b/frontend/src/features/share-menu/content.tsx similarity index 98% rename from frontend/src/features/event/results/share-menu.tsx rename to frontend/src/features/share-menu/content.tsx index 77f3e2b1d..db758c307 100644 --- a/frontend/src/features/event/results/share-menu.tsx +++ b/frontend/src/features/share-menu/content.tsx @@ -9,7 +9,7 @@ import { useToast } from "@/features/system-feedback"; import { MESSAGES } from "@/lib/messages"; import { cn } from "@/lib/utils/classname"; -export default function ShareMenu({ +export default function ShareMenuContent({ eventTitle, eventCode, }: { diff --git a/frontend/src/features/share-menu/menu.tsx b/frontend/src/features/share-menu/menu.tsx new file mode 100644 index 000000000..9217d89b7 --- /dev/null +++ b/frontend/src/features/share-menu/menu.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { useState } from "react"; + +import { FloatingDrawer } from "@/features/drawer"; +import ShareMenuContent from "@/features/share-menu/content"; +import BaseDialog from "@/features/system-feedback/dialog/components/base"; +import useCheckMobile from "@/lib/hooks/use-check-mobile"; + +export default function ShareMenu({ + trigger, + eventTitle, + eventCode, + isNested = false, +}: { + trigger: React.ReactNode; + eventTitle: string; + eventCode: string; + isNested?: number | boolean; +}) { + const isMobile = useCheckMobile(); + + const [isOpen, setIsOpen] = useState(false); + + return isMobile ? ( + + + + ) : ( + + + + ); +} From 597ca69248a9db7d4219329f101759c5b526928b Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Sun, 12 Jul 2026 15:07:32 -0400 Subject: [PATCH 12/21] Add external open state management --- frontend/src/features/share-menu/menu.tsx | 29 ++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/frontend/src/features/share-menu/menu.tsx b/frontend/src/features/share-menu/menu.tsx index 9217d89b7..88c9dc5cf 100644 --- a/frontend/src/features/share-menu/menu.tsx +++ b/frontend/src/features/share-menu/menu.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useCallback, useState } from "react"; import { FloatingDrawer } from "@/features/drawer"; import ShareMenuContent from "@/features/share-menu/content"; @@ -12,22 +12,39 @@ export default function ShareMenu({ eventTitle, eventCode, isNested = false, + open: controlledOpen, + onOpenChange, }: { trigger: React.ReactNode; eventTitle: string; eventCode: string; isNested?: number | boolean; + open?: boolean; + onOpenChange?: (open: boolean) => void; }) { const isMobile = useCheckMobile(); - const [isOpen, setIsOpen] = useState(false); + /* OPEN STATE MANAGEMENT */ + const [internalOpen, setInternalOpen] = useState(false); + const isControlled = controlledOpen !== undefined; + const open = isControlled ? controlledOpen : internalOpen; + + const handleOpenChange = useCallback( + (newOpen: boolean) => { + if (!isControlled) { + setInternalOpen(newOpen); + } + onOpenChange?.(newOpen); + }, + [isControlled, onOpenChange], + ); return isMobile ? ( @@ -37,8 +54,8 @@ export default function ShareMenu({ From e786132c117909393326d61c25f3882983820d7b Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Sun, 12 Jul 2026 15:11:06 -0400 Subject: [PATCH 13/21] Change dashboard copy button to share button --- .../dashboard/components/copy-button.tsx | 44 ------------------- .../features/dashboard/components/event.tsx | 4 +- .../dashboard/components/share-button.tsx | 44 +++++++++++++++++++ 3 files changed, 46 insertions(+), 46 deletions(-) delete mode 100644 frontend/src/features/dashboard/components/copy-button.tsx create mode 100644 frontend/src/features/dashboard/components/share-button.tsx diff --git a/frontend/src/features/dashboard/components/copy-button.tsx b/frontend/src/features/dashboard/components/copy-button.tsx deleted file mode 100644 index d5bc3f569..000000000 --- a/frontend/src/features/dashboard/components/copy-button.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { MouseEvent } from "react"; - -import { CopyIcon } from "lucide-react"; - -import { useToast } from "@/features/system-feedback"; -import { MESSAGES } from "@/lib/messages"; -import { cn } from "@/lib/utils/classname"; - -export type DashboardCopyButtonProps = { - code: string; -}; - -export default function DashboardCopyButton({ - code, -}: DashboardCopyButtonProps) { - const { addToast } = useToast(); - const eventUrl = - typeof window !== "undefined" ? `${window.location.origin}/${code}` : ""; - - const copyToClipboard = async (e: MouseEvent) => { - e.preventDefault(); // avoid triggering the parent link - - try { - await navigator.clipboard.writeText(eventUrl); - addToast("copy", MESSAGES.COPY_LINK_SUCCESS); - } catch (err) { - console.error("Failed to copy: ", err); - addToast("error", MESSAGES.COPY_LINK_FAILURE); - } - }; - - return ( - - ); -} diff --git a/frontend/src/features/dashboard/components/event.tsx b/frontend/src/features/dashboard/components/event.tsx index a180ef55a..88a144a90 100644 --- a/frontend/src/features/dashboard/components/event.tsx +++ b/frontend/src/features/dashboard/components/event.tsx @@ -5,9 +5,9 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { EventType } from "@/core/event/types"; -import DashboardCopyButton from "@/features/dashboard/components/copy-button"; import DateRangeRow from "@/features/dashboard/components/date-range-row"; import ParticipantRow from "@/features/dashboard/components/participant-row"; +import DashboardShareButton from "@/features/dashboard/components/share-button"; import WeekdayRow from "@/features/dashboard/components/weekday-row"; import { cn } from "@/lib/utils/classname"; import { @@ -119,7 +119,7 @@ export default function DashboardEvent({
- + {myEvent && ( <> + } + eventTitle={title} + eventCode={code} + open={isOpen} + onOpenChange={setIsOpen} + /> + ); +} From deff5bb2e75ddc72d7b22259ac80a48de7c0a130 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Sun, 12 Jul 2026 17:22:15 -0400 Subject: [PATCH 14/21] Contain dialog and drawer click propagation --- frontend/src/features/drawer/components/base.tsx | 3 ++- .../src/features/system-feedback/dialog/components/base.tsx | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/features/drawer/components/base.tsx b/frontend/src/features/drawer/components/base.tsx index 04d03d6ce..c4bdeb543 100644 --- a/frontend/src/features/drawer/components/base.tsx +++ b/frontend/src/features/drawer/components/base.tsx @@ -138,7 +138,7 @@ export default function BaseDrawer({ {showOverlay && ( onOpenChange?.(false)} + onClick={(e) => e.stopPropagation()} className={cn( "fixed inset-0", frostedGlass ? "bg-black/1" : "bg-black/30", @@ -149,6 +149,7 @@ export default function BaseDrawer({ e.stopPropagation()} className={cn( "fixed bottom-0 left-0 right-0 flex outline-none", _type !== "floating" && "h-[100dvh]", diff --git a/frontend/src/features/system-feedback/dialog/components/base.tsx b/frontend/src/features/system-feedback/dialog/components/base.tsx index 8dd0d5d40..b9bce261e 100644 --- a/frontend/src/features/system-feedback/dialog/components/base.tsx +++ b/frontend/src/features/system-feedback/dialog/components/base.tsx @@ -77,6 +77,7 @@ export default function BaseDialog({ e.stopPropagation()} className={cn( "dialog-overlay fixed inset-0 z-40 bg-gray-700/40 transition-opacity", overlayClassName, @@ -84,6 +85,7 @@ export default function BaseDialog({ />
e.stopPropagation()} className={cn( "dialog-content fixed inset-0 z-40 m-auto flex flex-col gap-2 overflow-hidden", "bg-panel rounded-3xl p-6 shadow-md focus:outline-none", From 89727e59fa7b695c6f33b455414c3d534cd9a0d4 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Sun, 12 Jul 2026 17:23:09 -0400 Subject: [PATCH 15/21] Add period to link info text --- frontend/src/features/share-menu/content.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/share-menu/content.tsx b/frontend/src/features/share-menu/content.tsx index db758c307..d409505df 100644 --- a/frontend/src/features/share-menu/content.tsx +++ b/frontend/src/features/share-menu/content.tsx @@ -74,7 +74,7 @@ export default function ShareMenuContent({ {currentURL ? currentURL.replace(/^https?:\/\//, "") : "Loading..."}
- Anyone can join the event using this link + Anyone can join the event using this link.
Date: Sun, 12 Jul 2026 17:37:32 -0400 Subject: [PATCH 16/21] Add event title to share menu --- frontend/src/features/share-menu/content.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/features/share-menu/content.tsx b/frontend/src/features/share-menu/content.tsx index d409505df..55403603d 100644 --- a/frontend/src/features/share-menu/content.tsx +++ b/frontend/src/features/share-menu/content.tsx @@ -63,6 +63,7 @@ export default function ShareMenuContent({
+
{eventTitle}
Date: Sun, 12 Jul 2026 17:44:41 -0400 Subject: [PATCH 17/21] Remove unused prop --- frontend/src/features/event/editor/editor.tsx | 1 - frontend/src/features/event/grid/grid.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index e69c53ab1..4e107b371 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -108,7 +108,6 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { Date: Wed, 15 Jul 2026 20:00:23 -0400 Subject: [PATCH 18/21] Add custom dialog title to time selector --- .../src/features/event/components/selectors/time.tsx | 9 +++++---- frontend/src/features/event/editor/editor.tsx | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/features/event/components/selectors/time.tsx b/frontend/src/features/event/components/selectors/time.tsx index 60f11274a..c41aa68d5 100644 --- a/frontend/src/features/event/components/selectors/time.tsx +++ b/frontend/src/features/event/components/selectors/time.tsx @@ -2,9 +2,10 @@ import Selector from "@/features/selector/components/selector"; import { BaseSelectorWrapperProps } from "@/features/selector/types"; import { convert12To24 } from "@/lib/utils/date-time-format"; -export default function TimeSelector( - props: BaseSelectorWrapperProps, -) { +export default function TimeSelector({ + dialogTitle, + ...props +}: { dialogTitle: string } & BaseSelectorWrapperProps) { const options = Array.from({ length: 24 }, (_, i) => { const hour = i % 12 === 0 ? 12 : i % 12; const period = i < 12 ? "am" : "pm"; @@ -21,7 +22,7 @@ export default function TimeSelector( ); diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index 80dd0fd38..334287e83 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -160,6 +160,7 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { value={eventRange.timeRange.from} onChange={setStartTime} placeholder="Start Time" + dialogTitle="Select Start Time" /> @@ -169,6 +170,7 @@ function EventEditorContent({ type, initialData }: EventEditorProps) { value={eventRange.timeRange.to} onChange={setEndTime} placeholder="End Time" + dialogTitle="Select End Time" />
From 57ff123020aed293ffb7550fa58c335cb480aad8 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Thu, 16 Jul 2026 17:38:58 -0400 Subject: [PATCH 19/21] Fix grid scroll behavior --- frontend/src/features/event/editor/editor.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/event/editor/editor.tsx b/frontend/src/features/event/editor/editor.tsx index 4e107b371..e462d4a04 100644 --- a/frontend/src/features/event/editor/editor.tsx +++ b/frontend/src/features/event/editor/editor.tsx @@ -187,8 +187,10 @@ function EventEditorContent({ type, initialData }: EventEditorProps) {
-
- {grid} +
+
+
{grid}
+
From 3eefbb5720add800c59c44a01ea9469d203c7552 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Mon, 20 Jul 2026 17:03:58 -0400 Subject: [PATCH 20/21] Add version data --- frontend/src/features/version-history/data.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/src/features/version-history/data.ts b/frontend/src/features/version-history/data.ts index d4f833d97..f48cd6dd8 100644 --- a/frontend/src/features/version-history/data.ts +++ b/frontend/src/features/version-history/data.ts @@ -194,6 +194,17 @@ export function getVersionHistoryData(): VersionHistoryData { "Fixed an issue where the mobile results page drawer would not display the footer when opened", ], }, + { + version: "v0.4.6", + releaseDate: { year: 2026, month: 6, day: 20 }, + changes: [ + "Added error messages to length-limited text fields", + "Added the share menu to dashboard events", + "Updated time selector drawer titles", + "Fixed the trigger area of the event date selector", + "Removed the grid preview dialog from the event editor", + ], + }, ], }, ]; From 3d615140ea613490e81c8151758d55ff7d4b4877 Mon Sep 17 00:00:00 2001 From: jzgom067 Date: Tue, 21 Jul 2026 16:19:15 -0400 Subject: [PATCH 21/21] Cancel invalid display name checks --- frontend/src/app/(event)/[event-code]/painting/page-client.tsx | 2 ++ 1 file changed, 2 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 8fb4afac0..c9b5d0222 100644 --- a/frontend/src/app/(event)/[event-code]/painting/page-client.tsx +++ b/frontend/src/app/(event)/[event-code]/painting/page-client.tsx @@ -111,11 +111,13 @@ export default function ClientPage({ const handleNameChange = (value: string) => { setDisplayName(value); if (value === "") { + checkNameAvailability.cancel(); setErrors((prev) => ({ ...prev, displayName: MESSAGES.ERROR_NAME_MISSING, })); } else if (value.length > MAX_DISPLAY_NAME_LENGTH) { + checkNameAvailability.cancel(); setErrors((prev) => ({ ...prev, displayName: MESSAGES.ERROR_NAME_LENGTH,