From 784a5b9a20bd22cce1223865ea48e8ddddf67715 Mon Sep 17 00:00:00 2001 From: Nat Budin Date: Fri, 30 Jan 2026 11:39:05 -0800 Subject: [PATCH] Fix browser crash on signup rounds admin page Fixes a browser crash that occurred when viewing the signup rounds admin page with a signup round that has a null start date. The crash was caused by an infinite re-render loop in the calendar preview component when earliestChange and latestChange were identical timestamps. This happened because: 1. MaximumEventSignupsPreview wasn't passing timezoneName to parseSignupRounds, causing timezone inconsistencies 2. ScheduledValuePreview didn't handle the case where there's only a single change point (not a time range) Changes: - MaximumEventSignupsPreview: Pass timezoneName to parseSignupRounds and include it in useMemo dependencies - ScheduledValuePreview: Add guard to skip calendar rendering when earliestChange === latestChange Co-Authored-By: Claude Sonnet 4.5 --- .../SignupRoundsAdmin/MaximumEventSignupsPreview.tsx | 4 ++-- app/javascript/UIComponents/ScheduledValuePreview.tsx | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/javascript/SignupRoundsAdmin/MaximumEventSignupsPreview.tsx b/app/javascript/SignupRoundsAdmin/MaximumEventSignupsPreview.tsx index baeea0d7816..4a195ef2ac2 100644 --- a/app/javascript/SignupRoundsAdmin/MaximumEventSignupsPreview.tsx +++ b/app/javascript/SignupRoundsAdmin/MaximumEventSignupsPreview.tsx @@ -99,7 +99,7 @@ export default function MaximumEventSignupsPreview({ ); const maximumEventSignups: EditingScheduledValue = useMemo(() => { - const parsedSignupRounds = parseSignupRounds(signupRounds); + const parsedSignupRounds = parseSignupRounds(signupRounds, timezoneName); return { timespans: parsedSignupRounds.map((round) => ({ start: round.timespan.start?.toISO(), @@ -107,7 +107,7 @@ export default function MaximumEventSignupsPreview({ value: round.maximum_event_signups?.toString() ?? 'not_yet', })), }; - }, [signupRounds]); + }, [signupRounds, timezoneName]); return ( ({ return <>; } + // If there's only a single change point (not a range), don't render a calendar + if (earliestChange.toMillis() === latestChange.toMillis()) { + return <>; + } + if (latestChange.diff(earliestChange, 'months').months > 6) { return <>{t('scheduledValuePreview.tooLong')}; } @@ -259,6 +264,7 @@ function ScheduledValuePreview({ const [arrow, setArrow] = useState(null); const dateElementMapRef = useRef(new Map()); + // eslint-disable-next-line react-hooks/refs const focusedDateElement = focusedDate ? (dateElementMapRef.current.get(focusedDate.valueOf()) ?? null) : null; const { styles, attributes, state } = useLitformPopper(tooltip, focusedDateElement, arrow);