feat: prevent ticket reassignment based on promo code#139
feat: prevent ticket reassignment based on promo code#139
Conversation
Use the /apply endpoint's allows_to_reassign response to control whether tickets can be reassigned. When a promo code disallows reassignment, the widget hides the assignee radio options and shows a non-transferable notice. Reset the flag on validation errors so switching to an unsupported ticket type clears the notice.
📝 WalkthroughWalkthroughThis PR refactors promo code validation to remove the onError callback pattern and adds ticket reassignment controls. A new VALIDATE_PROMO_CODE_ERROR action type is introduced, while validatePromoCode simplifies to return API responses directly. The system now tracks reassignment permissions via promoCodeAllowsReassign state, with ticket and promo code constraints enforced across components. UI styling is adjusted for the promo code input layout, and Sentry error tracking is temporarily disabled. Changes
Sequence DiagramsequenceDiagram
participant User
participant TicketTypeComp as TicketTypeComponent
participant RegLite as RegistrationLite
participant Actions as validatePromoCode Action
participant API as Backend API
participant Reducer as Redux Reducer
participant PersonalComp as PersonalInfoComponent
User->>TicketTypeComp: Select ticket
TicketTypeComp->>RegLite: handleTicketChange (if promo code active)
RegLite->>Actions: validatePromoCode(ticketData)
Actions->>API: getRequest(validate endpoint)
API-->>Actions: response {allows_to_reassign}
alt Validation Success
Actions->>Reducer: dispatch VALIDATE_PROMO_CODE
Reducer->>Reducer: update promoCodeAllowsReassign
Reducer-->>RegLite: state updated
RegLite->>TicketTypeComp: Pass reassignment permissions
TicketTypeComp->>User: Show/hide reassignment UI
else Validation Error
Actions->>Reducer: dispatch VALIDATE_PROMO_CODE_ERROR
Reducer->>Reducer: reset promoCodeAllowsReassign to true
Reducer-->>RegLite: state updated
RegLite->>User: Display error message
end
RegLite->>PersonalComp: Pass promoCodeAllowsReassign
User->>PersonalComp: Submit registration
alt Non-Transferable Ticket
PersonalComp->>PersonalComp: Auto-assign purchaser as attendee
else Transferable Ticket
PersonalComp->>PersonalComp: Allow manual ticket owner selection
end
PersonalComp->>User: Confirm submission
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). 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.
🧹 Nitpick comments (5)
src/utils/utils.js (1)
146-146:isSentryInitializedis now dead code.This function is no longer called anywhere since
handleSentryExceptionwas changed to only log to console. Either remove it along with the Sentry changes, or keep it if you plan to restore Sentry functionality soon.🧹 Proposed cleanup if Sentry won't be restored soon
-const isSentryInitialized = () => !!window.SENTRY_DSN; - // TODO: Fix Sentry dependency issue export const handleSentryException = (err) => console.log("Error on registration: ", err);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/utils.js` at line 146, The isSentryInitialized helper is dead code since handleSentryException no longer uses Sentry; remove the unused function isSentryInitialized from utils.js and any related imports/usages, or if you intend to restore Sentry telemetry, rewire handleSentryException to check isSentryInitialized (and call Sentry.captureException) instead of only logging to console; refer to the isSentryInitialized function name and handleSentryException to locate the change points and make the corresponding cleanup or restoration.src/components/registration-lite.js (1)
287-310: Review the error handling condition.The error handler only calls
onErrorwhene?.res?.body?.errorsexists. If the API returns a different error structure (e.g., network error, timeout, or error withoutbody.errors), the UI won't display feedback to the user—only Sentry logging occurs.Consider whether all error scenarios should surface to the user:
♻️ Proposed fix to handle broader error cases
.catch((e) => { - if (e?.res?.body?.errors) { - onError(e, e.res); - } + if (e?.res?.body?.errors) { + onError(e, e.res); + } else if (e?.res) { + // Handle errors with different structure + const fallbackErrors = [e?.res?.body?.message || 'An error occurred validating the promo code.']; + onError(e, { body: { errors: fallbackErrors } }); + } handleSentryException(e); })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/registration-lite.js` around lines 287 - 310, handleValidatePromocode currently only calls onError when e?.res?.body?.errors exists, so other failures (network, timeout, different error shapes) never surface to the UI; update the catch in handleValidatePromocode to always call onError with a user-friendly error payload for any rejected promise from validatePromoCode (pass the original error as first arg and a normalized response object as second), preserving the existing branch that uses e.res when available (i.e., prefer e.res for detailed errors but fall back to a default { body: { errors: [<friendly message or e.message>] } }); keep calling handleSentryException(e) and ensure stopWidgetLoading() still runs in finally.src/components/ticket-type/index.js (1)
102-105: Consider adding minimal error logging in the catch block.The empty
.catch(() => {})silently swallows errors. WhilevalidatePromoCodehandles the Redux state update viaVALIDATE_PROMO_CODE_ERROR, having no logging here can make debugging difficult if issues arise.♻️ Optional: Add console logging for debugging
// Validate promo code if already applied (ticket selected after promo applied) if (promoCode) { - validatePromoCode({ id: t.id, ticketQuantity: minQuantity, sub_type: t.sub_type }).catch(() => {}); + validatePromoCode({ id: t.id, ticketQuantity: minQuantity, sub_type: t.sub_type }).catch((e) => { + console.log('Promo code validation failed for ticket change:', e); + }); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ticket-type/index.js` around lines 102 - 105, The empty catch on the validatePromoCode call swallows errors; update the promise error handler so it logs a minimal, contextual message and the error (for example include promoCode, ticket id t.id, minQuantity and t.sub_type) instead of doing nothing — locate the validatePromoCode invocation in the ticket-type component and replace .catch(() => {}) with a catch that logs a short message plus the caught error to console or the existing logger.src/components/personal-information/index.js (1)
54-65: Consider using constants oruseMemoinstead of functions for computed values.
isSingleTicketOrderandshouldDisplayTicketAssignmentare defined as functions but don't take any parameters - they read from component scope. This is inconsistent withticketTypeAllowsReassign,canReassign, andisNonTransferablewhich are computed as constants.♻️ Proposed refactor for consistency
- // if there's only one ticket on the order and there is no invitation available, handle ticket assignment - const isSingleTicketOrder = () => formValues.ticketQuantity === 1 && !invitation && !isPrePaidTicketType(formValues.ticketType); - - // check if reassignment is allowed by both promo code AND ticket type - const ticketTypeAllowsReassign = formValues.ticketType?.allows_to_reassign !== false; - const canReassign = promoCodeAllowsReassign && ticketTypeAllowsReassign; - - // show radio options only if reassignment is allowed by both sources - const shouldDisplayTicketAssignment = () => isSingleTicketOrder() && canReassign; - - // show notice when ticket is non-transferable (either promo code or ticket type disallows) - const isNonTransferable = isSingleTicketOrder() && !canReassign; + // if there's only one ticket on the order and there is no invitation available, handle ticket assignment + const isSingleTicketOrder = formValues.ticketQuantity === 1 && !invitation && !isPrePaidTicketType(formValues.ticketType); + + // check if reassignment is allowed by both promo code AND ticket type + const ticketTypeAllowsReassign = formValues.ticketType?.allows_to_reassign !== false; + const canReassign = promoCodeAllowsReassign && ticketTypeAllowsReassign; + + // show radio options only if reassignment is allowed by both sources + const shouldDisplayTicketAssignment = isSingleTicketOrder && canReassign; + + // show notice when ticket is non-transferable (either promo code or ticket type disallows) + const isNonTransferable = isSingleTicketOrder && !canReassign;Then update usages from
isSingleTicketOrder()andshouldDisplayTicketAssignment()toisSingleTicketOrderandshouldDisplayTicketAssignment.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/personal-information/index.js` around lines 54 - 65, Replace the zero-arg functions isSingleTicketOrder and shouldDisplayTicketAssignment with computed constants (or wrap them in useMemo if you want memoization) that read from formValues, invitation, promoCodeAllowsReassign and isPrePaidTicketType (i.e., compute const isSingleTicketOrder = formValues.ticketQuantity === 1 && !invitation && !isPrePaidTicketType(formValues.ticketType) and const shouldDisplayTicketAssignment = isSingleTicketOrder && canReassign), keep ticketTypeAllowsReassign, canReassign and isNonTransferable as-is, and update all usages to refer to these identifiers without calling them as functions (remove the trailing () where used).src/reducer.js (1)
169-172: Consider resettingpromoCodeAllowsReassignwhen setting a new promo code.When
SET_CURRENT_PROMO_CODEis dispatched (e.g., user applies a different promo code), thepromoCodeAllowsReassignvalue from the previous promo code persists until validation completes. This could cause a brief UI inconsistency where the reassignment state reflects the old promo code.♻️ Proposed fix to reset the flag when applying a new code
case SET_CURRENT_PROMO_CODE:{ const { currentPromoCode } = payload; - return { ...state, promoCode: currentPromoCode } + return { ...state, promoCode: currentPromoCode, promoCodeAllowsReassign: true } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/reducer.js` around lines 169 - 172, In the reducer case handling SET_CURRENT_PROMO_CODE (in src/reducer.js) you currently set promoCode from payload.currentPromoCode but leave promoCodeAllowsReassign from the previous state; update that case to also reset promoCodeAllowsReassign (e.g., to false or null/undefined) when applying a new promo code so the old flag doesn’t briefly persist in the UI; ensure you modify the object returned in the SET_CURRENT_PROMO_CODE branch (the case block that references currentPromoCode) to include promoCodeAllowsReassign: <desired-reset-value>.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/components/personal-information/index.js`:
- Around line 54-65: Replace the zero-arg functions isSingleTicketOrder and
shouldDisplayTicketAssignment with computed constants (or wrap them in useMemo
if you want memoization) that read from formValues, invitation,
promoCodeAllowsReassign and isPrePaidTicketType (i.e., compute const
isSingleTicketOrder = formValues.ticketQuantity === 1 && !invitation &&
!isPrePaidTicketType(formValues.ticketType) and const
shouldDisplayTicketAssignment = isSingleTicketOrder && canReassign), keep
ticketTypeAllowsReassign, canReassign and isNonTransferable as-is, and update
all usages to refer to these identifiers without calling them as functions
(remove the trailing () where used).
In `@src/components/registration-lite.js`:
- Around line 287-310: handleValidatePromocode currently only calls onError when
e?.res?.body?.errors exists, so other failures (network, timeout, different
error shapes) never surface to the UI; update the catch in
handleValidatePromocode to always call onError with a user-friendly error
payload for any rejected promise from validatePromoCode (pass the original error
as first arg and a normalized response object as second), preserving the
existing branch that uses e.res when available (i.e., prefer e.res for detailed
errors but fall back to a default { body: { errors: [<friendly message or
e.message>] } }); keep calling handleSentryException(e) and ensure
stopWidgetLoading() still runs in finally.
In `@src/components/ticket-type/index.js`:
- Around line 102-105: The empty catch on the validatePromoCode call swallows
errors; update the promise error handler so it logs a minimal, contextual
message and the error (for example include promoCode, ticket id t.id,
minQuantity and t.sub_type) instead of doing nothing — locate the
validatePromoCode invocation in the ticket-type component and replace .catch(()
=> {}) with a catch that logs a short message plus the caught error to console
or the existing logger.
In `@src/reducer.js`:
- Around line 169-172: In the reducer case handling SET_CURRENT_PROMO_CODE (in
src/reducer.js) you currently set promoCode from payload.currentPromoCode but
leave promoCodeAllowsReassign from the previous state; update that case to also
reset promoCodeAllowsReassign (e.g., to false or null/undefined) when applying a
new promo code so the old flag doesn’t briefly persist in the UI; ensure you
modify the object returned in the SET_CURRENT_PROMO_CODE branch (the case block
that references currentPromoCode) to include promoCodeAllowsReassign:
<desired-reset-value>.
In `@src/utils/utils.js`:
- Line 146: The isSentryInitialized helper is dead code since
handleSentryException no longer uses Sentry; remove the unused function
isSentryInitialized from utils.js and any related imports/usages, or if you
intend to restore Sentry telemetry, rewire handleSentryException to check
isSentryInitialized (and call Sentry.captureException) instead of only logging
to console; refer to the isSentryInitialized function name and
handleSentryException to locate the change points and make the corresponding
cleanup or restoration.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/actions.jssrc/components/personal-information/index.jssrc/components/promocode-input/index.module.scsssrc/components/registration-lite.jssrc/components/ticket-type/index.jssrc/components/ticket-type/index.module.scsssrc/reducer.jssrc/utils/utils.js
/applyendpoint'sallows_to_reassignresponse to control ticket reassignment UIVALIDATE_PROMO_CODE_ERRORaction to reset reassignment flag when validation fails (e.g. switching to an unsupported ticket type)ref: https://app.clickup.com/t/86b7fapmr
Summary by CodeRabbit
Release Notes
New Features
Improvements