Skip to content

feat(card): waitlist ends on the rejection/appeal screen (drop the cooldown)#2303

Merged
Hugo0 merged 2 commits into
mainfrom
hotfix/waitlist-end-on-rejection
Jun 29, 2026
Merged

feat(card): waitlist ends on the rejection/appeal screen (drop the cooldown)#2303
Hugo0 merged 2 commits into
mainfrom
hotfix/waitlist-end-on-rejection

Conversation

@Hugo0

@Hugo0 Hugo0 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

What

Make CardRejectionScreen the terminal waitlist screen and delete the separate CardWaitlistJoinedScreen cooldown.

  • New alreadyJoined prop: 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.tsx waitlist case no longer branches to the cooldown — it always renders CardRejectionScreen with alreadyJoined={!!waitlistJoinedAt}.
  • /dev/rejection-builder gets an alreadyJoined toggle to preview both states.

Why

Stuck-outside users used to flip to a bare cooldown screen after joining, and every later /card visit 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

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 to dev. Supersedes #2301 (cooldown CTA — now removed).

…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.
@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jun 29, 2026 1:36pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 07052e21-561a-41c2-bd40-d37c2d435a6c

📥 Commits

Reviewing files that changed from the base of the PR and between 574ce1b and 4e8af84.

📒 Files selected for processing (1)
  • src/components/Card/CardRejectionScreen.tsx

Walkthrough

CardWaitlistJoinedScreen is removed entirely. Its "already joined" state is folded into CardRejectionScreen via a new alreadyJoined prop that swaps the join button for a check-circle confirmation. The card page state machine and the dev rejection-builder are updated to use this prop.

Changes

Waitlist joined state consolidation

Layer / File(s) Summary
alreadyJoined prop in CardRejectionScreen
src/components/Card/CardRejectionScreen.tsx
Adds optional alreadyJoined?: boolean to Props (defaulting to false), imports Icon, and conditionally renders a check-circle confirmation row or the existing join button.
Card page state machine update
src/app/(mobile-ui)/card/page.tsx
Removes CardWaitlistJoinedScreen import and changes the waitlist case to always render CardRejectionScreen with alreadyJoined derived from cardInfo.waitlistJoinedAt. CardWaitlistJoinedScreen component file is deleted.
Dev builder preview update
src/app/(mobile-ui)/dev/rejection-builder/page.tsx
Adds alreadyJoined state, a toggle control in the controls panel, and wires alreadyJoined and setAlreadyJoined(true) as onJoined into the CardRejectionScreen preview.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: the waitlist now ends on the rejection/appeal screen and drops the cooldown.
Description check ✅ Passed The description matches the implemented waitlist flow changes, including the new prop, page routing, and dev preview toggle.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the enhancement New feature or request label Jun 29, 2026
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 5846.79 → 5842.93 (-3.86)
Findings: -1 net (+14 new, -15 resolved)

🆕 New findings (14)

  • critical complexity — src/app/(mobile-ui)/card/page.tsx — CC 94, MI 57.5, SLOC 374
  • high hotspot — src/app/(mobile-ui)/card/page.tsx — 37 commits, +1055/-465 lines since 6 months ago
  • medium high-mdd — src/app/(mobile-ui)/card/page.tsx:52 — CardPage: MDD 101.2 (uses across many lines from declarations)
  • medium high-dlt — src/app/(mobile-ui)/card/page.tsx:52 — CardPage: DLT 48 (calls 48 distinct functions — high context load)
  • medium structural-dup — app/(mobile-ui)/dev/rejection-builder/page.tsx:156 — 38 duplicate lines / 192 tokens with app/(mobile-ui)/dev/share-builder/page.tsx:407
  • medium high-mdd — src/components/Card/CardRejectionScreen.tsx:49 — CardRejectionScreen: MDD 31.4 (uses across many lines from declarations)
  • medium complexity — src/app/(mobile-ui)/dev/rejection-builder/page.tsx — CC 22, MI 73.06, SLOC 64
  • medium complexity — src/components/Card/CardRejectionScreen.tsx — CC 20, MI 58.21, SLOC 92
  • low high-dlt — src/components/Card/CardRejectionScreen.tsx:49 — CardRejectionScreen: DLT 22 (calls 22 distinct functions — high context load)
  • low high-mdd — src/app/(mobile-ui)/card/page.tsx:319 — : MDD 15.8 (uses across many lines from declarations)
  • low high-dlt — src/components/Card/CardRejectionScreen.tsx:88 — handleAppeal: DLT 15 (calls 15 distinct functions — high context load)
  • low high-mdd — src/components/Card/CardRejectionScreen.tsx:88 — handleAppeal: MDD 13.9 (uses across many lines from declarations)
  • low structural-dup — app/(mobile-ui)/dev/rejection-builder/page.tsx:40 — 11 duplicate lines / 59 tokens with app/(mobile-ui)/dev/share-builder/page.tsx:156
  • low react-inline-arrows — src/app/(mobile-ui)/dev/rejection-builder/page.tsx:1 — 8 inline arrow handlers (extract or use useCallback)

✅ Resolved (15)

  • src/app/(mobile-ui)/card/page.tsx — CC 95, MI 57.44, SLOC 376
  • src/app/(mobile-ui)/card/page.tsx — 36 commits, +1049/-455 lines since 6 months ago
  • src/app/(mobile-ui)/card/page.tsx:53 — CardPage: MDD 104.3 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:53 — CardPage: DLT 48 (calls 48 distinct functions — high context load)
  • app/(mobile-ui)/dev/rejection-builder/page.tsx:139 — 38 duplicate lines / 192 tokens with app/(mobile-ui)/dev/share-builder/page.tsx:407
  • src/components/Card/CardWaitlistJoinedScreen.tsx:29 — CardWaitlistJoinedScreen: MDD 32.0 (uses across many lines from declarations)
  • src/components/Card/CardRejectionScreen.tsx:41 — CardRejectionScreen: MDD 31.2 (uses across many lines from declarations)
  • src/components/Card/CardRejectionScreen.tsx — CC 19, MI 58.28, SLOC 92
  • src/app/(mobile-ui)/dev/rejection-builder/page.tsx — CC 18, MI 73.31, SLOC 57
  • src/app/(mobile-ui)/card/page.tsx:436 — CC 15 SLOC 47
  • src/components/Card/CardRejectionScreen.tsx:41 — CardRejectionScreen: DLT 22 (calls 22 distinct functions — high context load)
  • src/app/(mobile-ui)/card/page.tsx:320 — : MDD 15.8 (uses across many lines from declarations)
  • src/components/Card/CardRejectionScreen.tsx:79 — handleAppeal: DLT 15 (calls 15 distinct functions — high context load)
  • src/components/Card/CardRejectionScreen.tsx:79 — handleAppeal: MDD 13.9 (uses across many lines from declarations)
  • app/(mobile-ui)/dev/rejection-builder/page.tsx:39 — 11 duplicate lines / 59 tokens with app/(mobile-ui)/dev/share-builder/page.tsx:156

📈 Painscore deltas (top movers)

File Before After Δ
src/app/(mobile-ui)/dev/rejection-builder/page.tsx 4.1 5.0 +0.9
src/components/Card/CardWaitlistJoinedScreen.tsx 5.2 0.0 -5.2

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1601 ran, 0 failed, 0 skipped, 24.8s

📊 Coverage (unit)

metric %
statements 54.4%
branches 36.9%
functions 42.0%
lines 54.3%
⏱ 10 slowest test cases
time test
3.2s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.4s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.2s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.2s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Perk claimed shows shake class + go home button
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Manteca PIX form ready shows merchant card + amount input + pay button
0.1s src/services/__tests__/resolveClaimLink.test.ts › restores the pristine password after a redirect mangles the fragment
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle too long for US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (invalid characters)
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between aeb2c46 and 574ce1b.

📒 Files selected for processing (4)
  • src/app/(mobile-ui)/card/page.tsx
  • src/app/(mobile-ui)/dev/rejection-builder/page.tsx
  • src/components/Card/CardRejectionScreen.tsx
  • src/components/Card/CardWaitlistJoinedScreen.tsx
💤 Files with no reviewable changes (1)
  • src/components/Card/CardWaitlistJoinedScreen.tsx

Comment on lines +472 to 483
// 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}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Comment on lines +40 to +45
/** 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. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Comment on lines +196 to +211
{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&apos;re on the list — we&apos;ll holler when it&apos;s your turn
</div>
) : (
<Button
onClick={handleJoin}
loading={joining}
disabled={joining || sharing}
variant="stroke"
className="w-full"
>
Join the waitlist anyway
</Button>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.
@Hugo0 Hugo0 merged commit 622968b into main Jun 29, 2026
17 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant