-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Pricing updates #3034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pricing updates #3034
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughIntroduce tier-aware pricing: add Changes
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Files/areas needing extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 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 |
|
|
||
| const PRO_TIER_PRICE_IDS = { | ||
| 2: [ | ||
| // TODO: Add prod price IDs here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| const BUSINESS_TIER_PRICE_IDS = { | ||
| 2: [ | ||
| // TODO: Add prod price IDs here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
|
||
| const ADVANCED_TIER_PRICE_IDS = { | ||
| 2: [ | ||
| // TODO: Add prod price IDs here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| "price_1SO0N2AlJJEpqkPVJUa43N6P", // monthly (test) | ||
| ], | ||
| 3: [ | ||
| // TODO: Add prod price IDs here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this 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
// TODOplaceholders. In production, subscribing to these new tiers will yield price IDs that don’t appear inPRO_TIER_PRICE_IDS,BUSINESS_TIER_PRICE_IDS, orADVANCED_TIER_PRICE_IDS, sogetPlanAndTierFromPriceIdwill 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
📒 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.tsxpackages/ui/src/icons/plan-feature-icons.tsxpackages/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
ConnectedDots4is correctly added to support the icon update below.
69-69: LGTM! Semantic improvement.Changing the
partnersicon fromUsers2toConnectedDots4better 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
inertattribute (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-deafulttypo has been addressed—line 101 now correctly usestext-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 byWorkspaceAuth(from the parent[slug]/layout.tsx), which checks the workspace loading state and prevents rendering children until data is available. Therefore, whenAdjustUsageRowaccessesworkspace[workspaceLimitKey], the workspace data is guaranteed to be loaded, making the suggested guard unnecessary.Likely an incorrect or invalid review comment.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this 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 downgradesWhen 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 withplan = "free"butplanTierstill stuck at the higher tier. Downstream flows (usage management, plan comparison, webhook utilities) now rely onplanTierto decide limits, so leaving the old tier causes inconsistent limits and UI messaging. Please explicitly set the tier back to1when 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
planTierhas a default value of1in the parameter list (line 21), the type definition (line 26) still requires it asplanTier: number. This means callers must explicitly passplanTier, 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 syncReiterating the earlier note: the
useCallbackstill ignores the modal props, so the returned component closes over the very firsttype. 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 threadtypeinto 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
planTierfield 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 }fromgetPlanAndTierFromPriceId({ priceId }), but the function name suggests it also returnsplanTier. WhileupdateWorkspacePlanlikely extracts tier information internally from thepriceId, explicitly destructuring and validatingplanTierhere 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
📒 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.tsxpackages/ui/src/icons/plan-feature-icons.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/plan-usage.tsxpackages/email/src/templates/clicks-exceeded.tsxpackages/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.tsxapps/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.tsxapps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/adjust-usage-row.tsxapps/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.tsxapps/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 fromUsers2toConnectedDots4is 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-defaulton 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
onChangeapps/web/ui/workspaces/upgrade-plan-button.tsx (1)
14-60: LGTM! Tier parameter correctly integrated.The optional
tierparameter is properly:
- Typed as
tier?: numberin 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.linkto the static"https://dub.co/pricing". While this may align with the pricing restructuring in this PR, please confirm:
- Is
nextPlan.linkno 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)togetPlanDetails({ plan: planId })correctly adapts to the new tier-aware API. SinceplanTieris 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.
apps/web/app/api/workspaces/[idOrSlug]/billing/upgrade/route.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
planandperiodquery parameters but omitstier. 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-64BUSINESS_TIER_PRICE_IDS[2]: Lines 91-92ADVANCED_TIER_PRICE_IDS[2]: Lines 109-110ADVANCED_TIER_PRICE_IDS[3]: Lines 115-116Once 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
📒 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.tsxapps/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.tsxapps/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.tsxpackages/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.tsxpackages/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_DOMAINto 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
upgradePlanSchemaprovides 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 aZodError, it's caught by thewithWorkspacewrapper's try-catch block (line 414), which callshandleAndReturnErrorResponse. This function useshandleApiErrorthat explicitly checks forZodErrorinstances (line 128 in errors.ts) and converts them to user-friendly API responses with HTTP 422 status using thefromZodErrorutility.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 > 1check (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
idtype to"links" | "events"(line 272) to match resources with tier management- Initialize
useManageUsageModalwith the resource type (lines 283-285)- Retrieve
planTierfrom 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
AdjustUsageRowcomponent correctly:
- Receives callbacks that update the
eventsUsageandlinksUsagestate (lines 283-284)- Triggers
recommendedPlanrecalculation 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:
- Plan card rendering (line 146): Destructures
planandplanTierfor each card- Current plan detection (line 155): Compares both plan name and
planTier === currentPlanTier- Downgrade detection (lines 165-170): Passes
currentTierandnewTierto enhancedisDowngradePlan- Recommended badge (lines 188-197): Displays when the recommended plan and tier match, excluding downgrades and current plan
- Upgrade button (line 246): Passes
tierprop whenplanTier > 1to enable tier-specific checkoutThe 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.namefor headers and feature lookups (lines 326, 337)The existing feature comparison logic works seamlessly with the new structure.
apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/settings/billing/upgrade/page-client.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
📒 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.tsxapps/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.tsxapps/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_DOMAINto 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-deafulttotext-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.
For adding the new products/prices to prod:
advanced2_monthly,advanced3_yearly)Summary by CodeRabbit
New Features
UI
Bug Fixes