Skip to content

Conversation

@ItzNotABug
Copy link
Member

@ItzNotABug ItzNotABug commented Dec 18, 2025

What does this PR do?

(Provide a description of what this PR does.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

Summary by CodeRabbit

Release Notes

  • New Features

    • Unified plan management system with improved plan selection and tier navigation
  • Bug Fixes

    • Enhanced error handling for payment method operations and billing workflows
  • Refactor

    • Consolidated billing operations under organization and account management APIs
    • Streamlined payment method and billing address handling
    • Improved navigation routing across billing flows
    • Modernized type system for billing and organization data structures

✏️ Tip: You can customize this high-level summary in your review settings.

@ItzNotABug ItzNotABug self-assigned this Dec 18, 2025
@appwrite
Copy link

appwrite bot commented Dec 18, 2025

Console (appwrite/console)

Project ID: 688b7bf400350cbd60e9

Sites (1)
Site Status Logs Preview QR
 console-stage
688b7cf6003b1842c9dc
Ready Ready View Logs Preview URL QR Code

Tip

Storage files get ClamAV malware scanning and encryption by default

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 18, 2025

Walkthrough

This pull request performs a comprehensive architectural refactoring of the billing and organization management system. The changes migrate from custom billing SDK types to Appwrite Console's centralized Models namespace, consolidate API calls from the billing namespace to organizations/account/console namespaces, and introduce new billing helper functions for plan management. The refactoring includes removing deprecated types from the billing SDK, updating component prop types, restructuring API payloads across 100+ files, and introducing cloud-aware organization fetching via a new getTeamOrOrganizationList helper. Overall control flow and user-facing behavior remain preserved while the underlying data types and API surfaces are modernized.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Billing SDK Refactor' directly and clearly summarizes the primary change in the changeset - a comprehensive refactoring of billing-related SDK interfaces and API surface.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@ItzNotABug ItzNotABug marked this pull request as ready for review December 31, 2025 13:01
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: 17

Caution

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

⚠️ Outside diff range comments (7)
src/routes/(console)/organization-[organization]/billing/availableCredit.svelte (1)

36-51: Add error handling to the API request.

The request() function lacks error handling. If the API call fails, the UI remains in an inconsistent state with no feedback to the user. This is inconsistent with similar components in the billing directory, which all wrap SDK calls in try-catch blocks.

🔎 Proposed fix to add error handling
 async function request() {
     if (!$organization?.$id) return;
 
     // fast path return!
     if (!areCreditsSupported) return;
 
     /**
      * The initial `creditList` from `+page.ts` can include up to 25 items by default.
      *
      * Technically, we could reuse that for offsets < 25 (i.e., the first 5 pages with limit = 5)
      * to avoid an extra request. But for now, we always fetch fresh data.
      */
-    creditList = await sdk.forConsole.organizations.listCredits({
-        organizationId: $organization.$id,
-        queries: [Query.limit(limit), Query.offset(offset)]
-    });
+    try {
+        creditList = await sdk.forConsole.organizations.listCredits({
+            organizationId: $organization.$id,
+            queries: [Query.limit(limit), Query.offset(offset)]
+        });
+    } catch (error) {
+        console.error('Failed to load credits:', error);
+        // Consider showing user feedback (toast/alert) here
+        return;
+    }
 
     creditList = {
src/lib/components/billing/selectPaymentMethod.svelte (1)

25-29: Avoid manually constructing SDK model objects.

Manually spreading and reconstructing Models.PaymentMethodList violates the principle that SDK models should be treated as immutable data from the API. This pattern can break if the SDK type has readonly properties, class methods, or internal state.

Consider refetching the payment methods list after adding a new card, or check if the SDK provides a method to update the list. The existing invalidate calls on lines 32-35 suggest this data will be refreshed anyway.

💡 Suggested refactor

Since you're already invalidating dependencies, consider removing the manual update and relying on the data refetch:

 async function cardSaved(card: Models.PaymentMethod) {
     value = card.$id;
 
-    if (value) {
-        methods = {
-            ...methods,
-            total: methods.total + 1,
-            paymentMethods: [...methods.paymentMethods, card]
-        };
-    }
-
     await Promise.all([
         invalidate(Dependencies.UPGRADE_PLAN),
         invalidate(Dependencies.ORGANIZATION)
     ]);
 }

If immediate UI update is required before refetch, verify that the SDK models support this mutation pattern or use a local state approach that doesn't mutate the SDK model.

src/lib/stores/billing.ts (1)

534-552: Use organizations.getPaymentMethod instead of account.getPaymentMethod.

checkForMandate retrieves an organization's payment method but uses sdk.forConsole.account.getPaymentMethod without passing the organization ID. This differs from paymentExpired (line 470) which uses sdk.forConsole.organizations.getPaymentMethod with organizationId. For consistency and to ensure proper organization-level access control, use the same endpoint pattern as paymentExpired:

const paymentMethod = await sdk.forConsole.organizations.getPaymentMethod({
    organizationId: org.$id,
    paymentMethodId: paymentId
});
src/routes/(console)/create-organization/+page.ts (1)

45-57: Missing await in getCoupon causes the function to return a Promise instead of resolving it.

The try/catch block won't catch any errors from the SDK call because the promise is returned directly without awaiting. This means errors will propagate as unhandled promise rejections rather than being caught and returning null.

🔎 Proposed fix
 async function getCoupon(url: URL): Promise<Models.Coupon | null> {
     if (url.searchParams.has('code')) {
         const coupon = url.searchParams.get('code');
         try {
-            return sdk.forConsole.account.getCoupon({
+            return await sdk.forConsole.account.getCoupon({
                 couponId: coupon
             });
         } catch (e) {
             return null;
         }
     }
     return null;
 }
src/routes/(console)/organization-[organization]/billing/wizard/addCredit.svelte (1)

26-31: Incorrect error wrapping loses error details.

throw new Error(error) will call error.toString() which typically results in [object Object]. This loses the original error's stack trace and structured information.

🔎 Proposed fix
         } catch (error) {
             couponData.code = coupon;
             couponData.status = 'error';
             $wizard.interceptorNotificationEnabled = false;
-            throw new Error(error);
+            throw error;
         }
src/routes/(public)/sites/deploy/+page.ts (1)

72-77: Inconsistent API call syntax - uses positional arguments instead of object syntax.

This file uses positional arguments for organizations.create:

await sdk.forConsole.organizations.create(
    ID.unique(),
    'Personal Projects',
    BillingPlan.FREE,
    null
);

Other files in this PR (e.g., src/routes/(public)/template-[template]/+page.ts lines 43-47 and src/routes/(public)/sites/deploy/+page.ts lines 88-92) use object syntax:

await sdk.forConsole.organizations.create({
    organizationId: ID.unique(),
    name: 'Personal Projects',
    billingPlan: BillingPlan.FREE
});

This positional syntax may be incorrect or deprecated. Please verify and align with the object-based syntax used elsewhere.

🔎 Suggested fix using object syntax
             if (isCloud) {
-                await sdk.forConsole.organizations.create(
-                    ID.unique(),
-                    'Personal Projects',
-                    BillingPlan.FREE,
-                    null
-                );
+                await sdk.forConsole.organizations.create({
+                    organizationId: ID.unique(),
+                    name: 'Personal Projects',
+                    billingPlan: BillingPlan.FREE
+                });
             } else {
src/routes/(public)/functions/deploy/+page.ts (1)

67-85: Same API syntax inconsistency as sites/deploy.

This file has the identical issue with positional arguments for organizations.create (lines 72-77) that was flagged in src/routes/(public)/sites/deploy/+page.ts. Please apply the same fix to use object syntax.

🔎 Suggested fix using object syntax
             if (isCloud) {
-                await sdk.forConsole.organizations.create(
-                    ID.unique(),
-                    'Personal Projects',
-                    BillingPlan.FREE,
-                    null
-                );
+                await sdk.forConsole.organizations.create({
+                    organizationId: ID.unique(),
+                    name: 'Personal Projects',
+                    billingPlan: BillingPlan.FREE
+                });
             } else {
🧹 Nitpick comments (19)
src/lib/flags.ts (1)

8-20: Consider removing unused code or clarifying its purpose.

The isFlagEnabled function is marked as unused and never called or exported. The file also exports an empty flags object (line 22), suggesting incomplete or placeholder implementation. Consider either:

  1. Removing the unused function to reduce clutter
  2. Adding a comment explaining why this code is being retained for future use
src/routes/(console)/organization-[organization]/billing/taxId.svelte (1)

20-23: LGTM! SDK refactor is correct.

The migration from sdk.forConsole.billing.updateTaxId to sdk.forConsole.organizations.setBillingTaxId with named parameters is well-executed and aligns with the PR objectives.

Optional: Consider adding null-safety for consistency.

Lines 15 and 49 use optional chaining on $organization, but line 21 directly accesses $organization.$id. While the route likely ensures the organization is loaded, adding optional chaining or an early return would improve consistency and defensive coding:

🔎 Optional refactor for null-safety
 async function updateTaxId() {
+    if (!$organization) return;
+    
     try {
         await sdk.forConsole.organizations.setBillingTaxId({
             organizationId: $organization.$id,
             taxId
         });
src/routes/(public)/(guest)/login/+page.ts (1)

8-27: API migration looks good; consider explicit typing for consistency.

The migration to console.getCoupon and console.getCampaign with object-based parameters is correct and matches the patterns in register/+page.ts and apply-credit/+page.ts.

For consistency with similar files in this refactor (e.g., register/+page.ts lines 7-8, apply-credit/+page.ts lines 6-7), consider explicitly typing couponData as Models.Coupon.

🔎 Optional: Add explicit type for consistency
     if (url.searchParams.has('code')) {
         const code = url.searchParams.get('code');
         let campaign: Models.Campaign;
+        let couponData: Models.Coupon;
         try {
-            const couponData = await sdk.forConsole.console.getCoupon({
+            couponData = await sdk.forConsole.console.getCoupon({
                 couponId: code
             });
src/lib/components/billing/alerts/paymentMandate.svelte (1)

10-15: Consider adding error handling for payment operations.

Payment verification failures should provide clear feedback to users. Wrapping the async operations in try-catch would improve the user experience.

🔎 Suggested error handling
 async function verifyPaymentMethod() {
+    try {
         const method = await sdk.forConsole.account.updatePaymentMethodMandateOptions({
             paymentMethodId: $paymentMissingMandate.$id
         });
         await confirmSetup(method.clientSecret, method.providerMethodId);
+    } catch (error) {
+        // Handle error appropriately (e.g., show toast notification)
+        console.error('Payment verification failed:', error);
+    }
 }
src/lib/components/billing/alerts/selectProjectCloud.svelte (1)

45-47: Consider typing the caught error for type safety.

The caught exception e is not typed, and accessing e.message directly is not type-safe. Consider typing it explicitly or using a type guard.

🔎 Suggested improvement
-} catch (e) {
-    error = e.message;
+} catch (e) {
+    error = e instanceof Error ? e.message : String(e);
 }
src/routes/(console)/account/payments/editPaymentModal.svelte (1)

30-32: Consider awaiting invalidate before showing the notification.

The invalidate(Dependencies.PAYMENT_METHODS) call is not awaited. If the user performs another action immediately after closing the modal, the payment methods list might not yet be refreshed, leading to stale data being displayed.

🔎 Proposed fix
             trackEvent(Submit.PaymentMethodUpdate);
-            invalidate(Dependencies.PAYMENT_METHODS);
+            await invalidate(Dependencies.PAYMENT_METHODS);
             show = false;
src/lib/components/billing/emptyCardCloud.svelte (1)

20-20: proPlanName is not reactive to store changes.

proPlanName is assigned once when the component initializes. If plansInfo store updates after mount (e.g., due to data refresh), this value won't update. Consider using $derived if reactivity is needed:

🔎 Proposed fix using $derived
-    const proPlanName = getBasePlanFromGroup(BillingPlanGroup.Pro).name;
+    const proPlanName = $derived(getBasePlanFromGroup(BillingPlanGroup.Pro).name);

However, if plan names are stable after initial load, the current implementation is acceptable.

src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte (1)

18-37: Inconsistent await usage for invalidate().

In removeDefaultMethod (line 30), invalidate() is called without await, while in removeBackupMethod (line 53), it's awaited. This inconsistency could lead to different behaviors—the backup method waits for cache invalidation before proceeding, while the default method does not.

🔎 Proposed fix for consistency
         trackEvent(Submit.OrganizationPaymentDelete);
-        invalidate(Dependencies.ORGANIZATION);
+        await invalidate(Dependencies.ORGANIZATION);
         showDelete = false;

Also applies to: 40-59

src/routes/(console)/organization-[organization]/domains/domain-[domain]/settings/+page.ts (1)

8-16: Add type annotation for the load function.

The load function is missing its PageLoad type annotation which other route loaders in the codebase use for type safety.

Suggested fix
+import type { PageLoad } from './$types';
-export const load = async ({ params, parent, depends }) => {
+export const load: PageLoad = async ({ params, parent, depends }) => {
src/routes/(console)/create-organization/+page.ts (1)

35-43: Simplify redundant conditional check.

The if (plan) check is redundant since url.searchParams.get('plan') returns null if the param doesn't exist, and the outer if already confirms the param exists.

🔎 Proposed simplification
-function getPlanFromUrl(url: URL): string | null {
-    if (url.searchParams.has('plan')) {
-        const plan = url.searchParams.get('plan');
-        if (plan) {
-            return plan;
-        }
-    }
-    return BillingPlan.FREE;
-}
+function getPlanFromUrl(url: URL): string {
+    return url.searchParams.get('plan') ?? BillingPlan.FREE;
+}
src/routes/+page.ts (1)

37-49: Inconsistent path construction between resolve() and base.

Line 39 uses resolve('/') while lines 43-48 use ${base}/.... This inconsistency could lead to different behavior depending on how the app is deployed.

Consider standardizing on one approach throughout the function for maintainability:

     if (userVisitedEducationPage()) {
         await handleGithubEducationMembership(account.name, account.email);
         redirect(303, resolve('/'));
     } else if (organizations.total && !isApplyingCredit) {
         const teamId = account.prefs.organization ?? organizations.teams[0].$id;
         if (!teamId) {
-            redirect(303, `${base}/account/organizations${url.search}`);
+            redirect(303, `${resolve('/(console)/account/organizations')}${url.search}`);
         } else {
-            redirect(303, `${base}/organization-${teamId}${url.search}`);
+            redirect(303, `${resolve(`/(console)/organization-${teamId}`)}${url.search}`);
         }
     } else if (!isApplyingCredit) {
-        redirect(303, `${base}/onboarding/create-project${url.search}`);
+        redirect(303, `${resolve('/(console)/onboarding/create-project')}${url.search}`);
     }
src/routes/(console)/account/organizations/+page.svelte (2)

40-47: Stale comment references old function name.

The comment on line 41 says "use tierToPlan" but the code now uses billingIdToPlan.

🔎 Proposed fix
-        // For known plans, use tierToPlan
+        // For known plans, use billingIdToPlan
         const tierData = billingIdToPlan(billingPlan);

76-84: Union type Models.Preferences | Models.Organization seems overly broad.

The function accepts Models.Preferences but then passes the value to isCloudOrg and isOrganizationOnTrial which expect Models.Organization. This works due to the type guard, but the signature is misleading since the function only returns a meaningful result for Models.Organization.

Consider narrowing the parameter type or documenting why Preferences is accepted:

     function isPayingOrganization(
-        team: Models.Preferences | Models.Organization
+        team: Models.Organization
     ): Models.Organization | null {
src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte (1)

88-104: Missing error handling in reactive payment method fetches.

The reactive blocks that fetch payment methods don't handle errors. If the API calls fail, the promises are silently rejected which could leave the UI in an inconsistent state.

🔎 Proposed fix
     $: if (organization?.backupPaymentMethodId) {
         sdk.forConsole.organizations
             .getPaymentMethod({
                 organizationId: organization.$id,
                 paymentMethodId: organization.backupPaymentMethodId
             })
-            .then((res) => (backupPaymentMethod = res));
+            .then((res) => (backupPaymentMethod = res))
+            .catch(() => (backupPaymentMethod = null));
     }

     $: if (organization?.paymentMethodId) {
         sdk.forConsole.organizations
             .getPaymentMethod({
                 organizationId: organization.$id,
                 paymentMethodId: organization.paymentMethodId
             })
-            .then((res) => (defaultPaymentMethod = res));
+            .then((res) => (defaultPaymentMethod = res))
+            .catch(() => (defaultPaymentMethod = null));
     }
src/routes/(console)/organization-[organization]/change-plan/+page.ts (1)

31-36: Address the TODO or remove it.

The TODO comment questions the current implementation logic. If there's a known simplification, consider implementing it; otherwise, add context or remove the comment.

src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte (1)

127-134: Inconsistent invalidate await pattern.

Line 97 awaits invalidate(Dependencies.PAYMENT_METHODS), but lines 133-134 do not await the invalidation calls. Consider awaiting these for consistency, or if intentional fire-and-forget, add a brief comment explaining why.

🔎 Suggested fix for consistent await pattern
-            invalidate(Dependencies.ORGANIZATION);
-            invalidate(Dependencies.INVOICES);
+            await invalidate(Dependencies.ORGANIZATION);
+            await invalidate(Dependencies.INVOICES);
src/lib/stores/stripe.ts (1)

88-94: Hardcoded base URL should use environment configuration.

The URL https://cloud.appwrite.io/console is hardcoded here and again at line 226. Consider using an environment variable or the existing base import from $app/paths combined with window.location.origin for consistency and flexibility across environments.

🔎 Suggested approach
-        const baseUrl = 'https://cloud.appwrite.io/console';
+        const baseUrl = `${window.location.origin}${base}`;
         const accountUrl = `${baseUrl}/account/payments?clientSecret=${clientSecret}`;
         const orgUrl = `${baseUrl}/organization-${organizationId}/billing?clientSecret=${clientSecret}`;

Apply the same pattern at line 226.

src/lib/components/billing/estimatedTotalBox.svelte (1)

27-31: Consider consistent couponId null handling.

The two estimation functions handle empty couponId differently:

  • getEstimate: couponId === '' ? null : couponId
  • getUpdatePlanEstimate: couponId && couponId.length > 0 ? couponId : null

Both approaches work, but the first could pass undefined as-is while the second explicitly converts to null. Consider unifying the logic for maintainability.

🔎 Suggested unified approach
 async function getEstimate(
     billingPlan: string,
     collaborators: string[],
     couponId: string | undefined
 ) {
     try {
         estimation = await sdk.forConsole.organizations.estimationCreateOrganization({
             billingPlan,
             invites: collaborators ?? [],
-            couponId: couponId === '' ? null : couponId
+            couponId: couponId?.length ? couponId : null
         });

Also applies to: 57-62

src/routes/(console)/onboarding/create-project/+page.ts (1)

64-64: Consider using union type for nullable assignment.

The variable is typed as Models.ProjectList but initialized to null. While TypeScript non-strict mode tolerates this, a more accurate type would be Models.ProjectList | null.

🔎 Proposed fix
-let projects: Models.ProjectList = null;
+let projects: Models.ProjectList | null = null;

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: 8

Caution

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

⚠️ Outside diff range comments (6)
src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte (1)

68-86: Add missing success analytics event for backup payment method.

The addBackupPaymentMethod function lacks the trackEvent(Submit.OrganizationPaymentUpdate) call on success, while the identical addPaymentMethod function includes it on line 57. Add trackEvent(Submit.OrganizationPaymentUpdate); after the invalidate call on line 78 for consistency.

src/routes/(console)/create-organization/+page.svelte (1)

80-85: Potential null dereference when extracting URL parameters.

Both organizationId and invites parameters are extracted without null checks. If a user navigates to ?type=payment_confirmed&id=xxx without the invites param, calling .split(',') on null will throw a TypeError.

🛠️ Suggested fix with null guards
         if (type === 'payment_confirmed') {
             const organizationId = page.url.searchParams.get('id');
-            const invites = page.url.searchParams.get('invites').split(',');
-            await validate(organizationId, invites);
+            const invitesParam = page.url.searchParams.get('invites');
+            if (organizationId && invitesParam) {
+                const invites = invitesParam.split(',').filter(Boolean);
+                await validate(organizationId, invites);
+            }
         }
src/routes/(console)/account/payments/editPaymentModal.svelte (1)

37-51: Critical: Duplicate trackEvent and invalidate calls will cause double tracking and unnecessary cache invalidation.

Lines 43-44 and 46-47 contain duplicated code that will:

  1. Track the PaymentMethodUpdate analytics event twice per submission
  2. Call invalidate(Dependencies.PAYMENT_METHODS) twice (once sync, once awaited)

This appears to be a merge conflict artifact or copy-paste error.

🐛 Proposed fix
 async function handleSubmit() {
     try {
         await sdk.forConsole.account.updatePaymentMethod({
             paymentMethodId: selectedPaymentMethod.$id,
             expiryMonth: parseInt(month),
             expiryYear: year
         });

-        trackEvent(Submit.PaymentMethodUpdate);
-        invalidate(Dependencies.PAYMENT_METHODS);
-        show = false;
         trackEvent(Submit.PaymentMethodUpdate);
         await invalidate(Dependencies.PAYMENT_METHODS);
+        show = false;
         addNotification({
             type: 'success',
             message: 'Your payment method has been updated'
         });
     } catch (e) {
         error = e.message;
         trackError(e, Submit.PaymentMethodUpdate);
     }
 }
src/routes/(public)/sites/deploy/+page.ts (1)

100-105: Silent failure may leave user without feedback.

If organization creation fails, the error is only logged to console. The user receives no notification, and organizations remains empty, which could cause confusion or downstream issues in the UI.

Consider propagating the error or showing a user-facing notification.

src/routes/(public)/functions/deploy/+page.ts (1)

67-88: Same getBasePlanFromGroup undefined risk and code duplication.

This organization creation logic is nearly identical to src/routes/(public)/sites/deploy/+page.ts (lines 83-105). Both have the same potential TypeError if getBasePlanFromGroup returns undefined.

Consider extracting this shared logic into a helper function (e.g., ensureOrganizationExists() in $lib/stores/organization) to reduce duplication and centralize error handling.

src/routes/(console)/organization-[organization]/billing/replaceCard.svelte (1)

97-117: Error handling doesn't propagate to caller.

Both addPaymentMethod and addBackupPaymentMethod catch errors and set the error state but don't rethrow. In handleSubmit (line 79-81), these functions are awaited but errors won't propagate to the outer try-catch, potentially allowing the success notification and show = false to execute even after a failure.

🐛 Suggested fix
     async function addPaymentMethod(paymentMethodId: string) {
-        try {
-            await sdk.forConsole.organizations.setDefaultPaymentMethod({
-                organizationId: organization.$id,
-                paymentMethodId
-            });
-        } catch (err) {
-            error = err.message;
-        }
+        await sdk.forConsole.organizations.setDefaultPaymentMethod({
+            organizationId: organization.$id,
+            paymentMethodId
+        });
     }

     async function addBackupPaymentMethod(paymentMethodId: string) {
-        try {
-            await sdk.forConsole.organizations.setBackupPaymentMethod({
-                organizationId: organization.$id,
-                paymentMethodId
-            });
-        } catch (err) {
-            error = err.message;
-        }
+        await sdk.forConsole.organizations.setBackupPaymentMethod({
+            organizationId: organization.$id,
+            paymentMethodId
+        });
     }
🤖 Fix all issues with AI agents
In @src/lib/stores/billing.ts:
- Around line 570-572: The call to
getBasePlanFromGroup(BillingPlanGroup.Starter).$id may be undefined; first call
getBasePlanFromGroup(BillingPlanGroup.Starter) into a local variable (e.g.,
basePlan) and check for null/undefined before using .$id, then build the queries
array conditionally so you only include Query.notEqual('billingPlan',
basePlan.$id) when basePlan and basePlan.$id exist (otherwise omit that filter
or use a safe default), and pass the resulting queries to
sdk.forConsole.organizations.list to avoid accessing .$id on undefined.
- Around line 532-540: In checkForMissingPaymentMethod, avoid directly accessing
getBasePlanFromGroup(BillingPlanGroup.Starter).$id because getBasePlanFromGroup
can return undefined; first assign const basePlan =
getBasePlanFromGroup(BillingPlanGroup.Starter) and if basePlan is undefined
either return/exit early or throw/log an error, then use basePlan.$id in the
Query.equal call passed to sdk.forConsole.organizations.list; alternatively
guard using optional chaining and a fallback value before building the query so
you never dereference undefined.
- Around line 88-100: getBasePlanFromGroup can return undefined when proPlans is
empty; change its signature to Models.BillingPlan | undefined and explicitly
handle the empty case instead of assuming [0] is defined. In
getBasePlanFromGroup, compute const base =
proPlans.sort((a,b)=>a.order-b.order)[0]; then return base ?? undefined (or
return a sensible fallback plan found from plansInfoStore if intended), and
update callers to handle a possible undefined result; reference: function
getBasePlanFromGroup, variable proPlans, plansInfoStore, and
BillingPlanGroup.Starter.
- Around line 102-111: The function billingIdToPlan silently falls back to
BillingPlanGroup.Pro when billingId is missing from plansInfo; instead make
failures explicit by validating plansInfo.has(billingId) and throwing a
descriptive error (or returning a Result/optional) rather than defaulting to
Pro. Update billingIdToPlan to check plansInfo (via get(plansInfo)) and if the
id is absent throw an Error like "Unknown billingId: <id>" (or change the
signature to return undefined/null or a Result type), and remove the silent
fallback to getBasePlanFromGroup(BillingPlanGroup.Pro) to avoid incorrect
billing/UI behavior.

In @src/routes/(console)/create-organization/+page.svelte:
- Around line 90-97: The validatePayment branch in the validate() flow currently
preloads and navigates to `${base}/console/organization-${org.$id}` which is
inconsistent with the create() flow; update the two references in the
validatePayment success path (the calls around
sdk.forConsole.organizations.validatePayment and the subsequent preloadData and
goto) to use `${base}/organization-${org.$id}` instead so both validate() and
create() navigate to the same canonical `/organization-{id}` URL.

In @src/routes/(console)/organization-[organization]/billing/+page.ts:
- Around line 15-20: The redirect uses a 301 permanent status in the
redirect(...) call which can be cached by browsers; change the status code from
301 to a temporary redirect such as 302 (or 307 if preserving method) in the
redirect(resolve(...)) invocation so authorization-based redirects aren’t
cached—update the redirect(301, resolve(...)) call to redirect(302,
resolve(...)) (or 307) where the redirect(...) and resolve(...) symbols are
used.

In
@src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte:
- Around line 36-39: The exported props backupMethod and primaryMethod are
declared as non-optional but are used with optional chaining (e.g.,
primaryMethod?.lastError, primaryMethod?.userId), so update their types to be
optional/nullable (e.g., Models.PaymentMethod | undefined) in the export
declarations (export let backupMethod and export let primaryMethod) to match
runtime usage and avoid type errors; ensure any other references to those
symbols (backupMethod, primaryMethod) continue to use safe checks or optional
chaining.

In @src/routes/(public)/sites/deploy/+page.ts:
- Around line 88-92: The create call uses
getBasePlanFromGroup(BillingPlanGroup.Starter).$id which can be undefined in the
(public) route because plansInfo is only populated in the (console) layout;
update the organization creation to guard for undefined by calling
getBasePlanFromGroup(BillingPlanGroup.Starter), check its return value (from the
getBasePlanFromGroup helper) and use a safe fallback billing plan id (e.g., a
DEFAULT_PLAN_ID constant or a known starter plan id) or fetch/populate plansInfo
before calling; ensure you reference getBasePlanFromGroup and the organization
creation block (await sdk.forConsole.organizations.create) when adding the
null-check/fallback and logging an informative error if neither a plan nor
fallback exist.
🧹 Nitpick comments (6)
src/routes/(console)/account/payments/editAddressModal.svelte (1)

36-46: API migration looks correct.

The transition from billing.updateAddress to account.updateBillingAddress with named parameters is properly implemented.

Minor optional simplification for the ternary expressions:

✨ Optional simplification
             await sdk.forConsole.account.updateBillingAddress({
                 billingAddressId: selectedAddress.$id,
                 country: selectedAddress.country,
                 streetAddress: selectedAddress.streetAddress,
                 city: selectedAddress.city,
                 state: selectedAddress.state,
-                postalCode: selectedAddress.postalCode ? selectedAddress.postalCode : undefined,
-                addressLine2: selectedAddress.addressLine2
-                    ? selectedAddress.addressLine2
-                    : undefined
+                postalCode: selectedAddress.postalCode || undefined,
+                addressLine2: selectedAddress.addressLine2 || undefined
             });
src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte (1)

40-59: Optional: Inconsistent dialog dismissal timing between the two methods.

Pre-existing behavior: removeBackupMethod sets showDelete = false at line 42 before the async operation completes, causing the dialog to close immediately. In contrast, removeDefaultMethod uses a finally block to close the dialog after the operation.

Consider aligning the two methods for consistent UX where the dialog remains open until the operation completes or fails.

♻️ Suggested alignment
 async function removeBackupMethod() {
     if ($organization?.billingPlan !== BillingPlan.FREE && !hasOtherMethod) return;
-    showDelete = false;

     try {
         await sdk.forConsole.organizations.deleteBackupPaymentMethod({
             organizationId: $organization.$id
         });
         addNotification({
             type: 'success',
             message: `The payment method has been removed from ${$organization.name}`
         });
         trackEvent(Submit.OrganizationBackupPaymentDelete);
         await invalidate(Dependencies.PAYMENT_METHODS);
-        showDelete = false;
     } catch (e) {
         error = e.message;
         trackError(e, Submit.OrganizationBackupPaymentDelete);
+    } finally {
+        showDelete = false;
     }
 }
src/lib/components/support.svelte (1)

22-29: Type assertion is acceptable but consider adding a type guard for safety.

The (team as Models.Organization) assertions work because the runtime objects contain billing details when in cloud mode. However, this relies on implicit knowledge that organizationList.teams contains Organization objects rather than plain Team objects.

Consider extracting a type guard or helper function if this pattern is used elsewhere:

💡 Optional improvement
// In a shared helper file
function hasOrganizationBilling(team: Models.Team): team is Models.Organization {
    return 'billingPlanDetails' in team;
}
src/routes/(console)/onboarding/create-organization/+page.svelte (1)

24-34: Inconsistent null safety between creation and analytics.

Line 27 accesses getBasePlanFromGroup(BillingPlanGroup.Starter).$id without null safety, while line 31 uses optional chaining (?.name). If the function can return undefined (warranting the ?. on line 31), the same protection should be applied on line 27.

♻️ Suggested fix for consistency
             organization = await sdk.forConsole.organizations.create({
                 organizationId: ID.unique(),
                 name: organizationName,
-                billingPlan: getBasePlanFromGroup(BillingPlanGroup.Starter).$id
+                billingPlan: getBasePlanFromGroup(BillingPlanGroup.Starter)?.$id
             });
src/routes/(console)/organization-[organization]/billing/+page.ts (1)

31-39: Repeated type casts suggest incomplete type propagation.

Multiple lines cast organization to Models.Organization (lines 31, 53, 65). If organization from parent() is already typed correctly, these casts are unnecessary. If not, consider fixing the type at the source (in the parent layout's return type).

Also applies to: 46-56, 63-66

src/lib/stores/billing.ts (1)

113-141: TODOs indicate incomplete refactoring.

Lines 113 and 128 contain TODOs suggesting these functions should return BillingPlan objects instead of strings. Consider addressing these before merging or creating tracking issues.

Would you like me to help refactor these functions to return Models.BillingPlan objects, or open an issue to track this work?

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfd4636 and 5cc88f6.

📒 Files selected for processing (22)
  • src/lib/components/archiveProject.svelte
  • src/lib/components/billing/alerts/paymentMandate.svelte
  • src/lib/components/billing/alerts/selectProjectCloud.svelte
  • src/lib/components/support.svelte
  • src/lib/stores/billing.ts
  • src/lib/stores/sdk.ts
  • src/routes/(console)/+layout.svelte
  • src/routes/(console)/+layout.ts
  • src/routes/(console)/account/payments/editAddressModal.svelte
  • src/routes/(console)/account/payments/editPaymentModal.svelte
  • src/routes/(console)/create-organization/+page.svelte
  • src/routes/(console)/onboarding/create-organization/+page.svelte
  • src/routes/(console)/organization-[organization]/+layout.ts
  • src/routes/(console)/organization-[organization]/billing/+page.svelte
  • src/routes/(console)/organization-[organization]/billing/+page.ts
  • src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
  • src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
  • src/routes/(console)/organization-[organization]/billing/replaceAddress.svelte
  • src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
  • src/routes/(console)/project-[region]-[project]/databases/+page.ts
  • src/routes/(public)/functions/deploy/+page.ts
  • src/routes/(public)/sites/deploy/+page.ts
💤 Files with no reviewable changes (1)
  • src/lib/stores/sdk.ts
✅ Files skipped from review due to trivial changes (1)
  • src/routes/(console)/organization-[organization]/+layout.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/routes/(console)/organization-[organization]/billing/replaceAddress.svelte
  • src/lib/components/archiveProject.svelte
  • src/lib/components/billing/alerts/selectProjectCloud.svelte
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{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)/onboarding/create-organization/+page.svelte
  • src/routes/(console)/+layout.svelte
  • src/lib/components/support.svelte
  • src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
  • src/routes/(console)/project-[region]-[project]/databases/+page.ts
  • src/routes/(console)/organization-[organization]/billing/+page.ts
  • src/routes/(console)/account/payments/editAddressModal.svelte
  • src/routes/(public)/sites/deploy/+page.ts
  • src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
  • src/routes/(public)/functions/deploy/+page.ts
  • src/routes/(console)/organization-[organization]/billing/+page.svelte
  • src/routes/(console)/+layout.ts
  • src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
  • src/routes/(console)/account/payments/editPaymentModal.svelte
  • src/lib/stores/billing.ts
  • src/routes/(console)/create-organization/+page.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)/onboarding/create-organization/+page.svelte
  • src/routes/(console)/+layout.svelte
  • src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
  • src/routes/(console)/account/payments/editAddressModal.svelte
  • src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
  • src/routes/(console)/organization-[organization]/billing/+page.svelte
  • src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
  • src/routes/(console)/account/payments/editPaymentModal.svelte
  • src/routes/(console)/create-organization/+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)/onboarding/create-organization/+page.svelte
  • src/routes/(console)/+layout.svelte
  • src/lib/components/support.svelte
  • src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
  • src/routes/(console)/project-[region]-[project]/databases/+page.ts
  • src/routes/(console)/organization-[organization]/billing/+page.ts
  • src/routes/(console)/account/payments/editAddressModal.svelte
  • src/routes/(public)/sites/deploy/+page.ts
  • src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
  • src/routes/(public)/functions/deploy/+page.ts
  • src/routes/(console)/organization-[organization]/billing/+page.svelte
  • src/routes/(console)/+layout.ts
  • src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
  • src/routes/(console)/account/payments/editPaymentModal.svelte
  • src/lib/stores/billing.ts
  • src/routes/(console)/create-organization/+page.svelte
**/*.svelte

📄 CodeRabbit inference engine (AGENTS.md)

Use Svelte 5 + SvelteKit 2 syntax with TypeScript for component development

Files:

  • src/routes/(console)/onboarding/create-organization/+page.svelte
  • src/routes/(console)/+layout.svelte
  • src/lib/components/support.svelte
  • src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
  • src/routes/(console)/account/payments/editAddressModal.svelte
  • src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
  • src/routes/(console)/organization-[organization]/billing/+page.svelte
  • src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
  • src/routes/(console)/account/payments/editPaymentModal.svelte
  • src/routes/(console)/create-organization/+page.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)/onboarding/create-organization/+page.svelte
  • src/routes/(console)/+layout.svelte
  • src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
  • src/routes/(console)/project-[region]-[project]/databases/+page.ts
  • src/routes/(console)/organization-[organization]/billing/+page.ts
  • src/routes/(console)/account/payments/editAddressModal.svelte
  • src/routes/(public)/sites/deploy/+page.ts
  • src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
  • src/routes/(public)/functions/deploy/+page.ts
  • src/routes/(console)/organization-[organization]/billing/+page.svelte
  • src/routes/(console)/+layout.ts
  • src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
  • src/routes/(console)/account/payments/editPaymentModal.svelte
  • src/routes/(console)/create-organization/+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/support.svelte
**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

**/*.ts: Define types inline or in .d.ts files, avoid creating separate .types.ts files
Use TypeScript in non-strict mode; any type is tolerated in this project

Files:

  • src/routes/(console)/project-[region]-[project]/databases/+page.ts
  • src/routes/(console)/organization-[organization]/billing/+page.ts
  • src/routes/(public)/sites/deploy/+page.ts
  • src/routes/(public)/functions/deploy/+page.ts
  • src/routes/(console)/+layout.ts
  • src/lib/stores/billing.ts
🧠 Learnings (7)
📚 Learning: 2025-12-05T09:24:15.846Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2670
File: src/routes/(console)/organization-[organization]/+layout.ts:134-150
Timestamp: 2025-12-05T09:24:15.846Z
Learning: In the Appwrite console codebase, `prefs.organization` is maintained to always reference an organization with Platform.Appwrite, so when using preferences.organization as a fallback in redirect logic, no additional platform validation is required.

Applied to files:

  • src/routes/(console)/onboarding/create-organization/+page.svelte
  • src/routes/(public)/sites/deploy/+page.ts
  • src/routes/(public)/functions/deploy/+page.ts
  • 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/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)/+layout.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)/+layout.svelte
  • src/routes/(console)/organization-[organization]/billing/+page.svelte
  • src/lib/stores/billing.ts
📚 Learning: 2025-10-26T10:20:29.792Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2509
File: src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte:47-61
Timestamp: 2025-10-26T10:20:29.792Z
Learning: When deleting teams in the Appwrite Console codebase, only `Dependencies.TEAMS` needs to be invalidated. Additional invalidations for `Dependencies.TEAM` (detail route) and `Dependencies.MEMBERSHIPS` are not necessary because the deleted teams and their memberships no longer exist.

Applied to files:

  • src/lib/components/support.svelte
  • src/routes/(public)/sites/deploy/+page.ts
  • src/routes/(public)/functions/deploy/+page.ts
📚 Learning: 2025-10-07T14:17:11.597Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2413
File: src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts:28-28
Timestamp: 2025-10-07T14:17:11.597Z
Learning: In src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts, the empty `vectordb: {}` entry in baseTerminology is intentional. The vectordb database type is not currently used because the backend API is not finalized, and no database type returns 'vectordb' at the moment.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/+page.ts
📚 Learning: 2025-10-26T10:21:24.499Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2509
File: src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte:24-39
Timestamp: 2025-10-26T10:21:24.499Z
Learning: SDK methods from `sdk.forProject(...).users`, `sdk.forProject(...).teams`, and other Appwrite SDK endpoints always throw `AppwriteException` from `appwrite.io/console`, so caught errors can be returned directly without normalization in deletion handlers.

Applied to files:

  • src/routes/(console)/+layout.ts
📚 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/lib/stores/billing.ts
🧬 Code graph analysis (5)
src/routes/(console)/project-[region]-[project]/databases/+page.ts (1)
src/lib/stores/organization.ts (1)
  • currentPlan (18-18)
src/routes/(console)/organization-[organization]/billing/+page.ts (4)
src/lib/constants.ts (1)
  • DEFAULT_BILLING_PROJECTS_LIMIT (4-4)
src/lib/stores/organization.ts (1)
  • organization (14-17)
src/lib/helpers/load.ts (3)
  • getPage (8-10)
  • getLimit (12-16)
  • pageToOffset (4-6)
src/lib/stores/billing.ts (1)
  • paymentMethods (62-65)
src/routes/(public)/functions/deploy/+page.ts (4)
src/lib/stores/organization.ts (1)
  • getTeamOrOrganizationList (22-36)
src/lib/system.ts (1)
  • isCloud (26-26)
src/lib/stores/sdk.ts (1)
  • sdk (171-194)
src/lib/stores/billing.ts (1)
  • getBasePlanFromGroup (88-100)
src/routes/(console)/+layout.ts (2)
src/lib/system.ts (1)
  • isCloud (26-26)
src/lib/stores/sdk.ts (1)
  • sdk (171-194)
src/lib/stores/billing.ts (5)
src/lib/sdk/billing.ts (1)
  • BillingPlansMap (15-15)
src/lib/stores/sdk.ts (1)
  • sdk (171-194)
src/lib/stores/organization.ts (1)
  • currentPlan (18-18)
src/lib/system.ts (1)
  • isCloud (26-26)
src/lib/constants.ts (1)
  • NEW_DEV_PRO_UPGRADE_COUPON (6-6)
⏰ 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). (2)
  • GitHub Check: e2e
  • GitHub Check: build
🔇 Additional comments (37)
src/routes/(console)/account/payments/editAddressModal.svelte (2)

15-15: LGTM!

Type migration from custom Address to Models.BillingAddress aligns with the centralized Models namespace refactoring.


49-52: Good fix for the notification message.

Changing from "added" to "updated" is semantically correct for an edit modal.

src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte (2)

22-24: LGTM! API migration to organizations namespace is correct.

The change from billing.removeOrganizationPaymentMethod to organizations.deleteDefaultPaymentMethod with the new object parameter structure aligns with the SDK refactor pattern.


45-47: LGTM! Consistent API migration pattern.

The backup payment method deletion correctly migrates to organizations.deleteBackupPaymentMethod with the same object parameter structure.

src/routes/(console)/project-[region]-[project]/databases/+page.ts (1)

47-49: Type migration is correct and verified.

The change from Plan to Models.BillingPlan on line 47 is correct and aligns with the store definition in src/lib/stores/organization.ts. The backupsEnabled property exists on Models.BillingPlan and is safely accessed on line 49 with optional chaining and a nullish coalescing fallback.

src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte (2)

34-34: LGTM!

The type import for the centralized Models namespace is correctly added to support the type migration.


47-66: LGTM!

The API call migration to organizations.setDefaultPaymentMethod with object-based parameters is clean. Error handling, analytics tracking, and cache invalidation are properly preserved.

src/routes/(console)/create-organization/+page.svelte (6)

11-11: LGTM on import updates.

The migration to billingIdToPlan and isPaymentAuthenticationRequired helpers, along with the Models namespace from @appwrite.io/console, aligns with the SDK refactoring objectives.

Also applies to: 15-15


26-28: LGTM on state variable type updates.

Using data.plan for selectedPlan and Partial<Models.Coupon> | null for selectedCoupon properly aligns with the new Models-based type system.


49-51: LGTM on coupon API migration.

The migration from getCouponAccount to sdk.forConsole.console.getCoupon({ couponId }) follows the consolidated API pattern.


114-132: LGTM on create organization API migration.

The migration to sdk.forConsole.organizations.create() with the new payload structure (organizationId, name, billingPlan, couponId, invites, budget, taxId) correctly implements the refactored API. The conditional handling for free vs. paid plans is well-structured.


134-151: LGTM on payment authentication flow.

The use of isPaymentAuthenticationRequired(org) helper and accessing org.organizationId for the PaymentAuthentication type is correct. The redirect URL construction and subsequent validation call are properly handled.


155-169: LGTM on tracking and post-create navigation.

The tracking event properly uses billingIdToPlan(selectedPlan)?.name with optional chaining for safety. The conditional navigation based on isPaymentAuthenticationRequired correctly handles the success flow.

src/lib/components/support.svelte (1)

10-14: LGTM! Clean migration to Models namespace.

The import changes correctly migrate from the legacy plansInfo store lookup to using the Models type from @appwrite.io/console. This aligns with the broader refactoring effort.

src/routes/(console)/account/payments/editPaymentModal.svelte (1)

10-20: LGTM! Type migration to Models namespace.

The import and prop type changes correctly align with the broader SDK refactoring effort.

src/routes/(console)/+layout.svelte (2)

9-9: LGTM! Import updates align with Models namespace migration.

The import changes correctly support the type migration from custom types to Models.Organization.

Also applies to: 58-58


294-317: LGTM! Function signature and billing trial check updated correctly.

The migration from Organization to Models.Organization and the direct access to org.billingTrialDays (instead of deriving from plansInfo) simplifies the logic and aligns with the new data model.

src/routes/(console)/+layout.ts (3)

4-5: LGTM! Import updates for the SDK migration.

The imports correctly bring in Models, Platform, and Query from @appwrite.io/console to support the new API surface.


17-21: LGTM! API migration to console namespace with platform parameter.

The change from billing.getPlans to console.getPlans({ platform: Platform.Appwrite }) correctly implements the new API surface for plan retrieval.


65-72: LGTM! Type-safe plan mapping with Models namespace.

The toPlanMap function signature and implementation correctly use Models.BillingPlanList and Models.BillingPlan types. Using plan.$id as the string key is appropriate for the new model structure.

src/routes/(console)/organization-[organization]/billing/+page.svelte (3)

11-16: LGTM! Import migration from tierToPlan to billingIdToPlan.

The import change correctly aligns with the renamed helper function in the billing store.


49-52: LGTM! API calls migrated to organizations namespace with object parameters.

The changes correctly update:

  • billing.getInvoice(orgId, invoiceId)organizations.getInvoice({ organizationId, invoiceId })
  • billing.updateInvoiceStatus(...)organizations.validateInvoice({ organizationId, invoiceId })

The object parameter style aligns with the new SDK conventions.

Also applies to: 67-70, 80-83


126-134: LGTM! Plan name resolution updated to use billingIdToPlan.

The downgrade alert correctly uses billingIdToPlan() for both the downgraded and current plan names, consistent with the Models-based typing.

src/routes/(public)/sites/deploy/+page.ts (1)

5-10: LGTM! Imports align with the SDK refactor.

The imports correctly bring in the unified helpers (getTeamOrOrganizationList, getBasePlanFromGroup) and updated types (BillingPlanGroup, Models) from the appropriate modules using $lib aliases as per coding guidelines.

src/routes/(console)/onboarding/create-organization/+page.svelte (1)

4-5: LGTM! Imports correctly updated for the refactor.

The imports properly use $lib aliases and align with the new billing SDK structure.

Also applies to: 13-13

src/routes/(public)/functions/deploy/+page.ts (1)

5-10: LGTM! Imports align with the SDK refactor.

Consistent with the sites deploy page refactor.

src/routes/(console)/organization-[organization]/billing/+page.ts (3)

1-9: LGTM! Imports properly structured.

The imports correctly use $lib aliases and bring in the necessary types from @appwrite.io/console.


77-92: LGTM! API calls properly migrated to organizations namespace.

The Promise.all correctly uses the new endpoints (account.listPaymentMethods, account.listBillingAddresses, organizations.getAvailableCredits, console.getPlan) with proper payload shapes.


120-141: LGTM! Helper function properly typed with Models.

The getOrganizationPaymentMethods function correctly uses Models.Organization, Models.PaymentMethodList, and Models.PaymentMethod types.

src/routes/(console)/organization-[organization]/billing/replaceCard.svelte (1)

12-13: LGTM! Type imports and prop definitions properly updated.

Good practice aliasing Stripe's PaymentMethod as StripePaymentMethod to avoid confusion with Appwrite's Models.PaymentMethod.

Also applies to: 23-25, 31-31

src/lib/stores/billing.ts (7)

3-3: LGTM! Imports properly consolidated.

Imports correctly use $lib aliases and bring in necessary types from @appwrite.io/console.

Also applies to: 17-23, 27-27


62-70: LGTM! Derived stores properly typed with Models.

The stores correctly derive from page data with appropriate Model types.


169-196: LGTM! getServiceLimit signature properly updated.

The function correctly accepts an optional Models.BillingPlan parameter and maintains backward compatibility with the existing tier string parameter.


198-224: LGTM! failedInvoice store updated to use organizations API.

The API call correctly uses sdk.forConsole.organizations.listInvoices with the new payload shape.


599-605: LGTM! URL helpers properly use resolve() for type-safe paths.

The upgradeURL derived store and hideBillingHeaderRoutes correctly use SvelteKit's resolve() function for path building.


607-610: LGTM! Type guard properly implemented.

The isPaymentAuthenticationRequired type guard correctly uses the in operator to narrow the union type.


406-408: Undefined access concern is invalid.

The billingIdToPlan() function has a guaranteed return type of Models.BillingPlan and cannot return undefined. It always returns either the mapped plan from plansInfoStore or falls back to getBasePlanFromGroup(BillingPlanGroup.Pro). Therefore, accessing .name on the result is safe and will not cause an undefined access error.

Likely an incorrect or invalid review comment.

Comment on lines +88 to +100
export function getBasePlanFromGroup(billingPlanGroup: BillingPlanGroup): Models.BillingPlan {
const plansInfoStore = get(plansInfo);

// hot fix for now, starter doesn't have a group atm.
const correctBillingPlanGroup =
billingPlanGroup === BillingPlanGroup.Starter ? null : billingPlanGroup;

const proPlans = Array.from(plansInfoStore.values()).filter(
(plan) => plan.group === correctBillingPlanGroup
);

return proPlans.sort((a, b) => a.order - b.order)[0];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

getBasePlanFromGroup can return undefined, causing runtime errors.

When proPlans is empty (no plans match the group), proPlans.sort(...)[0] returns undefined. The return type declares Models.BillingPlan but undefined is returned in practice. This causes TypeError when callers access .$id or .name.

🐛 Suggested fix with explicit undefined handling
-export function getBasePlanFromGroup(billingPlanGroup: BillingPlanGroup): Models.BillingPlan {
+export function getBasePlanFromGroup(billingPlanGroup: BillingPlanGroup): Models.BillingPlan | undefined {
     const plansInfoStore = get(plansInfo);
+    if (!plansInfoStore?.size) return undefined;

     // hot fix for now, starter doesn't have a group atm.
     const correctBillingPlanGroup =
         billingPlanGroup === BillingPlanGroup.Starter ? null : billingPlanGroup;

     const proPlans = Array.from(plansInfoStore.values()).filter(
         (plan) => plan.group === correctBillingPlanGroup
     );

-    return proPlans.sort((a, b) => a.order - b.order)[0];
+    if (proPlans.length === 0) return undefined;
+    return proPlans.sort((a, b) => a.order - b.order)[0];
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function getBasePlanFromGroup(billingPlanGroup: BillingPlanGroup): Models.BillingPlan {
const plansInfoStore = get(plansInfo);
// hot fix for now, starter doesn't have a group atm.
const correctBillingPlanGroup =
billingPlanGroup === BillingPlanGroup.Starter ? null : billingPlanGroup;
const proPlans = Array.from(plansInfoStore.values()).filter(
(plan) => plan.group === correctBillingPlanGroup
);
return proPlans.sort((a, b) => a.order - b.order)[0];
}
export function getBasePlanFromGroup(billingPlanGroup: BillingPlanGroup): Models.BillingPlan | undefined {
const plansInfoStore = get(plansInfo);
if (!plansInfoStore?.size) return undefined;
// hot fix for now, starter doesn't have a group atm.
const correctBillingPlanGroup =
billingPlanGroup === BillingPlanGroup.Starter ? null : billingPlanGroup;
const proPlans = Array.from(plansInfoStore.values()).filter(
(plan) => plan.group === correctBillingPlanGroup
);
if (proPlans.length === 0) return undefined;
return proPlans.sort((a, b) => a.order - b.order)[0];
}
🤖 Prompt for AI Agents
In @src/lib/stores/billing.ts around lines 88 - 100, getBasePlanFromGroup can
return undefined when proPlans is empty; change its signature to
Models.BillingPlan | undefined and explicitly handle the empty case instead of
assuming [0] is defined. In getBasePlanFromGroup, compute const base =
proPlans.sort((a,b)=>a.order-b.order)[0]; then return base ?? undefined (or
return a sensible fallback plan found from plansInfoStore if intended), and
update callers to handle a possible undefined result; reference: function
getBasePlanFromGroup, variable proPlans, plansInfoStore, and
BillingPlanGroup.Starter.

Comment on lines +102 to 111
export function billingIdToPlan(billingId: string): Models.BillingPlan {
const plansInfoStore = get(plansInfo);
if (plansInfoStore.has(billingId)) {
return plansInfoStore.get(billingId);
} else {
// fallback to PRO group's 1st item.
// TODO: @itznotabug, @dlohani - but should we fallback to PRO?.
return getBasePlanFromGroup(BillingPlanGroup.Pro);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "billingIdToPlan" --type=ts --type=svelte -C2

Repository: appwrite/console

Length of output: 90


🏁 Script executed:

rg -n "billingIdToPlan" --type=ts --type=js -C2

Repository: appwrite/console

Length of output: 1989


🏁 Script executed:

rg -n "plansInfo" --type=ts --type=js -C1 | head -100

Repository: appwrite/console

Length of output: 3972


🏁 Script executed:

rg -n "getNextTierBillingPlan|getPreviousTierBillingPlan" --type=ts --type=js -C2

Repository: appwrite/console

Length of output: 810


🏁 Script executed:

rg -n "getNextTierBillingPlan|getPreviousTierBillingPlan" --type=ts --type=js | grep -v "export function"

Repository: appwrite/console

Length of output: 42


🏁 Script executed:

wc -l src/lib/stores/billing.ts

Repository: appwrite/console

Length of output: 89


🏁 Script executed:

sed -n '102,145p' src/lib/stores/billing.ts

Repository: appwrite/console

Length of output: 1431


🏁 Script executed:

rg -n "getNextTierBillingPlan|getPreviousTierBillingPlan" --type=ts --type=js --type=svelte

Repository: appwrite/console

Length of output: 90


🏁 Script executed:

rg -n "getNextTierBillingPlan|getPreviousTierBillingPlan" --type=ts --type=js

Repository: appwrite/console

Length of output: 249


🏁 Script executed:

rg -n "getNextTierBillingPlan|getPreviousTierBillingPlan" --type=ts

Repository: appwrite/console

Length of output: 249


🏁 Script executed:

git ls-files | xargs rg "getNextTierBillingPlan|getPreviousTierBillingPlan" 2>/dev/null | head -20

Repository: appwrite/console

Length of output: 496


🏁 Script executed:

cat -n src/lib/components/emptyCardImageCloud.svelte

Repository: appwrite/console

Length of output: 1652


🏁 Script executed:

rg -n "enum BillingPlan|class BillingPlan|type BillingPlan" --type=ts

Repository: appwrite/console

Length of output: 199


🏁 Script executed:

sed -n '612,630p' src/lib/constants.ts

Repository: appwrite/console

Length of output: 568


🏁 Script executed:

sed -n '20,90p' src/routes/\(console\)/+layout.ts

Repository: appwrite/console

Length of output: 1680


🏁 Script executed:

sed -n '1,100p' src/routes/\(console\)/project-[region]-[project]/+layout.ts

Repository: appwrite/console

Length of output: 3589


Make fallback behavior for unknown billing IDs explicit.

The function currently falls back to BillingPlanGroup.Pro for unknown billing IDs without raising an error. The TODO comment on line 108 indicates this fallback is deliberate but uncertain. If a billing ID becomes invalid or there's a mismatch between the server state and the plansInfo cache, silently returning Pro could cause incorrect billing behavior or UI display.

Either validate that the billing ID exists before returning it, or throw an error to make failures explicit rather than falling back silently.

🤖 Prompt for AI Agents
In @src/lib/stores/billing.ts around lines 102 - 111, The function
billingIdToPlan silently falls back to BillingPlanGroup.Pro when billingId is
missing from plansInfo; instead make failures explicit by validating
plansInfo.has(billingId) and throwing a descriptive error (or returning a
Result/optional) rather than defaulting to Pro. Update billingIdToPlan to check
plansInfo (via get(plansInfo)) and if the id is absent throw an Error like
"Unknown billingId: <id>" (or change the signature to return undefined/null or a
Result type), and remove the silent fallback to
getBasePlanFromGroup(BillingPlanGroup.Pro) to avoid incorrect billing/UI
behavior.

Comment on lines 532 to +540
export async function checkForMissingPaymentMethod() {
const orgs = await sdk.forConsole.billing.listOrganization([
Query.notEqual('billingPlan', BillingPlan.FREE),
Query.isNull('paymentMethodId'),
Query.isNull('backupPaymentMethodId'),
Query.equal('platform', Platform.Appwrite)
]);
const orgs = await sdk.forConsole.organizations.list({
queries: [
Query.notEqual('billingPlan', getBasePlanFromGroup(BillingPlanGroup.Starter).$id),
Query.isNull('paymentMethodId'),
Query.isNull('backupPaymentMethodId'),
Query.equal('platform', Platform.Appwrite)
]
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Query uses getBasePlanFromGroup which can return undefined.

Line 535 accesses getBasePlanFromGroup(BillingPlanGroup.Starter).$id. If the function returns undefined, this will throw a TypeError.

🐛 Suggested defensive fix
 export async function checkForMissingPaymentMethod() {
+    const starterPlan = getBasePlanFromGroup(BillingPlanGroup.Starter);
+    if (!starterPlan) return;
+    
     const orgs = await sdk.forConsole.organizations.list({
         queries: [
-            Query.notEqual('billingPlan', getBasePlanFromGroup(BillingPlanGroup.Starter).$id),
+            Query.notEqual('billingPlan', starterPlan.$id),
             Query.isNull('paymentMethodId'),
             Query.isNull('backupPaymentMethodId'),
             Query.equal('platform', Platform.Appwrite)
         ]
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function checkForMissingPaymentMethod() {
const orgs = await sdk.forConsole.billing.listOrganization([
Query.notEqual('billingPlan', BillingPlan.FREE),
Query.isNull('paymentMethodId'),
Query.isNull('backupPaymentMethodId'),
Query.equal('platform', Platform.Appwrite)
]);
const orgs = await sdk.forConsole.organizations.list({
queries: [
Query.notEqual('billingPlan', getBasePlanFromGroup(BillingPlanGroup.Starter).$id),
Query.isNull('paymentMethodId'),
Query.isNull('backupPaymentMethodId'),
Query.equal('platform', Platform.Appwrite)
]
});
export async function checkForMissingPaymentMethod() {
const starterPlan = getBasePlanFromGroup(BillingPlanGroup.Starter);
if (!starterPlan) return;
const orgs = await sdk.forConsole.organizations.list({
queries: [
Query.notEqual('billingPlan', starterPlan.$id),
Query.isNull('paymentMethodId'),
Query.isNull('backupPaymentMethodId'),
Query.equal('platform', Platform.Appwrite)
]
});
🤖 Prompt for AI Agents
In @src/lib/stores/billing.ts around lines 532 - 540, In
checkForMissingPaymentMethod, avoid directly accessing
getBasePlanFromGroup(BillingPlanGroup.Starter).$id because getBasePlanFromGroup
can return undefined; first assign const basePlan =
getBasePlanFromGroup(BillingPlanGroup.Starter) and if basePlan is undefined
either return/exit early or throw/log an error, then use basePlan.$id in the
Query.equal call passed to sdk.forConsole.organizations.list; alternatively
guard using optional chaining and a fallback value before building the query so
you never dereference undefined.

Comment on lines +570 to +572
const organizations = await sdk.forConsole.organizations.list({
queries: [Query.notEqual('billingPlan', getBasePlanFromGroup(BillingPlanGroup.Starter).$id)]
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Same getBasePlanFromGroup undefined risk.

Line 571 accesses getBasePlanFromGroup(BillingPlanGroup.Starter).$id without null safety.

🤖 Prompt for AI Agents
In @src/lib/stores/billing.ts around lines 570 - 572, The call to
getBasePlanFromGroup(BillingPlanGroup.Starter).$id may be undefined; first call
getBasePlanFromGroup(BillingPlanGroup.Starter) into a local variable (e.g.,
basePlan) and check for null/undefined before using .$id, then build the queries
array conditionally so you only include Query.notEqual('billingPlan',
basePlan.$id) when basePlan and basePlan.$id exist (otherwise omit that filter
or use a safe default), and pass the resulting queries to
sdk.forConsole.organizations.list to avoid accessing .$id on undefined.

Comment on lines +90 to 97
const org = await sdk.forConsole.organizations.validatePayment({
organizationId,
invites
});
if (!isPaymentAuthenticationRequired(org)) {
await preloadData(`${base}/console/organization-${org.$id}`);
await goto(`${base}/console/organization-${org.$id}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Inconsistent navigation path compared to create() function.

The validate function navigates to ${base}/console/organization-${org.$id} (lines 96-97), but the create function navigates to ${base}/organization-${org.$id} (lines 163-164). Since this file is located in src/routes/(console)/, the route group parentheses don't affect the URL path, so the correct path should be /organization-{id} without the /console segment.

🛠️ Suggested fix to align paths
-            await preloadData(`${base}/console/organization-${org.$id}`);
-            await goto(`${base}/console/organization-${org.$id}`);
+            await preloadData(`${base}/organization-${org.$id}`);
+            await goto(`${base}/organization-${org.$id}`);
🤖 Prompt for AI Agents
In @src/routes/(console)/create-organization/+page.svelte around lines 90 - 97,
The validatePayment branch in the validate() flow currently preloads and
navigates to `${base}/console/organization-${org.$id}` which is inconsistent
with the create() flow; update the two references in the validatePayment success
path (the calls around sdk.forConsole.organizations.validatePayment and the
subsequent preloadData and goto) to use `${base}/organization-${org.$id}`
instead so both validate() and create() navigate to the same canonical
`/organization-{id}` URL.

Comment on lines +15 to +20
return redirect(
301,
resolve('/(console)/organization-[organization]', {
organization: organization.$id
})
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Verify 301 (permanent) redirect is intentional.

Using HTTP 301 for authorization-based redirects may cause browsers to cache the redirect permanently. If a user later gains billing.read scope, they might still be redirected due to browser cache.

Consider using 302 (temporary) or 307 for authorization-based redirects.

♻️ Suggested fix
     if (!scopes.includes('billing.read')) {
         return redirect(
-            301,
+            302,
             resolve('/(console)/organization-[organization]', {
                 organization: organization.$id
             })
         );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return redirect(
301,
resolve('/(console)/organization-[organization]', {
organization: organization.$id
})
);
return redirect(
302,
resolve('/(console)/organization-[organization]', {
organization: organization.$id
})
);
🤖 Prompt for AI Agents
In @src/routes/(console)/organization-[organization]/billing/+page.ts around
lines 15 - 20, The redirect uses a 301 permanent status in the redirect(...)
call which can be cached by browsers; change the status code from 301 to a
temporary redirect such as 302 (or 307 if preserving method) in the
redirect(resolve(...)) invocation so authorization-based redirects aren’t
cached—update the redirect(301, resolve(...)) call to redirect(302,
resolve(...)) (or 307) where the redirect(...) and resolve(...) symbols are
used.

Comment on lines +36 to +39
export let organization: Models.Organization;
export let methods: Models.PaymentMethodList;
export let backupMethod: Models.PaymentMethod;
export let primaryMethod: Models.PaymentMethod;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prop types may need to be optional.

The props backupMethod and primaryMethod are typed as non-optional Models.PaymentMethod, but optional chaining is used throughout the component (e.g., primaryMethod?.lastError on lines 93-95, primaryMethod?.userId on line 129). This suggests these props can be undefined at runtime.

Consider updating the types to match actual usage:

Suggested fix
 export let organization: Models.Organization;
 export let methods: Models.PaymentMethodList;
-export let backupMethod: Models.PaymentMethod;
-export let primaryMethod: Models.PaymentMethod;
+export let backupMethod: Models.PaymentMethod | undefined;
+export let primaryMethod: Models.PaymentMethod | undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export let organization: Models.Organization;
export let methods: Models.PaymentMethodList;
export let backupMethod: Models.PaymentMethod;
export let primaryMethod: Models.PaymentMethod;
export let organization: Models.Organization;
export let methods: Models.PaymentMethodList;
export let backupMethod: Models.PaymentMethod | undefined;
export let primaryMethod: Models.PaymentMethod | undefined;
🤖 Prompt for AI Agents
In
@src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
around lines 36 - 39, The exported props backupMethod and primaryMethod are
declared as non-optional but are used with optional chaining (e.g.,
primaryMethod?.lastError, primaryMethod?.userId), so update their types to be
optional/nullable (e.g., Models.PaymentMethod | undefined) in the export
declarations (export let backupMethod and export let primaryMethod) to match
runtime usage and avoid type errors; ensure any other references to those
symbols (backupMethod, primaryMethod) continue to use safe checks or optional
chaining.

Comment on lines +88 to +92
await sdk.forConsole.organizations.create({
organizationId: ID.unique(),
name: 'Personal Projects',
billingPlan: getBasePlanFromGroup(BillingPlanGroup.Starter).$id
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for getBasePlanFromGroup definition
rg -n "getBasePlanFromGroup" --type=ts -B 2 -A 8 | head -100

Repository: appwrite/console

Length of output: 6624


🏁 Script executed:

#!/bin/bash
# Search for plansInfo store definition and initialization
rg -n "plansInfo" --type=ts -B 2 -A 3

Repository: appwrite/console

Length of output: 6727


🏁 Script executed:

#!/bin/bash
# Check the PageLoad function in the file being reviewed
cat -n src/routes/\(public\)/sites/deploy/+page.ts | sed -n '1,110p'

Repository: appwrite/console

Length of output: 4493


🏁 Script executed:

#!/bin/bash
# Check if there's a (public) layout that provides plansInfo
find src/routes -name "+layout.ts" | xargs grep -l "plansInfo"

Repository: appwrite/console

Length of output: 150


🏁 Script executed:

#!/bin/bash
# Get the complete getBasePlanFromGroup function implementation
sed -n '88,111p' src/lib/stores/billing.ts

Repository: appwrite/console

Length of output: 966


🏁 Script executed:

#!/bin/bash
# Check the (public) layout structure and what it provides
find src/routes/\(public\) -name "+layout.ts" -o -name "+layout.js" | head -5

Repository: appwrite/console

Length of output: 153


🏁 Script executed:

#!/bin/bash
# Check if there's a root (public) layout that provides plansInfo
cat -n src/routes/\(public\)/\(guest\)/+layout.ts | head -50

Repository: appwrite/console

Length of output: 412


🏁 Script executed:

#!/bin/bash
# Check for any +layout at src/routes/(public) root
ls -la src/routes/\(public\)/ | grep layout

Repository: appwrite/console

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Verify plansInfo initialization - check if parent() in deploy page has access to it
rg -n "parent()" src/routes/\(public\)/sites/deploy/+page.ts -B 5 -A 5

Repository: appwrite/console

Length of output: 497


🏁 Script executed:

#!/bin/bash
# Check the root layout to understand the hierarchy
cat -n src/routes/+layout.ts 2>/dev/null || echo "No root +layout.ts"

Repository: appwrite/console

Length of output: 3064


🏁 Script executed:

#!/bin/bash
# Verify that plansInfo is only provided by (console) layout
rg -n "plansInfo:" src/routes --type=ts | grep -E "return \{|plansInfo"

Repository: appwrite/console

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Check if there's any mechanism in public routes that provides plansInfo
rg -n "plansInfo" src/routes/\(public\) --type=ts

Repository: appwrite/console

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Verify the exact context in deploy/+page.ts around line 91
sed -n '85,95p' src/routes/\(public\)/sites/deploy/+page.ts

Repository: appwrite/console

Length of output: 507


🏁 Script executed:

#!/bin/bash
# Check what the root layout provides for public routes
sed -n '42,46p' src/routes/+layout.ts

Repository: appwrite/console

Length of output: 184


🏁 Script executed:

#!/bin/bash
# Final verification: confirm that plansInfo would be empty for public routes
rg -n "derived.*plansInfo|export.*plansInfo" src/lib/stores/billing.ts -A 1

Repository: appwrite/console

Length of output: 209


🏁 Script executed:

#!/bin/bash
# One final check: verify getBasePlanFromGroup is not being guarded anywhere
sed -n '85,105p' src/routes/\(public\)/sites/deploy/+page.ts

Repository: appwrite/console

Length of output: 813


Potential runtime error: getBasePlanFromGroup() returns undefined for public routes.

getBasePlanFromGroup(BillingPlanGroup.Starter) accesses a store (plansInfo) that is only populated by the (console) layout (src/routes/(console)/+layout.ts). Since this page is in the (public) route group, plansInfo will be empty, causing the function to return undefined (line 99 in src/lib/stores/billing.ts returns proPlans.sort()[0] on an empty array). Accessing .$id on undefined throws a TypeError.

While the try-catch at lines 86–104 prevents the app from crashing, it silently fails and blocks organization creation. Either populate plansInfo in the public route context or provide a fallback billing plan ID.

🤖 Prompt for AI Agents
In @src/routes/(public)/sites/deploy/+page.ts around lines 88 - 92, The create
call uses getBasePlanFromGroup(BillingPlanGroup.Starter).$id which can be
undefined in the (public) route because plansInfo is only populated in the
(console) layout; update the organization creation to guard for undefined by
calling getBasePlanFromGroup(BillingPlanGroup.Starter), check its return value
(from the getBasePlanFromGroup helper) and use a safe fallback billing plan id
(e.g., a DEFAULT_PLAN_ID constant or a known starter plan id) or fetch/populate
plansInfo before calling; ensure you reference getBasePlanFromGroup and the
organization creation block (await sdk.forConsole.organizations.create) when
adding the null-check/fallback and logging an informative error if neither a
plan nor fallback exist.

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.

2 participants