Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@TWilson023
Copy link
Collaborator

@TWilson023 TWilson023 commented Oct 30, 2025

For adding the new products/prices to prod:

  • Create new products and prices for each tier (e.g. Advanced Tier 2, Advanced Tier 3)
  • Set price lookup keys (e.g. advanced2_monthly, advanced3_yearly)
  • Add new products+prices to billing customer portal settings

Summary by CodeRabbit

  • New Features

    • Workspace plan tiers persisted with tier-aware pricing, limits, and upgrade flows; planTier added to schema and database.
  • UI

    • Manage Usage modal, Adjust Usage row, Manage buttons, and “Recommended” badges; upgrade actions accept and respect tiers.
  • Bug Fixes

    • Tiered limits consistently applied across billing, webhooks, tokens and usage updates; upgrade emails and billing links now point to billing/settings.

@vercel
Copy link
Contributor

vercel bot commented Oct 30, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Nov 7, 2025 10:35pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 30, 2025

Walkthrough

Introduce tier-aware pricing: add planTier to DB and schemas, replace flat price-id helpers with getPlanAndTierFromPriceId/PlanDetails, persist planTier and tiered limits to workspaces and restricted tokens, thread tier through upgrade APIs/UI, and add a Manage Usage modal and sliders.

Changes

Cohort / File(s) Summary
Stripe webhook handlers
apps/web/app/(ee)/api/stripe/webhook/checkout-session-completed.ts, apps/web/app/(ee)/api/stripe/webhook/customer-subscription-deleted.ts, apps/web/app/(ee)/api/stripe/webhook/customer-subscription-updated.ts
Swap getPlanFromPriceIdgetPlanAndTierFromPriceId, destructure { plan, planTier }, persist planTier, and populate workspace limit fields from plan.limits.
Update workspace plan util
apps/web/app/(ee)/api/stripe/webhook/utils/update-workspace-plan.ts
Remove separate plan param; derive newPlan/newPlanTier via getPlanAndTierFromPriceId; use newPlan.limits and persist planTier; update restricted tokens and program messaging logic accordingly.
Upgrade API route & button
apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts, apps/web/ui/workspaces/upgrade-plan-button.tsx
Add Zod validation (includes optional tier), compute Stripe lookup key including tier, search prices by lookup key, and include optional tier in upgrade payload.
Pricing utilities (tiered)
packages/utils/src/constants/pricing.tsx
Introduce PlanDetails with per-tier pricing/limits, per-tier price-id mappings, getPlanAndTierFromPriceId, getPlanDetails({ plan, planTier }), enrichPlanWithTierData, tier-aware isDowngradePlan, and getSuggestedPlan; remove getPlanFromPriceId.
Manage Usage UI & sliders
apps/web/ui/modals/manage-usage-modal.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx, apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
Add ManageUsageModal + useManageUsageModal hook, AdjustUsageRow and UsageSlider, narrow UsageTabCard id type, integrate modal-triggered manage flow and usage-based plan suggestion.
Upgrade page client
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
Track eventsUsage/linksUsage, compute recommendedPlan via getSuggestedPlan, include workspace planTier, display "Recommended" badge, and pass tier to UpgradePlanButton.
Schema & DB
apps/web/lib/zod/schemas/workspaces.ts, packages/prisma/schema/workspace.prisma
Add planTier (nullable) to Zod WorkspaceSchema and add planTier Int @default(1) to Prisma Project model.
Email templates / upgrade email
packages/email/src/templates/upgrade-email.tsx, packages/email/src/templates/clicks-exceeded.tsx, packages/email/src/templates/links-limit.tsx
UpgradeEmail accepts planTier and calls getPlanDetails({ plan, planTier }); other templates remove getNextPlan and use APP_DOMAIN billing URLs.
Modal callsites
apps/web/ui/modals/upgraded-modal.tsx, apps/web/ui/modals/welcome-modal.tsx
Update getPlanDetails usage to getPlanDetails({ plan, planTier }).
UI slider styling
packages/ui/src/slider.tsx
Move slider sizing to CSS variables (--thumb-radius, --track-height) and apply to track/range/thumb.
Icons
packages/ui/src/icons/plan-feature-icons.tsx
Replace PLAN_FEATURE_ICONS.partners icon with ConnectedDots4.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant UI as Upgrade UI / Modal
    participant API as Upgrade API
    participant Stripe
    participant Webhook as Stripe Webhook
    participant Pricing as Pricing Utils
    participant DB as Workspace DB

    User->>UI: open ManageUsageModal / adjust events & links
    UI->>Pricing: getSuggestedPlan(events, links)
    UI->>API: POST /upgrade { plan, period, tier? }
    API->>Stripe: create checkout session (price key may include tier)
    User->>Stripe: complete checkout
    Stripe->>Webhook: checkout.session.completed
    Webhook->>Pricing: getPlanAndTierFromPriceId(priceId)
    Pricing-->>Webhook: { plan, planTier }
    Webhook->>DB: update workspace { plan, planTier, limits: plan.limits.* }
    Webhook->>DB: update restricted tokens (rateLimit = plan.limits.api)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Files/areas needing extra attention:

  • packages/utils/src/constants/pricing.tsx — new types, per-tier mappings, correctness of getPlanAndTierFromPriceId, getPlanDetails, and getSuggestedPlan.
  • Webhook handlers and apps/web/app/(ee)/api/stripe/webhook/utils/update-workspace-plan.ts — mapping of limits, early-return behavior, token updates, and persistence of planTier.
  • Upgrade flow end-to-end: route validation, UpgradePlanButton payload (tier), and Stripe price lookup key composition.
  • DB + Zod alignment and migration implications for planTier.
  • New modal/slider components — step computation, default selection logic, and suggested plan interactions.

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

🐇 I hopped through tiers beneath the checkout light,
Sliders tuned for quotas, modal warm and bright.
Plan and tier now anchored, limits tucked in tight,
Webhooks hum their updates, tokens set aright.
A rabbit's little cheer — pricing, tiers, delight.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Pricing updates' is vague and generic, using non-descriptive terminology that doesn't convey the specific scope or main focus of the substantial changes across multiple files and systems. Provide a more specific and descriptive title that captures the primary change, such as 'Add support for tiered pricing and plan variants' or 'Implement multi-tier pricing system with dynamic plan selection'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pricing-updates

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.


const PRO_TIER_PRICE_IDS = {
2: [
// TODO: Add prod price IDs here
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

⚠️ Need prod price IDs here


const BUSINESS_TIER_PRICE_IDS = {
2: [
// TODO: Add prod price IDs here
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

⚠️ Need prod price IDs here


const ADVANCED_TIER_PRICE_IDS = {
2: [
// TODO: Add prod price IDs here
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

⚠️ Need prod price IDs here

"price_1SO0N2AlJJEpqkPVJUa43N6P", // monthly (test)
],
3: [
// TODO: Add prod price IDs here
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

⚠️ Need prod price IDs here

Copy link
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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/utils/src/constants/pricing.tsx (1)

61-124: Add the new production price IDs before shipping.

The tier maps still contain only test IDs and // TODO placeholders. In production, subscribing to these new tiers will yield price IDs that don’t appear in PRO_TIER_PRICE_IDS, BUSINESS_TIER_PRICE_IDS, or ADVANCED_TIER_PRICE_IDS, so getPlanAndTierFromPriceId will return { plan: null, planTier: 1 }, causing subscription webhooks (e.g., updateWorkspacePlan) to bail out and leave workspaces stuck on the wrong plan. Please wire up the real prod IDs for every tier before merge.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e7171d5 and 5a50578.

📒 Files selected for processing (7)
  • apps/web/app/(ee)/api/stripe/webhook/customer-subscription-deleted.ts (2 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/utils/update-workspace-plan.ts (5 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx (1 hunks)
  • apps/web/ui/modals/manage-usage-modal.tsx (1 hunks)
  • packages/email/src/templates/upgrade-email.tsx (1 hunks)
  • packages/ui/src/icons/plan-feature-icons.tsx (2 hunks)
  • packages/utils/src/constants/pricing.tsx (13 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/email/src/templates/upgrade-email.tsx
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
📚 Learning: 2025-09-24T15:50:16.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.

Applied to files:

  • apps/web/app/(ee)/api/stripe/webhook/utils/update-workspace-plan.ts
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/settings/**/*.{js,jsx,ts,tsx} : Only use components exported by hubspot/ui-extensions in settings components

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx
📚 Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.

Applied to files:

  • apps/web/ui/modals/manage-usage-modal.tsx
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/ui/modals/manage-usage-modal.tsx
  • packages/ui/src/icons/plan-feature-icons.tsx
  • packages/utils/src/constants/pricing.tsx
📚 Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.

Applied to files:

  • apps/web/ui/modals/manage-usage-modal.tsx
🧬 Code graph analysis (3)
apps/web/app/(ee)/api/stripe/webhook/utils/update-workspace-plan.ts (1)
packages/utils/src/constants/pricing.tsx (1)
  • getPlanAndTierFromPriceId (527-545)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx (3)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-46)
packages/utils/src/constants/pricing.tsx (2)
  • SELF_SERVE_PAID_PLANS (491-493)
  • ENTERPRISE_PLAN (487-489)
packages/ui/src/slider.tsx (1)
  • Slider (19-89)
apps/web/ui/modals/manage-usage-modal.tsx (5)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-46)
packages/utils/src/constants/pricing.tsx (4)
  • SELF_SERVE_PAID_PLANS (491-493)
  • ENTERPRISE_PLAN (487-489)
  • getSuggestedPlan (602-638)
  • isDowngradePlan (579-600)
packages/ui/src/slider.tsx (1)
  • Slider (19-89)
packages/ui/src/toggle-group.tsx (1)
  • ToggleGroup (15-85)
apps/web/ui/workspaces/upgrade-plan-button.tsx (1)
  • UpgradePlanButton (12-92)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (6)
packages/ui/src/icons/plan-feature-icons.tsx (2)

6-6: LGTM!

The import of ConnectedDots4 is correctly added to support the icon update below.


69-69: LGTM! Semantic improvement.

Changing the partners icon from Users2 to ConnectedDots4 better represents the concept of a connected partner network compared to a generic users icon. This is a clear visual enhancement.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx (4)

1-52: LGTM! Well-structured expandable usage adjustment UI.

The component provides a clean expandable/collapsible interface with smooth animations and proper accessibility handling via the inert attribute (React 19). The responsive grid layout and motion transitions are well-implemented.


66-80: Previous optional chaining issue has been resolved.

The usageSteps calculation correctly extracts, deduplicates, and sorts all limit values from plans and tiers. The logic is sound: flatMap gathers base and tier limits, sorts them numerically, then uses Set for deduplication while preserving order.

Note: The past review comment about unsafe optional chaining on line 72 has been addressed—the current code correctly uses .map( without the optional ?. operator.


99-127: Previous typo has been corrected.

The render logic is well-implemented with appropriate use of NumberFlow for animated numeric display and a clever index-based slider pattern for non-linear usage steps. The "(current plan)" indicator correctly shows when the selected value matches the workspace's current limit.

Note: The past review comment about the text-content-deafult typo has been addressed—line 101 now correctly uses text-content-default.


82-95: The original review comment is incorrect; code is safe due to WorkspaceAuth protection.

The billing upgrade page at [slug]/(ee)/settings/billing/upgrade/ is wrapped by WorkspaceAuth (from the parent [slug]/layout.tsx), which checks the workspace loading state and prevents rendering children until data is available. Therefore, when AdjustUsageRow accesses workspace[workspaceLimitKey], the workspace data is guaranteed to be loaded, making the suggested guard unnecessary.

Likely an incorrect or invalid review comment.

@steven-tey
Copy link
Collaborator

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 7, 2025

✅ Actions performed

Full review triggered.

Copy link
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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
apps/web/app/(ee)/api/stripe/webhook/customer-subscription-deleted.ts (1)

111-125: Reset planTier for free downgrades

When the subscription is deleted we force the workspace onto the Free plan, but the update payload never resets planTier. Any workspace that cancels from Advanced Tier 2/3 (or Business Tier 2) will end up with plan = "free" but planTier still stuck at the higher tier. Downstream flows (usage management, plan comparison, webhook utilities) now rely on planTier to decide limits, so leaving the old tier causes inconsistent limits and UI messaging. Please explicitly set the tier back to 1 when downgrading to Free.

       data: {
         plan: "free",
+        planTier: 1,
         usageLimit: FREE_PLAN.limits.clicks!,
         linksLimit: FREE_PLAN.limits.links!,
♻️ Duplicate comments (2)
packages/email/src/templates/upgrade-email.tsx (1)

21-26: Make planTier optional in the type definition.

While planTier has a default value of 1 in the parameter list (line 21), the type definition (line 26) still requires it as planTier: number. This means callers must explicitly pass planTier, even though a sensible default exists. Making the type optional would allow callers to omit it safely.

 export default function UpgradeEmail({
   name = "Brendon Urie",
   email = "[email protected]",
   plan = "Business",
   planTier = 1,
 }: {
   name: string | null;
   email: string;
   plan: string;
-  planTier: number;
+  planTier?: number;
 }) {

This aligns with the past review feedback and improves the API ergonomics.

apps/web/ui/modals/manage-usage-modal.tsx (1)

262-279: Keep the modal type in sync

Reiterating the earlier note: the useCallback still ignores the modal props, so the returned component closes over the very first type. If a caller flips between "links" and "events" (or we reuse this hook elsewhere with dynamic props), the modal continues to render the previous configuration. Please thread type into the dependency list (or destructure it and pass it explicitly) so the callback re-renders with the latest props.

 export function useManageUsageModal(
   props: Omit<
     ManageUsageModalProps,
     "showManageUsageModal" | "setShowManageUsageModal"
   >,
 ) {
+  const { type } = props;
   const [showManageUsageModal, setShowManageUsageModal] = useState(false);

   const ManageUsageModalCallback = useCallback(() => {
     return (
       <ManageUsageModal
         showManageUsageModal={showManageUsageModal}
         setShowManageUsageModal={setShowManageUsageModal}
-        {...props}
+        type={type}
       />
     );
-  }, [showManageUsageModal, setShowManageUsageModal]);
+  }, [showManageUsageModal, setShowManageUsageModal, type]);
🧹 Nitpick comments (3)
apps/web/lib/zod/schemas/workspaces.ts (1)

31-34: Consider adding validation constraints to planTier.

The planTier field accepts any number without constraints. If tiers are expected to be positive integers within a specific range, adding validation would prevent invalid data.

For example, if tiers start at 1:

 planTier: z
   .number()
+  .int()
+  .positive()
   .nullable()
   .describe("The tier of the workspace's plan."),
apps/web/app/(ee)/api/stripe/webhook/customer-subscription-updated.ts (1)

12-12: Consider whether planTier from the price should be captured.

The code destructures only { plan } from getPlanAndTierFromPriceId({ priceId }), but the function name suggests it also returns planTier. While updateWorkspacePlan likely extracts tier information internally from the priceId, explicitly destructuring and validating planTier here would make the tier transition clearer and could help with debugging.

-  const { plan } = getPlanAndTierFromPriceId({ priceId });
+  const { plan, planTier } = getPlanAndTierFromPriceId({ priceId });

This would also allow logging the tier transition if needed:

console.log(`Updating workspace from tier ${workspace.planTier} to tier ${planTier}`);
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx (1)

98-99: Drop the stray console.log

console.log({ plans }); snuck into the render path and will spam production consoles. Please remove it.

-  console.log({ plans });
-
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16f71a3 and 5a50578.

📒 Files selected for processing (20)
  • apps/web/app/(ee)/api/stripe/webhook/checkout-session-completed.ts (4 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/customer-subscription-deleted.ts (2 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/customer-subscription-updated.ts (3 hunks)
  • apps/web/app/(ee)/api/stripe/webhook/utils/update-workspace-plan.ts (5 hunks)
  • apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts (2 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (5 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx (10 hunks)
  • apps/web/lib/zod/schemas/workspaces.ts (1 hunks)
  • apps/web/ui/modals/manage-usage-modal.tsx (1 hunks)
  • apps/web/ui/modals/upgraded-modal.tsx (1 hunks)
  • apps/web/ui/modals/welcome-modal.tsx (1 hunks)
  • apps/web/ui/workspaces/upgrade-plan-button.tsx (2 hunks)
  • packages/email/src/templates/clicks-exceeded.tsx (1 hunks)
  • packages/email/src/templates/links-limit.tsx (2 hunks)
  • packages/email/src/templates/upgrade-email.tsx (1 hunks)
  • packages/prisma/schema/workspace.prisma (1 hunks)
  • packages/ui/src/icons/plan-feature-icons.tsx (2 hunks)
  • packages/ui/src/slider.tsx (3 hunks)
  • packages/utils/src/constants/pricing.tsx (13 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
📚 Learning: 2025-10-15T01:52:37.048Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx:270-303
Timestamp: 2025-10-15T01:52:37.048Z
Learning: In React components with dropdowns or form controls that show modals for confirmation (e.g., role selection, delete confirmation), local state should be reverted to match the prop value when the modal is cancelled. This prevents the UI from showing an unconfirmed change. The solution is to either: (1) pass an onClose callback to the modal that resets the local state, or (2) observe modal visibility state and reset on close. Example context: RoleCell component in apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/members/page-client.tsx where role dropdown should revert to user.role when UpdateUserModal is cancelled.

Applied to files:

  • apps/web/ui/modals/manage-usage-modal.tsx
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/ui/modals/manage-usage-modal.tsx
  • packages/ui/src/icons/plan-feature-icons.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
  • packages/email/src/templates/clicks-exceeded.tsx
  • packages/utils/src/constants/pricing.tsx
📚 Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.

Applied to files:

  • apps/web/ui/modals/manage-usage-modal.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
📚 Learning: 2025-10-06T15:48:45.956Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2935
File: packages/prisma/schema/workspace.prisma:21-36
Timestamp: 2025-10-06T15:48:45.956Z
Learning: In the Dub repository (dubinc/dub), Prisma schema changes are not managed with separate migration files. Do not flag missing Prisma migration files when schema changes are made to files like `packages/prisma/schema/workspace.prisma` or other schema files.

Applied to files:

  • packages/prisma/schema/workspace.prisma
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
📚 Learning: 2025-09-24T15:50:16.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
  • apps/web/app/(ee)/api/stripe/webhook/utils/update-workspace-plan.ts
📚 Learning: 2025-09-24T16:10:37.349Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/ui/partners/partner-about.tsx:11-11
Timestamp: 2025-09-24T16:10:37.349Z
Learning: In the Dub codebase, the team prefers to import Icon as a runtime value from "dub/ui" and uses Icon as both a type and variable name in component props, even when this creates shadowing. This is their established pattern and should not be suggested for refactoring.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
📚 Learning: 2025-05-29T09:49:19.604Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2433
File: apps/web/ui/modals/add-payment-method-modal.tsx:60-62
Timestamp: 2025-05-29T09:49:19.604Z
Learning: The `/api/workspaces/${slug}/billing/payment-methods` POST endpoint in the billing API returns either an error (handled by response.ok check) or a response object containing a `url` property for successful requests.

Applied to files:

  • apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts
🔇 Additional comments (6)
packages/ui/src/icons/plan-feature-icons.tsx (2)

6-6: LGTM!

The import is correctly added and follows the existing import pattern.


69-69: Icon update is appropriate and verified.

The "partners" feature ID is actively used in plan pricing configuration (packages/utils/src/constants/pricing.tsx:299) and displayed in plan comparison UI. The change from Users2 to ConnectedDots4 is semantically appropriate for representing a partner network ecosystem and will render correctly in all contexts where this feature is shown.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx (1)

1-128: LGTM! Past review issues have been addressed.

The implementation correctly:

  • Uses .map() without optional chaining on line 72 (past issue resolved)
  • Uses the correct spelling text-content-default on line 101 (past typo fixed)
  • Deduplicates and sorts usage steps across plans and tiers
  • Finds the default value closest to the current workspace limit
  • Properly manages state and propagates changes via onChange
apps/web/ui/workspaces/upgrade-plan-button.tsx (1)

14-60: LGTM! Tier parameter correctly integrated.

The optional tier parameter is properly:

  • Typed as tier?: number in the component props
  • Destructured from props
  • Included in the upgrade API request payload

This aligns with the tier-aware pricing changes throughout the PR.

packages/email/src/templates/clicks-exceeded.tsx (1)

74-74: Verify the hardcoded pricing URL is intentional.

The link was changed from the dynamic nextPlan.link to the static "https://dub.co/pricing". While this may align with the pricing restructuring in this PR, please confirm:

  • Is nextPlan.link no longer available or valid?
  • Should all plan upgrade links in emails now point to the general pricing page instead of plan-specific pages?

If the change is intentional, consider whether other email templates need similar updates for consistency.

apps/web/ui/modals/upgraded-modal.tsx (1)

42-42: LGTM! Function signature update aligns with tier-aware pricing.

The change from getPlanDetails(planId) to getPlanDetails({ plan: planId }) correctly adapts to the new tier-aware API. Since planTier is not provided here, it will use the default tier value (tier 1) as defined in the utility function, which is appropriate for this upgrade modal context.

@steven-tey steven-tey changed the base branch from main to remove-rollup-temp-fix November 7, 2025 20:44
Base automatically changed from remove-rollup-temp-fix to main November 7, 2025 22:02
Copy link
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.

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts (1)

87-87: Consider including tier in the success URL.

The success URL includes plan and period query parameters but omits tier. This could make it harder to track which specific tier users upgraded to, especially for analytics or onboarding flows.

Apply this diff to include tier in the success URL:

-      success_url: `${APP_DOMAIN}/${workspace.slug}?${onboarding ? "onboarded" : "upgraded"}=true&plan=${plan}&period=${period}`,
+      success_url: `${APP_DOMAIN}/${workspace.slug}?${onboarding ? "onboarded" : "upgraded"}=true&plan=${plan}&period=${period}&tier=${tier}`,
♻️ Duplicate comments (1)
packages/utils/src/constants/pricing.tsx (1)

61-120: Verify the production price IDs are correct before deployment.

The tier price ID arrays now contain IDs marked as (prod), which addresses the blocker from previous reviews. However, given the critical nature of production billing configuration, please confirm these are the correct production Stripe price IDs created for the new tiered plans:

  • PRO_TIER_PRICE_IDS[2]: Lines 63-64
  • BUSINESS_TIER_PRICE_IDS[2]: Lines 91-92
  • ADVANCED_TIER_PRICE_IDS[2]: Lines 109-110
  • ADVANCED_TIER_PRICE_IDS[3]: Lines 115-116

Once verified, the implementation correctly maintains both test and prod IDs for multi-environment support.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea52eb1 and 3779167.

📒 Files selected for processing (7)
  • apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (5 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx (11 hunks)
  • apps/web/ui/workspaces/upgrade-plan-button.tsx (2 hunks)
  • packages/email/src/templates/clicks-exceeded.tsx (2 hunks)
  • packages/email/src/templates/links-limit.tsx (2 hunks)
  • packages/utils/src/constants/pricing.tsx (13 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/email/src/templates/clicks-exceeded.tsx
  • apps/web/ui/workspaces/upgrade-plan-button.tsx
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/settings/**/*.{js,jsx,ts,tsx} : Only use components exported by hubspot/ui-extensions in settings components

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
📚 Learning: 2025-08-26T15:05:55.081Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2736
File: apps/web/lib/swr/use-bounty.ts:11-16
Timestamp: 2025-08-26T15:05:55.081Z
Learning: In the Dub codebase, workspace authentication and route structures prevent endless loading states when workspaceId or similar route parameters are missing, so gating SWR loading states on parameter availability is often unnecessary.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
  • apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts
📚 Learning: 2025-05-29T04:45:18.504Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2448
File: packages/email/src/templates/partner-program-summary.tsx:0-0
Timestamp: 2025-05-29T04:45:18.504Z
Learning: In the PartnerProgramSummary email template (packages/email/src/templates/partner-program-summary.tsx), the stat titles are hardcoded constants ("Clicks", "Leads", "Sales", "Earnings") that will always match the ICONS object keys after toLowerCase() conversion, so icon lookup failures are not possible.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx
  • packages/utils/src/constants/pricing.tsx
📚 Learning: 2025-09-24T15:50:16.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
📚 Learning: 2025-09-24T16:10:37.349Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/ui/partners/partner-about.tsx:11-11
Timestamp: 2025-09-24T16:10:37.349Z
Learning: In the Dub codebase, the team prefers to import Icon as a runtime value from "dub/ui" and uses Icon as both a type and variable name in component props, even when this creates shadowing. This is their established pattern and should not be suggested for refactoring.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
  • packages/email/src/templates/links-limit.tsx
🧬 Code graph analysis (4)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (2)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-46)
apps/web/ui/modals/manage-usage-modal.tsx (1)
  • useManageUsageModal (261-286)
apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts (2)
apps/web/lib/zod/schemas/misc.ts (1)
  • booleanQuerySchema (24-29)
apps/web/lib/stripe/index.ts (1)
  • stripe (4-10)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx (4)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-46)
packages/utils/src/constants/pricing.tsx (4)
  • getSuggestedPlan (608-644)
  • PlanDetails (14-50)
  • PLANS (130-485)
  • isDowngradePlan (585-606)
apps/web/ui/workspaces/upgrade-plan-button.tsx (1)
  • UpgradePlanButton (12-92)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx (1)
  • AdjustUsageRow (8-52)
packages/email/src/templates/links-limit.tsx (1)
packages/email/src/react-email.d.ts (3)
  • Link (14-14)
  • Text (15-15)
  • Section (8-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (15)
packages/email/src/templates/links-limit.tsx (2)

1-1: LGTM: Import change aligns with URL construction pattern.

The addition of APP_DOMAIN to the imports is appropriate, as the file now constructs billing settings URLs using this constant instead of plan-specific upgrade links.


108-108: LGTM: CTA button URL correctly updated.

The primary call-to-action button now uses the consistent billing settings URL pattern, directing users to the appropriate upgrade page.

apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts (2)

10-18: Schema-based validation is a solid improvement.

The upgradePlanSchema provides clear, type-safe validation for all upgrade parameters. The tier validation with min/max bounds and default value is particularly well-implemented.


22-24: No action needed — ZodError handling is already properly implemented.

The schema validation errors are already handled correctly. When upgradePlanSchema.parse() throws a ZodError, it's caught by the withWorkspace wrapper's try-catch block (line 414), which calls handleAndReturnErrorResponse. This function uses handleApiError that explicitly checks for ZodError instances (line 128 in errors.ts) and converts them to user-friendly API responses with HTTP 422 status using the fromZodError utility.

packages/utils/src/constants/pricing.tsx (5)

173-185: LGTM! Tier structure is well-designed.

The tiered pricing and limits are correctly defined:

  • Tier 2 pricing scales proportionally with limit increases
  • Only variable limits (links/clicks) are overridden at the tier level
  • Price ID references correctly point to the tier-specific arrays

The structure allows clean tier-aware enrichment via enrichPlanWithTierData.

Also applies to: 261-273, 368-391


501-529: LGTM! Clean tier enrichment implementation.

The function correctly:

  • Guards tier data access with planTier > 1 check (line 506)
  • Merges tier-specific limits and pricing with base plan data
  • Updates feature text for variable metrics (clicks/links) to reflect tier values
  • Preserves all base plan properties while overriding only tier-specific fields

531-551: LGTM! Correct price ID to plan/tier resolution.

The function properly:

  • Resolves price IDs to their corresponding plan (line 536)
  • Determines the correct tier by checking tier-specific price ID arrays (lines 539-545)
  • Defaults to tier 1 for base plan subscriptions
  • Returns enriched plan data with tier-specific limits and pricing applied

608-644: LGTM! Correct tier-aware plan recommendation.

The function properly:

  • Starts tier iteration from 1 (line 622), avoiding the tier-0 issue flagged in previous reviews
  • Filters tier keys to only include tier ≥ 2 (line 626), ensuring base plan uses tier 1
  • Checks tier-specific limits against usage requirements (lines 628-630)
  • Returns the first (lowest-cost) plan+tier combination that satisfies requirements
  • Falls back to Enterprise for unlimited needs (line 643)

585-606: LGTM! Correct downgrade detection with tier support.

The updated function properly handles both:

  • Cross-plan downgrades (e.g., Business → Pro) via plan index comparison (line 603)
  • Within-plan tier downgrades (e.g., Pro Tier 2 → Pro Tier 1) via tier comparison (line 604)

Defaulting undefined tiers to 1 (line 604) is correct for base plan subscriptions.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsx (3)

263-286: LGTM! Clean modal integration with proper typing.

The changes correctly:

  • Narrow the id type to "links" | "events" (line 272) to match resources with tier management
  • Initialize useManageUsageModal with the resource type (lines 283-285)
  • Retrieve planTier from workspace context for tier-aware operations (line 281)

The type safety ensures only supported resources can open the manage usage modal.


302-403: LGTM! Structural change supports modal integration.

The wrapper div (line 302) correctly positions:

  • The ManageUsageModal component (line 303)
  • The main usage button (lines 304-403)
  • The Manage/Upgrade button (added below at lines 404-413)

All existing usage display logic, progress calculations, and loading states are preserved.


404-413: LGTM! Smart tier management UX.

The Manage/Upgrade button correctly:

  • Appears only for tier-manageable resources ("links" | "events") and non-enterprise plans (line 404)
  • Adapts to usage levels: shows "Upgrade" in primary variant when usage ≥ 90%, otherwise "Manage" in secondary (lines 408-409)
  • Triggers the tier management modal (line 407)

The conditional styling provides clear visual priority when users approach their limits.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx (3)

279-286: LGTM! Clean usage adjustment integration.

The AdjustUsageRow component correctly:

  • Receives callbacks that update the eventsUsage and linksUsage state (lines 283-284)
  • Triggers recommendedPlan recalculation via the useMemo dependency chain
  • Is positioned in the sticky header area for easy access

This enables users to simulate usage scenarios and see recommended tier changes in real-time.


146-263: LGTM! Proper tier-aware upgrade/downgrade logic.

The changes correctly integrate tier awareness:

  1. Plan card rendering (line 146): Destructures plan and planTier for each card
  2. Current plan detection (line 155): Compares both plan name and planTier === currentPlanTier
  3. Downgrade detection (lines 165-170): Passes currentTier and newTier to enhanced isDowngradePlan
  4. Recommended badge (lines 188-197): Displays when the recommended plan and tier match, excluding downgrades and current plan
  5. Upgrade button (line 246): Passes tier prop when planTier > 1 to enable tier-specific checkout

The conditional logic ensures proper UX for tier upgrades, downgrades, and recommendations.


324-337: LGTM! Table rendering updated for new data structure.

The comparison table correctly:

  • Destructures { plan } from the tier-aware plans array (lines 325, 336)
  • Uses plan.name for headers and feature lookups (lines 326, 337)

The existing feature comparison logic works seamlessly with the new structure.

Copy link
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.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3779167 and 7baf25e.

📒 Files selected for processing (4)
  • apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx (1 hunks)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx (11 hunks)
  • packages/email/src/templates/links-limit.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.
📚 Learning: 2025-05-29T09:49:19.604Z
Learnt from: devkiran
Repo: dubinc/dub PR: 2433
File: apps/web/ui/modals/add-payment-method-modal.tsx:60-62
Timestamp: 2025-05-29T09:49:19.604Z
Learning: The `/api/workspaces/${slug}/billing/payment-methods` POST endpoint in the billing API returns either an error (handled by response.ok check) or a response object containing a `url` property for successful requests.

Applied to files:

  • apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts
📚 Learning: 2025-10-15T01:05:43.266Z
Learnt from: steven-tey
Repo: dubinc/dub PR: 2958
File: apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx:432-457
Timestamp: 2025-10-15T01:05:43.266Z
Learning: In apps/web/app/app.dub.co/(dashboard)/[slug]/settings/members/page-client.tsx, defer refactoring the custom MenuItem component (lines 432-457) to use the shared dub/ui MenuItem component to a future PR, as requested by steven-tey.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
📚 Learning: 2025-09-19T18:46:43.787Z
Learnt from: CR
Repo: dubinc/dub PR: 0
File: packages/hubspot-app/CLAUDE.md:0-0
Timestamp: 2025-09-19T18:46:43.787Z
Learning: Applies to packages/hubspot-app/app/settings/**/*.{js,jsx,ts,tsx} : Only use components exported by hubspot/ui-extensions in settings components

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx
📚 Learning: 2025-09-24T16:10:37.349Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/ui/partners/partner-about.tsx:11-11
Timestamp: 2025-09-24T16:10:37.349Z
Learning: In the Dub codebase, the team prefers to import Icon as a runtime value from "dub/ui" and uses Icon as both a type and variable name in component props, even when this creates shadowing. This is their established pattern and should not be suggested for refactoring.

Applied to files:

  • packages/email/src/templates/links-limit.tsx
  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
📚 Learning: 2025-09-24T15:50:16.414Z
Learnt from: TWilson023
Repo: dubinc/dub PR: 2872
File: apps/web/app/(ee)/partners.dub.co/(dashboard)/profile/profile-details-form.tsx:180-189
Timestamp: 2025-09-24T15:50:16.414Z
Learning: TWilson023 prefers to keep security vulnerability fixes separate from refactoring PRs when the vulnerable code is existing and was only moved/relocated rather than newly introduced.

Applied to files:

  • apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
🧬 Code graph analysis (4)
apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts (3)
apps/web/lib/zod/schemas/misc.ts (1)
  • booleanQuerySchema (24-29)
apps/web/lib/stripe/index.ts (1)
  • stripe (4-10)
apps/web/lib/api/errors.ts (1)
  • DubApiError (75-92)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx (3)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-46)
packages/utils/src/constants/pricing.tsx (3)
  • SELF_SERVE_PAID_PLANS (495-497)
  • ENTERPRISE_PLAN (491-493)
  • getPlanDetails (553-565)
packages/ui/src/slider.tsx (1)
  • Slider (19-89)
packages/email/src/templates/links-limit.tsx (1)
packages/email/src/react-email.d.ts (3)
  • Link (14-14)
  • Text (15-15)
  • Section (8-8)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx (3)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-46)
packages/utils/src/constants/pricing.tsx (4)
  • getSuggestedPlan (608-644)
  • PlanDetails (14-50)
  • PLANS (130-485)
  • isDowngradePlan (585-606)
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx (1)
  • AdjustUsageRow (14-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (12)
packages/email/src/templates/links-limit.tsx (4)

1-1: LGTM!

The addition of APP_DOMAIN to imports is appropriate and used consistently throughout the file to construct billing settings URLs.


84-91: Past grammatical issue resolved.

The sentence now correctly reads "you'll need to upgrade to a higher plan to add more links." The missing "to" has been added, and the text flows naturally.


95-102: Past redundancy issue resolved.

The redundant "upgrade to the" phrase has been removed. The sentence now correctly reads "Once you hit your limit, you'll need to upgrade to a higher plan to add more links."


108-108: LGTM!

The CTA button href is consistent with the other billing settings links in the template and aligns with the PR's simplified upgrade flow.

apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts (1)

10-36: LGTM! Schema validation and price lookup are well-implemented.

The upgradePlanSchema provides comprehensive input validation with clear constraints, and the dynamic lookup key generation correctly handles tier-based pricing. The price validation at lines 31-36 prevents downstream errors with a descriptive message that includes the lookupKey for debugging.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsx (3)

78-78: Optional chaining fix confirmed.

The unsafe optional chaining has been corrected. Previously flagged issue is now resolved.


122-122: Typo fix confirmed.

The class name typo has been corrected from text-content-deafult to text-content-default.


14-58: Clean animation and layout implementation.

The collapsible UI with motion animation, backdrop grid, and responsive two-column layout is well-structured and provides good UX.

apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx (4)

48-54: Tier-aware plan logic is well-integrated.

The planTier extraction from workspace data (with a sensible default of 1) and the plans array construction that leverages recommendedPlan when available create a cohesive tier-aware upgrade experience.

Also applies to: 83-96


186-194: Recommended badge logic is correct.

The conditional rendering properly checks that the plan matches the recommendation, is not a downgrade, and is not the current plan before showing the "Recommended" badge.


230-261: Upgrade button logic handles all plan states correctly.

The Enterprise plan contact flow, tier parameter (only passed when > 1), and the disabled/current/downgrade states are all handled appropriately.


277-284: Usage adjustment component is properly wired.

The AdjustUsageRow callbacks correctly update the parent state for events and links usage, enabling the recommendation logic.

@steven-tey steven-tey merged commit b6f6c2a into main Nov 7, 2025
8 checks passed
@steven-tey steven-tey deleted the pricing-updates branch November 7, 2025 22:40
@coderabbitai coderabbitai bot mentioned this pull request Nov 14, 2025
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.

3 participants