feat(events): support scheduling public events in advance - #429
Conversation
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>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis 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. ChangesScheduled Public Events Feature
🎯 4 (Complex) | ⏱️ ~60 minutes Possibly Related PRs
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
locationstable and additionaleventscolumns;getEventsLEFT JOINs locations;CleanEventDatavalidates time/timezone/description and upserts a location viaGetOrCreateLocation. - Frontend: new
homehub,events/[id]/confirmationpage,PlacesAutocomplete,TimeField, andtimezonehelpers;EventFormextended with a collapsible details panel and scheduled-event fields. - Plumbing:
googlePlacesApiKeyflows 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (22)
Makefilecli/cmd/db.gofrontend-v2/src/app/(authed)/events/[id]/confirmation/event-confirmation.tsxfrontend-v2/src/app/(authed)/events/[id]/confirmation/page.tsxfrontend-v2/src/app/(authed)/events/[id]/page.tsxfrontend-v2/src/app/(authed)/events/event-form.tsxfrontend-v2/src/app/(authed)/events/new/page.tsxfrontend-v2/src/app/(authed)/events/places-autocomplete.tsxfrontend-v2/src/app/(authed)/home/home-hub.tsxfrontend-v2/src/app/(authed)/home/page.tsxfrontend-v2/src/app/(authed)/layout.tsxfrontend-v2/src/app/authed-page-provider.tsxfrontend-v2/src/app/session.tsfrontend-v2/src/components/nav.tsxfrontend-v2/src/components/ui/time-field.tsxfrontend-v2/src/lib/api.tsfrontend-v2/src/lib/timezone.tspkg/shared/db-migrations/20260530120000_advance_events_and_locations.down.sqlpkg/shared/db-migrations/20260530120000_advance_events_and_locations.up.sqlpkg/shared/wipe.goserver/src/main.goserver/src/model/event.go
- 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>
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>
There was a problem hiding this comment.
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 winTighten wall-clock validation before formatting or badge math.
frontend-v2/src/lib/api.ts:293-309only narrowsstart_time/end_timeto nullable strings, so these helpers still need to reject impossible clocks. Right now Line 108 and Line 140 accept values like25:99or09:30junk, which can surface bogus labels informatEventTimeRange()and false positives inisEventHappeningNow().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
📒 Files selected for processing (8)
frontend-v2/src/app/(authed)/events/[id]/confirmation/event-confirmation.tsxfrontend-v2/src/app/(authed)/events/[id]/page.tsxfrontend-v2/src/app/(authed)/events/event-form.tsxfrontend-v2/src/app/(authed)/events/places-autocomplete.tsxfrontend-v2/src/app/(authed)/home/home-hub.tsxfrontend-v2/src/lib/api.tsfrontend-v2/src/lib/timezone.tsserver/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
left a comment
There was a problem hiding this comment.
exciting times. thanks Jake!
| set -a && . server/debug.env && set +a && \ | ||
| set -a && \ | ||
| . server/debug.env && \ | ||
| { [ -f .env ] && . ./.env || true; } && \ |
There was a problem hiding this comment.
should launch.json be updated too? 🙏:)
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
not the biggest fan of dev containers. haha. lemme see what i can do.
There was a problem hiding this comment.
i think i figured it out
There was a problem hiding this comment.
✅ 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 |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 🤖
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>
There was a problem hiding this comment.
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
📒 Files selected for processing (13)
README.mdfrontend-v2/src/app/(authed)/events/[id]/confirmation/event-confirmation.tsxfrontend-v2/src/app/(authed)/events/event-form-schema.tsfrontend-v2/src/app/(authed)/events/event-form-sections.tsxfrontend-v2/src/app/(authed)/events/event-form.tsxfrontend-v2/src/app/(authed)/events/places-autocomplete.tsxfrontend-v2/src/app/(authed)/events/useEventForm.tsfrontend-v2/src/app/(authed)/home/home-hub.tsxfrontend-v2/src/app/authed-page-provider.tsxfrontend-v2/src/lib/time.tsserver/src/config/config.goserver/src/main.goserver/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
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>
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>
…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>
# Conflicts: # frontend-v2/package.json # frontend-v2/pnpm-lock.yaml
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>
There was a problem hiding this comment.
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.
| set -a && . server/debug.env && set +a && \ | ||
| set -a && \ | ||
| . server/debug.env && \ | ||
| { [ -f .env ] && . ./.env || true; } && \ |
There was a problem hiding this comment.
✅ 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.
| set -a && . server/debug.env && set +a && \ | ||
| set -a && \ | ||
| . server/debug.env && \ | ||
| { [ -f .env ] && . ./.env || true; } && \ |
There was a problem hiding this comment.
✅ 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 |
| </p> | ||
| )} | ||
| </div> | ||
| {detailsExpanded ? ( |
| </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. |
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>
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. |
… 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>
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
… 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>
* 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>
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, andwipe/seed/Makefile housekeeping.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Documentation