feat: add registration closed functionality to events - #70
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a non-null Changes
Sequence Diagram(s)sequenceDiagram
participant Client as UI (Listing/Detail/Cart)
participant Server as EventService
participant DB as Database
Client->>Server: fetch events / event by id (include registration_closed)
Server->>DB: SELECT ... , registration_closed FROM events
DB-->>Server: rows (with registration_closed)
Server-->>Client: event payloads include registration_closed
Client->>Client: render CTAs (if registration_closed -> disable/suppress)
Client->>Server: validateCart (cart items list)
Server->>DB: fetch current events by ids (include registration_closed, availability)
DB-->>Server: current event states
Server-->>Client: validation result (items retained, marked registrationClosed/maxAvailable)
Client->>Client: persistCart (strip runtime registrationClosed before localStorage)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 a new events.registration_closed flag and threads it through admin editing, event fetching, cart validation, and checkout/payment enforcement to prevent new purchases when registration is closed.
Changes:
- Added
registration_closedcolumn via DB migration and surfaced it in event/service queries. - Enforced “registration closed” during checkout validation and payment order creation.
- Updated admin edit flow + UI and updated cart UI/behavior to mark closed items and block checkout.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/views/events/EventsPageView.js | Shows “Registration Closed” state and disables some CTAs based on event.registration_closed. |
| src/views/events/EventDetailView.js | Shows “Registration Closed” state and disables some CTAs based on event.registration_closed. |
| src/views/cart/CartView.js | Visually marks closed items and blocks checkout when closed items exist. |
| src/store/slices/cartSlice.js | Strips/persists registrationClosed flag and sets it during cart validation from DB. |
| src/services/payment-service.js | Blocks order creation when registration_closed is true. |
| src/services/event-service.js | Includes registration_closed in event payloads returned to the frontend. |
| src/services/database/migrations/009_add_registration_closed.sql | Adds registration_closed column to events. |
| src/services/checkout-service.js | Treats registration_closed as a validation error for checkout items. |
| src/services/admin-service.js | Allows updating registration_closed via registrationClosed input. |
| src/pages/api/admin/events/[id].js | Accepts registrationClosed in PUT and passes to admin service. |
| src/pages/admin/events/[id]/edit.js | Adds a “Close Registration” switch in the admin event edit UI. |
Comments suppressed due to low confidence (1)
src/store/slices/cartSlice.js:181
validateCartno longer removes sold-out events (dbAvailable <= 0) and also stops capping quantity to availability. This breaks the invariant described inaddToCart/updateQuantity(cap atmaxAvailable) and can allow checkout to proceed with impossible quantities until the backend rejects it. Consider either (a) returningnullfor sold-out items (consistent withvalidationRemovedCountmessaging), or (b) keeping them with an explicitsoldOutflag +maxAvailable: 0, and always cappingquantitytodbAvailable.
const dbAvailable = Math.max(0, (dbEvent.seats || 0) - (dbEvent.registered || 0))
return {
...item,
title: dbEvent.title,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const spotsLeft = event.seats > 0 ? event.seats - (event.registered || 0) : null | ||
| const almostFull = spotsLeft !== null && spotsLeft <= Math.ceil(event.seats * 0.2) | ||
| const isRegClosed = !!event.registration_closed | ||
| const isDisabled = isRegClosed |
There was a problem hiding this comment.
isDisabled only reflects registration_closed, so sold-out events (seats - registered <= 0) will still show enabled “Add to Cart” / “Buy Ticket” actions. Because addToCart is capped by maxAvailable, clicks can become no-ops (or route to checkout and fail server-side). Consider folding availability into isDisabled (and restoring the Sold Out label) so the UI matches backend behavior.
| const isDisabled = isRegClosed | |
| const seatLimit = Number(event.seats) | |
| const registeredCount = Number(event.registered ?? 0) | |
| const hasSeatLimit = Number.isFinite(seatLimit) | |
| const isSoldOut = hasSeatLimit && seatLimit - registeredCount <= 0 | |
| const isDisabled = isRegClosed || isSoldOut |
| const currentImage = allImages[activeImageIndex] || null | ||
| const hasGallery = allImages.length > 1 | ||
| const isOver = event.start_time ? new Date(event.start_time).getTime() <= Date.now() : false | ||
| const isRegClosed = !!event.registration_closed | ||
| const isDisabled = isRegClosed |
There was a problem hiding this comment.
isDisabled is set only from registration_closed, so the purchase CTAs can remain enabled when spotsLeft <= 0. This leads to “Buy Now” routing users into checkout where validation fails, and “Add to Cart” can silently do nothing due to maxAvailable: 0. Consider disabling CTAs (and showing a Sold Out state) when spotsLeft <= 0 as well.
| const dbAvailable = Math.max(0, (dbEvent.seats || 0) - (dbEvent.registered || 0)) | ||
| // Registration closed — mark it but keep in cart so user sees reason | ||
| if (dbEvent.registration_closed) { | ||
| return { ...item, title: dbEvent.title, registrationClosed: true } |
There was a problem hiding this comment.
For registration-closed events, validateCart keeps the item but only updates title. That leaves other DB-sourced fields (ticketPrice/startTime/venue/image/maxAvailable) potentially stale in the cart UI. Consider refreshing the same fields you refresh in the non-closed branch, and setting maxAvailable to 0 (or similar) to clearly mark it as unavailable.
| return { ...item, title: dbEvent.title, registrationClosed: true } | |
| return { | |
| ...item, | |
| title: dbEvent.title, | |
| ticketPrice: parseFloat(dbEvent.ticket_price) || 0, | |
| startTime: dbEvent.start_time, | |
| venue: dbEvent.venue || item.venue, | |
| image: dbEvent.images?.[0] || item.image, | |
| maxAvailable: 0, | |
| registrationClosed: true, | |
| quantity: item.quantity | |
| } |
| maxAvailable: dbAvailable, | ||
| // Cap stored quantity to actual availability — no fallback to 1 | ||
| quantity: Math.min(item.quantity, dbAvailable) | ||
| maxAvailable: dbAvailable || null, |
There was a problem hiding this comment.
maxAvailable: dbAvailable || null turns a real availability of 0 into null (treated as “unknown/unlimited” elsewhere, e.g. updateQuantity uses Infinity when maxAvailable is null). Keeping maxAvailable as a number (including 0) preserves correct quantity caps and UI disablement logic.
| maxAvailable: dbAvailable || null, | |
| maxAvailable: dbAvailable, |
| <Button | ||
| variant='contained' | ||
| size='large' | ||
| fullWidth | ||
| disabled={validating} | ||
| disabled={validating || hasClosedItems} | ||
| endIcon={<Icon icon='tabler:arrow-right' />} |
There was a problem hiding this comment.
Checkout is disabled only for hasClosedItems, but with the current validateCart behavior sold-out/unavailable items can remain in the cart and still allow “Proceed to Checkout”, leading to a failure later in checkout validation. Consider also preventing checkout when any cart item is unavailable (e.g. maxAvailable === 0 / soldOut flag) and surfacing a clear message.
| ALTER TABLE events | ||
| ADD COLUMN IF NOT EXISTS registration_closed BOOLEAN NOT NULL DEFAULT FALSE; | ||
|
|
||
| -- Index for quick filtering | ||
| CREATE INDEX IF NOT EXISTS idx_events_registration_closed ON events(registration_closed); |
There was a problem hiding this comment.
This migration adds events.registration_closed, but the repo keeps full schema snapshots in both schema.sql (root) and src/services/database/schema.sql. Those schema files currently don’t include registration_closed, so fresh DB bootstraps from schema.sql will diverge from migrated DBs. Please update both schema files to include the new column.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
src/store/slices/cartSlice.js (2)
186-188: Potential UX issue:maxAvailable: dbAvailable || nullloses sold-out cap.When
dbAvailableis0(sold out),0 || nullevaluates tonull, removing the quantity cap. Users can increase quantity for sold-out events and only discover the issue at checkout.Consider preserving
0to maintain the cap:♻️ Preserve zero availability for proper UI capping
- maxAvailable: dbAvailable || null, + maxAvailable: dbAvailable >= 0 ? dbAvailable : null,Or more explicitly:
- maxAvailable: dbAvailable || null, + maxAvailable: typeof dbAvailable === 'number' ? dbAvailable : null,This ensures
QuantityControlproperly disables the increment button when events are sold out.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/store/slices/cartSlice.js` around lines 186 - 188, The assignment "maxAvailable: dbAvailable || null" discards a legitimate 0 value (sold out) and should preserve zero so UI can disable increments; in the cart slice where maxAvailable is set (look for the mapping that assigns maxAvailable and the dbAvailable variable, e.g., in cartSlice reducer/selector), replace the falsy-or pattern with a nullish check so 0 is kept (for example use the nullish coalescing approach or an explicit undefined/null check) so maxAvailable becomes 0 when dbAvailable is 0 and only falls back to null when dbAvailable is actually null/undefined.
172-175: Consider updating all item fields even for closed registrations.When
registration_closedis true, onlytitleandregistrationClosedare updated. Other fields liketicketPrice,venue, andstartTimeretain potentially stale values. While checkout is blocked, users may see outdated prices in the cart summary which could be confusing.♻️ Suggested improvement to keep all fields fresh
// Registration closed — mark it but keep in cart so user sees reason if (dbEvent.registration_closed) { - return { ...item, title: dbEvent.title, registrationClosed: true } + const dbAvailable = Math.max(0, (dbEvent.seats || 0) - (dbEvent.registered || 0)) + return { + ...item, + title: dbEvent.title, + ticketPrice: parseFloat(dbEvent.ticket_price) || 0, + startTime: dbEvent.start_time, + venue: dbEvent.venue || item.venue, + image: dbEvent.images?.[0] || item.image, + maxAvailable: dbAvailable || null, + registrationClosed: true + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/store/slices/cartSlice.js` around lines 172 - 175, The branch in cartSlice that handles dbEvent.registration_closed only updates title and registrationClosed, leaving ticketPrice, venue, startTime, etc. stale; change the return to copy the current item’s immutable fields (e.g., id, quantity) but overwrite all event-related fields from dbEvent and still set registrationClosed: true (e.g., merge/pick fields like ticketPrice, venue, startTime, capacity, title from dbEvent into the item while preserving user-specific keys), ensuring the cart summary shows fresh event data even when checkout is blocked.src/views/events/EventsPageView.js (1)
106-107: Consider removing the redundantisDisabledalias or documenting its purpose.Currently
isDisabled = isRegClosedis a simple alias. If this is intentional scaffolding for future conditions (e.g.,isRegClosed || isSoldOut), consider adding a brief comment. Otherwise, usingisRegCloseddirectly would be clearer.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/events/EventsPageView.js` around lines 106 - 107, The alias isDisabled duplicates isRegClosed; either remove the redundant constant and replace usages of isDisabled with isRegClosed, or keep isDisabled as intended scaffolding by adding a short comment above the declaration (e.g., "scaffold for future combined disabled logic like isRegClosed || isSoldOut") and update the code to make clear it's deliberate; locate the constants isRegClosed and isDisabled in EventsPageView.js and perform the change plus adjust any downstream references (e.g., button disabled props or conditional rendering) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/services/checkout-service.js`:
- Around line 49-50: The new guard that checks event.registration_closed must
also be enforced inside createBookings(): when you reload the event under lock
in createBookings() (the block that re-queries the event and acquires the lock
around lines where the event is reloaded under lock), include
registration_closed in the SELECT or explicitly reject when
event.registration_closed is true so a direct caller or a post-validation race
cannot create bookings for closed events; update the query or add an if-check
immediately after reload in createBookings() to throw/return the same error used
by the preflight path to keep behavior consistent.
In `@src/services/database/migrations/009_add_registration_closed.sql`:
- Around line 4-8: The base schema file is out of sync with the migration: add
the new column and index to schema.sql so fresh DBs match migrated DBs;
specifically, update the CREATE TABLE events definition to include
"registration_closed BOOLEAN NOT NULL DEFAULT FALSE" and add a matching CREATE
INDEX IF NOT EXISTS idx_events_registration_closed ON
events(registration_closed) (or place the index after the table DDL) to mirror
the migration that adds registration_closed and its index.
In `@src/views/events/EventDetailView.js`:
- Around line 236-237: The change replaced the sold-out CTA state by making
isDisabled mirror isRegClosed; restore explicit handling by preserving ctaState
and using it in the CTA button branches instead of relying on isDisabled alone.
Update the render logic that builds the CTA (the branches that reference
isDisabled and event.registration_link) to check ctaState (e.g., "sold_out",
"closed", "open") and only render clickable registration_link actions when
ctaState indicates the event is open; keep isRegClosed for the closed-flag but
do not remove or overwrite the sold-out state so sold-out buttons render
non-clickable as intended.
---
Nitpick comments:
In `@src/store/slices/cartSlice.js`:
- Around line 186-188: The assignment "maxAvailable: dbAvailable || null"
discards a legitimate 0 value (sold out) and should preserve zero so UI can
disable increments; in the cart slice where maxAvailable is set (look for the
mapping that assigns maxAvailable and the dbAvailable variable, e.g., in
cartSlice reducer/selector), replace the falsy-or pattern with a nullish check
so 0 is kept (for example use the nullish coalescing approach or an explicit
undefined/null check) so maxAvailable becomes 0 when dbAvailable is 0 and only
falls back to null when dbAvailable is actually null/undefined.
- Around line 172-175: The branch in cartSlice that handles
dbEvent.registration_closed only updates title and registrationClosed, leaving
ticketPrice, venue, startTime, etc. stale; change the return to copy the current
item’s immutable fields (e.g., id, quantity) but overwrite all event-related
fields from dbEvent and still set registrationClosed: true (e.g., merge/pick
fields like ticketPrice, venue, startTime, capacity, title from dbEvent into the
item while preserving user-specific keys), ensuring the cart summary shows fresh
event data even when checkout is blocked.
In `@src/views/events/EventsPageView.js`:
- Around line 106-107: The alias isDisabled duplicates isRegClosed; either
remove the redundant constant and replace usages of isDisabled with isRegClosed,
or keep isDisabled as intended scaffolding by adding a short comment above the
declaration (e.g., "scaffold for future combined disabled logic like isRegClosed
|| isSoldOut") and update the code to make clear it's deliberate; locate the
constants isRegClosed and isDisabled in EventsPageView.js and perform the change
plus adjust any downstream references (e.g., button disabled props or
conditional rendering) accordingly.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0c359b03-73fb-4928-a43a-3042c9653a76
📒 Files selected for processing (11)
src/pages/admin/events/[id]/edit.jssrc/pages/api/admin/events/[id].jssrc/services/admin-service.jssrc/services/checkout-service.jssrc/services/database/migrations/009_add_registration_closed.sqlsrc/services/event-service.jssrc/services/payment-service.jssrc/store/slices/cartSlice.jssrc/views/cart/CartView.jssrc/views/events/EventDetailView.jssrc/views/events/EventsPageView.js
| e.registration_closed, | ||
| d.name AS "departmentName" |
There was a problem hiding this comment.
Enforce the same guard inside createBookings().
This new check only protects the preflight path. Later in the same file, createBookings() still reloads the event under lock but does not select or reject registration_closed (Lines 227-245), so a direct caller—or a close-registration race after validation—can still create a confirmed booking for a closed event.
Suggested follow-up in createBookings()
const event = await t.oneOrNone(`
SELECT
id,
name AS title,
ticket_price,
max_tickets AS seats,
registered,
status,
- visibility
+ visibility,
+ registration_closed
FROM events
WHERE id = $1
FOR UPDATE
`, [eventId])
if (!event) throw new Error(`Event ${eventId} not found`)
if (event.status !== 'published') throw new Error(`Event "${event.title}" is not available`)
if (event.visibility !== 'public') throw new Error(`Event "${event.title}" is not public`)
+ if (event.registration_closed) throw new Error(`Registration is closed for "${event.title}"`)Also applies to: 71-74
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/services/checkout-service.js` around lines 49 - 50, The new guard that
checks event.registration_closed must also be enforced inside createBookings():
when you reload the event under lock in createBookings() (the block that
re-queries the event and acquires the lock around lines where the event is
reloaded under lock), include registration_closed in the SELECT or explicitly
reject when event.registration_closed is true so a direct caller or a
post-validation race cannot create bookings for closed events; update the query
or add an if-check immediately after reload in createBookings() to throw/return
the same error used by the preflight path to keep behavior consistent.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/store/slices/cartSlice.js (1)
180-191:⚠️ Potential issue | 🟠 MajorReconcile quantity when availability shrinks.
Keeping
item.quantityhere leaves the cart subtotal overstated and keeps checkout open for partially oversubscribed items.src/services/checkout-service.js:68-92caps the quantity later, so the user only sees the correction after leaving the cart. If you need the original request for messaging, store it separately instead of usingquantity.Suggested fix
const dbAvailable = Math.max(0, (dbEvent.seats || 0) - (dbEvent.registered || 0)) + const reconciledQuantity = + dbAvailable > 0 ? Math.min(item.quantity, dbAvailable) : item.quantity return { ...item, title: dbEvent.title, ticketPrice: parseFloat(dbEvent.ticket_price) || 0, startTime: dbEvent.start_time, venue: dbEvent.venue || item.venue, image: dbEvent.images?.[0] || item.image, maxAvailable: dbAvailable || null, registrationClosed: false, - quantity: item.quantity + quantity: reconciledQuantity }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/store/slices/cartSlice.js` around lines 180 - 191, The cart item is left with the original item.quantity even when dbAvailable has shrunk; update the mapping in cartSlice (the code that returns the merged item object) to set quantity to Math.min(item.quantity, dbAvailable || 0) and store the original request as a separate field (e.g., requestedQuantity or originalQuantity) so messaging can still reference the user's requested amount; also set registrationClosed to true when dbAvailable === 0 and ensure maxAvailable uses null only when there is no numeric availability, preserving numeric zero.src/views/cart/CartView.js (1)
584-613:⚠️ Potential issue | 🟠 MajorRe-check availability after
validateCart()before routing to checkout.The disabled state is computed before the click. Because
hydrateCart()strips runtime availability flags, the firstvalidateCart()in this handler can mark items closed or sold out after the click has already started, and this code still forwards every current cart item to checkout.Suggested fix
const currentItems = store.getState().cart.items + const hasUnavailableItems = currentItems.some( + item => item.registrationClosed || item.maxAvailable === 0 || item.maxAvailable === null + ) + if (hasUnavailableItems) { + toast.error('Remove unavailable events from your cart to proceed.') + return + } dispatch(setCheckoutItems({ items: currentItems.map(i => ({ eventId: i.eventId, quantity: i.quantity })), source: 'cart' }))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/cart/CartView.js` around lines 584 - 613, The click handler forwards the pre-hydrated cart items to checkout even if validateCart() removed closed/sold-out events; instead, after calling validateCart() and confirming result.meta.requestStatus === 'fulfilled', use the returned validEvents (result.payload) to build the checkout items (map each validEvent to { eventId, quantity }) when calling setCheckoutItems, so only validated items are sent to checkout; keep the existing empty-check and routing logic (including setExistingUser and router.push) but replace store.getState().cart.items with the validated validEvents list to prevent closed/sold-out items from reaching checkout (refer to validateCart, setCheckoutItems, result.payload / validEvents, and setExistingUser).src/views/events/EventDetailView.js (1)
1022-1103:⚠️ Potential issue | 🟠 MajorThe second mobile CTA block lost the external-registration path.
The hero CTA still branches on
event.registration_link, but the Rounds-tab CTA now sends every open event through internal checkout. For externally registered events, mobile users seeBuy Now/Add to Cartinstead ofRegister Now.Suggested fix
- ) : ( + ) : event.registration_link ? ( + <Button + variant='contained' + disableElevation + fullWidth + href={event.registration_link} + target='_blank' + rel='noopener noreferrer' + startIcon={<Icon icon='tabler:external-link' fontSize={18} />} + > + Register Now + </Button> + ) : ( <> <Button variant='contained'🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/events/EventDetailView.js` around lines 1022 - 1103, The mobile CTA always renders internal checkout buttons when ctaState === 'open', causing externally-registered events to show "Buy Now"/"Add to Cart" instead of the external flow; update the open-CTA branch in EventDetailView (the JSX that currently renders the two Buttons with onClick handlers that call setCheckoutItems and addToCart) to first check event.registration_link and, if present, render the external "Register Now" button that navigates to event.registration_link (preserving login/returnUrl behavior if needed) instead of the internal Buy Now/Add to Cart buttons; keep the existing internal checkout logic (dispatch(setCheckoutItems(...)), dispatch(setExistingUser(...)), router.push('/checkout')) only for events without event.registration_link.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/views/events/EventDetailView.js`:
- Around line 236-240: The ctaState logic in EventDetailView.js relies on
event.registration_closed but getEventById in src/services/event-service.js
doesn’t fetch that field; update getEventById to include e.registration_closed
in its query/selection and ensure the returned event object maps
registration_closed (boolean) so EventDetailView's ctaState and isDisabled logic
see the correct value; verify any GraphQL/SQL projection or DTO mapping in
getEventById includes registration_closed.
---
Outside diff comments:
In `@src/store/slices/cartSlice.js`:
- Around line 180-191: The cart item is left with the original item.quantity
even when dbAvailable has shrunk; update the mapping in cartSlice (the code that
returns the merged item object) to set quantity to Math.min(item.quantity,
dbAvailable || 0) and store the original request as a separate field (e.g.,
requestedQuantity or originalQuantity) so messaging can still reference the
user's requested amount; also set registrationClosed to true when dbAvailable
=== 0 and ensure maxAvailable uses null only when there is no numeric
availability, preserving numeric zero.
In `@src/views/cart/CartView.js`:
- Around line 584-613: The click handler forwards the pre-hydrated cart items to
checkout even if validateCart() removed closed/sold-out events; instead, after
calling validateCart() and confirming result.meta.requestStatus === 'fulfilled',
use the returned validEvents (result.payload) to build the checkout items (map
each validEvent to { eventId, quantity }) when calling setCheckoutItems, so only
validated items are sent to checkout; keep the existing empty-check and routing
logic (including setExistingUser and router.push) but replace
store.getState().cart.items with the validated validEvents list to prevent
closed/sold-out items from reaching checkout (refer to validateCart,
setCheckoutItems, result.payload / validEvents, and setExistingUser).
In `@src/views/events/EventDetailView.js`:
- Around line 1022-1103: The mobile CTA always renders internal checkout buttons
when ctaState === 'open', causing externally-registered events to show "Buy
Now"/"Add to Cart" instead of the external flow; update the open-CTA branch in
EventDetailView (the JSX that currently renders the two Buttons with onClick
handlers that call setCheckoutItems and addToCart) to first check
event.registration_link and, if present, render the external "Register Now"
button that navigates to event.registration_link (preserving login/returnUrl
behavior if needed) instead of the internal Buy Now/Add to Cart buttons; keep
the existing internal checkout logic (dispatch(setCheckoutItems(...)),
dispatch(setExistingUser(...)), router.push('/checkout')) only for events
without event.registration_link.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 89b73815-227a-4911-8b9b-5a03452e5a7a
📒 Files selected for processing (6)
schema.sqlsrc/services/database/schema.sqlsrc/store/slices/cartSlice.jssrc/views/cart/CartView.jssrc/views/events/EventDetailView.jssrc/views/events/EventsPageView.js
🚧 Files skipped from review as they are similar to previous changes (1)
- src/views/events/EventsPageView.js
| // ctaState: 'closed' | 'soldout' | 'open' | ||
| const ctaState = !!event.registration_closed ? 'closed' | ||
| : (spotsLeft <= 0) ? 'soldout' | ||
| : 'open' | ||
| const isDisabled = ctaState !== 'open' |
There was a problem hiding this comment.
ctaState depends on a field the detail query still doesn't fetch.
src/services/event-service.js:240-280 still omits e.registration_closed from getEventById(). On this page, !!event.registration_closed will therefore stay false, so closed events with seats remaining render the open CTA until a later server-side rejection.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/views/events/EventDetailView.js` around lines 236 - 240, The ctaState
logic in EventDetailView.js relies on event.registration_closed but getEventById
in src/services/event-service.js doesn’t fetch that field; update getEventById
to include e.registration_closed in its query/selection and ensure the returned
event object maps registration_closed (boolean) so EventDetailView's ctaState
and isDisabled logic see the correct value; verify any GraphQL/SQL projection or
DTO mapping in getEventById includes registration_closed.
Bhav-ikkk
left a comment
There was a problem hiding this comment.
You did unnessary code changes, when we can just create one one condition and handle things from UI, this changes will intoduce another handling for registration closed, and also change the hash code of restricted files
There was a problem hiding this comment.
Modification in this file is prohibited, just handle closing from UI
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)
src/views/cart/CartView.js (1)
565-589:⚠️ Potential issue | 🟠 MajorMissing registration-closed check after validation allows checkout with unavailable items.
The onClick handler re-validates the cart but doesn't check if any items were marked with
registrationClosed: truebefore proceeding. This allows users to bypass the intended gate: if a user clicks checkout whilehasUnavailableItemsis false (before initial validation completes or during a brief window), and validation then marks items as unavailable, the handler will still proceed to checkout.The
validEvents.length === 0check only catches the case where ALL events are removed, not where some have closed registration.🔧 Proposed fix to check registration-closed status after validation
const validEvents = result.payload ?? [] if (validEvents.length === 0) { // All events removed (sold out / unpublished) — EmptyCart will render return } // Set checkout items (eventId + quantity only) and navigate const currentItems = store.getState().cart.items + // Block checkout if any items have registration closed + if (currentItems.some(item => item.registrationClosed)) { + toast.error('Some events have closed registration. Please remove them to proceed.') + return + } dispatch(setCheckoutItems({ items: currentItems.map(i => ({ eventId: i.eventId, quantity: i.quantity })), source: 'cart' }))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/cart/CartView.js` around lines 565 - 589, After calling validateCart() in the onClick handler, inspect the validation result (result.payload returned by validateCart) for items marked registrationClosed (or otherwise flagged as unavailable); if any item has registrationClosed === true (or is missing from validEvents), show a toast error like "Some items are no longer available" and return instead of proceeding to setCheckoutItems or navigating; keep the existing dispatch(setCheckoutItems(...)), dispatch(setExistingUser(...)) and router.push(...) logic only after confirming no registrationClosed items are present. Ensure you reference validateCart, result.payload (validEvents), setCheckoutItems, setExistingUser, toast.error and router.push when implementing the check.
🧹 Nitpick comments (1)
src/views/cart/CartView.js (1)
345-345: Note timing ofhasUnavailableItemsevaluation.Per the cart slice logic,
registrationClosedis stripped during hydration (line 88 ofcartSlice.js) and only restored whenvalidateCart.fulfilledruns. This meanshasUnavailableItemswill befalseon initial render until validation completes. Thevalidatingflag in the disabled condition (line 563) mitigates the window, but see the related comment on the checkout handler for a remaining gap.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/cart/CartView.js` at line 345, The current computation of hasUnavailableItems (const hasUnavailableItems = items.some(item => item.registrationClosed)) happens before validateCart.fulfilled restores registrationClosed, so it can be false on initial render; update the logic in CartView to consider validation state: compute hasUnavailableItems to be true if validating is true and there is any indication of potential unavailability (e.g., an item has a pre-hydration flag or missing registration info) or, preferably, derive availability from the validated cart payload when validateCart.fulfilled exists; in practice, change the check to use the validated cart state (or guard with the validating flag) so that the checkout handler and disabled condition reliably reflect availability until validation completes (referencing hasUnavailableItems, items, registrationClosed, validateCart.fulfilled, and validating).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/views/cart/CartView.js`:
- Around line 565-589: After calling validateCart() in the onClick handler,
inspect the validation result (result.payload returned by validateCart) for
items marked registrationClosed (or otherwise flagged as unavailable); if any
item has registrationClosed === true (or is missing from validEvents), show a
toast error like "Some items are no longer available" and return instead of
proceeding to setCheckoutItems or navigating; keep the existing
dispatch(setCheckoutItems(...)), dispatch(setExistingUser(...)) and
router.push(...) logic only after confirming no registrationClosed items are
present. Ensure you reference validateCart, result.payload (validEvents),
setCheckoutItems, setExistingUser, toast.error and router.push when implementing
the check.
---
Nitpick comments:
In `@src/views/cart/CartView.js`:
- Line 345: The current computation of hasUnavailableItems (const
hasUnavailableItems = items.some(item => item.registrationClosed)) happens
before validateCart.fulfilled restores registrationClosed, so it can be false on
initial render; update the logic in CartView to consider validation state:
compute hasUnavailableItems to be true if validating is true and there is any
indication of potential unavailability (e.g., an item has a pre-hydration flag
or missing registration info) or, preferably, derive availability from the
validated cart payload when validateCart.fulfilled exists; in practice, change
the check to use the validated cart state (or guard with the validating flag) so
that the checkout handler and disabled condition reliably reflect availability
until validation completes (referencing hasUnavailableItems, items,
registrationClosed, validateCart.fulfilled, and validating).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 699089ce-bb20-42e9-9ac3-3873d5a7aa0f
📒 Files selected for processing (1)
src/views/cart/CartView.js
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 (2)
src/views/events/EventsPageView.js (1)
186-301:⚠️ Potential issue | 🟠 MajorKeep sold-out separate from registration-closed.
Lines 186-301 and 367-482 now gate every CTA only on
isRegClosed, even thoughspotsLeftis still used to compute availability. A full event withregistration_closed = falsewill show liveRegister Now/Buy Ticket/Add to Cartactions again, and the cart dispatch can be sentmaxAvailable: 0. Please restore a shared CTA state (open/soldOut/closed) and use it in both blocks.Possible fix
const spotsLeft = event.seats > 0 ? event.seats - (event.registered || 0) : null const isRegClosed = !!event.registration_closed + const isSoldOut = event.seats > 0 && spotsLeft <= 0 + const ctaState = isRegClosed ? 'closed' : isSoldOut ? 'soldOut' : 'open'Use
ctaStateinstead ofisRegClosedalone when branching the mobile and desktop CTA buttons.Also applies to: 367-482
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/events/EventsPageView.js` around lines 186 - 301, The CTA logic currently only checks isRegClosed which causes sold-out events to show live actions; compute a shared ctaState (e.g., 'open' | 'soldOut' | 'closed') based on registration_closed and spotsLeft/event.seats (spotsLeft <= 0 => 'soldOut', registration_closed => 'closed', else 'open') and replace all isRegClosed branches in both the mobile and desktop CTA blocks with checks against ctaState (disable or change labels when ctaState === 'soldOut' and disable when 'closed'), and ensure the addToCart dispatch (addToCart) and setCheckoutItems/buy flow (setCheckoutItems, setExistingUser, router.push) respect maxAvailable (use null for unlimited, 0 when sold out) so you never dispatch a purchase with maxAvailable: 0 for an actionable "Add to Cart" or "Buy Ticket".src/views/events/EventDetailView.js (1)
1018-1099:⚠️ Potential issue | 🟠 MajorThis mobile CTA block drops the external registration path.
Unlike Lines 519-540, Lines 1018-1099 never check
event.registration_link. For link-based events, opening the Rounds tab on mobile swapsRegister Nowfor internalBuy Now/Add to Cart, which sends the user down the wrong flow. Mirror the first mobile CTA block here, or reuse a shared CTA renderer so both sections stay in sync.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/events/EventDetailView.js` around lines 1018 - 1099, Mobile CTA block always renders internal Buy Now / Add to Cart and ignores event.registration_link; update the conditional in the block that renders the Buy Now/Add to Cart (the JSX around the Button handlers that call dispatch(setCheckoutItems), dispatch(addToCart), setExistingUser, getEventImage, spotsLeft, etc.) to first check if event.registration_link exists and, if so, render the external “Register Now” link button (opening registration_link) instead of the internal flow, mirroring the logic used in the earlier mobile CTA (the block at lines ~519-540), or refactor both spots into a shared renderer (e.g., EventCTA or renderCTA) and use it here so both CTA locations stay in sync.
♻️ Duplicate comments (1)
src/views/events/EventDetailView.js (1)
236-236:⚠️ Potential issue | 🟠 MajorKeep
sold outdistinct fromregistration closed.Line 236 now drives all CTAs from
registration_closed, butspotsLeftis still computed and forwarded into the purchase paths. Sold-out events withregistration_closed = falsewill render liveRegister Now/Buy Now/Add to Cartactions again, and the add-to-cart path now handsmaxAvailable: 0tosrc/store/slices/cartSlice.js:94-125, which silently drops the add. Please reintroduce a sharedctaStateand use it in all three CTA sections.Possible fix
const isOver = event.start_time ? new Date(event.start_time).getTime() <= Date.now() : false const isRegClosed = !!event.registration_closed + const isSoldOut = event.seats > 0 && spotsLeft <= 0 + const ctaState = isRegClosed ? 'closed' : isSoldOut ? 'soldOut' : 'open'Then branch the mobile and desktop CTAs on
ctaStateinstead ofisRegClosedalone.Also applies to: 501-604, 1018-1099, 1197-1327
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/events/EventDetailView.js` at line 236, The CTA logic is incorrectly driven only by isRegClosed (const isRegClosed = !!event.registration_closed) while spotsLeft is still passed into purchase flows; reintroduce a shared ctaState (e.g., 'closed' | 'sold_out' | 'open') computed from isRegClosed and spotsLeft (spotsLeft <= 0 => 'sold_out', isRegClosed => 'closed', else 'open') and replace uses of isRegClosed in all CTA render branches (desktop and mobile CTAs referenced around the blocks you noted) so that Register/Buy/Add-to-Cart rendering and handlers branch on ctaState; also ensure the add-to-cart / purchase payloads (where maxAvailable is passed into src/store/slices/cartSlice.js) derive maxAvailable from spotsLeft only when ctaState === 'open' (or set maxAvailable = 0 for 'sold_out'/'closed') so no attempt is made to add when sold out.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/views/events/EventDetailView.js`:
- Around line 1018-1099: Mobile CTA block always renders internal Buy Now / Add
to Cart and ignores event.registration_link; update the conditional in the block
that renders the Buy Now/Add to Cart (the JSX around the Button handlers that
call dispatch(setCheckoutItems), dispatch(addToCart), setExistingUser,
getEventImage, spotsLeft, etc.) to first check if event.registration_link exists
and, if so, render the external “Register Now” link button (opening
registration_link) instead of the internal flow, mirroring the logic used in the
earlier mobile CTA (the block at lines ~519-540), or refactor both spots into a
shared renderer (e.g., EventCTA or renderCTA) and use it here so both CTA
locations stay in sync.
In `@src/views/events/EventsPageView.js`:
- Around line 186-301: The CTA logic currently only checks isRegClosed which
causes sold-out events to show live actions; compute a shared ctaState (e.g.,
'open' | 'soldOut' | 'closed') based on registration_closed and
spotsLeft/event.seats (spotsLeft <= 0 => 'soldOut', registration_closed =>
'closed', else 'open') and replace all isRegClosed branches in both the mobile
and desktop CTA blocks with checks against ctaState (disable or change labels
when ctaState === 'soldOut' and disable when 'closed'), and ensure the addToCart
dispatch (addToCart) and setCheckoutItems/buy flow (setCheckoutItems,
setExistingUser, router.push) respect maxAvailable (use null for unlimited, 0
when sold out) so you never dispatch a purchase with maxAvailable: 0 for an
actionable "Add to Cart" or "Buy Ticket".
---
Duplicate comments:
In `@src/views/events/EventDetailView.js`:
- Line 236: The CTA logic is incorrectly driven only by isRegClosed (const
isRegClosed = !!event.registration_closed) while spotsLeft is still passed into
purchase flows; reintroduce a shared ctaState (e.g., 'closed' | 'sold_out' |
'open') computed from isRegClosed and spotsLeft (spotsLeft <= 0 => 'sold_out',
isRegClosed => 'closed', else 'open') and replace uses of isRegClosed in all CTA
render branches (desktop and mobile CTAs referenced around the blocks you noted)
so that Register/Buy/Add-to-Cart rendering and handlers branch on ctaState; also
ensure the add-to-cart / purchase payloads (where maxAvailable is passed into
src/store/slices/cartSlice.js) derive maxAvailable from spotsLeft only when
ctaState === 'open' (or set maxAvailable = 0 for 'sold_out'/'closed') so no
attempt is made to add when sold out.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8b801651-d11c-4248-bafc-796880a55971
📒 Files selected for processing (2)
src/views/events/EventDetailView.jssrc/views/events/EventsPageView.js
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/views/events/EventDetailView.js (1)
1020-1101:⚠️ Potential issue | 🟠 MajorThis mobile CTA branch dropped the
event.registration_linkpath.On the Rounds tab, open events with an external registration link now render Buy Now / Add to Cart instead of Register Now. That sends users into checkout for flows that should stay off-site. Line 503 already has the correct branch order; this block should mirror it.
🔁 Mirror the top mobile CTA branch here
- ) : ( + ) : event.registration_link ? ( + <Button + variant='contained' + disableElevation + fullWidth + href={event.registration_link} + target='_blank' + rel='noopener noreferrer' + startIcon={<Icon icon='tabler:external-link' fontSize={18} />} + sx={{ + bgcolor: color, + color: c.white, + borderRadius: '12px', + fontWeight: 700, + fontSize: '0.95rem', + textTransform: 'none', + py: 1.5, + '&:hover': { bgcolor: alpha(color, 0.88) } + }} + > + Register Now + </Button> + ) : (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/events/EventDetailView.js` around lines 1020 - 1101, The mobile CTA branch currently renders the internal checkout buttons (Buy Now / Add to Cart) even when event.registration_link exists; update the conditional around the Buttons inside the isRegClosed false branch to mirror the top mobile CTA logic by first checking for event.registration_link and rendering a single "Register Now" button that opens the external URL (instead of calling dispatch(setCheckoutItems) / router.push('/checkout') or dispatch(addToCart)); otherwise keep the existing Buy Now and Add to Cart flows (references: isRegClosed, event.registration_link, setCheckoutItems, setExistingUser, addToCart, getEventImage, router.push).
♻️ Duplicate comments (2)
src/views/events/EventDetailView.js (2)
238-239:⚠️ Potential issue | 🟠 Major
registration_closedstill isn't loaded for this view.The relevant snippet from
src/services/event-service.js:240-298still omitse.registration_closedfromgetEventById(). That makes!!event.registration_closedfalse here, so the new closed-state CTAs never activate on the detail page.🛠️ Include the field in the detail query
--- a/src/services/event-service.js +++ b/src/services/event-service.js @@ e.ticket_price, e.registration_link, + e.registration_closed, e.created_at,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/events/EventDetailView.js` around lines 238 - 239, EventDetailView uses event.registration_closed (seen as isRegClosed = !!event.registration_closed) but getEventById in src/services/event-service.js still omits e.registration_closed; update the getEventById query to include e.registration_closed (ensure the select/projection that builds the event object returns registration_closed) so EventDetailView receives that field and the closed-state CTAs activate; locate the getEventById function and add e.registration_closed to the returned fields.
503-543:⚠️ Potential issue | 🟠 MajorDon't reopen external registration for sold-out events.
Both CTA blocks still go straight from
isRegClosedtoevent.registration_link, so sold-out linked events render a live Register Now action instead of a disabled sold-out state. Line 573 and Line 1327 already model sold-out for the internal CTA path; the external-link path should keep that state too.Also applies to: 1199-1245
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/events/EventDetailView.js` around lines 503 - 543, External registration links still render an active "Register Now" button for sold-out events because the external-link branch doesn't respect the isRegClosed state; update the conditional logic in EventDetailView so the external registration Button is only rendered when event.registration_link is present AND isRegClosed is false (e.g., change the branch to require event.registration_link && !isRegClosed), otherwise render the same disabled "Registration Closed" Button used for the internal CTA; apply the same fix to the other external-CTA occurrence referenced (the block around lines 1199-1245).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/views/events/EventsPageView.js`:
- Around line 108-110: Define an explicit sold-out boolean (e.g., const
isSoldOut = spotsLeft === 0) and use that as a first-class gate for
rendering/behaviour of the CTA instead of relying solely on isRegClosed; update
places that currently check isRegClosed before using event.registration_link or
rendering the "Register Now" button (references: isRegClosed, spotsLeft,
almostFull, event.registration_link, the Register Now CTA rendering logic) so
that if isSoldOut is true you render the sold-out state/disabled CTA
consistently, otherwise fall back to registration_closed logic and the external
registration_link branch.
---
Outside diff comments:
In `@src/views/events/EventDetailView.js`:
- Around line 1020-1101: The mobile CTA branch currently renders the internal
checkout buttons (Buy Now / Add to Cart) even when event.registration_link
exists; update the conditional around the Buttons inside the isRegClosed false
branch to mirror the top mobile CTA logic by first checking for
event.registration_link and rendering a single "Register Now" button that opens
the external URL (instead of calling dispatch(setCheckoutItems) /
router.push('/checkout') or dispatch(addToCart)); otherwise keep the existing
Buy Now and Add to Cart flows (references: isRegClosed, event.registration_link,
setCheckoutItems, setExistingUser, addToCart, getEventImage, router.push).
---
Duplicate comments:
In `@src/views/events/EventDetailView.js`:
- Around line 238-239: EventDetailView uses event.registration_closed (seen as
isRegClosed = !!event.registration_closed) but getEventById in
src/services/event-service.js still omits e.registration_closed; update the
getEventById query to include e.registration_closed (ensure the
select/projection that builds the event object returns registration_closed) so
EventDetailView receives that field and the closed-state CTAs activate; locate
the getEventById function and add e.registration_closed to the returned fields.
- Around line 503-543: External registration links still render an active
"Register Now" button for sold-out events because the external-link branch
doesn't respect the isRegClosed state; update the conditional logic in
EventDetailView so the external registration Button is only rendered when
event.registration_link is present AND isRegClosed is false (e.g., change the
branch to require event.registration_link && !isRegClosed), otherwise render the
same disabled "Registration Closed" Button used for the internal CTA; apply the
same fix to the other external-CTA occurrence referenced (the block around lines
1199-1245).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2f863657-df54-4679-8751-5864d3e5dc65
📒 Files selected for processing (2)
src/views/events/EventDetailView.jssrc/views/events/EventsPageView.js
Bhav-ikkk
left a comment
There was a problem hiding this comment.
This looks nice and correct
added registration closed functionality to events
Summary by CodeRabbit
New Features
Behavior Changes
Bug Fixes