feat(dashboard): a registrations section for an event - #357
Conversation
Greptile SummaryThe PR adds an event registrations management section with registration and guest-booking controls plus ticket-tier creation.
Confidence Score: 3/5The PR does not appear safe to merge until registration closing uses the same timezone fallback semantics as backend enforcement. Timezone-less events still persist the organizer browser’s wall clock while the backend evaluates the cutoff in the server timezone, and unresolvable event zones still silently persist the browser wall clock, so registration can remain open or close earlier than requested. Files Needing Attention: dashboard/src/utils/timeZones.ts, dashboard/src/components/dashboard/events/EventRegistrationDialog.vue
|
| Filename | Overview |
|---|---|
| dashboard/src/utils/timeZones.ts | Adds event-zone wall-clock formatting, but its null and error fallbacks still produce cutoffs in the browser timezone rather than the timezone used by backend registration enforcement. |
| dashboard/src/components/dashboard/events/EventRegistrationDialog.vue | Adds registration open/close persistence and directly stores the timezone utility’s naive datetime result. |
| dashboard/src/pages/manage/events/EventRegistrations.vue | Adds the registrations workspace, status controls, and ticket-tier list while forwarding the event’s nullable timezone to the cutoff dialog. |
| buzz/api/events/services.py | Extends managed-event details with registration and guest-booking fields and derives the current registration status. |
| dashboard/src/components/dashboard/events/GuestRegistrationDialog.vue | Adds coordinated controls for guest booking and its optional verification method. |
| dashboard/src/components/dashboard/events/TicketTypeDialog.vue | Adds validated creation of event ticket tiers with price, currency, and optional capacity. |
| dashboard/src/data/tickets.ts | Adds scoped ticket-type and enabled-currency list resources. |
| dashboard/src/router.ts | Registers the static event registrations route before the dynamic management fallback. |
Reviews (11): Last reviewed commit: "test(e2e): drive the registrations secti..." | Re-trigger Greptile
dca02a6 to
bf146a1
Compare
4a99d7a to
9bf0e09
Compare
bf146a1 to
262bade
Compare
| try { | ||
| return format(zone ?? undefined) | ||
| } catch { | ||
| // An event carrying a zone this runtime cannot resolve falls back to the browser's. | ||
| return format() |
There was a problem hiding this comment.
Fallback shifts registration cutoff
When an event contains an invalid, obsolete, or browser-unsupported timezone, nowInZone falls back to the organizer's browser timezone. The backend then interprets that naive timestamp in the event timezone, causing registration to close earlier or later than requested.
Knowledge Base Used:
Prompt To Fix With AI
This is a comment left during a code review.
Path: dashboard/src/utils/timeZones.ts
Line: 52-56
Comment:
**Fallback shifts registration cutoff**
When an event contains an invalid, obsolete, or browser-unsupported timezone, `nowInZone` falls back to the organizer's browser timezone. The backend then interprets that naive timestamp in the event timezone, causing registration to close earlier or later than requested.
**Knowledge Base Used:**
- [Events domain](https://app.greptile.com/bwh-tech/-/custom-context/knowledge-base/bwhtech/buzz/-/docs/events-domain.md)
- [Public booking flow](https://app.greptile.com/bwh-tech/-/custom-context/knowledge-base/bwhtech/buzz/-/docs/booking-flow.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.262bade to
eef8f42
Compare
9bf0e09 to
d4e6c72
Compare
❌ UI Demo Check failedThis pull request changes the UI (11 file(s) under 🛠️ How to fix
Either one re-runs this check automatically. |
d4e6c72 to
abf8c38
Compare
eef8f42 to
1ab6f08
Compare
abf8c38 to
7463e7e
Compare
1ab6f08 to
5f7cb40
Compare
7463e7e to
14b0dd6
Compare
5f7cb40 to
d54e43e
Compare
14b0dd6 to
53eecac
Compare
d54e43e to
b15748b
Compare
53eecac to
3353f4d
Compare
Attendees becomes Guests in the event sidebar, and the destination is now a page rather than the placeholder: everyone holding a ticket, listed by name. Only submitted tickets count — a draft is a booking still being paid for, not somebody coming. The team permission hooks already scope Event Ticket, so the list is a plain query with no endpoint behind it. The page header both event pages share moves into EventPageHeader, with the section name as a prop and a slot for whatever the page puts on the right. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A count of registrations sits above the list, and a row now carries the person rather than just their name: avatar, name, email, the add-ons they picked as badges, and the ticket type they hold. Add-ons live in a child table, which a plain list query cannot reach, so the page moves onto get_event_guests. Both the ticket type and the add-ons are named rather than numbered — Event Ticket Type and Ticket Add-on are autonamed, so the link value on its own is a docname nobody recognises. Their titles are looked up in one query each, keyed by string, since an autonamed doctype returns integer names while every link to it travels as a string. Read access is the bar, not write: the list shows the team what the doctype already shows it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The border was packaging around a number that reads fine without it, and the box stretched the full column to hold two short lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The page opens on how the event is doing — the number registered, and whether registrations are still open — then the list itself, searchable by name or email. Filtering happens over the list already loaded, so a keystroke costs nothing. Whether registrations are closed is the booking flow's own rule, reused rather than restated: an explicit cutoff if the event sets one, otherwise the end of the event. A guest row now reads as a line rather than a stack, with the email beside the name — it is what tells two people of the same name apart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3353f4d to
23d20d0
Compare
b15748b to
21ebcd6
Compare
The page labelled its header from `get_event`, which is gated on write because it backs the edit form. A member with read access could load the guest list but not the name of the event it belonged to, so the header fell back to "Event". The guest payload already fetches the event to work out whether registrations have closed, so the title comes off a document that was loaded anyway — and the page drops a request rather than gaining one.
The manage page needs three things the detail payload did not carry: when registrations close, whether they are closed right now, and how guests are let in. `registrations_closed` is derived rather than stored — `Buzz Event` has no accept-registrations flag, so an event with no cutoff still closes once it has ended. `are_registrations_closed` already answers that for the booking flow and reads only the fields DETAIL_FIELDS carries, so it takes the row as-is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Closing registration means "closed as of right now", and Frappe stores Datetime without an offset, so the value written has to be the event's local time rather than the browser's. The Swedish locale already formats in Frappe's shape, which saves assembling it part by part. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Everything that decides whether people can sign up, in one place: two status tiles above the ticket tiers the event sells. Registration has no boolean behind it, so its dialog writes the cutoff — off is now in the event's timezone, on clears it. An event that has already ended still reads Closed after reopening, which is the same answer the public page gives. Guest registration carries its verification method in the same dialog, since neither field means much without the other. Ticket tiers are read and inserted straight through `useList`: the tiers inherit their event's team scope from the permission hooks, so no endpoint of their own. Only creation for now — edit, delete and publish are still Desk work, and the sold counters are Python properties a list call cannot carry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Each toggle test reads the shared event's current state and puts it back, rather than assuming how a previous run left it. Two things the tree makes awkward: an open dialog hides the tile behind it, so the state has to be read before clicking, and the select is a custom listbox rather than a native one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
23d20d0 to
e13531d
Compare
Stacked on
version-2-dashboard-events-guests.Adds a Registrations section to the event workspace: two status tiles that
gate signup, and the ticket tiers the event sells.
Registration has no boolean behind it.
Buzz Eventonly hasregistrations_close_at, andare_registrations_closed()already derivesopen/closed from it for the booking flow. So the toggle writes the cutoff:
off is now in the event's own timezone, on clears it. Consequence worth
knowing: an event that has already ended still reads Closed after
reopening — same answer the public page gives, so it is honest, not a bug.
If we want a real flag later, that is a doctype change plus booking-flow
work, not this PR.
Guest registration carries
guest_verification_methodin the samedialog, since neither field means much without the other.
Ticket tiers read and insert straight through
useList— they inherittheir event's team scope from the permission hooks, so no endpoint. Creation
only. Edit, delete and publish stay in Desk for now, and
tickets_sold/remaining_ticketsare Python properties a list call cannot fetch, so rowsshow title and price and nothing else.
No new endpoint anywhere.
get_eventgrew four fields; writes go throughthe
frappe.client.set_valueresource the details page already uses.Also here:
nowInZoneinutils/timeZones.ts(Frappe's naive datetime, inthe event's zone) and the first
Switchusage in this dashboard.Checked: 40
buzz.api.eventstests, 18 timeZones unit tests, typecheck,pre-commit, and the chromium e2e project. One pre-existing failure there,
auth.spec.ts › should login via UI, which fails on a stashed tree too.🤖 Generated with Claude Code