Fix: disable free plan in selection if one already exists#2662
Fix: disable free plan in selection if one already exists#2662ItzNotABug merged 1 commit intomainfrom
Conversation
Console (appwrite/console)Project ID: Sites (1)
Tip SSR frameworks are fully supported with configurable build runtimes |
WalkthroughThis change set refactors the billing plan selection workflow to introduce free organization tracking and modernize reactive patterns. The PlanSelection component gains a Tooltip that displays "Only 1 free organization is allowed per account" using a new shouldShowTooltip helper and accepts a new anyOrgFree boolean prop. Two page components (+page.svelte files) transition from legacy Svelte export patterns to Svelte 5's $props() and $state() syntax, wrapping reactive variables in $state() decorators. The anyOrgFree flag propagates through the component hierarchy from page data to inform plan selection behavior. A coupon fetch handler is simplified by removing an intermediate response variable. Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Pre-merge checks and finishing touches✅ Passed checks (3 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/lib/components/billing/planSelection.svelte (1)
21-24: Consider renaming for clarity.The function name
shouldShowTooltipis misleading because it actually returns whether to disable the tooltip (since it's used withdisabled={shouldShowTooltip(plan)}). While the logic is correct, this could confuse future maintainers.Consider renaming to better reflect its purpose:
-function shouldShowTooltip(plan: Plan) { +function shouldDisableTooltip(plan: Plan) { if (plan.$id !== BillingPlan.FREE) return true; else return !anyOrgFree; }Or inverting the logic with a clearer name:
-function shouldShowTooltip(plan: Plan) { - if (plan.$id !== BillingPlan.FREE) return true; - else return !anyOrgFree; +function shouldShowFreeTooltip(plan: Plan) { + return plan.$id === BillingPlan.FREE && anyOrgFree; }Then update line 29:
-<Tooltip disabled={shouldShowTooltip(plan)} maxWidth="fit-content"> +<Tooltip disabled={!shouldShowFreeTooltip(plan)} maxWidth="fit-content">
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/lib/components/billing/planSelection.svelte(2 hunks)src/routes/(console)/create-organization/+page.svelte(4 hunks)src/routes/(console)/organization-[organization]/change-plan/+page.svelte(1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx,svelte}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx,svelte}: Import reusable modules from the src/lib directory using the $lib alias
Use minimal comments in code; reserve comments for TODOs or complex logic explanations
Use $lib, $routes, and $themes aliases instead of relative paths for module imports
Files:
src/routes/(console)/create-organization/+page.sveltesrc/routes/(console)/organization-[organization]/change-plan/+page.sveltesrc/lib/components/billing/planSelection.svelte
src/routes/**/*.svelte
📄 CodeRabbit inference engine (AGENTS.md)
Use SvelteKit file conventions: +page.svelte for components, +page.ts for data loaders, +layout.svelte for wrappers, +error.svelte for error handling, and dynamic route params in square brackets like [param]
Files:
src/routes/(console)/create-organization/+page.sveltesrc/routes/(console)/organization-[organization]/change-plan/+page.svelte
**/*.{ts,tsx,js,jsx,svelte,json}
📄 CodeRabbit inference engine (AGENTS.md)
Use 4 spaces for indentation, single quotes, 100 character line width, and no trailing commas per Prettier configuration
Files:
src/routes/(console)/create-organization/+page.sveltesrc/routes/(console)/organization-[organization]/change-plan/+page.sveltesrc/lib/components/billing/planSelection.svelte
**/*.svelte
📄 CodeRabbit inference engine (AGENTS.md)
Use Svelte 5 + SvelteKit 2 syntax with TypeScript for component development
Files:
src/routes/(console)/create-organization/+page.sveltesrc/routes/(console)/organization-[organization]/change-plan/+page.sveltesrc/lib/components/billing/planSelection.svelte
src/routes/**
📄 CodeRabbit inference engine (AGENTS.md)
Configure dynamic routes using SvelteKit convention with [param] syntax in route directory names
Files:
src/routes/(console)/create-organization/+page.sveltesrc/routes/(console)/organization-[organization]/change-plan/+page.svelte
src/lib/components/**/*.svelte
📄 CodeRabbit inference engine (AGENTS.md)
Use PascalCase for component file names and place them in src/lib/components/[feature]/ directory structure
Files:
src/lib/components/billing/planSelection.svelte
🧠 Learnings (6)
📚 Learning: 2025-11-25T03:15:27.539Z
Learnt from: CR
Repo: appwrite/console PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:15:27.539Z
Learning: Applies to src/routes/**/*.svelte : Use SvelteKit file conventions: +page.svelte for components, +page.ts for data loaders, +layout.svelte for wrappers, +error.svelte for error handling, and dynamic route params in square brackets like [param]
Applied to files:
src/routes/(console)/create-organization/+page.svelte
📚 Learning: 2025-10-05T09:41:40.439Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2398
File: src/routes/(console)/verify-email/+page.svelte:48-51
Timestamp: 2025-10-05T09:41:40.439Z
Learning: In SvelteKit 5, `page` imported from `$app/state` is a reactive state object (using runes), not a store. It should be accessed as `page.data` without the `$` prefix, unlike the store-based `$page` from `$app/stores` in earlier versions.
Applied to files:
src/routes/(console)/create-organization/+page.svelte
📚 Learning: 2025-10-13T05:13:54.542Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2413
File: src/routes/(console)/project-[region]-[project]/databases/table.svelte:33-39
Timestamp: 2025-10-13T05:13:54.542Z
Learning: In Svelte 5, `import { page } from '$app/state'` provides a reactive state proxy that can be accessed directly (e.g., `page.params`), unlike the older `import { page } from '$app/stores'` which returns a readable store requiring the `$page` syntax for auto-subscription in components.
Applied to files:
src/routes/(console)/create-organization/+page.svelte
📚 Learning: 2025-11-25T03:15:27.539Z
Learnt from: CR
Repo: appwrite/console PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:15:27.539Z
Learning: Applies to src/lib/components/**/*.svelte : Use PascalCase for component file names and place them in src/lib/components/[feature]/ directory structure
Applied to files:
src/routes/(console)/create-organization/+page.svelte
📚 Learning: 2025-11-25T03:15:27.539Z
Learnt from: CR
Repo: appwrite/console PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:15:27.539Z
Learning: Applies to **/*.{ts,tsx,js,jsx,svelte} : Use $lib, $routes, and $themes aliases instead of relative paths for module imports
Applied to files:
src/routes/(console)/create-organization/+page.svelte
📚 Learning: 2025-10-13T05:16:07.656Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2413
File: src/routes/(console)/project-[region]-[project]/databases/database-[database]/header.svelte:54-58
Timestamp: 2025-10-13T05:16:07.656Z
Learning: In SvelteKit apps, shared layout components (like headers) that use `$derived(page.data.*)` should use optional chaining when accessing properties that may not be present on all routes. During page transitions, reactive statements can briefly evaluate with different page.data structures, so optional chaining prevents runtime errors when navigating between routes with different data shapes (e.g., between `/databases` and `/databases/database-[database]`).
Applied to files:
src/routes/(console)/create-organization/+page.svelte
🔇 Additional comments (8)
src/routes/(console)/organization-[organization]/change-plan/+page.svelte (2)
327-330: LGTM: Clean integration of free organization check.The new
anyOrgFreeprop correctly propagates the free organization status to the plan selection component, enabling the tooltip and disable logic.
297-300: Good defensive logic for preventing invalid downgrades.The button disable condition correctly prevents downgrading to the Free plan when a free organization already exists, providing a second layer of protection beyond the UI-level disabling.
src/lib/components/billing/planSelection.svelte (3)
33-33: Correct disable logic for free plan restriction.The
disabledcondition properly prevents selection of the Free plan when a free organization already exists, which aligns with the PR objective.
52-54: Clear and helpful tooltip message.The tooltip content effectively communicates the restriction to users in plain language.
34-34: Verify iftooltipShowprop is still needed onLabelCard.The
tooltipShowprop onLabelCardmay be redundant now that the card is wrapped in aTooltipcomponent. Confirm whetherLabelCarduses this prop internally or if it can be safely removed to avoid duplicate tooltip behavior.src/routes/(console)/create-organization/+page.svelte (3)
49-59: Good simplification of coupon fetch logic.Removing the intermediate variable makes the code more concise without affecting functionality.
25-42: Svelte 5 migration follows correct patterns (with one exception).The migration to
$props()and$state()follows Svelte 5 conventions correctly, with proper TypeScript typing. However, see the critical issue on line 32 regardingisSubmitting.
28-28: No action needed—resolve('/(console)')is the appropriate choice.Using
resolve()for navigation initialization is correct. It resolves app-root paths through SvelteKit's navigation system, unlikebasewhich is a static asset prefix. These serve different purposes and are not interchangeable.

What does this PR do?
Mark Free as disabled when one already exists on an account.
Test Plan
Manual.
Related PRs and Issues
N/A.
Have you read the Contributing Guidelines on issues?
Yes.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.