feat(card): waitlist ends on the rejection/appeal screen (drop the cooldown)#2303
Conversation
…p the cooldown) Stuck-outside users used to flip from CardRejectionScreen (shareable 'not tonight' asset + Tweet to appeal) to a bare CardWaitlistJoinedScreen cooldown once they joined — and every later /card visit showed that cooldown, so they could never re-grab/re-share their appeal asset. Make the rejection screen the TERMINAL waitlist screen: once joined (alreadyJoined) the 'Join anyway' button becomes an 'on the list' confirmation, but the asset + Tweet-to-appeal stay. Symmetric with the in-crowd. Delete CardWaitlistJoinedScreen; /dev/rejection-builder gets an alreadyJoined toggle to QA both states.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 21 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
Walkthrough
ChangesWaitlist joined state consolidation
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
Code-analysis diffPainscore total: 5846.79 → 5842.93 (-3.86) 🆕 New findings (14)
✅ Resolved (15)
📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@src/app/`(mobile-ui)/card/page.tsx:
- Around line 472-483: The joined state in CardRejectionScreen is only updated
after refetchCardInfo(), so alreadyJoined can stay false briefly and let the
join CTA reappear. Update the local source of truth in the card page (the
alreadyJoined prop driven by cardInfo.waitlistJoinedAt) as soon as
CardRejectionScreen.handleJoin succeeds, either by seeding a local override or
synchronously writing to the card-info query data before calling
refetchCardInfo(). Keep the fix centered around CardRejectionScreen, onJoined,
and the cardInfo.waitlistJoinedAt check.
In `@src/components/Card/CardRejectionScreen.tsx`:
- Around line 40-45: The CARD_WAITLIST_VIEWED event in CardRejectionScreen is
hardcoding already_joined to false, which is incorrect when the screen mounts
with alreadyJoined=true. Update the view-tracking payload in CardRejectionScreen
to use the actual alreadyJoined prop/state so returning waitlist users are
recorded correctly, and make sure any related event payload at the referenced
tracking call site is consistent.
- Around line 196-211: The success branch in CardRejectionScreen is still able
to render with a stale joinError from a prior failed join attempt. Update the
state flow around handleAppeal and handleJoin so that a successful join or
appeal clears joinError before setting alreadyJoined, ensuring the “You’re on
the list” UI in CardRejectionScreen cannot appear alongside an old “Failed to
join waitlist” message.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a5d185e4-e7d4-4afa-9c6c-7718d43a66df
📒 Files selected for processing (4)
src/app/(mobile-ui)/card/page.tsxsrc/app/(mobile-ui)/dev/rejection-builder/page.tsxsrc/components/Card/CardRejectionScreen.tsxsrc/components/Card/CardWaitlistJoinedScreen.tsx
💤 Files with no reviewable changes (1)
- src/components/Card/CardWaitlistJoinedScreen.tsx
| // The Berghain-style "not tonight" rejection is the TERMINAL | ||
| // waitlist screen — a shareable door let-down (tags @joinpeanut) | ||
| // that doubles as the waitlist-join CTA. Once they join we keep | ||
| // them here (`alreadyJoined`) so the asset + "Tweet to appeal" | ||
| // stay grabbable — no separate cooldown screen to dead-end on. | ||
| return ( | ||
| <CardRejectionScreen | ||
| username={user?.user?.username ?? undefined} | ||
| alreadyJoined={!!cardInfo!.waitlistJoinedAt} | ||
| onPrev={onBack} | ||
| onJoined={refetchCardInfo} | ||
| /> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Promote the joined state locally instead of waiting for the refetch.
CardRejectionScreen.handleJoin() clears its joining flag in finally, but onJoined here only starts refetchCardInfo(). Until that query resolves, alreadyJoined={!!cardInfo!.waitlistJoinedAt} is still false, so the "Join the waitlist anyway" CTA can briefly come back after a successful join. That reopens duplicate taps/events in the exact terminal flow this PR is adding. Seed a local joined override or update the card-info query data synchronously before refetching.
🤖 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 `@src/app/`(mobile-ui)/card/page.tsx around lines 472 - 483, The joined state
in CardRejectionScreen is only updated after refetchCardInfo(), so alreadyJoined
can stay false briefly and let the join CTA reappear. Update the local source of
truth in the card page (the alreadyJoined prop driven by
cardInfo.waitlistJoinedAt) as soon as CardRejectionScreen.handleJoin succeeds,
either by seeding a local override or synchronously writing to the card-info
query data before calling refetchCardInfo(). Keep the fix centered around
CardRejectionScreen, onJoined, and the cardInfo.waitlistJoinedAt check.
| /** True once the user is already on the waitlist — swaps the "Join anyway" | ||
| * button for an "on the list" confirmation while keeping the asset + appeal. */ | ||
| alreadyJoined?: boolean | ||
| onPrev?: () => void | ||
| /** Called after the user joins the waitlist. Parent should refetch /card, | ||
| * which flips the state machine to <CardWaitlistJoinedScreen /> (cooldown). */ | ||
| /** Called after the user joins the waitlist. Parent refetches /card; the | ||
| * user stays on this screen, now in its `alreadyJoined` state. */ |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Record the real joined state in the view event.
Because this screen can now mount with alreadyJoined=true, Line 65's CARD_WAITLIST_VIEWED payload is now wrong for returning waitlist users (already_joined is still hardcoded to false). That will skew the funnel for the merged terminal screen.
Also applies to: 54-54
🤖 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 `@src/components/Card/CardRejectionScreen.tsx` around lines 40 - 45, The
CARD_WAITLIST_VIEWED event in CardRejectionScreen is hardcoding already_joined
to false, which is incorrect when the screen mounts with alreadyJoined=true.
Update the view-tracking payload in CardRejectionScreen to use the actual
alreadyJoined prop/state so returning waitlist users are recorded correctly, and
make sure any related event payload at the referenced tracking call site is
consistent.
| {alreadyJoined ? ( | ||
| <div className="flex items-center justify-center gap-2 py-2 text-sm font-bold text-n-1"> | ||
| <Icon name="check-circle" size={18} /> | ||
| You're on the list — we'll holler when it's your turn | ||
| </div> | ||
| ) : ( | ||
| <Button | ||
| onClick={handleJoin} | ||
| loading={joining} | ||
| disabled={joining || sharing} | ||
| variant="stroke" | ||
| className="w-full" | ||
| > | ||
| Join the waitlist anyway | ||
| </Button> | ||
| )} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Clear or hide stale join errors once the success state is shown.
joinError from Line 185 can survive into this branch: if "Join the waitlist anyway" fails once and a later appeal successfully joins the user, handleAppeal() never clears the old error, so the screen can show "You're on the list" and "Failed to join waitlist" at the same time.
🤖 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 `@src/components/Card/CardRejectionScreen.tsx` around lines 196 - 211, The
success branch in CardRejectionScreen is still able to render with a stale
joinError from a prior failed join attempt. Update the state flow around
handleAppeal and handleJoin so that a successful join or appeal clears joinError
before setting alreadyJoined, ensuring the “You’re on the list” UI in
CardRejectionScreen cannot appear alongside an old “Failed to join waitlist”
message.
…hift) The 'on the list' confirmation that replaces the 'Join anyway' button was py-2 (~36px) vs the button's h-13 (52px), so the CTA block shrank on join and shifted the layout. Fix the indicator to h-13 so the swap is height-stable.
What
Make
CardRejectionScreenthe terminal waitlist screen and delete the separateCardWaitlistJoinedScreencooldown.alreadyJoinedprop: once the user is on the waitlist, the secondary "Join the waitlist anyway" button becomes a "✓ you're on the list — we'll holler" confirmation. The shareable "not tonight" asset + primary "Tweet to appeal" stay.card/page.tsxwaitlistcase no longer branches to the cooldown — it always rendersCardRejectionScreenwithalreadyJoined={!!waitlistJoinedAt}./dev/rejection-buildergets an alreadyJoined toggle to preview both states.Why
Stuck-outside users used to flip to a bare cooldown screen after joining, and every later
/cardvisit showed that cooldown — so they could never re-grab or re-share their appeal asset (the viral@joinpeanut"not tonight" image). Now they stay on the asset/appeal screen permanently. Symmetric with the in-crowd (everyone ends on their shareable asset).Safe
<canvas>(text + bundled mascot only), so it's immune to the blank-asset capture bug (fix(card): blank share asset — wait for the card-face canvas before capture #2302).CardWaitlistJoinedScreenhad no tests and no other importers.QA
/dev/rejection-builder→ toggle "Waitlist state" → see the "Join anyway" button swap to "✓ on the list" with the asset + appeal intact.Hotfix off
main; needs back-merge todev. Supersedes #2301 (cooldown CTA — now removed).