Skip to content

feat(events): support scheduling public events in advance - #429

Merged
jakehobbs merged 25 commits into
mainfrom
jake/events-in-advance
Jul 19, 2026
Merged

feat(events): support scheduling public events in advance#429
jakehobbs merged 25 commits into
mainfrom
jake/events-in-advance

Conversation

@jakehobbs

@jakehobbs jakehobbs commented May 31, 2026

Copy link
Copy Markdown
Member

Adds a richer event model so events can be created ahead of time — start/end time, IANA timezone, description, online flag, and public flag — surfaced in the event form via a scheduled-event flow (Places address autocomplete + timezone-aware time fields) that appears when "Public event" is checked. Location is stored denormalized on the event itself (a free-text display name plus optional Google Place id and coordinates) rather than in a shared table. After creating a public event the user now lands on a confirmation page summarizing the event with next-step choices (take attendance, edit, create another, done) instead of being dropped on the attendance page. Also introduces a Home hub listing today's events, repoints the post-login redirect and nav logo to /home, and serves a referrer-restricted Google Places key through the authed user-info endpoint. Backend changes include the migration for the new event columns, event model read/write of the new fields, and wipe/seed/Makefile housekeeping.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added event confirmation interface with event details and navigation options.
    • Introduced home dashboard displaying today's events with real-time "Happening now" status.
    • Enabled advanced event scheduling with date, time, and timezone support.
    • Added location selection for events using Google Places integration.
    • Implemented collapsible event details summary for easier event editing.
    • Updated event creation workflow with improved form organization and field gating.
  • Documentation

    • Updated setup instructions for Google Places API key configuration.

Add a richer event model so events can be created ahead of time:
start/end time, IANA timezone, description, online flag, public flag, and
a deduped Google Place location (new `locations` table). The event form
expands into a scheduled-event flow when "Public event" is checked, with
a Google Places address autocomplete and timezone-aware time fields.

After creating a public event, the user lands on a confirmation page
(name/date/time/location summary) offering "Take attendance now", "Edit
event", "Create another event", and "Done" instead of being dropped on
the attendance page. Adds a new Home hub listing today's events, points
the post-login redirect and nav logo at /home, and serves a
referrer-restricted Google Places key via the authed user-info endpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jakehobbs
jakehobbs requested a review from alexsapps as a code owner May 31, 2026 00:02
Copilot AI review requested due to automatic review settings May 31, 2026 00:02
@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 086c0b56-f56f-4c96-858b-98da5bf0a165

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements scheduled public events with Google Places location integration, timezone-aware event discovery, and a confirmation flow. Changes span database schema (new locations table, extended events), server-side event model and validation, updated API contracts, frontend session/context wiring, timezone utilities, TimeField and PlacesAutocomplete components, refactored event form with schema-driven validation, and a new HomeHub discovery page.

Changes

Scheduled Public Events Feature

Layer / File(s) Summary
Development tooling, database schema, and wipe
Makefile, cli/cmd/db.go, pkg/shared/db-migrations/20260530120000_advance_events_and_locations.*, pkg/shared/wipe.go
Makefile run exports debug envs and optional .env; seed-data SQL uses explicit column list; up-migration creates locations table with deduplication and extends events with advance-event columns and FK; down-migration reverses; wipe drops locations after events.
Server event model and persistence
server/src/model/event.go
EventJSON and LocationJSON types extended; Event DB struct adds LocationID, IsOnline, Description, StartTime, EndTime, Timezone, IsPublic, and resolved location fields; getEvents performs LEFT JOIN locations; insertEvent/updateEvent set new columns; CleanEventData validates/normalizes time/timezone, upserts locations, enforces public-event rules.
Backend API contracts and handlers
server/src/main.go, server/src/config/config.go, frontend-v2/src/lib/api.ts
HomeHandler redirects to /v2/home; AuthedUserInfoHandler includes googlePlacesApiKey; API schemas updated with googlePlacesApiKey in user response, advance-event fields in event GET/list, and location submission in SaveEventParams; config comment clarifies referrer-restricted key usage.
Frontend session, context, and layout wiring
frontend-v2/src/app/session.ts, frontend-v2/src/app/authed-page-provider.tsx, frontend-v2/src/app/(authed)/layout.tsx
fetchSession returns googlePlacesApiKey from user data; AuthedPageContext extended with googlePlacesApiKey; layout passes the key to AuthedPageProvider.
Timezone utilities, TimeField, and PlacesAutocomplete
frontend-v2/src/lib/time.ts, frontend-v2/src/components/ui/time-field.tsx, frontend-v2/src/app/(authed)/events/places-autocomplete.tsx
Timezone module exports common timezones, todayInTimezone, formatWallClock, formatEventTimeRange, and isEventHappeningNow; TimeField component offers accessible HH:MM time input; PlacesAutocomplete loads Google Places library and emits PlaceValue on selection.
Event form schema, sections, and hook
frontend-v2/src/app/(authed)/events/event-form-schema.ts, frontend-v2/src/app/(authed)/events/event-form-sections.tsx, frontend-v2/src/app/(authed)/events/useEventForm.ts
EVENT_TYPES, constants, and EventFormMode exported; attendeeSchema and formSchema with Zod refinements (time ordering, public-event rules); exported sections (FieldError, clearLocation, EventDetailsSummaryBar, ScheduledEventFields, AttendeesSection); useEventForm hook manages TanStack Form state, fetches existing events, implements saveEventMutation with public-event confirmation routing.
EventForm component refactoring
frontend-v2/src/app/(authed)/events/event-form.tsx
EventForm adds startExpanded prop, uses useEventForm hook; conditionally renders collapsible detail fields, gates ScheduledEventFields and AttendeesSection based on form state.
Event confirmation and edit routing
frontend-v2/src/app/(authed)/events/[id]/confirmation/page.tsx, frontend-v2/src/app/(authed)/events/[id]/confirmation/event-confirmation.tsx, frontend-v2/src/app/(authed)/events/[id]/page.tsx
Confirmation page preloads event, hydrates EventConfirmation client component; EventConfirmation renders summary with navigation actions; edit page reads expanded query param to control form expansion.
HomeHub today's-events discovery
frontend-v2/src/app/(authed)/home/home-hub.tsx, frontend-v2/src/app/(authed)/home/page.tsx
HomeHub maintains ticking clock, computes viewer-timezone today, fetches and sorts today's events, renders with "Happening now" badges; HomePage wraps HomeHub in layout.
Navigation updates and documentation
frontend-v2/src/components/nav.tsx, frontend-v2/src/app/(authed)/events/new/page.tsx, README.md
Navbar logo becomes Link to /home; new event page title updated; README expanded with GOOGLE_PLACES_API_KEY setup.

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

  • dxe/adb#313: Modifies AuthedPageProvider context shape; this PR extends that same context with googlePlacesApiKey and dependency injection.
  • dxe/adb#314: Introduces ApiClient.getEventList; this PR extends its response schema to include advance-event fields (start_time, end_time, timezone) and uses it in HomeHub discovery.

Suggested Reviewers

  • alexsapps

Poem

🐰 Hop along the event timeline, dear developer—
Timezones tamed, locations placed, and "happening now" rings true,
From database to today's discovery, the whole stack works together,
A calendar that knows your local sky, and users see what's new! 🗺️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(events): support scheduling public events in advance' directly summarizes the main feature added in this PR: enabling advance scheduling of public events with new fields and a confirmation flow.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jake/events-in-advance

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for scheduling public events in advance with rich metadata (start/end time, IANA timezone, description, online flag, public flag, and a deduped Google Place location), wires it through the event form (revealed when "Public event" is checked), adds a post-create confirmation page and a Home hub of today's events, and repoints the post-login redirect/nav logo to /home. A new locations table with a (chapter_id, google_place_id) unique key backs location dedup; the Google Places key is served from the authed user-info endpoint.

Changes:

  • Schema + model: new locations table and additional events columns; getEvents LEFT JOINs locations; CleanEventData validates time/timezone/description and upserts a location via GetOrCreateLocation.
  • Frontend: new home hub, events/[id]/confirmation page, PlacesAutocomplete, TimeField, and timezone helpers; EventForm extended with a collapsible details panel and scheduled-event fields.
  • Plumbing: googlePlacesApiKey flows through session/AuthedPageProvider; nav logo and post-login redirect now target /home; wipe, seed, and Makefile housekeeping.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
pkg/shared/db-migrations/20260530120000_advance_events_and_locations.up.sql Creates locations table and adds scheduling/location columns to events.
pkg/shared/db-migrations/20260530120000_advance_events_and_locations.down.sql Reverts the up migration.
pkg/shared/wipe.go Drops the new locations table in the correct FK order.
server/src/model/event.go Adds JSON/DB fields, GetOrCreateLocation, time/timezone validation, and updates queries.
server/src/main.go Redirects to /home and exposes googlePlacesApiKey from authed user info.
cli/cmd/db.go Makes seed INSERT INTO events column-explicit for the new schema.
Makefile Sources optional .env alongside debug.env when running the server.
frontend-v2/src/lib/api.ts Adds location/scheduling fields to event schemas and save params; surfaces Places key.
frontend-v2/src/lib/timezone.ts New timezone helpers (browser tz, common zones, range/abbreviation formatting, happening-now).
frontend-v2/src/app/session.ts Threads googlePlacesApiKey through fetchSession.
frontend-v2/src/app/authed-page-provider.tsx Adds googlePlacesApiKey to the authed context.
frontend-v2/src/app/(authed)/layout.tsx Passes googlePlacesApiKey into the context.
frontend-v2/src/components/nav.tsx Makes the brand logo link to /home.
frontend-v2/src/components/ui/time-field.tsx New react-aria-based TimeField with a clear button.
frontend-v2/src/app/(authed)/events/places-autocomplete.tsx New Google Places autocomplete input that submits a deduped place.
frontend-v2/src/app/(authed)/events/event-form.tsx Adds scheduled-event fields, collapsible details, post-create confirmation routing.
frontend-v2/src/app/(authed)/events/new/page.tsx Updates title to "New event".
frontend-v2/src/app/(authed)/events/[id]/page.tsx Supports ?edit=1 to start the form expanded; title now "Event".
frontend-v2/src/app/(authed)/events/[id]/confirmation/page.tsx New confirmation page rendered after creating a public event.
frontend-v2/src/app/(authed)/events/[id]/confirmation/event-confirmation.tsx Confirmation UI with next-step actions.
frontend-v2/src/app/(authed)/home/page.tsx New Home route container.
frontend-v2/src/app/(authed)/home/home-hub.tsx Today's-events hub with "Happening now" badge and entry points.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend-v2/src/app/(authed)/events/places-autocomplete.tsx
Comment thread frontend-v2/src/app/(authed)/home/home-hub.tsx Outdated
Comment thread server/src/model/event.go Outdated
Comment thread frontend-v2/src/app/(authed)/events/event-form.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend-v2/src/app/`(authed)/events/[id]/page.tsx:
- Around line 41-42: The page at /events/[id] currently always renders EventForm
with mode="event" so the post-create "Take attendance now" CTA (which posts to
/events/${eventId}) lands in the editor instead of starting attendance; fix by
routing the CTA to the actual attendance handler or restoring the attendance
view: either update the confirmation screen to send the CTA to the real
attendance route (e.g. /events/${eventId}/attendance or the route handled by
your AttendancePage/AttendanceView) or change page.tsx to conditionally render
the attendance component (e.g. render AttendanceView when a query param or state
indicates attendance flow) instead of always rendering <EventForm mode="event">
so the CTA starts the intended attendance flow.

In `@frontend-v2/src/app/`(authed)/events/event-form.tsx:
- Around line 32-36: The "Today" shortcut and related logic use the browser's
timezone instead of the event form's selected timezone; update usages that
compute today (notably where eventDate is set and the shortcut handler around
todayInTimezone()) to call todayInTimezone(formValues.timezone) (or the
equivalent helper) so the calendar uses the currently selected timezone; locate
and modify the code paths referencing eventDate and the Today shortcut handler
(and the date comparison/initialization logic that currently relies on new
Date() or getBrowserTimezone()) to pass the form's timezone value from the
timezone dropdown/state into the todayInTimezone helper so the selected event
timezone determines "Today".

In `@frontend-v2/src/app/`(authed)/events/places-autocomplete.tsx:
- Around line 182-187: The onChange handler currently only calls onClear() when
the input is emptied, so a user editing a committed place can change the visible
text while the component still holds the old googlePlaceId/address; update the
onChange in places-autocomplete.tsx to clear the committed place as soon as the
user types (call onClear()) whenever the input value changes from a selected
state (i.e., on any non-selection user edit) — ensure this happens inside the
existing onChange (alongside setText) and avoid interfering with the code path
that sets the text when a suggestion is chosen (so that selection still commits
the place).

In `@frontend-v2/src/app/`(authed)/home/home-hub.tsx:
- Line 30: The memoized today value (const today = useMemo(() =>
todayInTimezone(getBrowserTimezone()), [])) never updates after midnight, so
pages left open show yesterday’s events; modify the logic that computes today
(the today variable / useMemo for todayInTimezone and getBrowserTimezone) to
recompute on a daily tick—either add the existing clock state as a dependency to
the useMemo or schedule a timeout/interval that re-evaluates today at the next
local midnight—so that todayInTimezone(getBrowserTimezone()) is called again
when the date changes.
- Around line 47-50: upcomingHref currently computes the one-year-ahead end by
string-slicing today which produces invalid dates on leap day; change
upcomingHref to parse the today string into a proper Date (or use the project's
timezone helper/date library), add one year via date arithmetic (e.g., add 1
year using date-fns/dayjs/luxon or Date.setFullYear(Date.getFullYear()+1) to
handle leap-year rollover), then format the resulting date back into the same
YYYYMMDD string for the end query param; update the logic in the upcomingHref
useMemo (and any helper that reads/outputs today) to use this date-based
approach instead of string slicing.

In `@frontend-v2/src/lib/timezone.ts`:
- Around line 49-56: todayInTimezone and nowMinutesInTimezone call new
Intl.DateTimeFormat with unvalidated timeZone and can throw RangeError; wrap the
Intl.DateTimeFormat/format calls in try/catch and handle invalid zones by
returning a safe fallback (e.g. for todayInTimezone return the local YYYY-MM-DD
string or empty string, for nowMinutesInTimezone return a sentinel like null or
-1), and update isEventHappeningNow (used by HomeHub when passing
event.timezone) to catch/propagate these fallbacks and return false when the
timezone is invalid so a bad event.timezone cannot crash the render.

In `@pkg/shared/db-migrations/20260530120000_advance_events_and_locations.up.sql`:
- Around line 17-25: The current fk_events_location only checks location_id and
allows cross-chapter links; update the schema so events enforces chapter-scoped
locations by making the FK composite: have events reference (location_id,
chapter_id) -> locations (id, chapter_id) (rename or replace fk_events_location
accordingly), ensure events has a chapter_id column, and add a unique/index on
locations (id, chapter_id) so the composite FK can reference those columns.

In `@server/src/model/event.go`:
- Around line 607-656: The code validates fields in isolation but allows
inconsistent scheduled-public events; after calling cleanEventTime and resolving
Timezone/Location (symbols: cleanEventTime, e.StartTime, e.EndTime, e.Timezone,
e.IsOnline, e.LocationID, e.IsPublic), add a coherent-schedule validation: if
e.IsPublic is true then require a non-empty StartTime and a non-empty Timezone
and require a concrete location choice (either e.IsOnline == true or
e.LocationID != nil); if EndTime is present ensure EndTime >= StartTime by
parsing/comparing the two times in the same day context (use the same
format/parse logic as cleanEventTime) and return a clear fmt.Errorf when any of
these checks fail (e.g. "invalid scheduled public event: missing start_time",
"missing timezone", "must be online or have a physical location", or "end_time
before start_time").
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fce8d66b-61d7-45d4-930a-a2d67ea091d2

📥 Commits

Reviewing files that changed from the base of the PR and between c78e8e9 and 012fa83.

📒 Files selected for processing (22)
  • Makefile
  • cli/cmd/db.go
  • frontend-v2/src/app/(authed)/events/[id]/confirmation/event-confirmation.tsx
  • frontend-v2/src/app/(authed)/events/[id]/confirmation/page.tsx
  • frontend-v2/src/app/(authed)/events/[id]/page.tsx
  • frontend-v2/src/app/(authed)/events/event-form.tsx
  • frontend-v2/src/app/(authed)/events/new/page.tsx
  • frontend-v2/src/app/(authed)/events/places-autocomplete.tsx
  • frontend-v2/src/app/(authed)/home/home-hub.tsx
  • frontend-v2/src/app/(authed)/home/page.tsx
  • frontend-v2/src/app/(authed)/layout.tsx
  • frontend-v2/src/app/authed-page-provider.tsx
  • frontend-v2/src/app/session.ts
  • frontend-v2/src/components/nav.tsx
  • frontend-v2/src/components/ui/time-field.tsx
  • frontend-v2/src/lib/api.ts
  • frontend-v2/src/lib/timezone.ts
  • pkg/shared/db-migrations/20260530120000_advance_events_and_locations.down.sql
  • pkg/shared/db-migrations/20260530120000_advance_events_and_locations.up.sql
  • pkg/shared/wipe.go
  • server/src/main.go
  • server/src/model/event.go

Comment thread frontend-v2/src/app/(authed)/events/[id]/page.tsx Outdated
Comment thread frontend-v2/src/app/(authed)/events/event-form.tsx Outdated
Comment thread frontend-v2/src/app/(authed)/events/places-autocomplete.tsx
Comment thread frontend-v2/src/app/(authed)/home/home-hub.tsx Outdated
Comment thread frontend-v2/src/app/(authed)/home/home-hub.tsx
Comment thread frontend-v2/src/lib/timezone.ts Outdated
Comment thread pkg/shared/db-migrations/20260530120000_advance_events_and_locations.up.sql Outdated
Comment thread server/src/model/event.go
@jakehobbs
jakehobbs marked this pull request as draft May 31, 2026 00:12
- Allow & and other chars in event descriptions (parameterized SQL +
  React-escaped output, so no injection risk); keep the check on names.
- Preserve stored location metadata on upsert instead of clobbering with
  blanks (IF(... <> '')/COALESCE).
- Enforce chapter-scoped location integrity via a composite FK
  (chapter_id, location_id) -> locations (chapter_id, id).
- Validate public events server-side: require start_time, timezone, and
  (in-person) a location. Overnight (end < start) intentionally deferred.
- Clear the committed place on manual edits to the location field so the
  displayed text and submitted location can't drift apart.
- Use the event's selected timezone for the "Today" shortcut.
- Roll the home page over to the next day at local midnight; guard invalid
  timezones in Intl calls; compute the one-year-ahead date via Date math.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jakehobbs
jakehobbs marked this pull request as ready for review May 31, 2026 02:21
jakehobbs and others added 3 commits May 30, 2026 21:07
The home header date is derived from the current time and browser
timezone, so SSR and the client can land on different calendar days
near midnight or across zones. Gate the date label on a client mount
flag so the server and first client render agree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…atch

The "View upcoming events" link's date-range query is derived from the
current day, which differs between SSR and client near midnight. Use a
stable /events fallback until mounted, then the date-filtered URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Frontend (frontend-v2):
- timezone.ts: reframe header for "upcoming" events and justify the
  wall-clock + IANA-zone storage choice over a UTC instant; drop the
  low-value Intl note; fix the misleading COMMON_TIMEZONES "fallback"
  wording; convert leading comments to JSDoc; move the en-CA note to its
  use; take an event object (Pick) instead of four positional strings.
- home-hub.tsx: drop a redundant comment, use sortedEvents?.map, and
  extract a TodayEventRow component.
- event-confirmation.tsx: pass the event to the time formatter, inline
  single-use locals, replace margins with parent gap, and navigate via
  <Link> buttons instead of router.push.
- event-form.tsx: correct the "optional" schema comment, drop a stale
  comment, rename the ?edit=1 query param to ?expanded=1, clarify the
  HH:MM slice and the onSubmit attendee check, and trim a verbose note.
- places-autocomplete.tsx: toast when Google Maps fails to load.
- events/[id]/page.tsx: Promise.all the params/searchParams awaits.
- api.ts: explain the empty-string default for googlePlacesApiKey.

Backend:
- model/event.go: reword "existing" to "older basic" attendance flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend-v2/src/lib/timezone.ts (1)

107-114: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tighten wall-clock validation before formatting or badge math.

frontend-v2/src/lib/api.ts:293-309 only narrows start_time/end_time to nullable strings, so these helpers still need to reject impossible clocks. Right now Line 108 and Line 140 accept values like 25:99 or 09:30junk, which can surface bogus labels in formatEventTimeRange() and false positives in isEventHappeningNow().

Suggested fix
+function parseWallClock(
+  time: string,
+): { hours: number; minutes: number; minutesText: string } | null {
+  const match = /^(\d{1,2}):(\d{2})(?::\d{2})?$/.exec(time.trim())
+  if (!match) return null
+
+  const hours = Number(match[1])
+  const minutes = Number(match[2])
+  if (hours > 23 || minutes > 59) return null
+
+  return { hours, minutes, minutesText: match[2] }
+}
+
 export function formatWallClock(time: string): string {
-  const match = /^(\d{1,2}):(\d{2})/.exec(time.trim())
-  if (!match) return ''
-  const hours = Number(match[1])
-  const minutes = match[2]
+  const parsed = parseWallClock(time)
+  if (!parsed) return ''
+  const { hours, minutesText } = parsed
   const period = hours >= 12 ? 'PM' : 'AM'
   const hour12 = hours % 12 === 0 ? 12 : hours % 12
-  return `${hour12}:${minutes} ${period}`
+  return `${hour12}:${minutesText} ${period}`
 }
 ...
 function toMinutesSinceMidnight(time: string): number | null {
-  const match = /^(\d{1,2}):(\d{2})/.exec(time.trim())
-  if (!match) return null
-  return Number(match[1]) * 60 + Number(match[2])
+  const parsed = parseWallClock(time)
+  return parsed ? parsed.hours * 60 + parsed.minutes : null
 }

Also applies to: 139-142

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend-v2/src/lib/timezone.ts` around lines 107 - 114, formatWallClock
currently accepts invalid inputs like "25:99" or "09:30junk" because it uses a
loose regex; tighten validation by requiring a full-string match for 24-hour
clocks and valid minutes (e.g. regex like ^([01]?\d|2[0-3]):([0-5]\d)$) and
reject non-matching values (return ''). Update formatWallClock to trim the
input, run the strict regex, parse hours/minutes only after a match and then
convert to 12-hour with AM/PM as before; apply the same strict validation
approach to helpers that rely on it such as isEventHappeningNow and
formatEventTimeRange so they don't receive or accept bogus times.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@frontend-v2/src/lib/timezone.ts`:
- Around line 107-114: formatWallClock currently accepts invalid inputs like
"25:99" or "09:30junk" because it uses a loose regex; tighten validation by
requiring a full-string match for 24-hour clocks and valid minutes (e.g. regex
like ^([01]?\d|2[0-3]):([0-5]\d)$) and reject non-matching values (return '').
Update formatWallClock to trim the input, run the strict regex, parse
hours/minutes only after a match and then convert to 12-hour with AM/PM as
before; apply the same strict validation approach to helpers that rely on it
such as isEventHappeningNow and formatEventTimeRange so they don't receive or
accept bogus times.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: af29fe85-8bac-460a-9c09-dac49c412740

📥 Commits

Reviewing files that changed from the base of the PR and between 2e7ba52 and d3f0fd4.

📒 Files selected for processing (8)
  • frontend-v2/src/app/(authed)/events/[id]/confirmation/event-confirmation.tsx
  • frontend-v2/src/app/(authed)/events/[id]/page.tsx
  • frontend-v2/src/app/(authed)/events/event-form.tsx
  • frontend-v2/src/app/(authed)/events/places-autocomplete.tsx
  • frontend-v2/src/app/(authed)/home/home-hub.tsx
  • frontend-v2/src/lib/api.ts
  • frontend-v2/src/lib/timezone.ts
  • server/src/model/event.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • frontend-v2/src/app/(authed)/events/[id]/page.tsx
  • frontend-v2/src/app/(authed)/events/[id]/confirmation/event-confirmation.tsx
  • server/src/model/event.go
  • frontend-v2/src/lib/api.ts
  • frontend-v2/src/app/(authed)/events/places-autocomplete.tsx
  • frontend-v2/src/app/(authed)/events/event-form.tsx

Restore timeRange/locationLabel locals so the formatter and the location
fallback are each evaluated once rather than inlined twice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@alexsapps alexsapps left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exciting times. thanks Jake!

Comment thread frontend-v2/src/app/(authed)/home/home-hub.tsx
Comment thread frontend-v2/src/app/(authed)/home/home-hub.tsx
Comment thread server/src/main.go Outdated
Comment thread frontend-v2/src/app/authed-page-provider.tsx Outdated
Comment thread Makefile Outdated
set -a && . server/debug.env && set +a && \
set -a && \
. server/debug.env && \
{ [ -f .env ] && . ./.env || true; } && \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should launch.json be updated too? 🙏:)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left this one: the Go Server debug config loads a single envFile (server/debug.env), and the new .env is optional (only needed for the Places key locally). Pointing the debugger at a .env most people won't have would break 'Go Server' for them. If you want Places in the debugger, easiest is to drop GOOGLE_PLACES_API_KEY into your local server/debug.env. I documented the .env route in the README.

(posted by claude)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so far makefile and launch.json behavior have been kept in sync and i'd like to keep it that way if possible, so we don't have situations where things work depending on how they're launched. perhaps we could have 'make deps' create the file if it's missing so it can be added to launch.json too?

@jakehobbs jakehobbs Jun 27, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not the biggest fan of dev containers. haha. lemme see what i can do.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think i figured it out

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ still wasn't working for me so i'm pushing some commits to your branch to fix 🙂

btw in theory launch.json has nothing to do with devcontainers, although maybe there is some dependency with our specific launch.json that you encountered.

added_attendees: addedAttendees,
deleted_attendees: deletedAttendees,
suppress_survey: value.suppressSurvey,
// Only send scheduled-event fields when they're in play, keeping the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

premature optimization?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a guard rather than an optimization: the conditional keeps the connection and quick-attendance save payloads exactly as they were (no is_public/time/location fields), so those flows — coaching especially, which hits a different endpoint — don't start sending event-only fields. Always including them risks clobbering/confusing those paths, so I'd keep it.

(posted by claude)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about guarding with just "!isConnection" and removing the comment? it's just a lot to understand right now with the shape of the json depending on certain fields being set.

@jakehobbs jakehobbs Jun 27, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — switched the spread to guard on just `!isConnection` and removed the comment. It is behavior-equivalent: the backend nulls out the empty scheduled fields for plain attendance saves (`cleanEventTime("")` → NULL, and the `is_public`-requires-start-time guard only fires when `is_public` is true), and the form always loads these fields into state, so nothing gets clobbered.

Kept the `includeUpcoming` variable itself though — it is still load-bearing for the "at least one attendee required" check above, since public/upcoming events legitimately have no attendees yet.

(Heads up: this block moved to `useEventForm.ts` since you commented.)

— posted by claude 🤖

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ ty!

Comment thread frontend-v2/src/lib/time.ts Outdated
Comment thread frontend-v2/src/lib/time.ts
Comment thread server/src/model/event.go Outdated
Comment thread server/src/model/event.go Outdated
jakehobbs and others added 2 commits June 4, 2026 19:26
Frontend:
- Split event-form.tsx (~950 lines) into event-form-schema.ts, a
  useEventForm hook, and event-form-sections.tsx, plus a shared FieldError
  component and clearLocation helper.
- Group the scheduled-event fields in a nested sub-box (still gated by the
  "Publicly listed" checkbox); stop force-collapsing details on save.
- Show timezone abbreviations in the picker options; drop the redundant
  "Times are in…" line. Remove the "Event details" eyebrow and assorted
  help/boilerplate text.
- Home hub: only render the events section for users with attendance access.
- Rename lib/timezone.ts -> lib/time.ts.
- Drop the frontend HH:MM:SS slice now that the backend returns HH:MM.
- Convert the mount-gate and places-autocomplete syncs off setState-in-effect
  to satisfy the react-hooks lint rules.

Backend:
- Return start_time/end_time as HH:MM from EventJSON.
- Use ValidationErrorf for public-event input validation (400 not 500).
- Tidy Google Places key comments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 188-190: The fenced code block containing the environment variable
example (the triple-backtick block around "GOOGLE_PLACES_API_KEY=...") needs a
language tag to satisfy markdownlint MD040; update that fenced block (the code
block with the example env line) to include a language specifier such as "bash"
(e.g., ```bash) so the markdown renderer and linter accept it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7ba07f26-2b82-41c6-9853-b957c186f927

📥 Commits

Reviewing files that changed from the base of the PR and between d3f0fd4 and 0b56cca.

📒 Files selected for processing (13)
  • README.md
  • frontend-v2/src/app/(authed)/events/[id]/confirmation/event-confirmation.tsx
  • frontend-v2/src/app/(authed)/events/event-form-schema.ts
  • frontend-v2/src/app/(authed)/events/event-form-sections.tsx
  • frontend-v2/src/app/(authed)/events/event-form.tsx
  • frontend-v2/src/app/(authed)/events/places-autocomplete.tsx
  • frontend-v2/src/app/(authed)/events/useEventForm.ts
  • frontend-v2/src/app/(authed)/home/home-hub.tsx
  • frontend-v2/src/app/authed-page-provider.tsx
  • frontend-v2/src/lib/time.ts
  • server/src/config/config.go
  • server/src/main.go
  • server/src/model/event.go
💤 Files with no reviewable changes (1)
  • frontend-v2/src/app/authed-page-provider.tsx
✅ Files skipped from review due to trivial changes (1)
  • server/src/config/config.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • server/src/main.go
  • frontend-v2/src/app/(authed)/events/[id]/confirmation/event-confirmation.tsx
  • frontend-v2/src/app/(authed)/events/places-autocomplete.tsx
  • frontend-v2/src/app/(authed)/home/home-hub.tsx
  • server/src/model/event.go

Comment thread README.md
Some venues aren't a clean Google Place (an intersection, a patch of public
land), so allow entering a location by hand: a free-text name plus optional
coordinates. Manual locations are stored on the event itself (new
manual_location_name/lat/lng columns) rather than the place-keyed locations
table, since there's no google_place_id to dedupe on. An event uses either a
location_id (Google Place) or the manual fields, never both; ToJSON surfaces
whichever is set as a unified location.

The form's location field gains a toggle to switch between Google Places search
and manual entry; public in-person events accept either.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jakehobbs
jakehobbs requested a review from alexsapps June 5, 2026 02:54
jakehobbs and others added 4 commits June 4, 2026 19:55
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pnpm prompts before wiping an out-of-sync node_modules, which hangs forever
in non-interactive setups (e.g. the Conductor devcontainer 'make deps' step).
Set confirm-modules-purge=false at the repo root and in frontend-v2 so the
reinstall proceeds without a TTY.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The .npmrc setting wasn't reliably suppressing pnpm's modules-purge prompt in
the Conductor devcontainer setup, so 'make deps' hung. Pass the flag explicitly
on the pnpm install commands, which is honored regardless of .npmrc resolution.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread frontend-v2/src/components/nav.tsx
jakehobbs and others added 2 commits June 27, 2026 17:12
…ns table

Locations were normalized into a shared `locations` table keyed by Google
place id, with the place name resolved via JOIN at read time. That made a
place's display name uneditable and shared across every event using it, so
renaming one event's location would silently change others.

Store the location directly on the event instead: a free-text name (always
editable, never shared) plus optional geo data — a Google place id and/or
coordinates. Google Places / lat-lng now serve only as a geo picker; the
name is what's displayed and is owned by the event. This removes the shared
table entirely (no GetOrCreateLocation, no JOIN) and folds the separate
manual-location columns into the unified location columns.

Also offer a curated location-name suggestion (Berkeley Animal Rights
Center) via a datalist so recurring venues get one canonical spelling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jakehobbs
jakehobbs requested a review from alexsapps June 28, 2026 01:05
# Conflicts:
#	frontend-v2/package.json
#	frontend-v2/pnpm-lock.yaml
jakehobbs and others added 6 commits June 27, 2026 18:11
pnpx fetched the latest prettier (3.9.1) instead of the project-pinned
3.7.3, so the hook flagged files that make fmt and CI consider clean.
Switch to pnpm exec prettier, matching make fmt, as the comment intends.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hook ran `pnpx prettier`, which downloads the latest prettier
(currently 3.9.1) rather than the project-pinned ~3.7.3. 3.9.1 changed
union-type wrapping, so it flagged files that make fmt, the devcontainer,
and the pinned version all consider clean — failing CI (which runs
./.githooks/pre-commit) and local commits.

Pin to prettier@3.7.3 so the check is self-contained (no root install
needed, unlike `pnpm exec`) and matches package.json everywhere.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
alexsapps
alexsapps previously approved these changes Jul 1, 2026

@alexsapps alexsapps left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking issues claude found which i may look into later if i have time:

Duplicated parent→local value sync pattern — places-autocomplete.tsx
PlacesAutocomplete syncs parent-driven changes with local state to avoid flashing stale text; home-hub.tsx does a similar sync via useSyncExternalStore (for hydration safety). The finder suggested extracting the shared pattern into a reusable hook to avoid future sync bugs.

FieldError component not used everywhere — event-form-sections.tsx
A new FieldError component was introduced for error messages, but attendee-input-field.tsx still renders errors inline with the same text-sm text-red-500 mt-1 classes. So error styling now lives in two places; changing it means editing both.

toMinutesSinceMidnight duplicates formatWallClock's parsing — time.ts
Both functions independently parse the same HH:MM regex. If the accepted time format ever changes, both must be updated. Suggested extracting a single parse helper.

setDateToToday efficiency nit — useEventForm.ts
Minor: reuse the already-memoized browserTz rather than recomputing browser-timezone detection. Low impact.

Comment thread Makefile Outdated
set -a && . server/debug.env && set +a && \
set -a && \
. server/debug.env && \
{ [ -f .env ] && . ./.env || true; } && \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ still wasn't working for me so i'm pushing some commits to your branch to fix 🙂

btw in theory launch.json has nothing to do with devcontainers, although maybe there is some dependency with our specific launch.json that you encountered.

Comment thread Makefile Outdated
set -a && . server/debug.env && set +a && \
set -a && \
. server/debug.env && \
{ [ -f .env ] && . ./.env || true; } && \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ ok ty! just a couple notes:

for prod, perhaps in the future (if this is a prod key) we should keep all the ADB keys in the ADB project, otherwise it's hard to know when a key can be deleted, and probably hard to see how much each app is consuming/costing us in GCP if they share projects or are split between projects. maybe i'll move it later if you don't object. although there are two ADB projects in GCP...which complicates things lol.

for dev, in the future, we can use the "DxE General Dev Test" project. this makes clear that it's safer to give the key to an untrusted contributor.

added_attendees: addedAttendees,
deleted_attendees: deletedAttendees,
suppress_survey: value.suppressSurvey,
// Only send scheduled-event fields when they're in play, keeping the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ ty!

</p>
)}
</div>
{detailsExpanded ? (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ sg 🙂

</form.Field>
) : (
<div className="rounded-md border border-dashed p-4 text-sm text-muted-foreground">
Save the event first — you can record attendance afterward.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ ty!

alexsapps and others added 2 commits July 1, 2026 22:37
formatWallClock and toMinutesSinceMidnight each parsed the same HH:MM
regex independently. Extract a single parseWallClock helper so the
accepted time format is defined in one place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move FieldError to its own module (avoiding a circular import with
event-form-sections, which imports AttendeeInputField) and use it in
attendee-input-field instead of a hand-rolled error paragraph, so error
styling lives in one place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@alexsapps

Copy link
Copy Markdown
Collaborator

non-blocking issues claude found which i may look into later if i have time:

Duplicated parent→local value sync pattern — places-autocomplete.tsx PlacesAutocomplete syncs parent-driven changes with local state to avoid flashing stale text; home-hub.tsx does a similar sync via useSyncExternalStore (for hydration safety). The finder suggested extracting the shared pattern into a reusable hook to avoid future sync bugs.

FieldError component not used everywhere — event-form-sections.tsx A new FieldError component was introduced for error messages, but attendee-input-field.tsx still renders errors inline with the same text-sm text-red-500 mt-1 classes. So error styling now lives in two places; changing it means editing both.

toMinutesSinceMidnight duplicates formatWallClock's parsing — time.ts Both functions independently parse the same HH:MM regex. If the accepted time format ever changes, both must be updated. Suggested extracting a single parse helper.

setDateToToday efficiency nit — useEventForm.ts Minor: reuse the already-memoized browserTz rather than recomputing browser-timezone detection. Low impact.

pushed commits to fix 2 of them. other 2 not worth fixing. feel free to revert if i'm missing anything with any of my commits.

jakehobbs added a commit that referenced this pull request Jul 19, 2026
… env var

Replaces the NEXT_PUBLIC_GOOGLE_PLACES_API_KEY build-time env var with a
minimal public GET /places_api_key endpoint returning the existing
referrer-restricted config.GooglePlacesAPIKey (already embedded in HTML
served to anonymous visitors by the legacy /international page, so no new
exposure). The React form now fetches the key at runtime via a typed
ApiClient method and only loads the Maps script once the key arrives.
Authed pages can use the key from /user/me (PR #429); this public route
covers the unauthenticated form.

Also applies parity-review fixes:
- always show the generic submit-error toast instead of leaking raw
  backend error text to anonymous visitors (matches legacy Vue behavior)
- make the terms-agreement button non-untoggleable, like the legacy
  Buefy radio-button
- clean up Places Autocomplete listeners and orphaned .pac-container
  dropdowns on unmount (fixes duplicate dropdowns under React strict mode)
- read autocomplete callbacks through a ref so the widget isn't rebuilt
  when the parent re-renders
- document the intentional deviation where a no-results event resets the
  chosen-location flag (legacy kept stale selections valid)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jakehobbs jakehobbs mentioned this pull request Jul 19, 2026
6 tasks
jakehobbs added a commit that referenced this pull request Jul 19, 2026
The /user/me key field doesn't exist on main yet; cite the PR that adds it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts:
#	.githooks/pre-commit
#	go.work.sum
@jakehobbs
jakehobbs merged commit 12895ce into main Jul 19, 2026
8 checks passed
@jakehobbs
jakehobbs deleted the jake/events-in-advance branch July 19, 2026 16:40
jakehobbs added a commit that referenced this pull request Jul 19, 2026
… into zod

Address PR review feedback, after merging main (#429):

- Move the events form's PlacesAutocomplete to components/ and generalize
  it (configurable Places types/fields, placeholder, load-error message,
  optional no-result callback, address_components pass-through); events
  keeps identical behavior via defaults, only its import path changed.
  CityAutocomplete is now a thin wrapper that keeps parseCityFromPlace,
  dropping the @types/google.maps dev dependency.
- Fold the city/interest/terms submit checks into the zod schema
  (formSchema + formSubmitSchema pattern from user-form.tsx) with the
  same messages shown inline in the same order, replacing the imperative
  checks and toasts.
- Schema narrowing removes the `interest as ...` cast and the
  `as FormValues` defaultValues cast.
- Make the agreement button toggleable again; the schema enforces it.
- Inline the once-used ERROR_MESSAGE constant.
- Update the getPlacesApiKey comment now that #429 is merged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jakehobbs added a commit that referenced this pull request Sep 3, 2026
* feat(frontend-v2): port international form to React

Ports frontend/FormInternational.vue to a public Next.js page at
/v2/international, submitting to the existing unauthenticated POST
/international Go endpoint with the same payload shape. Replaces the
Vue app's vue-google-autocomplete component with a small React wrapper
around the vanilla Google Places Autocomplete JS API, replicating its
address-component parsing (locality/administrative_area_level_1/country).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: serve Places API key from public endpoint instead of build-time env var

Replaces the NEXT_PUBLIC_GOOGLE_PLACES_API_KEY build-time env var with a
minimal public GET /places_api_key endpoint returning the existing
referrer-restricted config.GooglePlacesAPIKey (already embedded in HTML
served to anonymous visitors by the legacy /international page, so no new
exposure). The React form now fetches the key at runtime via a typed
ApiClient method and only loads the Maps script once the key arrives.
Authed pages can use the key from /user/me (PR #429); this public route
covers the unauthenticated form.

Also applies parity-review fixes:
- always show the generic submit-error toast instead of leaking raw
  backend error text to anonymous visitors (matches legacy Vue behavior)
- make the terms-agreement button non-untoggleable, like the legacy
  Buefy radio-button
- clean up Places Autocomplete listeners and orphaned .pac-container
  dropdowns on unmount (fixes duplicate dropdowns under React strict mode)
- read autocomplete callbacks through a ref so the widget isn't rebuilt
  when the parent re-renders
- document the intentional deviation where a no-results event resets the
  chosen-location flag (legacy kept stale selections valid)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(frontend-v2): use https for dxe.io links on international form

The apply form already links https://dxe.io/conduct, so HTTPS is safe
for the domain; the http:// URLs were carried over verbatim from the
legacy Vue page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(server): restrict /places_api_key route to GET

Matches the /api/csrf-token registration pattern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: apply repo comment-style directive to international form PR

Extract parseCityFromPlace/isMapsPlacesLoaded helpers in the city
autocomplete so its comment blocks are no longer needed, and compress
the remaining multi-line comments to one or two lines across the form,
api.ts additions, and the Go Places-key handler.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(frontend-v2): clarify getPlacesApiKey comment references PR #429

The /user/me key field doesn't exist on main yet; cite the PR that adds it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(frontend-v2): share Places autocomplete and move form checks into zod

Address PR review feedback, after merging main (#429):

- Move the events form's PlacesAutocomplete to components/ and generalize
  it (configurable Places types/fields, placeholder, load-error message,
  optional no-result callback, address_components pass-through); events
  keeps identical behavior via defaults, only its import path changed.
  CityAutocomplete is now a thin wrapper that keeps parseCityFromPlace,
  dropping the @types/google.maps dev dependency.
- Fold the city/interest/terms submit checks into the zod schema
  (formSchema + formSubmitSchema pattern from user-form.tsx) with the
  same messages shown inline in the same order, replacing the imperative
  checks and toasts.
- Schema narrowing removes the `interest as ...` cast and the
  `as FormValues` defaultValues cast.
- Make the agreement button toggleable again; the schema enforces it.
- Inline the once-used ERROR_MESSAGE constant.
- Update the getPlacesApiKey comment now that #429 is merged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(intl-form): remove obsolete comments

* feat(intl-form): show error inline and incl backend error msg

* fix(intl-form): make form unavailable when Places can't load

City is required and only settable from the Places dropdown, so without
Google Places there is no way to submit. Show an unavailable notice
instead of letting someone fill the whole form out first.

Adds an optional onUnavailable callback to the shared PlacesAutocomplete
so a form that depends on the field can surface the failure itself
(replacing the component's load-error toast).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Alexander Taylor <ajxtaylor@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants