feat: free CTA opens a $0 card-setup screen, enterprise CTA books a call - #60
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
📝 WalkthroughWalkthroughPartner pricing now uses a configured booking URL and marks its CTA as external. ChangesPricing CTA behavior
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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.
Actionable comments posted: 1
🤖 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 `@lib/config.ts`:
- Line 40: Rewrite the comments at lib/config.ts#L40-L40 and
lib/copy/pricing.ts#L16-L16 to explain their business rationale rather than
describing behavior: document that the booking URL centralizes enterprise
conversion routing, and that ctaExternal preserves the pricing page while
directing users to third-party booking flows.
🪄 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 Plus
Run ID: 9d55fbf3-c82d-490b-9500-d5393f68248f
📒 Files selected for processing (3)
app/pricing/page.tsxlib/config.tslib/copy/pricing.ts
Preview verificationPreview: https://marketing-8k2xee2u1-recoup.vercel.app — built from head commit A broken link was caught and fixed during this passThe first commit shipped Worth knowing for anyone touching this later: a Results
8/8 pass. Screenshots1. Partner card CTA on the preview 2. Where the CTA lands Follow-up worth considering (not in this PR)The slug fragility above is structural: any future rename of the Calendly event breaks production silently. An owned redirect ( Part of recoupable/chat#1902. |
The Partner plan CTA was a bare mailto, which drops the lead when no mail client is configured and gives us no scheduling or click tracking. Point it at the booking link in a new tab, with the URL held once in siteConfig. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Renaming the event to "Recoup for your roster" changed its Calendly slug from /30min to /roster, so the previous URL 404'd. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
719c239 to
9916d2d
Compare
The free plan CTA linked to the chat app. It now opens a Stripe setup-mode checkout so the visitor registers a card for later credit top-ups without being charged, which is what lets the credits auto-recharge path top an account up instead of dead-ending it at zero. Extracts the Privy sign-in gate shared with the Pro checkout into useGatedStripeRedirect rather than duplicating it, and corrects the FAQ claim that the Pro trial needs no card (the trial checkout does collect one). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
3 issues found across 8 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/checkout/createCardOnFileSession.ts">
<violation number="1" location="lib/checkout/createCardOnFileSession.ts:27">
P3: Future fixes to authenticated checkout requests or response handling can diverge between these two flows because this helper copies the full transport block from `createCheckoutSession`; a shared POST/hosted-URL helper would keep both implementations consistent.</violation>
</file>
<file name="components/pricing/CardOnFileButton.tsx">
<violation number="1" location="components/pricing/CardOnFileButton.tsx:25">
P2: The CTA can silently do nothing on the first click while Privy is initializing because it is disabled only for `isPending`, while `useGatedStripeRedirect.start()` drops calls when `ready` is false without feedback. Exposing the auth-ready state and disabling or queuing the action until initialization completes would prevent this lost CTA interaction.</violation>
</file>
<file name="hooks/useGatedStripeRedirect.ts">
<violation number="1" location="hooks/useGatedStripeRedirect.ts:52">
P2: A visitor can be redirected into the previously selected Stripe flow without clicking that CTA again: this ref is set before opening Privy, but it is only cleared when `authenticated` becomes true. If the visitor cancels or abandons the sign-in modal and later authenticates through another flow, the effect at lines 60–64 will automatically call the old `createSessionUrl` callback (paid checkout for Pro or card setup for Free). Clearing the deferred action on login cancellation/error or explicit modal close would prevent an unexpected checkout/setup redirect.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| <button | ||
| type="button" | ||
| onClick={() => void startCardSetup()} | ||
| disabled={isPending} |
There was a problem hiding this comment.
P2: The CTA can silently do nothing on the first click while Privy is initializing because it is disabled only for isPending, while useGatedStripeRedirect.start() drops calls when ready is false without feedback. Exposing the auth-ready state and disabling or queuing the action until initialization completes would prevent this lost CTA interaction.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At components/pricing/CardOnFileButton.tsx, line 25:
<comment>The CTA can silently do nothing on the first click while Privy is initializing because it is disabled only for `isPending`, while `useGatedStripeRedirect.start()` drops calls when `ready` is false without feedback. Exposing the auth-ready state and disabling or queuing the action until initialization completes would prevent this lost CTA interaction.</comment>
<file context>
@@ -0,0 +1,40 @@
+ <button
+ type="button"
+ onClick={() => void startCardSetup()}
+ disabled={isPending}
+ className={`${className} disabled:opacity-70 disabled:cursor-wait`}
+ >
</file context>
| if (!ready || isPending) return; | ||
| // Gate: signed out opens Privy and defers the redirect to a successful login. | ||
| if (!authenticated) { | ||
| pendingRedirect.current = true; |
There was a problem hiding this comment.
P2: A visitor can be redirected into the previously selected Stripe flow without clicking that CTA again: this ref is set before opening Privy, but it is only cleared when authenticated becomes true. If the visitor cancels or abandons the sign-in modal and later authenticates through another flow, the effect at lines 60–64 will automatically call the old createSessionUrl callback (paid checkout for Pro or card setup for Free). Clearing the deferred action on login cancellation/error or explicit modal close would prevent an unexpected checkout/setup redirect.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/useGatedStripeRedirect.ts, line 52:
<comment>A visitor can be redirected into the previously selected Stripe flow without clicking that CTA again: this ref is set before opening Privy, but it is only cleared when `authenticated` becomes true. If the visitor cancels or abandons the sign-in modal and later authenticates through another flow, the effect at lines 60–64 will automatically call the old `createSessionUrl` callback (paid checkout for Pro or card setup for Free). Clearing the deferred action on login cancellation/error or explicit modal close would prevent an unexpected checkout/setup redirect.</comment>
<file context>
@@ -0,0 +1,69 @@
+ if (!ready || isPending) return;
+ // Gate: signed out opens Privy and defers the redirect to a successful login.
+ if (!authenticated) {
+ pendingRedirect.current = true;
+ login();
+ return;
</file context>
| successUrl: string, | ||
| token: string, | ||
| ): Promise<string> { | ||
| const res = await fetch(`${siteConfig.apiUrl}/subscriptions/card-on-file`, { |
There was a problem hiding this comment.
P3: Future fixes to authenticated checkout requests or response handling can diverge between these two flows because this helper copies the full transport block from createCheckoutSession; a shared POST/hosted-URL helper would keep both implementations consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/checkout/createCardOnFileSession.ts, line 27:
<comment>Future fixes to authenticated checkout requests or response handling can diverge between these two flows because this helper copies the full transport block from `createCheckoutSession`; a shared POST/hosted-URL helper would keep both implementations consistent.</comment>
<file context>
@@ -0,0 +1,41 @@
+ successUrl: string,
+ token: string,
+): Promise<string> {
+ const res = await fetch(`${siteConfig.apiUrl}/subscriptions/card-on-file`, {
+ method: "POST",
+ headers: {
</file context>
Preview verification — free CTA card-on-file, end to endPreview: This run was only possible after api#797 merged and the api Results
13/13. The two flows are visibly different, which is the point
Screenshots1. "Start free" opens Privy in place on the marketing domain 2. Where it lands: Stripe card setup, no charge Notes
Part of recoupable/chat#1902 and recoupable/chat#1910. |
Addendum — enterprise CTA tested by clicking it, on this previewCorrecting a gap in the previous comment: row 10 there asserted the Partner CTA's Now done on
4/4. Asserted programmatically on the loaded page rather than by eye: 1. Partner card CTA on this preview 2. Where the click lands (new tab) Also fixed: I had broken my own earlier screenshotsPushing the card-on-file screenshots to Combined coverage for this PR is now 17/17: 13 on the free card-on-file flow, 4 on the enterprise CTA. |






The Partner (enterprise) plan CTA was a bare
mailto:link, which loses the lead the moment a visitor has no mail client configured, gives us no scheduling, and produces no trackable click. It now opens the booking link in a new tab.Changes
mailto:hi@recoupable.dev?subject=Recoupable%20Partner%20Planhttps://calendly.com/sweetman-recoupable/30mintarget="_blank"+rel="noopener noreferrer"), so the pricing page survives the clicklib/config.ts— newsiteConfig.bookingUrl, so the booking link has one home instead of being pasted into copy files.lib/copy/pricing.ts— Partner plan points atsiteConfig.bookingUrl, label changed, and a new optionalctaExternalflag onPricingPlan.app/pricing/page.tsx— honorsctaExternalfortarget/rel. Free and Pro CTAs are untouched and still open in the same tab.Verification
pnpm exec tsc --noEmit: clean.pnpm build: passes, all pages prerendered./pricing.htmlcontainscalendly.com/sweetman-recoupable/30minwithtarget="_blank" rel="noopener noreferrer", and zero occurrences ofPartner%20Plan(the old mailto is fully gone).https://calendly.com/sweetman-recoupable/30minreturns 200.Notes for reviewers
lib/copy/pricing.ts(that PR rewrites the plan cards). The conflict is trivial: whichever merges second keeps this Partner block. Also touchesapp/pricing/page.tsx, which #56 and #57 modify in different places./auditpage inlib/copy/audit.ts: "AI Transformation Inquiry" and "Partner Inquiry". They are candidates for the same treatment; say the word and I will point them atsiteConfig.bookingUrltoo.Part of recoupable/chat#1902.
🤖 Generated with Claude Code
Summary by cubic
Improves pricing conversions: Enterprise CTA now books a Calendly call in a new tab, and Free CTA opens a $0 Stripe card-setup to save a card for future top-ups. Also refactors checkout gating, updates copy, and fixes the Calendly slug to
/roster(recoupable/chat#1902).New Features
siteConfig.bookingUrl, opens in a new tab.CardOnFileButtontriggers a Stripe setup-mode session (no charge) viauseCardOnFile.Refactors
useGatedStripeRedirectto share Privy-gated Stripe redirects;useProCheckoutnow uses it.createCardOnFileSessionwith tests.Written for commit 164e8ee. Summary will update on new commits.
Summary by CodeRabbit