Skip to content

Paywall redirect to recommended plan#3500

Open
marcusljf wants to merge 1 commit intomainfrom
paywall-redirect
Open

Paywall redirect to recommended plan#3500
marcusljf wants to merge 1 commit intomainfrom
paywall-redirect

Conversation

@marcusljf
Copy link
Copy Markdown
Collaborator

@marcusljf marcusljf commented Feb 24, 2026

Standardized upgrade/paywall CTAs across the app to route through a shared billing upgrade URL helper, fixing cases where Billing Upgrade defaulted to the next plan instead of the feature-required plan.

upgrade-url.ts is the central URL builder:

  • getBillingUpgradePath(...) creates /${slug}/settings/billing/upgrade and appends optional plan, planTier, and modal params.
  • getBillingUpgradePathForFeature(...) maps gated features (e.g. partners/business/pro/advanced) to their recommended upgrade target so call sites can use a single feature key instead of hardcoded links/query strings.

This makes upgrade routing consistent and easier to maintain as paywalls grow.

Eg.
dub.co/acme/settings/billing/upgrade?plan=pro

Summary by CodeRabbit

Release Notes

  • Improvements
    • Billing upgrade flows now intelligently route users to appropriate plan recommendations based on the specific feature they're trying to access
    • Consistent upgrade experience across all dashboard sections, settings pages, and feature areas
    • Smart plan recommendations that automatically match to feature requirements for better upgrade guidance

Standardized upgrade/paywall CTAs across the app to route through a shared billing upgrade URL helper with plan-aware query params (plan, planTier). This fixes cases where upgrade flows defaulted to “next plan” instead of the feature-required plan (notably Partners), and centralizes plan recommendation logic for consistent behavior across all upgrade entry points.
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Feb 24, 2026

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

Project Deployment Actions Updated (UTC)
dub Building Building Preview Feb 24, 2026 9:23pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

This PR centralizes billing upgrade URL construction by introducing reusable helper functions that replace hardcoded upgrade paths across the codebase. A new utility module exports getBillingUpgradePath and getBillingUpgradePathForFeature to dynamically generate upgrade URLs with feature-specific recommendations.

Changes

Cohort / File(s) Summary
New Billing Upgrade Utility
apps/web/lib/billing/upgrade-url.ts
Introduces getBillingUpgradePath and getBillingUpgradePathForFeature functions, UpgradeRecommendation type, and FEATURE_RECOMMENDATIONS mapping for dynamic upgrade URL generation with optional plan recommendations and modal flags.
Settings & Dashboard Pages
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/domains/default/page-client.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/domains/email/page-client.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/analytics/conversion-tracking-toggle.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/integrations/[integrationSlug]/page-client.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/webhooks/page-client.tsx
Replaces hardcoded upgrade URLs with feature-specific calls to getBillingUpgradePathForFeature, improving URL generation for plan-usage, domain, analytics, integrations, and webhooks upgrade CTAs.
Program & Bounties
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/confirm-create-bounty-modal.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners-upgrade-cta.tsx
Updates program upgrade links to use dynamic billing upgrade path generation with feature mappings and optional partners upgrade modal flag.
Links Components
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx, apps/web/ui/links/link-builder/conversion-tracking-toggle.tsx, apps/web/ui/links/link-builder/link-preview.tsx, apps/web/ui/links/links-toolbar.tsx, apps/web/ui/links/short-link-input.tsx
Replaces hardcoded upgrade URLs with getBillingUpgradePath or getBillingUpgradePathForFeature, incorporating plan recommendations and feature-specific routing for link upgrade tooltips and CTAs.
Analytics Components
apps/web/ui/analytics/analytics-loading-spinner.tsx, apps/web/ui/analytics/chart-section.tsx, apps/web/ui/analytics/events/events-export-button.tsx, apps/web/ui/analytics/events/index.tsx, apps/web/ui/analytics/toggle.tsx
Updates analytics loading, charts, and event export upgrade links to use centralized billing upgrade helpers with feature-specific routing.
Folder Components
apps/web/ui/folders/add-folder-form.tsx, apps/web/ui/folders/edit-folder-sheet.tsx, apps/web/ui/folders/folder-dropdown.tsx
Replaces static upgrade URLs in folder creation and editing UI with dynamic paths via getBillingUpgradePathForFeature, applying consistent feature mappings across multiple CTA points.
Modal & Modal-Adjacent Components
apps/web/ui/modals/add-edit-domain-modal.tsx, apps/web/ui/modals/add-edit-tag-modal.tsx, apps/web/ui/modals/add-folder-modal.tsx, apps/web/ui/modals/link-builder/index.tsx, apps/web/ui/modals/link-qr-modal.tsx, apps/web/ui/modals/manage-usage-modal.tsx
Updates domain, tag, folder, link builder, QR, and usage modals to use dynamic upgrade URL generation with plan-based recommendations where applicable.
Partners & Rewards
apps/web/ui/partners/confirm-payouts-sheet.tsx, apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx
Replaces hardcoded partner upgrade URLs with calls to getBillingUpgradePathForFeature, including support for the showPartnersUpgradeModal flag in reward sheet.
Layout & Shared Components
apps/web/ui/layout/sidebar/sidebar-usage.tsx, apps/web/ui/layout/upgrade-banner.tsx, apps/web/ui/shared/upgrade-required-toast.tsx, apps/web/ui/workspaces/create-workspace-button.tsx
Updates layout and workspace upgrade CTAs to use getBillingUpgradePath with dynamic plan recommendations derived from nextPlan.
Integration Components
apps/web/ui/customers/customers-table/customers-table.tsx, apps/web/lib/integrations/segment/ui/set-write-key.tsx, apps/web/ui/domains/register-domain-form.tsx
Replaces static upgrade URLs in customers table, segment integration, and domain registration with feature-aware upgrade path generation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • #2518: Modifies UpgradeBanner component to use new getBillingUpgradePath and getNextPlan logic for upgrade link generation.
  • #3191: Updates plan-usage.tsx settings page that is modified in this PR to integrate billing URL helpers.
  • #2990: Relates to confirm-create-bounty-modal.tsx which is updated here to use feature-specific upgrade paths.

Suggested reviewers

  • steven-tey
  • devkiran

Poem

🐰 The upgrade paths now centralize with glee,
No hardcoded URLs for you and me!
Features map to plans, from pro to biz,
Dynamic routing—what a whiz! ✨
Forty files dance in harmony true,
One utility to rule them through! 🎯

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Paywall redirect to recommended plan' clearly and concisely summarizes the main change: introducing centralized billing upgrade URL helpers that route users to recommended plans instead of hard-coded paywall URLs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch paywall-redirect

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.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/ui/modals/add-edit-tag-modal.tsx (1)

211-243: ⚠️ Potential issue | 🟡 Minor

Remove the unreachable ternary — getNextPlan always returns a value

Two issues here:

  1. Dead code: getNextPlan returns PRO_PLAN when the input is undefined/null and always returns a plan object otherwise. The nextPlan ? ... : undefined guard is unreachable and can be simplified.

  2. Missing planTier: The recommendation only passes plan: nextPlan.name.toLowerCase(). However, getNextPlan() returns a PlanDetails object which does not have a tier property — it has an optional nested tiers object for multi-tier plans. The proposed fix that accesses nextPlan.tier won't work. Since tier information is not available from getNextPlan() alone, the simplest fix is to remove the ternary and pass the base plan name.

Proposed fix
  href={getBillingUpgradePath({
    slug,
-   recommendation: nextPlan
-     ? {
-         plan: nextPlan.name.toLowerCase(),
-       }
-     : undefined,
+   recommendation: {
+     plan: nextPlan.name.toLowerCase().split(" ")[0],
+   },
  })}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/modals/add-edit-tag-modal.tsx` around lines 211 - 243, The
ternary guard is unnecessary because getNextPlan(...) always returns a
PlanDetails object, so remove the unreachable conditional and pass the
recommendation directly; update the call building the recommendation to always
supply { plan: nextPlan.name.toLowerCase() } (i.e., drop the "nextPlan ? ... :
undefined" and any attempt to access a non-existent nextPlan.tier), referencing
getNextPlan, nextPlan and getBillingUpgradePath to locate the code to change.
🧹 Nitpick comments (3)
apps/web/ui/links/links-toolbar.tsx (1)

110-175: slug used inside useMemo but missing from its dependency array

getBillingUpgradePathForFeature is called with slug (lines 128 and 144) inside the bulkActions memo, but slug is not listed in the dependency array [plan, conversionsEnabled, selectedLinks]. While slug is effectively stable for the lifetime of a mounted component, the omission technically violates the exhaustive-deps rule and will trigger a lint warning if react-hooks/exhaustive-deps is enabled.

♻️ Proposed fix
-    [plan, conversionsEnabled, selectedLinks],
+    [plan, conversionsEnabled, selectedLinks, slug],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/links/links-toolbar.tsx` around lines 110 - 175, The bulkActions
useMemo references slug when calling getBillingUpgradePathForFeature but slug is
missing from its dependency array; update the dependency array for the useMemo
that defines bulkActions to include slug (in addition to plan,
conversionsEnabled, selectedLinks) so the memo properly invalidates when slug
changes and satisfies exhaustive-deps; locate the useMemo definition of
bulkActions in links-toolbar.tsx to apply this change.
apps/web/ui/links/link-builder/link-preview.tsx (1)

124-128: target="_blank" will open the in-app upgrade page in a new tab when slug is present.

When slug is defined, getBillingUpgradePathForFeature returns a relative path (/${slug}/settings/billing/upgrade?plan=pro). Combined with target="_blank" on line 128, this opens an in-app settings page in a new tab, which may not be the intended UX. If this was already the behavior before this PR, it could be addressed separately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/ui/links/link-builder/link-preview.tsx` around lines 124 - 128, The
link currently sets target="_blank" unconditionally which opens relative in-app
paths returned by getBillingUpgradePathForFeature({ slug, feature: "pro" }) in a
new tab; change LinkPreview so it computes href =
getBillingUpgradePathForFeature(...) and only sets target="_blank" when the href
is an external URL (e.g. startsWith('http')) or when slug is absent — for
relative paths (startsWith('/')) leave target undefined (open in same tab) to
preserve intended in-app navigation.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (1)

71-71: Remove redundant nextPlan recomputation; use the value from useWorkspace() hook instead.

Line 71 recomputes nextPlan via getNextPlan(plan), while upgrade-banner.tsx reads it directly from the useWorkspace() hook. Since plan comes from the same hook, this is redundant. Using the hook's nextPlan keeps the logic centralized and consistent across components.

Change:

const nextPlan = getNextPlan(plan);  // Remove this line

Then use nextPlan from the hook destructuring (line 47-70). This aligns with how upgrade-banner.tsx handles it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
at line 71, Remove the redundant recomputation of nextPlan: eliminate the local
call getNextPlan(plan) and instead use the nextPlan value provided by the
useWorkspace() hook (the same destructured nextPlan used in upgrade-banner.tsx).
Update any references in this file that currently read the locally computed
nextPlan to reference the hook's nextPlan so logic remains centralized and
consistent with upgrade-banner.tsx.
🤖 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 `@apps/web/ui/modals/add-edit-tag-modal.tsx`:
- Around line 211-243: The ternary guard is unnecessary because getNextPlan(...)
always returns a PlanDetails object, so remove the unreachable conditional and
pass the recommendation directly; update the call building the recommendation to
always supply { plan: nextPlan.name.toLowerCase() } (i.e., drop the "nextPlan ?
... : undefined" and any attempt to access a non-existent nextPlan.tier),
referencing getNextPlan, nextPlan and getBillingUpgradePath to locate the code
to change.

---

Nitpick comments:
In
`@apps/web/app/app.dub.co/`(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx:
- Line 71: Remove the redundant recomputation of nextPlan: eliminate the local
call getNextPlan(plan) and instead use the nextPlan value provided by the
useWorkspace() hook (the same destructured nextPlan used in upgrade-banner.tsx).
Update any references in this file that currently read the locally computed
nextPlan to reference the hook's nextPlan so logic remains centralized and
consistent with upgrade-banner.tsx.

In `@apps/web/ui/links/link-builder/link-preview.tsx`:
- Around line 124-128: The link currently sets target="_blank" unconditionally
which opens relative in-app paths returned by getBillingUpgradePathForFeature({
slug, feature: "pro" }) in a new tab; change LinkPreview so it computes href =
getBillingUpgradePathForFeature(...) and only sets target="_blank" when the href
is an external URL (e.g. startsWith('http')) or when slug is absent — for
relative paths (startsWith('/')) leave target undefined (open in same tab) to
preserve intended in-app navigation.

In `@apps/web/ui/links/links-toolbar.tsx`:
- Around line 110-175: The bulkActions useMemo references slug when calling
getBillingUpgradePathForFeature but slug is missing from its dependency array;
update the dependency array for the useMemo that defines bulkActions to include
slug (in addition to plan, conversionsEnabled, selectedLinks) so the memo
properly invalidates when slug changes and satisfies exhaustive-deps; locate the
useMemo definition of bulkActions in links-toolbar.tsx to apply this change.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ad6b53 and f95ac36.

📒 Files selected for processing (37)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/bounties/confirm-create-bounty-modal.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners-upgrade-cta.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/analytics/conversion-tracking-toggle.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/domains/default/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/domains/email/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/integrations/[integrationSlug]/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/webhooks/page-client.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx
  • apps/web/lib/billing/upgrade-url.ts
  • apps/web/lib/integrations/segment/ui/set-write-key.tsx
  • apps/web/ui/analytics/analytics-loading-spinner.tsx
  • apps/web/ui/analytics/chart-section.tsx
  • apps/web/ui/analytics/events/events-export-button.tsx
  • apps/web/ui/analytics/events/index.tsx
  • apps/web/ui/analytics/toggle.tsx
  • apps/web/ui/customers/customers-table/customers-table.tsx
  • apps/web/ui/domains/register-domain-form.tsx
  • apps/web/ui/folders/add-folder-form.tsx
  • apps/web/ui/folders/edit-folder-sheet.tsx
  • apps/web/ui/folders/folder-dropdown.tsx
  • apps/web/ui/layout/sidebar/sidebar-usage.tsx
  • apps/web/ui/layout/upgrade-banner.tsx
  • apps/web/ui/links/link-builder/conversion-tracking-toggle.tsx
  • apps/web/ui/links/link-builder/link-preview.tsx
  • apps/web/ui/links/links-toolbar.tsx
  • apps/web/ui/links/short-link-input.tsx
  • apps/web/ui/modals/add-edit-domain-modal.tsx
  • apps/web/ui/modals/add-edit-tag-modal.tsx
  • apps/web/ui/modals/add-folder-modal.tsx
  • apps/web/ui/modals/link-builder/index.tsx
  • apps/web/ui/modals/link-qr-modal.tsx
  • apps/web/ui/modals/manage-usage-modal.tsx
  • apps/web/ui/partners/confirm-payouts-sheet.tsx
  • apps/web/ui/partners/rewards/add-edit-reward-sheet.tsx
  • apps/web/ui/shared/upgrade-required-toast.tsx
  • apps/web/ui/workspaces/create-workspace-button.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant