From ef1435cdb4402b14cca805219d2efd4934337662 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 18 Dec 2025 13:05:32 +0530 Subject: [PATCH 01/79] remove: hardcoded `Plan` type and use billing models from sdk. --- package.json | 2 +- pnpm-lock.yaml | 10 +- src/lib/components/archiveProject.svelte | 3 +- .../components/billing/planSelection.svelte | 6 +- src/lib/sdk/billing.ts | 94 +------------------ src/lib/stores/billing.ts | 15 +-- src/lib/stores/organization.ts | 3 +- src/routes/(console)/+layout.svelte | 3 +- src/routes/(console)/+layout.ts | 7 +- .../account/organizations/+page.svelte | 4 +- .../(console)/apply-credit/+page.svelte | 6 +- .../organization-[organization]/+layout.ts | 5 +- .../billing/budgetAlert.svelte | 9 +- .../billing/budgetCap.svelte | 7 +- .../billing/planSummary.svelte | 9 +- .../billing/planSummaryOld.svelte | 10 +- .../organization-[organization]/header.svelte | 10 +- .../databases/+page.ts | 3 +- .../settings/updateMaxFileSize.svelte | 3 +- 19 files changed, 60 insertions(+), 149 deletions(-) diff --git a/package.json b/package.json index 15cb88fa7f..971f912a30 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@9b32107", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@49ffd53", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@865e2fc", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7492f49f0..d38362e774 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.25.3)(zod@3.24.3) '@appwrite.io/console': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@9b32107 - version: https://pkg.vc/-/@appwrite/@appwrite.io/console@9b32107 + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@49ffd53 + version: https://pkg.vc/-/@appwrite/@appwrite.io/console@49ffd53 '@appwrite.io/pink-icons': specifier: 0.25.0 version: 0.25.0 @@ -272,8 +272,8 @@ packages: '@analytics/type-utils@0.6.2': resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} - '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9b32107': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@9b32107} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@49ffd53': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@49ffd53} version: 1.10.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -3823,7 +3823,7 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9b32107': {} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@49ffd53': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: diff --git a/src/lib/components/archiveProject.svelte b/src/lib/components/archiveProject.svelte index 8a9797ca09..9fa20fc5b0 100644 --- a/src/lib/components/archiveProject.svelte +++ b/src/lib/components/archiveProject.svelte @@ -38,13 +38,12 @@ import { isCloud } from '$lib/system'; import { regions as regionsStore } from '$lib/stores/organization'; import type { Organization } from '$lib/stores/organization'; - import type { Plan } from '$lib/sdk/billing'; // props interface Props { projectsToArchive: Models.Project[]; organization: Organization; - currentPlan: Plan; + currentPlan: Models.BillingPlan; } let { projectsToArchive, organization, currentPlan }: Props = $props(); diff --git a/src/lib/components/billing/planSelection.svelte b/src/lib/components/billing/planSelection.svelte index 6499a47c0b..6d8d4a562b 100644 --- a/src/lib/components/billing/planSelection.svelte +++ b/src/lib/components/billing/planSelection.svelte @@ -4,21 +4,21 @@ import { currentPlan, organization } from '$lib/stores/organization'; import { Badge, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte'; import { LabelCard } from '..'; - import type { Plan } from '$lib/sdk/billing'; import { page } from '$app/state'; + import type { Models } from '@appwrite.io/console'; export let billingPlan: BillingPlan; export let isNewOrg = false; export let selfService = true; export let anyOrgFree = false; - $: plans = Object.values(page.data.plans.plans) as Plan[]; + $: plans = Object.values(page.data.plans.plans) as Models.BillingPlan[]; $: currentPlanInList = plans.some((plan) => plan.$id === $currentPlan?.$id); // experiment to remove scale plan temporarily $: plansWithoutScale = plans.filter((plan) => plan.$id != BillingPlan.SCALE); - function shouldShowTooltip(plan: Plan) { + function shouldShowTooltip(plan: Models.BillingPlan) { if (plan.$id !== BillingPlan.FREE) return true; else return !anyOrgFree; } diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index f728ca5e84..2cf540d92e 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -354,91 +354,7 @@ export type AddressesList = { total: number; }; -export type AdditionalResource = { - name: string; - currency: string; - invoiceDesc: string; - price: number; - unit: string; - value: number; - multiplier?: number; -}; - -export type PlanAddon = { - supported: boolean; - currency: string; - invoiceDesc: string; - price: number; - limit: number; - value: number; - type: string; - planIncluded: number; -}; - -export type Plan = { - $id: string; - name: string; - desc: string; - price: number; - order: number; - bandwidth: number; - storage: number; - imageTransformations: number; - webhooks: number; - users: number; - teams: number; - projects: number; - databases: number; - databasesAllowEncrypt: boolean; - databasesReads: number; - databasesWrites: number; - buckets: number; - fileSize: number; - functions: number; - executions: number; - GBHours: number; - realtime: number; - logs: number; - authPhone: number; - usage: { - bandwidth: AdditionalResource; - executions: AdditionalResource; - member: AdditionalResource; - realtime: AdditionalResource; - storage: AdditionalResource; - users: AdditionalResource; - databasesReads: AdditionalResource; - databasesWrites: AdditionalResource; - GBHours: AdditionalResource; - imageTransformations: AdditionalResource; - }; - addons: { - seats: PlanAddon; - projects: PlanAddon; - }; - trialDays: number; - budgetCapEnabled: boolean; - isAvailable: boolean; - selfService: boolean; - premiumSupport: boolean; - budgeting: boolean; - supportsMockNumbers: boolean; - backupsEnabled: boolean; - backupPolicies: number; - emailBranding: boolean; - supportsCredits: boolean; - supportsOrganizationRoles: boolean; - buildSize: number; // in MB - deploymentSize: number; // in MB - usagePerProject: boolean; -}; - -export type PlanList = { - plans: Plan[]; - total: number; -}; - -export type PlansMap = Map; +export type BillingPlansMap = Map; export type Roles = { scopes: string[]; @@ -567,7 +483,7 @@ export class Billing { }); } - async getOrganizationPlan(organizationId: string): Promise { + async getOrganizationPlan(organizationId: string): Promise { const path = `/organizations/${organizationId}/plan`; const uri = new URL(this.client.config.endpoint + path); return await this.client.call('get', uri, { @@ -575,7 +491,7 @@ export class Billing { }); } - async listPlans(queries: string[] = []): Promise { + async listPlans(queries: string[] = []): Promise { const path = `/console/plans`; const uri = new URL(this.client.config.endpoint + path); const params = { @@ -591,7 +507,7 @@ export class Billing { ); } - async getPlan(planId: string): Promise { + async getPlan(planId: string): Promise { const path = `/console/plans/${planId}`; const uri = new URL(this.client.config.endpoint + path); return await this.client.call('get', uri, { @@ -1440,7 +1356,7 @@ export class Billing { ); } - async getPlansInfo(): Promise { + async getPlansInfo(): Promise { const path = `/console/plans`; const params = {}; const uri = new URL(this.client.config.endpoint + path); diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 695ef874c7..5c7305125f 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -19,12 +19,11 @@ import type { InvoiceList, PaymentList, PaymentMethodData, - Plan, - PlansMap + BillingPlansMap } from '$lib/sdk/billing'; import { isCloud } from '$lib/system'; import { activeHeaderAlert, orgMissingPaymentMethod } from '$routes/(console)/store'; -import { AppwriteException, Query, Platform } from '@appwrite.io/console'; +import { AppwriteException, Query, Platform, type Models } from '@appwrite.io/console'; import { derived, get, writable } from 'svelte/store'; import { headerAlert } from './headerAlert'; import { addNotification, notifications } from './notifications'; @@ -72,7 +71,7 @@ export const billingLimitOutstandingInvoice = 'outstanding_invoice'; export const paymentMethods = derived(page, ($page) => $page.data.paymentMethods as PaymentList); export const addressList = derived(page, ($page) => $page.data.addressList as AddressesList); -export const plansInfo = derived(page, ($page) => $page.data.plansInfo as PlansMap); +export const plansInfo = derived(page, ($page) => $page.data.plansInfo as BillingPlansMap); export const daysLeftInTrial = writable(0); export const readOnly = writable(false); @@ -152,7 +151,11 @@ export type PlanServices = | 'authPhone' | 'imageTransformations'; -export function getServiceLimit(serviceId: PlanServices, tier: Tier = null, plan?: Plan): number { +export function getServiceLimit( + serviceId: PlanServices, + tier: Tier = null, + plan?: Models.BillingPlan +): number { if (!isCloud) return 0; if (!serviceId) return 0; @@ -626,7 +629,7 @@ export const billingURL = derived( export const hideBillingHeaderRoutes = [base + '/create-organization', base + '/account']; -export function calculateExcess(addon: AggregationTeam, plan: Plan) { +export function calculateExcess(addon: AggregationTeam, plan: Models.BillingPlan) { return { bandwidth: calculateResourceSurplus(addon.usageBandwidth, plan.bandwidth), storage: calculateResourceSurplus(addon.usageStorage, plan.storage, 'GB'), diff --git a/src/lib/stores/organization.ts b/src/lib/stores/organization.ts index 7393c6ed65..43b6074880 100644 --- a/src/lib/stores/organization.ts +++ b/src/lib/stores/organization.ts @@ -1,6 +1,5 @@ import { page } from '$app/stores'; import type { Tier } from './billing'; -import type { Plan } from '$lib/sdk/billing'; import { derived, writable } from 'svelte/store'; import { type Models, Platform } from '@appwrite.io/console'; @@ -61,6 +60,6 @@ export const organizationList = derived( ); export const organization = derived(page, ($page) => $page.data?.organization as Organization); -export const currentPlan = derived(page, ($page) => $page.data?.currentPlan as Plan); +export const currentPlan = derived(page, ($page) => $page.data?.currentPlan as Models.BillingPlan); export const members = derived(page, ($page) => $page.data.members as Models.MembershipList); export const regions = writable({ total: 0, regions: [] }); diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index 109bdabb3a..be3539bf44 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -23,7 +23,6 @@ checkForUsageLimit, checkPaymentAuthorizationRequired, paymentExpired, - plansInfo, showUsageRatesModal } from '$lib/stores/billing'; import { goto } from '$app/navigation'; @@ -310,7 +309,7 @@ await checkPaymentAuthorizationRequired(org); await checkForMandate(org); - if ($plansInfo.get(org.billingPlan)?.trialDays) { + if (org?.billingTrialDays) { calculateTrialDay(org); } } diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index 3cf1478a3a..5ad9ebcbd1 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -3,8 +3,7 @@ import { sdk } from '$lib/stores/sdk'; import { isCloud } from '$lib/system'; import type { LayoutLoad } from './$types'; import type { Tier } from '$lib/stores/billing'; -import type { Plan, PlanList } from '$lib/sdk/billing'; -import { Query } from '@appwrite.io/console'; +import { type Models, Query } from '@appwrite.io/console'; export const load: LayoutLoad = async ({ depends, parent }) => { const { organizations } = await parent(); @@ -56,8 +55,8 @@ export const load: LayoutLoad = async ({ depends, parent }) => { }; }; -function toPlanMap(plansArray: PlanList | null): Map { - const map = new Map(); +function toPlanMap(plansArray: Models.BillingPlanList | null): Map { + const map = new Map(); if (!plansArray?.plans.length) return map; const plans = plansArray.plans; diff --git a/src/routes/(console)/account/organizations/+page.svelte b/src/routes/(console)/account/organizations/+page.svelte index 2d16680439..22396b6dd6 100644 --- a/src/routes/(console)/account/organizations/+page.svelte +++ b/src/routes/(console)/account/organizations/+page.svelte @@ -16,7 +16,7 @@ import { Badge, Skeleton } from '@appwrite.io/pink-svelte'; import type { Models } from '@appwrite.io/console'; import type { Organization } from '$lib/stores/organization'; - import { daysLeftInTrial, plansInfo, tierToPlan, type Tier } from '$lib/stores/billing'; + import { daysLeftInTrial, tierToPlan, type Tier } from '$lib/stores/billing'; import { toLocaleDate } from '$lib/helpers/date'; import { BillingPlan } from '$lib/constants'; import { goto } from '$app/navigation'; @@ -62,7 +62,7 @@ if ($daysLeftInTrial <= 0) return false; if (organization.billingPlan === BillingPlan.FREE) return false; - return !!$plansInfo.get(organization.billingPlan)?.trialDays; + return !!organization?.billingTrialDays; } function isNonPayingOrganization(organization: Organization): boolean { diff --git a/src/routes/(console)/apply-credit/+page.svelte b/src/routes/(console)/apply-credit/+page.svelte index db9dbc0387..b7226b3e77 100644 --- a/src/routes/(console)/apply-credit/+page.svelte +++ b/src/routes/(console)/apply-credit/+page.svelte @@ -8,7 +8,7 @@ import { Button, Form, InputSelect, InputTags, InputText } from '$lib/elements/forms'; import { toLocaleDate } from '$lib/helpers/date'; import { Wizard } from '$lib/layout'; - import type { PaymentList, Plan } from '$lib/sdk/billing'; + import type { PaymentList } from '$lib/sdk/billing'; import { addNotification } from '$lib/stores/notifications'; import { organizationList, @@ -17,7 +17,7 @@ } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; import { confirmPayment } from '$lib/stores/stripe.js'; - import { ID } from '@appwrite.io/console'; + import { ID, type Models } from '@appwrite.io/console'; import { onMount } from 'svelte'; import { writable } from 'svelte/store'; import { isOrganization, plansInfo, type Tier } from '$lib/stores/billing'; @@ -70,7 +70,7 @@ let campaign = data?.campaign; let billingPlan: Tier = BillingPlan.PRO; let tempOrgId = null; - let currentPlan: Plan; + let currentPlan: Models.BillingPlan; $: onlyNewOrgs = campaign?.onlyNewOrgs || couponData?.onlyNewOrgs; diff --git a/src/routes/(console)/organization-[organization]/+layout.ts b/src/routes/(console)/organization-[organization]/+layout.ts index ef77274e81..39893dbd45 100644 --- a/src/routes/(console)/organization-[organization]/+layout.ts +++ b/src/routes/(console)/organization-[organization]/+layout.ts @@ -11,10 +11,9 @@ import ProjectsAtRisk from '$lib/components/billing/alerts/projectsAtRisk.svelte import { get } from 'svelte/store'; import { preferences } from '$lib/stores/preferences'; import { defaultRoles, defaultScopes } from '$lib/constants'; -import type { Plan } from '$lib/sdk/billing'; import { loadAvailableRegions } from '$routes/(console)/regions'; import type { Organization } from '$lib/stores/organization'; -import { Platform } from '@appwrite.io/console'; +import { type Models, Platform } from '@appwrite.io/console'; import { resolve } from '$app/paths'; export const load: LayoutLoad = async ({ params, depends, parent }) => { @@ -28,7 +27,7 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { let roles = isCloud ? [] : defaultRoles; let scopes = isCloud ? [] : defaultScopes; - let currentPlan: Plan = null; + let currentPlan: Models.BillingPlan | null = null; try { if (isCloud) { diff --git a/src/routes/(console)/organization-[organization]/billing/budgetAlert.svelte b/src/routes/(console)/organization-[organization]/billing/budgetAlert.svelte index 5743273b94..ad5b178684 100644 --- a/src/routes/(console)/organization-[organization]/billing/budgetAlert.svelte +++ b/src/routes/(console)/organization-[organization]/billing/budgetAlert.svelte @@ -12,11 +12,11 @@ import { Alert, Icon, Table } from '@appwrite.io/pink-svelte'; import { IconTrash } from '@appwrite.io/pink-icons-svelte'; import InputSelect from '$lib/elements/forms/inputSelect.svelte'; - import type { Plan } from '$lib/sdk/billing'; + import type { Models } from '@appwrite.io/console'; - export let organization: Organization; - export let currentPlan: Plan; export let alertsEnabled = false; + export let organization: Organization; + export let currentPlan: Models.BillingPlan; let search: string; let selectedAlert: number; @@ -53,13 +53,14 @@ alerts ); - invalidate(Dependencies.ORGANIZATION); + await invalidate(Dependencies.ORGANIZATION); addNotification({ type: 'success', isHtml: true, message: ` ${alerts.length === 0 ? 'Budget alerts removed from' : alerts.length > 1 ? `Budget alerts added to` : 'A budget alert has been added to'} ${organization.name} ` }); + trackEvent(Submit.BudgetAlertsUpdate, { alerts }); diff --git a/src/routes/(console)/organization-[organization]/billing/budgetCap.svelte b/src/routes/(console)/organization-[organization]/billing/budgetCap.svelte index bd08a88ca7..65062307b6 100644 --- a/src/routes/(console)/organization-[organization]/billing/budgetCap.svelte +++ b/src/routes/(console)/organization-[organization]/billing/budgetCap.svelte @@ -10,12 +10,13 @@ import { sdk } from '$lib/stores/sdk'; import { Alert, Link } from '@appwrite.io/pink-svelte'; import BudgetAlert from './budgetAlert.svelte'; - import type { Plan } from '$lib/sdk/billing'; + import type { Models } from '@appwrite.io/console'; - export let currentPlan: Plan; export let organization: Organization; - let capActive = organization?.billingBudget !== null; + export let currentPlan: Models.BillingPlan; + let budget = organization.billingBudget; + let capActive = organization?.billingBudget !== null; async function updateBudget() { try { diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index fb3df8a608..fe6b28b852 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -5,7 +5,7 @@ import { toLocaleDate } from '$lib/helpers/date'; import { upgradeURL } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; - import type { AggregationTeam, InvoiceUsage, Plan } from '$lib/sdk/billing'; + import type { AggregationTeam, InvoiceUsage } from '$lib/sdk/billing'; import { formatCurrency } from '$lib/helpers/numbers'; import { BillingPlan, DEFAULT_BILLING_PROJECTS_LIMIT } from '$lib/constants'; import { Click, trackEvent } from '$lib/actions/analytics'; @@ -25,6 +25,7 @@ import { IconTag } from '@appwrite.io/pink-icons-svelte'; import { page } from '$app/state'; import type { RowFactoryOptions } from '$routes/(console)/organization-[organization]/billing/store'; + import type { Models } from '@appwrite.io/console'; let { currentPlan, @@ -34,8 +35,8 @@ limit = undefined, offset = undefined }: { - currentPlan: Plan; - nextPlan?: Plan | null; + currentPlan: Models.BillingPlan; + nextPlan?: Models.BillingPlan | null; availableCredit?: number | undefined; currentAggregation?: AggregationTeam | undefined; limit?: number | undefined; @@ -229,7 +230,7 @@ } function getBillingData( - currentPlan: Plan, + currentPlan: Models.BillingPlan, currentAggregation: AggregationTeam | undefined, isSmallViewport: boolean ) { diff --git a/src/routes/(console)/organization-[organization]/billing/planSummaryOld.svelte b/src/routes/(console)/organization-[organization]/billing/planSummaryOld.svelte index 083aff4445..301792d33a 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummaryOld.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummaryOld.svelte @@ -3,9 +3,9 @@ import { CardGrid } from '$lib/components'; import { Button } from '$lib/elements/forms'; import { toLocaleDate } from '$lib/helpers/date'; - import { plansInfo, upgradeURL } from '$lib/stores/billing'; + import { upgradeURL } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; - import type { Aggregation, Invoice, Plan } from '$lib/sdk/billing'; + import type { Aggregation, Invoice } from '$lib/sdk/billing'; import { abbreviateNumber, formatCurrency, formatNumberWithCommas } from '$lib/helpers/numbers'; import { BillingPlan } from '$lib/constants'; import { Click, trackEvent } from '$lib/actions/analytics'; @@ -20,8 +20,9 @@ } from '@appwrite.io/pink-svelte'; import { IconInfo, IconTag } from '@appwrite.io/pink-icons-svelte'; import CancelDowngradeModel from './cancelDowngradeModal.svelte'; + import type { Models } from '@appwrite.io/console'; - export let currentPlan: Plan; + export let currentPlan: Models.BillingPlan; export let currentInvoice: Invoice | undefined = undefined; export let availableCredit: number | undefined = undefined; export let currentAggregation: Aggregation | undefined = undefined; @@ -31,7 +32,8 @@ const today = new Date(); const isTrial = new Date($organization?.billingStartDate).getTime() - today.getTime() > 0 && - $plansInfo.get($organization.billingPlan)?.trialDays; + $organization?.billingTrialDays; /* number of trial days. */ + const extraUsage = currentInvoice ? currentInvoice.amount - currentPlan?.price : 0; diff --git a/src/routes/(console)/organization-[organization]/header.svelte b/src/routes/(console)/organization-[organization]/header.svelte index 191131ae49..f35b360dcd 100644 --- a/src/routes/(console)/organization-[organization]/header.svelte +++ b/src/routes/(console)/organization-[organization]/header.svelte @@ -8,13 +8,7 @@ import { toLocaleDate } from '$lib/helpers/date'; import { isTabSelected } from '$lib/helpers/load'; import { Cover } from '$lib/layout'; - import { - daysLeftInTrial, - getServiceLimit, - plansInfo, - readOnly, - tierToPlan - } from '$lib/stores/billing'; + import { daysLeftInTrial, getServiceLimit, readOnly, tierToPlan } from '$lib/stores/billing'; import { members, newMemberModal, @@ -110,7 +104,7 @@ {:else if isCloud && organization?.billingPlan === BillingPlan.FREE} {/if} - {#if isCloud && organization?.billingTrialStartDate && $daysLeftInTrial > 0 && organization.billingPlan !== BillingPlan.FREE && $plansInfo.get(organization.billingPlan)?.trialDays} + {#if isCloud && organization?.billingTrialStartDate && $daysLeftInTrial > 0 && organization.billingPlan !== BillingPlan.FREE && organization?.billingTrialDays} diff --git a/src/routes/(console)/project-[region]-[project]/databases/+page.ts b/src/routes/(console)/project-[region]-[project]/databases/+page.ts index fcb828ff80..6c3719f61e 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/+page.ts @@ -7,7 +7,6 @@ import type { PageLoad, RouteParams } from './$types'; import type { BackupPolicy } from '$lib/sdk/backups'; import { isSelfHosted } from '$lib/system'; import { isCloud } from '$lib/system'; -import type { Plan } from '$lib/sdk/billing'; export const load: PageLoad = async ({ url, route, depends, params, parent }) => { depends(Dependencies.DATABASES); @@ -46,7 +45,7 @@ async function fetchDatabasesAndBackups( offset: number, params: RouteParams, search?: string | undefined, - currentPlan?: Plan + currentPlan?: Models.BillingPlan ) { const backupsEnabled = currentPlan?.backupsEnabled ?? true; diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/settings/updateMaxFileSize.svelte b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/settings/updateMaxFileSize.svelte index 6e8b681dce..a58dbc1743 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/settings/updateMaxFileSize.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/settings/updateMaxFileSize.svelte @@ -10,11 +10,10 @@ import { organization } from '$lib/stores/organization'; import { GRACE_PERIOD_OVERRIDE, isCloud } from '$lib/system'; import { updateBucket } from './+page.svelte'; - import type { Plan } from '$lib/sdk/billing'; import type { Models } from '@appwrite.io/console'; export let bucket: Models.Bucket; - export let currentPlan: Plan | null; + export let currentPlan: Models.BillingPlan | null; const service = currentPlan ? currentPlan['fileSize'] : null; const { value, unit, baseValue, units } = createByteUnitPair(bucket.maximumFileSize, 1000); From c462360681ec2e902bd503a70e6fdce30c7352b2 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 18 Dec 2025 13:09:25 +0530 Subject: [PATCH 02/79] remove: credits manual type. --- src/lib/sdk/billing.ts | 55 +------------------ .../billing/availableCredit.svelte | 15 ++--- 2 files changed, 11 insertions(+), 59 deletions(-) diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index 2cf540d92e..d195898758 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -102,55 +102,6 @@ export type Coupon = { onlyNewOrgs?: boolean; }; -export type Credit = { - /** - * Credit ID. - */ - $id: string; - /** - * Credit creation time in ISO 8601 format. - */ - $createdAt: string; - /** - * Credit update date in ISO 8601 format. - */ - $updatedAt: string; - /** - * coupon ID - */ - couponId: string; - /** - * ID of the User. - */ - userId: string; - /** - * ID of the Team. - */ - teamId: string; - /** - * Provided credit amount - */ - total: number; - /** - * Remaining credit amount - */ - credits: number; - /** - * Credit expiration time in ISO 8601 format. - */ - expiration: string; - /** - * Status of the credit. Can be one of `disabled`, `active` or `expired`. - */ - status: string; -}; - -export type CreditList = { - available: number; - credits: Credit[]; - total: number; -}; - export type AggregationTeam = { $id: string; /** @@ -880,7 +831,7 @@ export class Billing { ); } - async addCredit(organizationId: string, couponId: string): Promise { + async addCredit(organizationId: string, couponId: string): Promise { const path = `/organizations/${organizationId}/credits`; const params = { couponId @@ -895,7 +846,7 @@ export class Billing { params ); } - async listCredits(organizationId: string, queries = []): Promise { + async listCredits(organizationId: string, queries = []): Promise { const path = `/organizations/${organizationId}/credits`; const params = { queries @@ -925,7 +876,7 @@ export class Billing { ); } - async getCredit(organizationId: string, creditId: string): Promise { + async getCredit(organizationId: string, creditId: string): Promise { const path = `/organizations/${organizationId}/credits/${creditId}`; const params = { creditId diff --git a/src/routes/(console)/organization-[organization]/billing/availableCredit.svelte b/src/routes/(console)/organization-[organization]/billing/availableCredit.svelte index 52bc768599..0744f5e653 100644 --- a/src/routes/(console)/organization-[organization]/billing/availableCredit.svelte +++ b/src/routes/(console)/organization-[organization]/billing/availableCredit.svelte @@ -1,11 +1,10 @@ diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index d195898758..c52e2ed18a 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -4,31 +4,6 @@ import type { Client, Models } from '@appwrite.io/console'; import type { PaymentMethod } from '@stripe/stripe-js'; import type { Organization, OrganizationError, OrganizationList } from '../stores/organization'; -export type PaymentMethodData = { - $id: string; - $createdAt: string; - $updatedAt: string; - providerMethodId: string; - providerUserId: string; - userId: string; - expiryMonth: number; - expiryYear: number; - expired: boolean; - last4: string; - country: string; - brand: string; - clientSecret: string; - failed: boolean; - name: string; - mandateId?: string; - lastError?: string; -}; - -export type PaymentList = { - paymentMethods: PaymentMethodData[]; - total: number; -}; - export type Invoice = { $id: string; $createdAt: Date; @@ -1002,7 +977,7 @@ export class Billing { async getOrganizationPaymentMethod( organizationId: string, paymentMethodId: string - ): Promise { + ): Promise { const path = `/organizations/${organizationId}/payment-methods/${paymentMethodId}`; const params = { organizationId, @@ -1041,7 +1016,7 @@ export class Billing { //ACCOUNT - async listPaymentMethods(queries: [] = []): Promise { + async listPaymentMethods(queries: [] = []): Promise { const path = `/account/payment-methods`; const params = { queries @@ -1057,7 +1032,7 @@ export class Billing { ); } - async getPaymentMethod(paymentMethodId: string): Promise { + async getPaymentMethod(paymentMethodId: string): Promise { const path = `/account/payment-methods/${paymentMethodId}`; const params = { paymentMethodId @@ -1073,7 +1048,7 @@ export class Billing { ); } - async createPaymentMethod(): Promise { + async createPaymentMethod(): Promise { const path = `/account/payment-methods`; const params = {}; const uri = new URL(this.client.config.endpoint + path); @@ -1092,7 +1067,7 @@ export class Billing { providerMethodId: string | PaymentMethod, name: string, state: string | undefined = undefined - ): Promise { + ): Promise { const path = `/account/payment-methods/${paymentMethodId}/provider`; const params = { paymentMethodId, @@ -1118,7 +1093,7 @@ export class Billing { paymentMethodId: string, expiryMonth: string, expiryYear: string - ): Promise { + ): Promise { const path = `/account/payment-methods/${paymentMethodId}`; const params = { paymentMethodId, @@ -1136,7 +1111,7 @@ export class Billing { ); } - async deletePaymentMethod(paymentMethodId: string): Promise { + async deletePaymentMethod(paymentMethodId: string): Promise { const path = `/account/payment-methods/${paymentMethodId}`; const params = { paymentMethodId @@ -1151,7 +1126,7 @@ export class Billing { params ); } - async setDefaultPaymentMethod(paymentMethodId: string): Promise { + async setDefaultPaymentMethod(paymentMethodId: string): Promise { const path = `/account/payment-methods/${paymentMethodId}/default`; const params = { paymentMethodId @@ -1170,7 +1145,7 @@ export class Billing { async setupPaymentMandate( organizationId: string, paymentMethodId: string - ): Promise { + ): Promise { const path = `/account/payment-methods/${paymentMethodId}/setup`; const params = { organizationId, diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 5c7305125f..60746241d8 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -17,8 +17,6 @@ import type { AggregationTeam, Invoice, InvoiceList, - PaymentList, - PaymentMethodData, BillingPlansMap } from '$lib/sdk/billing'; import { isCloud } from '$lib/system'; @@ -69,7 +67,10 @@ export const roles = [ export const teamStatusReadonly = 'readonly'; export const billingLimitOutstandingInvoice = 'outstanding_invoice'; -export const paymentMethods = derived(page, ($page) => $page.data.paymentMethods as PaymentList); +export const paymentMethods = derived( + page, + ($page) => $page.data.paymentMethods as Models.PaymentMethodList +); export const addressList = derived(page, ($page) => $page.data.addressList as AddressesList); export const plansInfo = derived(page, ($page) => $page.data.plansInfo as BillingPlansMap); export const daysLeftInTrial = writable(0); @@ -538,7 +539,7 @@ export function checkForMarkedForDeletion(org: Organization) { } } -export const paymentMissingMandate = writable(null); +export const paymentMissingMandate = writable(null); export async function checkForMandate(org: Organization) { const paymentId = org.paymentMethodId ?? org.backupPaymentMethodId; diff --git a/src/lib/stores/stripe.ts b/src/lib/stores/stripe.ts index 9f4bc727bc..cda0bf776c 100644 --- a/src/lib/stores/stripe.ts +++ b/src/lib/stores/stripe.ts @@ -1,6 +1,6 @@ import type { Appearance, - PaymentMethod, + PaymentMethod as StripePaymentMethod, Stripe, StripeElement, StripeElements @@ -8,21 +8,21 @@ import type { import { sdk } from './sdk'; import { app } from './app'; import { get, writable } from 'svelte/store'; -import type { PaymentMethodData } from '$lib/sdk/billing'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { addNotification } from './notifications'; import { organization } from './organization'; import { base } from '$app/paths'; import { ThemeDarkCloud, ThemeLightCloud } from '$themes'; import Color from 'color'; +import type { Models } from '@appwrite.io/console'; export const stripe = writable(); -let paymentMethod: PaymentMethodData; +export const isStripeInitialized = writable(false); + let clientSecret: string; let elements: StripeElements; let paymentElement: StripeElement; - -export const isStripeInitialized = writable(false); +let paymentMethod: Models.PaymentMethod; export async function initializeStripe(node: HTMLElement) { if (!get(stripe)) return; @@ -116,12 +116,12 @@ export async function submitStripeCard(name: string, organizationId?: string) { } if (setupIntent && setupIntent.status === 'succeeded') { - const pm = setupIntent.payment_method as PaymentMethod | string | undefined; + const pm = setupIntent.payment_method as StripePaymentMethod | string | undefined; // If Stripe returned an expanded PaymentMethod object, check the card country. // If it returned a string id (common), `typeof pm === 'string'` and we skip this. if (typeof pm !== 'string' && pm?.card?.country === 'US') { // need to get state - return pm as PaymentMethod; + return pm as StripePaymentMethod; } // The backend expects a provider method ID (string). Extract the id @@ -130,7 +130,7 @@ export async function submitStripeCard(name: string, organizationId?: string) { if (typeof pm === 'string') { providerId = pm; } else { - providerId = (pm as PaymentMethod)?.id; + providerId = (pm as StripePaymentMethod)?.id; } if (!providerId) { diff --git a/src/routes/(console)/account/payments/editPaymentModal.svelte b/src/routes/(console)/account/payments/editPaymentModal.svelte index 7850cdea9a..5b1145f686 100644 --- a/src/routes/(console)/account/payments/editPaymentModal.svelte +++ b/src/routes/(console)/account/payments/editPaymentModal.svelte @@ -5,12 +5,12 @@ import { Dependencies } from '$lib/constants'; import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; - import type { PaymentMethodData } from '$lib/sdk/billing'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Alert } from '@appwrite.io/pink-svelte'; + import type { Models } from '@appwrite.io/console'; export let show = false; - export let selectedPaymentMethod: PaymentMethodData; + export let selectedPaymentMethod: Models.PaymentMethod; export let isLinked = false; const currentYear = new Date().getFullYear(); let error: string; diff --git a/src/routes/(console)/account/payments/paymentMethods.svelte b/src/routes/(console)/account/payments/paymentMethods.svelte index 8e53a8d3da..53d3b270a5 100644 --- a/src/routes/(console)/account/payments/paymentMethods.svelte +++ b/src/routes/(console)/account/payments/paymentMethods.svelte @@ -2,7 +2,6 @@ import { CardGrid, CreditCardInfo, Empty } from '$lib/components'; import { Button } from '$lib/elements/forms'; import { paymentMethods } from '$lib/stores/billing'; - import type { PaymentMethodData } from '$lib/sdk/billing'; import { organizationList, type Organization } from '$lib/stores/organization'; import { base } from '$app/paths'; import EditPaymentModal from './editPaymentModal.svelte'; @@ -26,10 +25,11 @@ Tag, Typography } from '@appwrite.io/pink-svelte'; + import type { Models } from '@appwrite.io/console'; export let showPayment = false; let showDropdown = []; - let selectedMethod: PaymentMethodData; + let selectedMethod: Models.PaymentMethod; let selectedLinkedOrgs: Organization[] = []; let showDelete = false; let showEdit = false; @@ -37,9 +37,7 @@ $: orgList = $organizationList.teams as unknown as Organization[]; - $: filteredMethods = $paymentMethods?.paymentMethods.filter( - (method: PaymentMethodData) => !!method?.last4 - ); + $: filteredMethods = $paymentMethods?.paymentMethods.filter((method) => !!method?.last4); const isMethodLinkedToOrg = (methodId: string, org: Organization) => methodId === org.paymentMethodId || methodId === org.backupPaymentMethodId; diff --git a/src/routes/(console)/apply-credit/+page.svelte b/src/routes/(console)/apply-credit/+page.svelte index b7226b3e77..29b8c85df7 100644 --- a/src/routes/(console)/apply-credit/+page.svelte +++ b/src/routes/(console)/apply-credit/+page.svelte @@ -8,7 +8,6 @@ import { Button, Form, InputSelect, InputTags, InputText } from '$lib/elements/forms'; import { toLocaleDate } from '$lib/helpers/date'; import { Wizard } from '$lib/layout'; - import type { PaymentList } from '$lib/sdk/billing'; import { addNotification } from '$lib/stores/notifications'; import { organizationList, @@ -48,7 +47,7 @@ let formComponent: Form; let couponForm: Form; let isSubmitting = writable(false); - let methods: PaymentList; + let methods: Models.PaymentMethodList; let paymentMethodId: string; let collaborators: string[]; let taxId: string; diff --git a/src/routes/(console)/organization-[organization]/billing/+page.svelte b/src/routes/(console)/organization-[organization]/billing/+page.svelte index 99729b8e9a..271912fd04 100644 --- a/src/routes/(console)/organization-[organization]/billing/+page.svelte +++ b/src/routes/(console)/organization-[organization]/billing/+page.svelte @@ -9,7 +9,6 @@ import PaymentHistory from './paymentHistory.svelte'; import TaxId from './taxId.svelte'; import { failedInvoice, tierToPlan, upgradeURL, useNewPricingModal } from '$lib/stores/billing'; - import type { PaymentMethodData } from '$lib/sdk/billing'; import { onMount } from 'svelte'; import { page } from '$app/state'; import { confirmPayment } from '$lib/stores/stripe'; @@ -31,11 +30,11 @@ // why are these reactive? $: defaultPaymentMethod = data?.paymentMethods?.paymentMethods?.find( - (method: PaymentMethodData) => method.$id === organization?.paymentMethodId + (method) => method.$id === organization?.paymentMethodId ); $: backupPaymentMethod = data?.paymentMethods?.paymentMethods?.find( - (method: PaymentMethodData) => method.$id === organization?.backupPaymentMethodId + (method) => method.$id === organization?.backupPaymentMethodId ); onMount(async () => { diff --git a/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte b/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte index 124518f1e3..6f45b4b94c 100644 --- a/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte +++ b/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte @@ -8,7 +8,6 @@ import { type Organization } from '$lib/stores/organization'; import { Button } from '$lib/elements/forms'; import { hasStripePublicKey, isCloud } from '$lib/system'; - import type { PaymentList, PaymentMethodData } from '$lib/sdk/billing'; import DeleteOrgPayment from './deleteOrgPayment.svelte'; import ReplaceCard from './replaceCard.svelte'; import EditPaymentModal from '$routes/(console)/account/payments/editPaymentModal.svelte'; @@ -33,17 +32,18 @@ IconSwitchHorizontal, IconTrash } from '@appwrite.io/pink-icons-svelte'; + import type { Models } from '@appwrite.io/console'; export let organization: Organization; - export let methods: PaymentList; + export let methods: Models.PaymentMethodList; - let showPayment = false; let showEdit = false; let showDelete = false; + let showPayment = false; let showReplace = false; let isSelectedBackup = false; - let backupPaymentMethod: PaymentMethodData; - let defaultPaymentMethod: PaymentMethodData; + let backupPaymentMethod: Models.PaymentMethod; + let defaultPaymentMethod: Models.PaymentMethod; async function addPaymentMethod(paymentMethodId: string) { try { diff --git a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte index ea4cc7d267..05551918fb 100644 --- a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte +++ b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte @@ -7,23 +7,23 @@ import { Dependencies } from '$lib/constants'; import { setPaymentMethod, submitStripeCard } from '$lib/stores/stripe'; import { onMount } from 'svelte'; - import type { PaymentList, PaymentMethodData } from '$lib/sdk/billing'; import { addNotification } from '$lib/stores/notifications'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { PaymentBoxes } from '$lib/components/billing'; - import type { PaymentMethod } from '@stripe/stripe-js'; + import type { PaymentMethod as StripePaymentMethod } from '@stripe/stripe-js'; + import type { Models } from '@appwrite.io/console'; export let organization: Organization; export let show = false; export let isBackup = false; - export let methods: PaymentList; + export let methods: Models.PaymentMethodList; let name: string; let error: string = null; let selectedPaymentMethodId: string; let showState: boolean = false; let state: string = ''; - let paymentMethod: PaymentMethod | null = null; + let paymentMethod: StripePaymentMethod | null = null; onMount(async () => { if (!organization.paymentMethodId && !organization.backupPaymentMethodId) { @@ -48,19 +48,19 @@ if (showState && !state) { throw Error('Please select a state'); } - let method: PaymentMethodData; + let method: Models.PaymentMethod; if (showState) { method = await setPaymentMethod(paymentMethod.id, name, state); } else { const card = await submitStripeCard(name, organization.$id); if (card && Object.hasOwn(card, 'id')) { - if ((card as PaymentMethod).card?.country === 'US') { - paymentMethod = card as PaymentMethod; + if ((card as StripePaymentMethod).card?.country === 'US') { + paymentMethod = card as StripePaymentMethod; showState = true; return; } } else if (card && Object.hasOwn(card, '$id')) { - method = card as PaymentMethodData; + method = card as Models.PaymentMethod; } } selectedPaymentMethodId = method.$id; diff --git a/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte b/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte index 65e217d2c6..ab7384d568 100644 --- a/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte +++ b/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte @@ -3,7 +3,7 @@ import { FakeModal } from '$lib/components'; import { Button } from '$lib/elements/forms'; import { Dependencies } from '$lib/constants'; - import type { Invoice, PaymentMethodData } from '$lib/sdk/billing'; + import type { Invoice } from '$lib/sdk/billing'; import { addNotification } from '$lib/stores/notifications'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { page } from '$app/state'; @@ -21,18 +21,21 @@ import { getApiEndpoint, sdk } from '$lib/stores/sdk'; import { formatCurrency } from '$lib/helpers/numbers'; import { base } from '$app/paths'; - import type { PaymentMethod } from '@stripe/stripe-js'; + import type { PaymentMethod as StripePaymentMethod } from '@stripe/stripe-js'; + import type { Models } from '@appwrite.io/console'; export let show = false; export let invoice: Invoice; + + let name: string; + let state: string = ''; let error: string = null; + let setAsDefault = false; let isButtonDisabled = false; - let name: string; let paymentMethodId: string; - let setAsDefault = false; let showState: boolean = false; - let state: string = ''; - let paymentMethod: PaymentMethod | null = null; + let paymentMethod: StripePaymentMethod | null = null; + const endpoint = getApiEndpoint(); onMount(async () => { @@ -60,15 +63,15 @@ if (showState && !state) { throw Error('Please select a state'); } - let method: PaymentMethodData; + let method: Models.PaymentMethod; if (showState) { method = await setPaymentMethod(paymentMethod.id, name, state); } else { const card = await submitStripeCard(name, $organization.$id); // When Stripe returns an expanded PaymentMethod for US cards, we need state. - if (Object.hasOwn(card, 'id') && (card as PaymentMethod)?.card) { - if ((card as PaymentMethod).card?.country === 'US') { - paymentMethod = card as PaymentMethod; + if (Object.hasOwn(card, 'id') && (card as StripePaymentMethod)?.card) { + if ((card as StripePaymentMethod).card?.country === 'US') { + paymentMethod = card as StripePaymentMethod; showState = true; return; } @@ -76,7 +79,7 @@ // Otherwise, we expect an Appwrite PaymentMethodData with `$id`. if (Object.hasOwn(card, '$id')) { - method = card as PaymentMethodData; + method = card as Models.PaymentMethod; } } const card = await sdk.forConsole.billing.getPaymentMethod(method.$id); diff --git a/src/routes/(console)/organization-[organization]/billing/wizard/paymentDetails.svelte b/src/routes/(console)/organization-[organization]/billing/wizard/paymentDetails.svelte index fd27def7e0..7ee8a49b65 100644 --- a/src/routes/(console)/organization-[organization]/billing/wizard/paymentDetails.svelte +++ b/src/routes/(console)/organization-[organization]/billing/wizard/paymentDetails.svelte @@ -1,7 +1,6 @@ {#if estimation} diff --git a/src/routes/(console)/organization-[organization]/settings/deleteOrganizationModal.svelte b/src/routes/(console)/organization-[organization]/settings/deleteOrganizationModal.svelte index dab95d83a3..25a97956de 100644 --- a/src/routes/(console)/organization-[organization]/settings/deleteOrganizationModal.svelte +++ b/src/routes/(console)/organization-[organization]/settings/deleteOrganizationModal.svelte @@ -15,15 +15,15 @@ import { tierToPlan } from '$lib/stores/billing'; import { Table, Tabs, Alert } from '@appwrite.io/pink-svelte'; import DeleteOrganizationEstimation from './deleteOrganizationEstimation.svelte'; - import type { EstimationDeleteOrganization, InvoiceList } from '$lib/sdk/billing'; + import type { Models } from '@appwrite.io/console'; export let showDelete = false; - export let invoices: InvoiceList; - let error: string = null; + export let invoices: Models.InvoiceList; + let error: string = null; let selectedTab = 'projects'; let organizationName: string = null; - let estimation: EstimationDeleteOrganization; + let estimation: Models.EstimationDeleteOrganization; async function deleteOrg() { try { @@ -112,7 +112,8 @@ This action is irreversible. {/if}

- {#if estimation && (estimation.unpaidInvoices.length > 0 || estimation.grossAmount > 0)} + + {#if estimation && (estimation.unpaidInvoices.length > 0 || estimation.unpaidInvoices.some((invoice) => invoice.grossAmount > 0))} {:else} {#if $projects.total > 0} diff --git a/src/routes/(console)/organization-[organization]/settings/invoicesTable.svelte b/src/routes/(console)/organization-[organization]/settings/invoicesTable.svelte index 84b7ca6093..514e8a575a 100644 --- a/src/routes/(console)/organization-[organization]/settings/invoicesTable.svelte +++ b/src/routes/(console)/organization-[organization]/settings/invoicesTable.svelte @@ -1,5 +1,4 @@ {#if $organization?.$id && $organization?.billingPlan === BillingPlan.FREE && $readOnly && !hideBillingHeaderRoutes.includes(page.url.pathname)} + title={`${$organization.name} usage has reached the ${billingIdToPlan($organization.billingPlan).name} plan limit`}> - Usage for the {$organization.name} organization has reached the limits of the {tierToPlan( + Usage for the {$organization.name} organization has reached the limits of the {billingIdToPlan( $organization.billingPlan ).name} plan. Consider upgrading to increase your resource usage. diff --git a/src/lib/components/billing/emptyCardCloud.svelte b/src/lib/components/billing/emptyCardCloud.svelte index b83676f28d..0ca7071967 100644 --- a/src/lib/components/billing/emptyCardCloud.svelte +++ b/src/lib/components/billing/emptyCardCloud.svelte @@ -1,21 +1,33 @@ - + {#if children} + {@render children()} + {:else} Upgrade to add {service} - Upgrade to a {tierToPlan(BillingPlan.PRO).name} plan to add {service} to your organization + Upgrade to a {proPlanName} plan to add {service} to your organization diff --git a/src/lib/components/backupRestoreBox.svelte b/src/lib/components/backupRestoreBox.svelte index f1de9d3bab..5210d20852 100644 --- a/src/lib/components/backupRestoreBox.svelte +++ b/src/lib/components/backupRestoreBox.svelte @@ -4,7 +4,7 @@ import { onMount } from 'svelte'; import { isCloud, isSelfHosted } from '$lib/system'; import { organization } from '$lib/stores/organization'; - import { BillingPlan, Dependencies } from '$lib/constants'; + import { Dependencies } from '$lib/constants'; import type { BackupArchive, BackupRestoration } from '$lib/sdk/backups'; import { goto, invalidate } from '$app/navigation'; import { page } from '$app/state'; @@ -125,7 +125,7 @@ onMount(() => { // fast path: don't subscribe if org is on a free plan or is self-hosted. - if (isSelfHosted || (isCloud && $organization?.billingPlan === BillingPlan.FREE)) return; + if (isSelfHosted || (isCloud && !$organization?.billingPlanDetails.backupsEnabled)) return; return realtime.forProject(page.params.region, 'console', (response) => { if (!response.channels.includes(`projects.${getProjectId()}`)) return; diff --git a/src/lib/components/billing/alerts/limitReached.svelte b/src/lib/components/billing/alerts/limitReached.svelte index 12605cded4..cb6e934b8d 100644 --- a/src/lib/components/billing/alerts/limitReached.svelte +++ b/src/lib/components/billing/alerts/limitReached.svelte @@ -2,26 +2,19 @@ import { base } from '$app/paths'; import { page } from '$app/state'; import { Click, trackEvent } from '$lib/actions/analytics'; - import { BillingPlan } from '$lib/constants'; import { Button } from '$lib/elements/forms'; import { HeaderAlert } from '$lib/layout'; - import { - hideBillingHeaderRoutes, - readOnly, - billingIdToPlan, - upgradeURL - } from '$lib/stores/billing'; + import { hideBillingHeaderRoutes, readOnly, upgradeURL } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; -{#if $organization?.$id && $organization?.billingPlan === BillingPlan.FREE && $readOnly && !hideBillingHeaderRoutes.includes(page.url.pathname)} +{#if $organization?.$id && !$organization?.billingPlanDetails.usage && $readOnly && !hideBillingHeaderRoutes.includes(page.url.pathname)} + title={`${$organization.name} usage has reached the ${$organization.billingPlanDetails.name} plan limit`}> - Usage for the {$organization.name} organization has reached the limits of the {billingIdToPlan( - $organization.billingPlan - ).name} + Usage for the {$organization.name} organization has reached the limits of the {$organization + .billingPlanDetails.name} plan. Consider upgrading to increase your resource usage. diff --git a/src/lib/components/billing/alerts/missingPaymentMethod.svelte b/src/lib/components/billing/alerts/missingPaymentMethod.svelte index 96c29c5d78..fd7f4b80ac 100644 --- a/src/lib/components/billing/alerts/missingPaymentMethod.svelte +++ b/src/lib/components/billing/alerts/missingPaymentMethod.svelte @@ -1,14 +1,13 @@ -{#if ($orgMissingPaymentMethod.billingPlan === BillingPlan.PRO || $orgMissingPaymentMethod.billingPlan === BillingPlan.SCALE) && !$orgMissingPaymentMethod.paymentMethodId && !$orgMissingPaymentMethod.backupPaymentMethodId && !hideBillingHeaderRoutes.includes(page.url.pathname)} +{#if $orgMissingPaymentMethod.billingPlanDetails.requiresPaymentMethod && !$orgMissingPaymentMethod.paymentMethodId && !$orgMissingPaymentMethod.backupPaymentMethodId && !hideBillingHeaderRoutes.includes(page.url.pathname)} diff --git a/src/lib/components/billing/alerts/newDevUpgradePro.svelte b/src/lib/components/billing/alerts/newDevUpgradePro.svelte index 733736f02a..7d38ec049c 100644 --- a/src/lib/components/billing/alerts/newDevUpgradePro.svelte +++ b/src/lib/components/billing/alerts/newDevUpgradePro.svelte @@ -2,7 +2,7 @@ import { base } from '$app/paths'; import { page } from '$app/state'; import { Click, trackEvent } from '$lib/actions/analytics'; - import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants'; + import { NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants'; import { Button } from '$lib/elements/forms'; import { organization } from '$lib/stores/organization'; import { activeHeaderAlert } from '$routes/(console)/store'; @@ -23,7 +23,7 @@ } -{#if show && $organization?.$id && $organization?.billingPlan === BillingPlan.FREE && !page.url.pathname.includes(base + '/account')} +{#if show && $organization?.$id && !$organization?.billingPlanDetails.supportsCredits && !page.url.pathname.includes(base + '/account')} ; export let billingBudget: number; @@ -82,8 +82,8 @@ } $: organizationId - ? getUpdatePlanEstimate(organizationId, billingPlan, collaborators, couponData?.code) - : getEstimate(billingPlan, collaborators, couponData?.code); + ? getUpdatePlanEstimate(organizationId, billingPlan.$id, collaborators, couponData?.code) + : getEstimate(billingPlan.$id, collaborators, couponData?.code); {#if estimation} @@ -106,6 +106,7 @@ {#if couponData?.status === 'active'} {/if} + Total due diff --git a/src/lib/components/billing/planComparisonBox.svelte b/src/lib/components/billing/planComparisonBox.svelte index 80ae87f25e..7a71d2f01e 100644 --- a/src/lib/components/billing/planComparisonBox.svelte +++ b/src/lib/components/billing/planComparisonBox.svelte @@ -1,6 +1,5 @@ @@ -29,9 +48,9 @@ @@ -57,7 +76,7 @@ {#if $currentPlan && !currentPlanInList} diff --git a/src/lib/components/billing/selectPlan.svelte b/src/lib/components/billing/selectPlan.svelte index 2b9d9498fd..017d1730ea 100644 --- a/src/lib/components/billing/selectPlan.svelte +++ b/src/lib/components/billing/selectPlan.svelte @@ -1,15 +1,24 @@ {#if billingPlan} @@ -19,10 +28,10 @@ diff --git a/src/lib/components/billing/usageRates.svelte b/src/lib/components/billing/usageRates.svelte index 0d8465f1dc..0db4ac04e7 100644 --- a/src/lib/components/billing/usageRates.svelte +++ b/src/lib/components/billing/usageRates.svelte @@ -2,22 +2,18 @@ import { Modal } from '$lib/components'; import { Button } from '$lib/elements/forms'; import { toLocaleDate } from '$lib/helpers/date'; - import { plansInfo } from '$lib/stores/billing'; - import { abbreviateNumber, formatCurrency, isWithinSafeRange } from '$lib/helpers/numbers'; - import { BillingPlan } from '$lib/constants'; import { Table, Typography } from '@appwrite.io/pink-svelte'; - import type { Models } from '@appwrite.io/console'; + import { BillingPlanGroup, type Models } from '@appwrite.io/console'; + import { abbreviateNumber, formatCurrency, isWithinSafeRange } from '$lib/helpers/numbers'; export let show = false; export let org: Models.Organization; - $: plan = $plansInfo?.get(org.billingPlan); - $: nextDate = org?.name ? new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1).toString() : org?.billingNextInvoiceDate; - $: isFree = org.billingPlan === BillingPlan.FREE; + $: isFree = org.billingPlanDetails.group === BillingPlanGroup.Starter; // equal or above means unlimited! const getCorrectSeatsCountValue = (count: number): string | number => { @@ -27,22 +23,22 @@ }; function getPlanLimit(key: string): number | false { - return plan[key] || false; + return org.billingPlanDetails[key] || false; } {#if isFree} - Usage on the {$plansInfo?.get(BillingPlan.FREE).name} plan is limited for the following resources. - Next billing period: {toLocaleDate(nextDate)}. + Usage on the {org.billingPlanDetails.name} plan is limited for the following resources. Next + billing period: {toLocaleDate(nextDate)}. - {:else if org.billingPlan === BillingPlan.PRO} + {:else if org.billingPlanDetails.group === BillingPlanGroup.Pro} Usage on the Pro plan will be charged at the end of each billing period at the following rates. Next billing period: {toLocaleDate(nextDate)}. - {:else if org.billingPlan === BillingPlan.SCALE} + {:else if org.billingPlanDetails.group === BillingPlanGroup.Scale} Usage on the Scale plan will be charged at the end of each billing period at the following rates. Next billing period: {toLocaleDate(nextDate)}. @@ -56,7 +52,7 @@ Limit Rate - {#each Object.values(plan.addons) as addon} + {#each Object.values(org.billingPlanDetails.addons) as addon} {addon.invoiceDesc} @@ -69,7 +65,7 @@ {/if} {/each} - {#each Object.entries(plan.usage) as [key, usage]} + {#each Object.entries(org.billingPlanDetails.usage) as [key, usage]} {@const limit = getPlanLimit(key)} {@const show = limit !== false} {#if show} diff --git a/src/lib/components/bottomModalAlert.svelte b/src/lib/components/bottomModalAlert.svelte index 623a510c19..37712efdac 100644 --- a/src/lib/components/bottomModalAlert.svelte +++ b/src/lib/components/bottomModalAlert.svelte @@ -10,8 +10,7 @@ } from '$lib/stores/bottom-alerts'; import { onMount } from 'svelte'; import { organization } from '$lib/stores/organization'; - import { BillingPlan } from '$lib/constants'; - import { upgradeURL } from '$lib/stores/billing'; + import { canUpgrade, upgradeURL } from '$lib/stores/billing'; import { addBottomModalAlerts } from '$routes/(console)/bottomAlerts'; import { project } from '$routes/(console)/project-[region]-[project]/store'; import { page } from '$app/state'; @@ -142,7 +141,7 @@ // the button component cannot have both href and on:click! function triggerWindowLink(alert: BottomModalAlertItem, event?: string) { const alertAction = alert.cta; - const shouldShowUpgrade = showUpgrade(); + const shouldShowUpgrade = canUpgrade($organization?.billingPlanDetails); // for correct event tracking after removal const currentModalId = currentModalAlert.id; @@ -170,26 +169,11 @@ }); } - function showUpgrade() { - const plan = currentModalAlert.plan; - const organizationPlan = $organization?.billingPlan; - switch (plan) { - case 'free': - return false; - case 'pro': - return organizationPlan === BillingPlan.FREE; - case 'scale': - return ( - organizationPlan === BillingPlan.FREE || organizationPlan === BillingPlan.PRO - ); - } - } - onMount(addBottomModalAlerts); {#if !isOnOnboarding && filteredModalAlerts.length > 0 && currentModalAlert} - {@const shouldShowUpgrade = showUpgrade()} + {@const shouldShowUpgrade = canUpgrade($organization?.billingPlanDetails)}
diff --git a/src/lib/components/breadcrumbs.svelte b/src/lib/components/breadcrumbs.svelte index 7f1a59c47c..57da33005f 100644 --- a/src/lib/components/breadcrumbs.svelte +++ b/src/lib/components/breadcrumbs.svelte @@ -25,7 +25,6 @@ import { ID, type Models, Query } from '@appwrite.io/console'; import { sdk } from '$lib/stores/sdk'; import { page } from '$app/state'; - import { BillingPlan } from '$lib/constants'; import { onDestroy } from 'svelte'; type Organization = { @@ -254,7 +253,7 @@ let badgeType: 'success' | undefined; $: badgeType = - $organization && $organization.billingPlan !== BillingPlan.FREE ? 'success' : undefined; + $organization && $organization.billingPlanDetails.price > 0 ? 'success' : undefined; diff --git a/src/lib/components/emptyCardImageCloud.svelte b/src/lib/components/emptyCardImageCloud.svelte index 870edc4889..977d82e4f5 100644 --- a/src/lib/components/emptyCardImageCloud.svelte +++ b/src/lib/components/emptyCardImageCloud.svelte @@ -2,7 +2,7 @@ import { isSmallViewport } from '$lib/stores/viewport'; import { organization } from '$lib/stores/organization'; import { Card, Layout, Typography } from '@appwrite.io/pink-svelte'; - import { getNextTierBillingPlan, billingIdToPlan } from '$lib/stores/billing'; + import { getNextTierBillingPlan } from '$lib/stores/billing'; export let source = 'empty_state_card'; export let responsive = false; @@ -26,7 +26,7 @@ - + diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index 9705442c47..fc97c9b257 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -1,7 +1,6 @@ @@ -11,7 +10,7 @@ {#if isCloud} - {#if $organization?.billingPlan !== BillingPlan.FREE} + {#if $organization?.billingPlanDetails.supportsOrganizationRoles} Roles Owner, Developer, Editor, Analyst and Billing. diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 33c900742a..2cb52605e3 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -609,17 +609,6 @@ export const eventServices: Array = [ } ]; -export enum BillingPlan { - FREE = 'tier-0', - PRO = 'tier-1', - SCALE = 'tier-2', - GITHUB_EDUCATION = 'auto-1', - CUSTOM = 'cont-1', - ENTERPRISE = 'ent-1' -} - -export const BASE_BILLING_PLANS: string[] = [BillingPlan.FREE, BillingPlan.PRO, BillingPlan.SCALE]; - export const feedbackDowngradeOptions = [ { value: 'availableFeatures', diff --git a/src/lib/layout/containerButton.svelte b/src/lib/layout/containerButton.svelte index de3f0024cf..516eead977 100644 --- a/src/lib/layout/containerButton.svelte +++ b/src/lib/layout/containerButton.svelte @@ -1,17 +1,17 @@ @@ -95,11 +95,19 @@ .join(', ')} {#if services.length} - - {#if $organization?.billingPlan !== BillingPlan.FREE && hasUsageFees} + {@const supportsUsage = Object.keys($currentPlan.usage).length > 0} + + {#if !supportsUsage && hasUsageFees} - You've reached the {services} limit for the {tier} plan. + You've reached the {services} limit for the {planName} plan. ($showUsageRatesModal = true)} >Excess usage fees will apply. @@ -108,7 +116,7 @@ {:else} - You've reached the {services} limit for the {tier} plan. Upgrade your @@ -132,7 +140,7 @@ on:click={() => (showDropdown = !showDropdown)}> - {:else if $organization?.billingPlan !== BillingPlan.SCALE} + {:else} {/if} - + {#if hasProjectLimitation}

You are limited to {limit} - {title.toLocaleLowerCase()} per project on the {tier} plan. - {#if $organization?.billingPlan === BillingPlan.FREE} You are limited to {limit} - {title.toLocaleLowerCase()} per organization on the {tier} plan. + {title.toLocaleLowerCase()} per organization on the {planName} plan. ($showUsageRatesModal = true)} >Excess usage fees will apply. @@ -165,8 +179,8 @@ {:else}

You are limited to {limit} - {title.toLocaleLowerCase()} per organization on the {tier} plan. - {#if $organization?.billingPlan === BillingPlan.FREE} + {title.toLocaleLowerCase()} per organization on the {planName} plan. + {#if canUpgrade($organization.billingPlan)} Upgrade for additional {title.toLocaleLowerCase()}. {/if} diff --git a/src/lib/layout/createProject.svelte b/src/lib/layout/createProject.svelte index c24e9234aa..5117b5738b 100644 --- a/src/lib/layout/createProject.svelte +++ b/src/lib/layout/createProject.svelte @@ -4,15 +4,13 @@ import { CustomId } from '$lib/components/index.js'; import { getFlagUrl } from '$lib/helpers/flag'; import { isCloud } from '$lib/system.js'; - import { currentPlan, organization } from '$lib/stores/organization'; import { Button } from '$lib/elements/forms'; - import { base } from '$app/paths'; import { page } from '$app/state'; import type { Models } from '@appwrite.io/console'; import { filterRegions } from '$lib/helpers/regions'; import type { Snippet } from 'svelte'; - import { BillingPlan } from '$lib/constants'; import { formatCurrency } from '$lib/helpers/numbers'; + import { resolve } from '$app/paths'; let { projectName = $bindable(), @@ -20,7 +18,7 @@ regions = [], region = $bindable(), showTitle = true, - billingPlan = undefined, + currentPlan = undefined, projects = undefined, submit }: { @@ -29,21 +27,24 @@ regions: Array; region: string; showTitle: boolean; - billingPlan?: BillingPlan; + currentPlan?: Models.BillingPlan; projects?: number; submit?: Snippet; } = $props(); let showCustomId = $state(false); - let isProPlan = $derived((billingPlan ?? $organization?.billingPlan) === BillingPlan.PRO); - let projectsLimited = $derived( - $currentPlan?.projects > 0 && projects && projects >= $currentPlan?.projects - ); - let isAddonProject = $derived( - $currentPlan?.addons?.projects?.supported && + + const projectsLimited = $derived.by(() => { + return currentPlan?.projects > 0 && projects && projects >= currentPlan?.projects; + }); + + const isAddonProject = $derived.by(() => { + return ( + currentPlan?.addons?.projects?.supported && projects && - projects >= $currentPlan?.addons?.projects?.planIncluded - ); + projects >= currentPlan?.addons?.projects?.planIncluded + ); + }); @@ -61,7 +62,7 @@ 0} Region cannot be changed after creation {/if} + {#if isAddonProject} Each added project comes with its own dedicated pool of resources. {/if} + {#if projectsLimited} + title={`You've reached your limit of ${currentPlan?.projects} projects`}> Extra projects are available on paid plans for an additional fee diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index 193aa63335..c1034864dd 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -9,14 +9,12 @@ import { organization, organizationList } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; import { user } from '$lib/stores/user'; - import { billingIdToPlan } from '$lib/stores/billing'; import { isCloud } from '$lib/system'; import SideNavigation from '$lib/layout/navigation.svelte'; import { hasOnboardingDismissed } from '$lib/helpers/onboarding'; import { isSidebarOpen, noWidthTransition } from '$lib/stores/sidebar'; - import { BillingPlan } from '$lib/constants'; import { page } from '$app/stores'; - import type { Models } from '@appwrite.io/console'; + import { BillingPlanGroup, type Models } from '@appwrite.io/console'; import { getSidebarState, isInDatabasesRoute, updateSidebarState } from '$lib/helpers/sidebar'; import { isTabletViewport } from '$lib/stores/viewport'; @@ -156,13 +154,13 @@ .toString(), organizations: $organizationList.teams.map((org) => { - const billingPlan = org['billingPlan']; + const billingPlan = org['billingPlanDetails'] as Models.BillingPlan; return { name: org.name, $id: org.$id, - showUpgrade: billingPlan === BillingPlan.FREE, - tierName: isCloud ? billingIdToPlan(billingPlan).name : null, - isSelected: $organization?.$id === org.$id + isSelected: $organization?.$id === org.$id, + tierName: isCloud ? billingPlan.name : null, + showUpgrade: billingPlan.group === BillingPlanGroup.Starter }; }), diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index a920e77082..e5fe585c71 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -9,7 +9,7 @@ import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentM import newDevUpgradePro from '$lib/components/billing/alerts/newDevUpgradePro.svelte'; import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequired.svelte'; import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte'; -import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants'; +import { NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants'; import { cachedStore } from '$lib/helpers/cache'; import { type Size, sizeToBytes } from '$lib/helpers/sizeConvertion'; import type { BillingPlansMap } from '$lib/sdk/billing'; @@ -25,10 +25,11 @@ import { import { derived, get, writable } from 'svelte/store'; import { headerAlert } from './headerAlert'; import { addNotification, notifications } from './notifications'; -import { currentPlan, organization, type OrganizationError } from './organization'; +import { currentPlan, type OrganizationError } from './organization'; import { canSeeBilling } from './roles'; import { sdk } from './sdk'; import { user } from './user'; + import BudgetLimitAlert from '$routes/(console)/organization-[organization]/budgetLimitAlert.svelte'; import TeamReadonlyAlert from '$routes/(console)/organization-[organization]/teamReadonlyAlert.svelte'; import ProjectsLimit from '$lib/components/billing/alerts/projectsLimit.svelte'; @@ -68,7 +69,8 @@ export const addressList = derived( page, ($page) => $page.data.addressList as Models.BillingAddressList ); -export const plansInfo = derived(page, ($page) => $page.data.plansInfo as BillingPlansMap); + +export const plansInfo = writable(new Map()); export const daysLeftInTrial = writable(0); export const readOnly = writable(false); @@ -77,24 +79,54 @@ export const showBudgetAlert = derived( ($page) => ($page.data.organization?.billingLimits.budgetLimit ?? 0) >= 100 ); +function makeBillingPlan(billingPlanOrId: string | Models.BillingPlan): Models.BillingPlan { + return typeof billingPlanOrId === 'string' ? billingIdToPlan(billingPlanOrId) : billingPlanOrId; +} + export function getRoleLabel(role: string) { return roles.find((r) => r.value === role)?.label ?? role; } -export function planHasGroup(billingPlanId: string, group: BillingPlanGroup) { - const plansInfoStore = get(plansInfo); - return plansInfoStore.get(billingPlanId)?.group === group; +export function isGitHubEducationPlan(billingPlanOrId: string | Models.BillingPlan): boolean { + const billingPlan = makeBillingPlan(billingPlanOrId); + return !isStarterPlan(billingPlan) && billingPlan.price === 0; +} + +export function isStarterPlan(billingPlanOrId: string | Models.BillingPlan): boolean { + const billingPlan = makeBillingPlan(billingPlanOrId); + return planHasGroup(billingPlan, BillingPlanGroup.Starter); +} + +export function canUpgrade(billingPlanOrId: string | Models.BillingPlan): boolean { + const billingPlan = makeBillingPlan(billingPlanOrId); + const nextTier = getNextTierBillingPlan(billingPlan.$id); + + // defaults back to PRO, so adjust the check! + return billingPlan.$id !== nextTier.$id; +} + +export function canDowngrade(billingPlanOrId: string | Models.BillingPlan): boolean { + const billingPlan = makeBillingPlan(billingPlanOrId); + const nextTier = getPreviousTierBillingPlan(billingPlan.$id); + + // defaults back to Starter, so adjust the check! + return billingPlan.$id !== nextTier.$id; +} + +export function planHasGroup( + billingPlanOrId: string | Models.BillingPlan, + group: BillingPlanGroup +): boolean { + const billingPlan = makeBillingPlan(billingPlanOrId); + + return billingPlan?.group === group; } 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 + (plan) => plan.group === billingPlanGroup ); return proPlans.sort((a, b) => a.order - b.order)[0]; @@ -111,34 +143,33 @@ export function billingIdToPlan(billingId: string): Models.BillingPlan { } } -// TODO: @itznotabug - just return the BillingPlan object! -export function getNextTierBillingPlan(tier: string): string { - const currentPlanData = billingIdToPlan(tier); +export function getNextTierBillingPlan(billingPlanId: string): Models.BillingPlan { + const currentPlanData = billingIdToPlan(billingPlanId); const currentOrder = currentPlanData.order; const plans = get(plansInfo); for (const [, plan] of plans) { + // TODO: @itznotabug, check for group maybe? if (plan.order === currentOrder + 1) { - return plan.$id; + return plan; } } - return getBasePlanFromGroup(BillingPlanGroup.Pro).$id; + return getBasePlanFromGroup(BillingPlanGroup.Pro); } -// TODO: @itznotabug - just return the BillingPlan object! -export function getPreviousTierBillingPlan(tier: string): string { - const currentPlanData = billingIdToPlan(tier); +export function getPreviousTierBillingPlan(billingPlanId: string): Models.BillingPlan { + const currentPlanData = billingIdToPlan(billingPlanId); const currentOrder = currentPlanData.order; const plans = get(plansInfo); for (const [, plan] of plans) { if (plan.order === currentOrder - 1) { - return plan.$id; + return plan; } } - return getBasePlanFromGroup(BillingPlanGroup.Starter).$id; + return getBasePlanFromGroup(BillingPlanGroup.Starter); } export type PlanServices = @@ -230,7 +261,10 @@ export const showUsageRatesModal = writable(false); export const useNewPricingModal = derived(currentPlan, ($plan) => $plan?.usagePerProject === true); export function checkForUsageFees(plan: string, id: PlanServices) { - if (plan === BillingPlan.PRO || plan === BillingPlan.SCALE) { + const billingPlan = billingIdToPlan(plan); + const supportsUsage = Object.keys(billingPlan.usage).length > 0; + + if (supportsUsage) { switch (id) { case 'bandwidth': case 'storage': @@ -245,11 +279,12 @@ export function checkForUsageFees(plan: string, id: PlanServices) { } else return false; } -export function checkForProjectLimitation(id: PlanServices) { - // Members are no longer limited on Pro and Scale plans (unlimited seats) +export function checkForProjectLimitation(plan: string, id: PlanServices) { if (id === 'members') { - const currentTier = get(organization)?.billingPlan; - if (currentTier === BillingPlan.PRO || currentTier === BillingPlan.SCALE) { + const billingPlan = billingIdToPlan(plan); + const hasUnlimitedProjects = billingPlan.projects === 0; + + if (hasUnlimitedProjects) { return false; // No project limitation for members on Pro/Scale plans } } @@ -305,7 +340,8 @@ export function calculateEnterpriseTrial(org: Models.Organization) { } export function calculateTrialDay(org: Models.Organization) { - if (org?.billingPlan === BillingPlan.FREE) return false; + if (!org.billingPlanDetails.trial) return false; + const endDate = new Date(org?.billingStartDate); const today = new Date(); @@ -327,13 +363,13 @@ export async function checkForProjectsLimit(org: Models.Organization, orgProject }); if (!plan) return; - if (plan.$id !== BillingPlan.FREE) return; if (!org.projects) return; if (org.projects.length > 0) return; const projectCount = orgProjectCount; if (projectCount === undefined) return; + // not unlimited and current exceeds plan limits! if (plan.projects > 0 && projectCount > plan.projects) { headerAlert.add({ id: 'projectsLimitReached', @@ -344,8 +380,11 @@ export async function checkForProjectsLimit(org: Models.Organization, orgProject } } -export async function checkForUsageLimit(org: Models.Organization) { - if (org?.status === teamStatusReadonly && org?.remarks === billingLimitOutstandingInvoice) { +export async function checkForUsageLimit(organization: Models.Organization) { + if ( + organization?.status === teamStatusReadonly && + organization?.remarks === billingLimitOutstandingInvoice + ) { headerAlert.add({ id: 'teamReadOnlyFailedInvoices', component: TeamReadonlyAlert, @@ -355,12 +394,14 @@ export async function checkForUsageLimit(org: Models.Organization) { readOnly.set(true); return; } - if (!org?.billingLimits && org?.status !== teamStatusReadonly) { + + if (!organization?.billingLimits && organization?.status !== teamStatusReadonly) { readOnly.set(false); return; } - if (org?.billingPlan !== BillingPlan.FREE) { - const { budgetLimit } = org?.billingLimits ?? {}; + + if (organization.billingPlanDetails.budgeting) { + const { budgetLimit } = organization?.billingLimits ?? {}; if (budgetLimit && budgetLimit >= 100) { readOnly.set(false); @@ -377,7 +418,7 @@ export async function checkForUsageLimit(org: Models.Organization) { } // TODO: @itznotabug - check with @abnegate, what do we do here? this is billing! - const { bandwidth, executions, storage, users } = org?.billingLimits ?? {}; + const { bandwidth, executions, storage, users } = organization?.billingLimits ?? {}; const resources = [ { value: bandwidth, name: 'bandwidth' }, { value: executions, name: 'executions' }, @@ -385,7 +426,7 @@ export async function checkForUsageLimit(org: Models.Organization) { { value: users, name: 'users' } ]; - const members = org.total; + const members = organization.total; const memberLimit = getServiceLimit('members'); const membersOverflow = memberLimit === Infinity ? 0 : Math.max(0, members - memberLimit); @@ -404,10 +445,16 @@ export async function checkForUsageLimit(org: Models.Organization) { if (now - lastNotification < 1000 * 60 * 60 * 24) return; localStorage.setItem('limitReachedNotification', now.toString()); - let message = `${org.name} has reached 75% of the ${billingIdToPlan(BillingPlan.FREE).name} plan's ${resources.find((r) => r.value >= 75).name} limit. Upgrade to ensure there are no service disruptions.`; - if (resources.filter((r) => r.value >= 75)?.length > 1) { - message = `Usage for ${org.name} has reached 75% of the ${billingIdToPlan(BillingPlan.FREE).name} plan limit. Upgrade to ensure there are no service disruptions.`; + + const threshold = 75; + const exceededResources = resources.filter((r) => r.value >= threshold); + + let message = `${organization.name} has reached ${threshold}% of its ${exceededResources[0].name} limit. Upgrade to ensure there are no service disruptions.`; + + if (exceededResources.length > 1) { + message = `Usage for ${organization.name} has reached ${threshold}% of its plan limits. Upgrade to ensure there are no service disruptions.`; } + addNotification({ type: 'warning', isHtml: true, @@ -419,7 +466,7 @@ export async function checkForUsageLimit(org: Models.Organization) { method: () => { goto( resolve('/(console)/organization-[organization]/usage', { - organization: org.$id + organization: organization.$id }) ); } @@ -429,7 +476,7 @@ export async function checkForUsageLimit(org: Models.Organization) { method: () => { goto( resolve('/(console)/organization-[organization]/change-plan', { - organization: org.$id + organization: organization.$id }) ); trackEvent(Click.OrganizationClickUpgrade, { @@ -446,7 +493,7 @@ export async function checkForUsageLimit(org: Models.Organization) { } export async function checkPaymentAuthorizationRequired(org: Models.Organization) { - if (org.billingPlan === BillingPlan.FREE) return; + if (!org.billingPlanDetails.requiresPaymentMethod) return; const invoices = await sdk.forConsole.organizations.listInvoices({ organizationId: org.$id, @@ -576,7 +623,7 @@ export async function checkForMissingPaymentMethod() { // Display upgrade banner for new users after 1 week for 30 days export async function checkForNewDevUpgradePro(org: Models.Organization) { // browser or plan check. - if (!browser || org?.billingPlan !== BillingPlan.FREE) return; + if (!browser || !org.billingPlanDetails.supportsCredits) return; // already dismissed by user! if (localStorage.getItem('newDevUpgradePro')) return; diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index 196d56b8a0..b09f0e59a8 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -1,6 +1,6 @@ - + {!areCreditsSupported ? 'Credits' : 'Available credit'} @@ -172,7 +171,7 @@ {/if} - {#if $organization?.billingPlan === BillingPlan.FREE} + {#if !areCreditsSupported} diff --git a/src/routes/(console)/organization-[organization]/header.svelte b/src/routes/(console)/organization-[organization]/header.svelte index ba61eccca1..38d8ed742a 100644 --- a/src/routes/(console)/organization-[organization]/header.svelte +++ b/src/routes/(console)/organization-[organization]/header.svelte @@ -3,7 +3,6 @@ import { base } from '$app/paths'; import { page } from '$app/state'; import { AvatarGroup, Tab, Tabs } from '$lib/components'; - import { BillingPlan } from '$lib/constants'; import { Button } from '$lib/elements/forms'; import { toLocaleDate } from '$lib/helpers/date'; import { isTabSelected } from '$lib/helpers/load'; @@ -11,8 +10,8 @@ import { daysLeftInTrial, getServiceLimit, - readOnly, - billingIdToPlan + isGitHubEducationPlan, + readOnly } from '$lib/stores/billing'; import { members, newMemberModal, newOrgModal } from '$lib/stores/organization'; import { @@ -25,7 +24,7 @@ import { GRACE_PERIOD_OVERRIDE, isCloud } from '$lib/system'; import { IconGithub, IconPlus, IconPlusSm } from '@appwrite.io/pink-icons-svelte'; import { Badge, Icon, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte'; - import type { Models } from '@appwrite.io/console'; + import { BillingPlanGroup, type Models } from '@appwrite.io/console'; let areMembersLimited: boolean = $state(false); @@ -98,14 +97,15 @@ {organization.name} - {#if isCloud && organization?.billingPlan === BillingPlan.GITHUB_EDUCATION} + + {#if isCloud && isGitHubEducationPlan(organization.billingPlanDetails)} - {:else if isCloud && organization?.billingPlan === BillingPlan.FREE} + {:else if isCloud && organization?.billingPlanDetails.group === BillingPlanGroup.Starter} {/if} - {#if isCloud && organization?.billingTrialStartDate && $daysLeftInTrial > 0 && organization.billingPlan !== BillingPlan.FREE && organization?.billingTrialDays} + {#if isCloud && organization?.billingTrialStartDate && $daysLeftInTrial > 0 && organization.billingPlanDetails.trial && organization?.billingTrialDays} @@ -145,10 +145,11 @@

- {organization?.billingPlan === BillingPlan.FREE + + {!organization?.billingPlanDetails.addons.seats.supported ? 'Upgrade to add more members' : `You've reached the members limit for the ${ - billingIdToPlan(organization?.billingPlan)?.name + organization?.billingPlanDetails?.name } plan`}
diff --git a/src/routes/(console)/organization-[organization]/members/+page.svelte b/src/routes/(console)/organization-[organization]/members/+page.svelte index c9ad661504..5882c78eea 100644 --- a/src/routes/(console)/organization-[organization]/members/+page.svelte +++ b/src/routes/(console)/organization-[organization]/members/+page.svelte @@ -34,8 +34,6 @@ ActionMenu, Tooltip } from '@appwrite.io/pink-svelte'; - import { BillingPlan } from '$lib/constants'; - import { billingIdToPlan } from '$lib/stores/billing'; export let data; @@ -46,7 +44,8 @@ // Calculate if button should be disabled and tooltip should show $: memberCount = data.organizationMembers?.total ?? 0; - $: isFreeWithMembers = $organization?.billingPlan === BillingPlan.FREE && memberCount >= 1; + $: supportsMembers = $organization?.billingPlanDetails.addons.seats; + $: isFreeWithMembers = !supportsMembers && memberCount >= 1; $: isButtonDisabled = isCloud ? isFreeWithMembers : false; const resend = async (member: Models.Membership) => { @@ -89,11 +88,9 @@
- {$organization?.billingPlan === BillingPlan.FREE + {!supportsMembers ? 'Upgrade to add more members' - : `You've reached the members limit for the ${ - billingIdToPlan($organization?.billingPlan)?.name - } plan`} + : `You've reached the members limit for the ${$organization?.billingPlanDetails?.name} plan`}
diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte index 64e4a342a5..6c6f79109c 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte @@ -6,6 +6,8 @@ import { bytesToSize, humanFileSize, mbSecondsToGBHours } from '$lib/helpers/sizeConvertion'; import { getServiceLimit, + isStarterPlan, + planHasGroup, showUsageRatesModal, upgradeURL, useNewPricingModal @@ -14,7 +16,6 @@ import ProjectBreakdown from './ProjectBreakdown.svelte'; import { formatNum } from '$lib/helpers/string'; import { accumulateFromEndingTotal, total } from '$lib/layout/usage.svelte'; - import { BillingPlan } from '$lib/constants'; import { Click, trackEvent } from '$lib/actions/analytics'; import TotalMembers from './totalMembers.svelte'; import { formatCurrency, formatNumberWithCommas } from '$lib/helpers/numbers'; @@ -22,6 +23,7 @@ import { IconChartSquareBar, IconInfo } from '@appwrite.io/pink-icons-svelte'; import { onMount } from 'svelte'; import type { UsageProjectInfo } from '../../store'; + import { BillingPlanGroup } from '@appwrite.io/console'; export let data; @@ -53,13 +55,15 @@ ]; onMount(async () => (usageProjects = await data.projects)); + + const currentBillingPlan = $organization.billingPlanDetails;
Usage - {#if $organization?.billingPlan === BillingPlan.FREE} + {#if isStarterPlan(currentBillingPlan)} {/if}
- {#if $organization.billingPlan === BillingPlan.SCALE} + {#if planHasGroup(currentBillingPlan, BillingPlanGroup.Scale)}

On the Scale plan, you'll be charged only for any usage that exceeds the thresholds per resource listed below. @@ -87,7 +91,7 @@ {/if}

- {:else if $organization.billingPlan === BillingPlan.PRO} + {:else if planHasGroup(currentBillingPlan, BillingPlanGroup.Pro)}

On the Pro plan, you'll be charged only for any usage that exceeds the thresholds per resource listed below. @@ -102,7 +106,7 @@ {/if}

- {:else if $organization.billingPlan === BillingPlan.FREE} + {:else if isStarterPlan(currentBillingPlan)}

If you exceed the limits of the Free plan, services for your organization's projects may be disrupted. diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte index 90219042af..570e5f7003 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte @@ -1,10 +1,7 @@ Members The number of members in your organization. - {#if $organization.billingPlan !== BillingPlan.FREE} + {#if !organizationMembersSupported}

@@ -37,11 +36,7 @@ - You can add unlimited organization members on the {billingIdToPlan( - $organization.billingPlan - ).name} plan {$organization.billingPlan === BillingPlan.PRO - ? `for ${formatCurrency(plan.addons.seats.price)} each per billing period.` - : '.'} + You can add unlimited organization members on paid plans. @@ -53,8 +48,7 @@
- - {#snippet children(paginatedItems: typeof members.memberships)} + {#snippet children(paginatedItems)} Members diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/(components)/details.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/(components)/details.svelte index 9b29e6da15..77377918f2 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/(components)/details.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/(components)/details.svelte @@ -1,9 +1,8 @@
Usage - {#if $organization?.billingPlan === BillingPlan.FREE} + + {#if planHasGroup(currentBillingPlan, BillingPlanGroup.Starter)} {/if}
+
- {#if $organization.billingPlan === BillingPlan.SCALE} + {#if planHasGroup(currentBillingPlan, BillingPlanGroup.Scale)}

On the Scale plan, you'll be charged only for any usage that exceeds the thresholds per resource listed below. ($showUsageRatesModal = true)} >Learn more about plan usage limits.

- {:else if $organization.billingPlan === BillingPlan.PRO} + {:else if planHasGroup(currentBillingPlan, BillingPlanGroup.Pro)}

On the Pro plan, you'll be charged only for any usage that exceeds the thresholds per resource listed below. ($showUsageRatesModal = true)} >Learn more about plan usage limits.

- {:else if $organization.billingPlan === BillingPlan.FREE} + {:else if planHasGroup(currentBillingPlan, BillingPlanGroup.Starter)}

- If you exceed the limits of the {plan} plan, services for your projects may be disrupted. + If you exceed the limits of the {currentBillingPlan.name} plan, services for your projects + may be disrupted. Upgrade for greater capacity. diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/configuration.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/configuration.svelte index 0e8b26ceff..9a6996dfbb 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/configuration.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/configuration.svelte @@ -186,7 +186,7 @@ items={variables} limit={6} hideFooter={variables.length <= 6}> - {#snippet children(paginatedItems: typeof variables)} + {#snippet children(paginatedItems)} - {#snippet children(paginatedItems: typeof data.templates)} + {#snippet children(paginatedItems)} {#each paginatedItems as template (template.name)} {@const templateFrameworks = template.frameworks.map((t) => t.name)} diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/instantRollbackModal.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/instantRollbackModal.svelte index dadbd653b8..d6d4746cfd 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/instantRollbackModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/instantRollbackModal.svelte @@ -128,7 +128,7 @@ (prodDeployment) => prodDeployment.$id !== deployment.$id )} - {#snippet children(paginatedItems: typeof items)} + {#snippet children(paginatedItems)} {#each paginatedItems as prodDeployment} - {#if isCloud && $organization.billingPlan === BillingPlan.FREE} + + + {@const isStarter = isStarterPlan($organization.billingPlan)} + {#if isCloud && isStarter} - Upgrade to Pro or Scale to adjust your CPU and RAM beyond the default. + Upgrade your plan to adjust your CPU and RAM beyond the default. diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/settings/updateMaxFileSize.svelte b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/settings/updateMaxFileSize.svelte index d0d4a0b0d6..b4642cf6b6 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/settings/updateMaxFileSize.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/settings/updateMaxFileSize.svelte @@ -2,15 +2,14 @@ import { Click, Submit, trackEvent } from '$lib/actions/analytics'; import { CardGrid } from '$lib/components'; import { Alert } from '@appwrite.io/pink-svelte'; - import { BillingPlan } from '$lib/constants'; import { Button, Form, InputNumber, InputSelect } from '$lib/elements/forms'; import { humanFileSize, sizeToBytes } from '$lib/helpers/sizeConvertion'; import { createByteUnitPair } from '$lib/helpers/unit'; - import { readOnly, upgradeURL } from '$lib/stores/billing'; + import { isStarterPlan, readOnly, upgradeURL } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; import { GRACE_PERIOD_OVERRIDE, isCloud } from '$lib/system'; import { updateBucket } from './+page.svelte'; - import type { Models } from '@appwrite.io/console'; + import { type Models } from '@appwrite.io/console'; export let bucket: Models.Bucket; export let currentPlan: Models.BillingPlan | null; @@ -45,7 +44,9 @@ {#if isCloud} {@const size = humanFileSize(sizeToBytes(service, 'MB', 1000))} - {#if $organization?.billingPlan === BillingPlan.FREE} + + {@const isStarter = isStarterPlan($organization.billingPlan)} + {#if isStarter} The {currentPlan.name} plan has a maximum upload file size limit of {Math.floor( parseInt(size.value) diff --git a/src/routes/(public)/functions/deploy/+page.ts b/src/routes/(public)/functions/deploy/+page.ts index 404d3ae47e..6a89052302 100644 --- a/src/routes/(public)/functions/deploy/+page.ts +++ b/src/routes/(public)/functions/deploy/+page.ts @@ -2,12 +2,12 @@ import { sdk } from '$lib/stores/sdk.js'; import { redirect } from '@sveltejs/kit'; import { base } from '$app/paths'; import { isCloud } from '$lib/system'; -import { BillingPlan } from '$lib/constants'; -import { ID } from '@appwrite.io/console'; +import { BillingPlanGroup, ID } from '@appwrite.io/console'; import { getTeamOrOrganizationList } from '$lib/stores/organization'; import { redirectTo } from '$routes/store'; import type { PageLoad } from './$types'; import { getRepositoryInfo } from '$lib/helpers/github'; +import { getBasePlanFromGroup } from '$lib/stores/billing'; export const load: PageLoad = async ({ parent, url }) => { const { account } = await parent(); @@ -72,7 +72,7 @@ export const load: PageLoad = async ({ parent, url }) => { await sdk.forConsole.billing.createOrganization( ID.unique(), 'Personal Projects', - BillingPlan.FREE, + getBasePlanFromGroup(BillingPlanGroup.Starter).$id, null ); } else { diff --git a/src/routes/(public)/sites/deploy/+page.ts b/src/routes/(public)/sites/deploy/+page.ts index 79bea62d31..79a54ace18 100644 --- a/src/routes/(public)/sites/deploy/+page.ts +++ b/src/routes/(public)/sites/deploy/+page.ts @@ -1,13 +1,13 @@ import { sdk } from '$lib/stores/sdk.js'; -import { redirect, error } from '@sveltejs/kit'; +import { error, redirect } from '@sveltejs/kit'; import { base } from '$app/paths'; import { isCloud } from '$lib/system'; -import { BillingPlan } from '$lib/constants'; -import { ID, type Models } from '@appwrite.io/console'; +import { BillingPlanGroup, ID, type Models } from '@appwrite.io/console'; import { getTeamOrOrganizationList } from '$lib/stores/organization'; import { redirectTo } from '$routes/store'; import type { PageLoad } from './$types'; import { getRepositoryInfo } from '$lib/helpers/github'; +import { getBasePlanFromGroup } from '$lib/stores/billing'; export const load: PageLoad = async ({ parent, url }) => { const { account } = await parent(); @@ -88,7 +88,7 @@ export const load: PageLoad = async ({ parent, url }) => { await sdk.forConsole.billing.createOrganization( ID.unique(), 'Personal Projects', - BillingPlan.FREE, + getBasePlanFromGroup(BillingPlanGroup.Starter).$id, null ); } else { From 69baca8ac8d4d0157ac5412a95813e1cf964be12 Mon Sep 17 00:00:00 2001 From: Darshan Date: Thu, 25 Dec 2025 17:11:04 +0530 Subject: [PATCH 30/79] remove: `isGitHubEducationPlan` method and implement proper checks. --- package.json | 2 +- pnpm-lock.yaml | 10 ++-- src/lib/helpers/program.ts | 12 ++++ src/lib/stores/billing.ts | 5 -- .../organization-[organization]/+layout.ts | 12 +++- .../billing/budgetAlert.svelte | 2 +- .../billing/planSummary.svelte | 2 +- .../billing/planSummaryOld.svelte | 4 +- .../organization-[organization]/header.svelte | 58 ++++++++++--------- 9 files changed, 64 insertions(+), 43 deletions(-) create mode 100644 src/lib/helpers/program.ts diff --git a/package.json b/package.json index a2e1778fe3..c00f69e178 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@5b9d902", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@865e2fc", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a968c01e9..07d17765a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.25.3)(zod@3.24.3) '@appwrite.io/console': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f - version: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@5b9d902 + version: https://pkg.vc/-/@appwrite/@appwrite.io/console@5b9d902 '@appwrite.io/pink-icons': specifier: 0.25.0 version: 0.25.0 @@ -272,8 +272,8 @@ packages: '@analytics/type-utils@0.6.2': resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==} - '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@5b9d902': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@5b9d902} version: 1.10.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -3823,7 +3823,7 @@ snapshots: '@analytics/type-utils@0.6.2': {} - '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f': {} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@5b9d902': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: diff --git a/src/lib/helpers/program.ts b/src/lib/helpers/program.ts new file mode 100644 index 0000000000..ead1fa9ecf --- /dev/null +++ b/src/lib/helpers/program.ts @@ -0,0 +1,12 @@ +import { IconGithub } from '@appwrite.io/pink-icons-svelte'; + +/** + * Models.BillingPlan > program + * |_ Models.Program > icon + * |_ icon > string + * + * So we need to map them as needed from Pink icons library + */ +export const IconsMap = { + github: IconGithub +}; diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index e5fe585c71..90bad262dd 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -87,11 +87,6 @@ export function getRoleLabel(role: string) { return roles.find((r) => r.value === role)?.label ?? role; } -export function isGitHubEducationPlan(billingPlanOrId: string | Models.BillingPlan): boolean { - const billingPlan = makeBillingPlan(billingPlanOrId); - return !isStarterPlan(billingPlan) && billingPlan.price === 0; -} - export function isStarterPlan(billingPlanOrId: string | Models.BillingPlan): boolean { const billingPlan = makeBillingPlan(billingPlanOrId); return planHasGroup(billingPlan, BillingPlanGroup.Starter); diff --git a/src/routes/(console)/organization-[organization]/+layout.ts b/src/routes/(console)/organization-[organization]/+layout.ts index 7b63473f18..b2d4e26616 100644 --- a/src/routes/(console)/organization-[organization]/+layout.ts +++ b/src/routes/(console)/organization-[organization]/+layout.ts @@ -44,6 +44,7 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { loadFailedInvoices(params.organization); } } + if (prefs.organization !== params.organization) { const newPrefs = { ...prefs, organization: params.organization }; sdk.forConsole.account.updatePrefs({ prefs: newPrefs }); @@ -56,8 +57,14 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { teamId: params.organization }) as Promise); - const [org, members, countryList, locale] = await Promise.all([ + const programPromise: Promise | null = + currentPlan && currentPlan?.program + ? sdk.forConsole.console.getProgram({ programId: currentPlan.program }) + : null; + + const [org, program, members, countryList, locale] = await Promise.all([ orgPromise, + programPromise, sdk.forConsole.teams.listMemberships({ teamId: params.organization }), sdk.forConsole.locale.listCountries(), sdk.forConsole.locale.get(), @@ -74,7 +81,8 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { roles, scopes, countryList, - locale + locale, + program }; } catch (e) { const newPrefs = { ...prefs, organization: null }; diff --git a/src/routes/(console)/organization-[organization]/billing/budgetAlert.svelte b/src/routes/(console)/organization-[organization]/billing/budgetAlert.svelte index 181c00eabb..eeeb5a5c4d 100644 --- a/src/routes/(console)/organization-[organization]/billing/budgetAlert.svelte +++ b/src/routes/(console)/organization-[organization]/billing/budgetAlert.svelte @@ -148,7 +148,7 @@

- {#if !!currentPlan.budgeting} + {#if !currentPlan.budgeting}
- {#if methods.total} + {#if paymentMethods.total} {#each filteredPaymentMethods as paymentMethod} addPaymentMethod(paymentMethod?.$id)}> @@ -309,12 +321,17 @@ selectedPaymentMethod={isSelectedBackup ? backupMethod : primaryMethod} /> {/if} {#if isCloud && hasStripePublicKey} - + {/if} {#if showDelete && isCloud && hasStripePublicKey} {@const hasOtherMethod = isSelectedBackup ? !!organization?.paymentMethodId : !!organization?.backupPaymentMethodId} + Date: Sun, 18 Jan 2026 11:49:25 +0530 Subject: [PATCH 41/79] update: address comments. --- src/routes/(console)/account/payments/+page.svelte | 4 +++- .../billing/retryPaymentModal.svelte | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/routes/(console)/account/payments/+page.svelte b/src/routes/(console)/account/payments/+page.svelte index f3e39a8310..6d36599cde 100644 --- a/src/routes/(console)/account/payments/+page.svelte +++ b/src/routes/(console)/account/payments/+page.svelte @@ -16,7 +16,9 @@ if (page.url.searchParams.has('clientSecret')) { const clientSecret = page.url.searchParams.get('clientSecret'); const paymentMethodId = page.url.searchParams.get('paymentMethodId'); - await confirmPayment({ clientSecret, paymentMethodId }); + if (clientSecret && paymentMethodId) { + await confirmPayment({ clientSecret, paymentMethodId }); + } } }); diff --git a/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte b/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte index 5cbcda62a7..9f351e2cb1 100644 --- a/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte +++ b/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte @@ -24,7 +24,7 @@ import type { Models } from '@appwrite.io/console'; export let show = false; - export let invoice: Models.Invoice; + export let invoice: Models.Invoice | null = null; let name: string; let state: string = ''; From 06dcdc4a1ba17f2033d660fb13e92fc1b08653fd Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 18 Jan 2026 12:10:33 +0530 Subject: [PATCH 42/79] update: address comments. --- src/lib/helpers/billing.ts | 16 ++++++++++++ src/lib/stores/billing.ts | 12 ++++----- src/routes/(console)/+layout.ts | 43 +++++++++++++++------------------ src/routes/+layout.ts | 27 +++++++++++++++++---- 4 files changed, 62 insertions(+), 36 deletions(-) create mode 100644 src/lib/helpers/billing.ts diff --git a/src/lib/helpers/billing.ts b/src/lib/helpers/billing.ts new file mode 100644 index 0000000000..de3c2f538e --- /dev/null +++ b/src/lib/helpers/billing.ts @@ -0,0 +1,16 @@ +import type { Models } from '@appwrite.io/console'; + +export function makePlansMap( + plansArray: Models.BillingPlanList | null +): Map { + const plansMap = new Map(); + if (!plansArray?.plans.length) return plansMap; + + const plans = plansArray.plans; + for (let index = 0; index < plans.length; index++) { + const plan = plans[index]; + plansMap.set(plan.$id, plan); + } + + return plansMap; +} diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 61608aa3c1..a7f425f8d3 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -88,12 +88,8 @@ export function planHasGroup(billingPlanId: string, group: BillingPlanGroup) { 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 + (plan) => plan.group === billingPlanGroup ); return proPlans.sort((a, b) => a.order - b.order)[0]; @@ -530,12 +526,14 @@ export function checkForMarkedForDeletion(org: Models.Organization) { } export async function checkForMissingPaymentMethod() { + const starterPlan = getBasePlanFromGroup(BillingPlanGroup.Starter); + 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) + Query.equal('platform', Platform.Appwrite), + Query.notEqual('billingPlan', starterPlan.$id) ] }); diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index 73f3acde08..15861da693 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -2,30 +2,38 @@ import { sdk } from '$lib/stores/sdk'; import { isCloud } from '$lib/system'; import type { LayoutLoad } from './$types'; import { Dependencies } from '$lib/constants'; -import { type Models, Platform, Query } from '@appwrite.io/console'; +import { Platform, Query } from '@appwrite.io/console'; +import { makePlansMap } from '$lib/helpers/billing'; export const load: LayoutLoad = async ({ depends, parent }) => { - const { organizations } = await parent(); + const { organizations, plansInfo } = await parent(); depends(Dependencies.RUNTIMES); depends(Dependencies.CONSOLE_VARIABLES); depends(Dependencies.ORGANIZATION); const { endpoint, project } = sdk.forConsole.client.config; + + const plansArrayPromise = + plansInfo || !isCloud + ? null + : sdk.forConsole.console.getPlans({ + platform: Platform.Appwrite + }); + const [preferences, plansArray, versionData, consoleVariables] = await Promise.all([ sdk.forConsole.account.getPrefs(), - isCloud - ? sdk.forConsole.console.getPlans({ - platform: Platform.Appwrite - }) - : null, + plansArrayPromise, fetch(`${endpoint}/health/version`, { headers: { 'X-Appwrite-Project': project as string } }).then((response) => response.json() as { version?: string }), sdk.forConsole.console.variables() ]); - const plansInfo = toPlanMap(plansArray); + let fallbackPlansInfoArray = plansInfo; + if (!fallbackPlansInfoArray) { + fallbackPlansInfoArray = makePlansMap(plansArray); + } const currentOrgId = preferences.organization ?? @@ -50,27 +58,14 @@ export const load: LayoutLoad = async ({ depends, parent }) => { } return { - plansInfo, roles: [], scopes: [], preferences, currentOrgId, organizations, consoleVariables, - version: versionData?.version ?? null, - allProjectsCount: projectsCount + allProjectsCount: projectsCount, + plansInfo: fallbackPlansInfoArray, + version: versionData?.version ?? null }; }; - -function toPlanMap(plansArray: Models.BillingPlanList | null): Map { - const map = new Map(); - if (!plansArray?.plans.length) return map; - - const plans = plansArray.plans; - for (let i = 0; i < plans.length; i++) { - const plan = plans[i]; - map.set(plan.$id, plan); - } - - return map; -} diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index a80e909b98..b78f253909 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -6,12 +6,13 @@ import { redirect } from '@sveltejs/kit'; import { Dependencies } from '$lib/constants'; import type { LayoutLoad } from './$types'; import { redirectTo } from './store'; -import { base, resolve } from '$app/paths'; +import { resolve } from '$app/paths'; import type { Account } from '$lib/stores/user'; -import { type AppwriteException } from '@appwrite.io/console'; +import { type AppwriteException, Platform } from '@appwrite.io/console'; import { isCloud, VARS } from '$lib/system'; import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; import { getTeamOrOrganizationList } from '$lib/stores/organization'; +import { makePlansMap } from '$lib/helpers/billing'; export const ssr = false; @@ -39,7 +40,10 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { } } + const plansInfo = await getPlatformPlans(); + return { + plansInfo, account: account, organizations: await getTeamOrOrganizationList() }; @@ -51,11 +55,13 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { } if (error.type === 'user_more_factors_required') { - if (url.pathname === `${base}/mfa`) + const mfaUrl = resolve('/(authenticated)/mfa'); + + if (url.pathname === mfaUrl) return { mfaRequired: true }; - redirect(303, withParams(`${base}/mfa`, url.searchParams)); + redirect(303, withParams(mfaUrl, url.searchParams)); } if (!isPublicRoute) { @@ -63,7 +69,8 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { checkPricingRefAndRedirect(url.searchParams, true); } - redirect(303, withParams(`${base}/login`, url.searchParams)); + const loginUrl = resolve('/(public)/(guest)/login'); + redirect(303, withParams(loginUrl, url.searchParams)); } }; @@ -71,3 +78,13 @@ function withParams(pathname: string, searchParams: URLSearchParams) { if (searchParams.size > 0) return `${pathname}?${searchParams.toString()}`; return pathname; } + +async function getPlatformPlans() { + if (!isCloud) return null; + + const plansArray = await sdk.forConsole.console.getPlans({ + platform: Platform.Appwrite + }); + + return makePlansMap(plansArray); +} From 67d3fabb9bdba3e8898540a0eec6632060178568 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 18 Jan 2026 12:30:34 +0530 Subject: [PATCH 43/79] fix: e2e. --- src/lib/stores/billing.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index a7f425f8d3..f9665a0f1d 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -76,19 +76,23 @@ export const showBudgetAlert = derived( ($page) => ($page.data.organization?.billingLimits.budgetLimit ?? 0) >= 100 ); +function getPlansInfoStore(): BillingPlansMap | null { + return get(plansInfo) ?? get(page).data.plansInfo ?? null; +} + export function getRoleLabel(role: string) { return roles.find((r) => r.value === role)?.label ?? role; } export function planHasGroup(billingPlanId: string, group: BillingPlanGroup) { - const plansInfoStore = get(plansInfo); - return plansInfoStore.get(billingPlanId)?.group === group; + const plansInfoStore = getPlansInfoStore(); + return plansInfoStore?.get(billingPlanId)?.group === group; } export function getBasePlanFromGroup(billingPlanGroup: BillingPlanGroup): Models.BillingPlan { - const plansInfoStore = get(plansInfo); + const plansInfoStore = getPlansInfoStore(); - const proPlans = Array.from(plansInfoStore.values()).filter( + const proPlans = Array.from(plansInfoStore?.values()).filter( (plan) => plan.group === billingPlanGroup ); @@ -96,8 +100,8 @@ export function getBasePlanFromGroup(billingPlanGroup: BillingPlanGroup): Models } export function billingIdToPlan(billingId: string): Models.BillingPlan { - const plansInfoStore = get(plansInfo); - if (plansInfoStore.has(billingId)) { + const plansInfoStore = getPlansInfoStore(); + if (plansInfoStore?.has(billingId)) { return plansInfoStore.get(billingId); } else { // fallback to PRO group's 1st item. From f3ca152653911b628eb58adc0608a3e4d586bd92 Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 18 Jan 2026 12:50:01 +0530 Subject: [PATCH 44/79] fix: e2e. --- src/lib/stores/billing.ts | 6 +++--- src/routes/(console)/+layout.ts | 4 ++++ .../(console)/onboarding/create-project/+page.ts | 10 ++++++---- src/routes/+layout.ts | 2 ++ 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index f9665a0f1d..cd93652618 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -67,9 +67,9 @@ export const addressList = derived( page, ($page) => $page.data.addressList as Models.BillingAddressList ); -export const plansInfo = derived(page, ($page) => $page.data.plansInfo as BillingPlansMap); -export const daysLeftInTrial = writable(0); export const readOnly = writable(false); +export const daysLeftInTrial = writable(0); +export const plansInfo = writable(new Map()); export const showBudgetAlert = derived( page, @@ -77,7 +77,7 @@ export const showBudgetAlert = derived( ); function getPlansInfoStore(): BillingPlansMap | null { - return get(plansInfo) ?? get(page).data.plansInfo ?? null; + return get(plansInfo) ?? get(page).data?.plansInfo ?? null; } export function getRoleLabel(role: string) { diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index 15861da693..19dba97b5e 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -4,6 +4,7 @@ import type { LayoutLoad } from './$types'; import { Dependencies } from '$lib/constants'; import { Platform, Query } from '@appwrite.io/console'; import { makePlansMap } from '$lib/helpers/billing'; +import { plansInfo as plansInfoStore } from '$lib/stores/billing'; export const load: LayoutLoad = async ({ depends, parent }) => { const { organizations, plansInfo } = await parent(); @@ -57,6 +58,9 @@ export const load: LayoutLoad = async ({ depends, parent }) => { } } + // just in case! + plansInfoStore.set(plansInfo); + return { roles: [], scopes: [], diff --git a/src/routes/(console)/onboarding/create-project/+page.ts b/src/routes/(console)/onboarding/create-project/+page.ts index 94ee3e32f0..bf3aef661b 100644 --- a/src/routes/(console)/onboarding/create-project/+page.ts +++ b/src/routes/(console)/onboarding/create-project/+page.ts @@ -24,14 +24,16 @@ export const load: PageLoad = async ({ parent }) => { if (!organizations?.total) { try { if (isCloud) { + const starterPlan = getBasePlanFromGroup(BillingPlanGroup.Starter); + const org = await sdk.forConsole.organizations.create({ organizationId: ID.unique(), name: 'Personal projects', - billingPlan: getBasePlanFromGroup(BillingPlanGroup.Starter).$id + billingPlan: starterPlan.$id }); trackEvent(Submit.OrganizationCreate, { - plan: getBasePlanFromGroup(BillingPlanGroup.Starter)?.name, + plan: starterPlan?.name, budget_cap_enabled: false, members_invited: 0 }); @@ -42,10 +44,10 @@ export const load: PageLoad = async ({ parent }) => { organization: org }; } else { - const e = new Error(org.message, { + const error = new Error(org.message, { cause: org }); - trackError(e, Submit.OrganizationCreate); + trackError(error, Submit.OrganizationCreate); } } else { return { diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index b78f253909..5bdef81d7c 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -13,6 +13,7 @@ import { isCloud, VARS } from '$lib/system'; import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect'; import { getTeamOrOrganizationList } from '$lib/stores/organization'; import { makePlansMap } from '$lib/helpers/billing'; +import { plansInfo as plansInfoStore } from '$lib/stores/billing'; export const ssr = false; @@ -41,6 +42,7 @@ export const load: LayoutLoad = async ({ depends, url, route }) => { } const plansInfo = await getPlatformPlans(); + plansInfoStore.set(plansInfo); return { plansInfo, From b21f3c03ba7a36235f42073dca2761fae01ca61b Mon Sep 17 00:00:00 2001 From: Darshan Date: Sun, 18 Jan 2026 12:57:51 +0530 Subject: [PATCH 45/79] address comments. --- src/lib/stores/billing.ts | 2 +- src/routes/(console)/+layout.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index cd93652618..ce2eee6dde 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -92,7 +92,7 @@ export function planHasGroup(billingPlanId: string, group: BillingPlanGroup) { export function getBasePlanFromGroup(billingPlanGroup: BillingPlanGroup): Models.BillingPlan { const plansInfoStore = getPlansInfoStore(); - const proPlans = Array.from(plansInfoStore?.values()).filter( + const proPlans = Array.from(plansInfoStore.values()).filter( (plan) => plan.group === billingPlanGroup ); diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index 19dba97b5e..3d1ad1093b 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -59,7 +59,7 @@ export const load: LayoutLoad = async ({ depends, parent }) => { } // just in case! - plansInfoStore.set(plansInfo); + plansInfoStore.set(fallbackPlansInfoArray); return { roles: [], From 9a0b4c1aa3c48ed90d2c866fa2fe16f503641e43 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 21 Jan 2026 16:43:46 +0530 Subject: [PATCH 46/79] remove: empty file. --- src/lib/components/billing/alerts/paymentMandate.svelte | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/lib/components/billing/alerts/paymentMandate.svelte diff --git a/src/lib/components/billing/alerts/paymentMandate.svelte b/src/lib/components/billing/alerts/paymentMandate.svelte deleted file mode 100644 index e69de29bb2..0000000000 From cc8811b2764b101503fa2c64fddf27a6a7fbe0d1 Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 26 Jan 2026 15:54:24 +0530 Subject: [PATCH 47/79] update: address comments. --- src/lib/components/backupRestoreBox.svelte | 2 +- src/lib/components/billing/index.ts | 1 - .../components/billing/planSelection.svelte | 11 +--- src/lib/components/billing/selectPlan.svelte | 59 ------------------- .../account/organizations/+page.svelte | 2 +- .../(console)/apply-credit/+page.svelte | 19 ++++-- 6 files changed, 20 insertions(+), 74 deletions(-) delete mode 100644 src/lib/components/billing/selectPlan.svelte diff --git a/src/lib/components/backupRestoreBox.svelte b/src/lib/components/backupRestoreBox.svelte index b506beb6fe..b97c4d5e3d 100644 --- a/src/lib/components/backupRestoreBox.svelte +++ b/src/lib/components/backupRestoreBox.svelte @@ -125,7 +125,7 @@ } onMount(() => { - // fast path: don't subscribe if org is on a free plan or is self-hosted. + // fast path: don't subscribe if org doesn't support backups or is self-hosted. if (isSelfHosted || (isCloud && !$organization?.billingPlanDetails.backupsEnabled)) return; return realtime.forProject(page.params.region, 'console', (response) => { diff --git a/src/lib/components/billing/index.ts b/src/lib/components/billing/index.ts index 11e9245b3f..eac388917f 100644 --- a/src/lib/components/billing/index.ts +++ b/src/lib/components/billing/index.ts @@ -6,4 +6,3 @@ export { default as PlanComparisonBox } from './planComparisonBox.svelte'; export { default as EmptyCardCloud } from './emptyCardCloud.svelte'; export { default as CreditsApplied } from './creditsApplied.svelte'; export { default as PlanSelection } from './planSelection.svelte'; -export { default as SelectPlan } from './selectPlan.svelte'; diff --git a/src/lib/components/billing/planSelection.svelte b/src/lib/components/billing/planSelection.svelte index 2d8128582d..fe1fd8f6a1 100644 --- a/src/lib/components/billing/planSelection.svelte +++ b/src/lib/components/billing/planSelection.svelte @@ -21,13 +21,8 @@ let selectedPlan = $state(selectedBillingPlan.$id); - const plans = $derived(Object.values(page.data.plans.plans) as Models.BillingPlan[]); - const currentPlanInList = $derived(plans.some((plan) => plan.$id === $currentPlan?.$id)); - - // experiment to remove scale plan temporarily - const plansWithoutScale = $derived( - plans.filter((plan) => plan.group != BillingPlanGroup.Scale) - ); + const visiblePlans = $derived(Object.values(page.data.plans.plans) as Models.BillingPlan[]); + const currentPlanInList = $derived(visiblePlans.some((plan) => plan.$id === $currentPlan?.$id)); function shouldShowTooltip(plan: Models.BillingPlan) { if (plan.group !== BillingPlanGroup.Starter) return true; @@ -44,7 +39,7 @@ - {#each plansWithoutScale as plan} + {#each visiblePlans as plan} - import { formatCurrency } from '$lib/helpers/numbers'; - import { plansInfo } from '$lib/stores/billing'; - import { organization } from '$lib/stores/organization'; - import { LabelCard } from '..'; - import { BillingPlanGroup, type Models } from '@appwrite.io/console'; - - export let billingPlan: string; - export let anyOrgFree = false; - export let isNewOrg = false; - let classes: string = ''; - export { classes as class }; - - function shouldDisable(plan: Models.BillingPlan) { - return plan.group === BillingPlanGroup.Starter && anyOrgFree; - } - - function shouldShowTooltip(plan: Models.BillingPlan) { - if (plan.group !== BillingPlanGroup.Starter) return true; - else return !anyOrgFree; - } - - -{#if billingPlan} -
    - {#each $plansInfo.values() as plan} -
  • - - -
    -

    - {plan.name} - {#if $organization?.billingPlanId === plan.$id && !isNewOrg} - Current plan - {/if} -

    -

    - {plan.desc} -

    -

    - {formatCurrency(plan?.price ?? 0)} -

    -
    -
    -
    -
  • - {/each} -
-{/if} diff --git a/src/routes/(console)/account/organizations/+page.svelte b/src/routes/(console)/account/organizations/+page.svelte index 2d500bf975..b7582b6643 100644 --- a/src/routes/(console)/account/organizations/+page.svelte +++ b/src/routes/(console)/account/organizations/+page.svelte @@ -66,7 +66,7 @@ } function isNonPayingOrganization(organization: Models.Organization): boolean { - // 0 priced plans don't need payment methods! + // plan doesn't require payments, it is a non-paying org! return !organization?.billingPlanDetails.requiresPaymentMethod; } diff --git a/src/routes/(console)/apply-credit/+page.svelte b/src/routes/(console)/apply-credit/+page.svelte index 915076ac94..e59f094fd7 100644 --- a/src/routes/(console)/apply-credit/+page.svelte +++ b/src/routes/(console)/apply-credit/+page.svelte @@ -279,6 +279,17 @@ ) { loadPaymentMethods(); } + + /* check if payment method selection is needed */ + $: needsPaymentMethod = + selectedOrgId && + (!selectedOrg?.billingPlanDetails.requiresPaymentMethod || !selectedOrg?.paymentMethodId); + + /* check if coupon code input should be shown */ + $: needsCouponInput = !data?.couponData?.code && selectedOrgId; + + /* show payment section if either payment method or coupon input is needed */ + $: showPaymentSection = needsPaymentMethod || needsCouponInput; @@ -302,7 +313,7 @@ {/if} - {#if selectedOrgId && (!selectedOrg?.billingPlanDetails.addons.seats.supported || !selectedOrg?.paymentMethodId)} + {#if selectedOrgId && !selectedOrg?.billingPlanDetails.addons.seats.supported} {#if selectedOrgId === newOrgId} - {#if (selectedOrgId && (!selectedOrg?.billingPlanDetails.requiresPaymentMethod || !selectedOrg?.paymentMethodId)) || (!data?.couponData?.code && selectedOrgId)} + {#if showPaymentSection}
- {#if selectedOrgId && (!selectedOrg?.billingPlanDetails.requiresPaymentMethod || !selectedOrg?.paymentMethodId)} + {#if needsPaymentMethod} {/if}
- {#if !data?.couponData?.code && selectedOrgId} + {#if needsCouponInput} Date: Mon, 26 Jan 2026 15:56:24 +0530 Subject: [PATCH 48/79] fix: lint. --- .../organization-[organization]/billing/planSummary.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index e737fd7c0f..c8c522d7ad 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -351,7 +351,9 @@ 'screenshots-generated', 'Screenshots generated', getResource(resources, 'screenshotsGenerated'), - currentPlan?.['screenshotsGenerated'], /* TODO: @itznotabug - needs correct SDK */ + currentPlan?.[ + 'screenshotsGenerated' + ] /* TODO: @itznotabug - needs correct SDK */ ), createResourceRow( 'gb-hours', From 6eb2269468f9158c521bfb03d042c6bc883f5d71 Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 26 Jan 2026 18:37:23 +0530 Subject: [PATCH 49/79] fix: tests. --- e2e/steps/free-project.ts | 8 ++++++-- e2e/steps/pro-project.ts | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/e2e/steps/free-project.ts b/e2e/steps/free-project.ts index cf0972e478..9b59b548a1 100644 --- a/e2e/steps/free-project.ts +++ b/e2e/steps/free-project.ts @@ -24,9 +24,13 @@ export async function createFreeProject(page: Page): Promise { const regionPicker = dialog.locator('button[role="combobox"]'); if (await regionPicker.isVisible()) { await regionPicker.click(); - await page.getByRole('option', { name: /New York/i }).click(); + const firstEnabledOption = page.locator('[role="option"]:not([data-disabled="true"])').first(); - region = 'nyc'; + if (await firstEnabledOption.count() > 0) { + const selectedRegion = await firstEnabledOption.getAttribute('data-value'); + await firstEnabledOption.click(); + region = selectedRegion?.replace(/"/g, '') || 'fra'; + } } await dialog.getByRole('button', { name: 'create' }).click(); diff --git a/e2e/steps/pro-project.ts b/e2e/steps/pro-project.ts index 3f3a7ec1fe..d33504b957 100644 --- a/e2e/steps/pro-project.ts +++ b/e2e/steps/pro-project.ts @@ -57,9 +57,13 @@ export async function createProProject(page: Page): Promise { const regionPicker = dialog.locator('button[role="combobox"]'); if (await regionPicker.isVisible()) { await regionPicker.click(); - await page.getByRole('option', { name: /New York/i }).click(); + const firstEnabledOption = page.locator('[role="option"]:not([data-disabled="true"])').first(); - region = 'nyc'; + if (await firstEnabledOption.count() > 0) { + const selectedRegion = await firstEnabledOption.getAttribute('data-value'); + await firstEnabledOption.click(); + region = selectedRegion?.replace(/"/g, '') || 'fra'; + } } await dialog.getByRole('button', { name: 'create' }).click(); From fe09e908773c5ad1692e0819ab8d760fc9965e56 Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 26 Jan 2026 18:42:13 +0530 Subject: [PATCH 50/79] lint. --- e2e/steps/free-project.ts | 6 ++++-- e2e/steps/pro-project.ts | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/e2e/steps/free-project.ts b/e2e/steps/free-project.ts index 9b59b548a1..b75791f103 100644 --- a/e2e/steps/free-project.ts +++ b/e2e/steps/free-project.ts @@ -24,9 +24,11 @@ export async function createFreeProject(page: Page): Promise { const regionPicker = dialog.locator('button[role="combobox"]'); if (await regionPicker.isVisible()) { await regionPicker.click(); - const firstEnabledOption = page.locator('[role="option"]:not([data-disabled="true"])').first(); + const firstEnabledOption = page + .locator('[role="option"]:not([data-disabled="true"])') + .first(); - if (await firstEnabledOption.count() > 0) { + if ((await firstEnabledOption.count()) > 0) { const selectedRegion = await firstEnabledOption.getAttribute('data-value'); await firstEnabledOption.click(); region = selectedRegion?.replace(/"/g, '') || 'fra'; diff --git a/e2e/steps/pro-project.ts b/e2e/steps/pro-project.ts index d33504b957..7500b3f74b 100644 --- a/e2e/steps/pro-project.ts +++ b/e2e/steps/pro-project.ts @@ -57,9 +57,11 @@ export async function createProProject(page: Page): Promise { const regionPicker = dialog.locator('button[role="combobox"]'); if (await regionPicker.isVisible()) { await regionPicker.click(); - const firstEnabledOption = page.locator('[role="option"]:not([data-disabled="true"])').first(); + const firstEnabledOption = page + .locator('[role="option"]:not([data-disabled="true"])') + .first(); - if (await firstEnabledOption.count() > 0) { + if ((await firstEnabledOption.count()) > 0) { const selectedRegion = await firstEnabledOption.getAttribute('data-value'); await firstEnabledOption.click(); region = selectedRegion?.replace(/"/g, '') || 'fra'; From 18d3a9bea47da9191a5ef0f44cea02a26cba1f89 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 11:34:04 +0530 Subject: [PATCH 51/79] update: split check into readable format; address comment. --- .../alerts/missingPaymentMethod.svelte | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/lib/components/billing/alerts/missingPaymentMethod.svelte b/src/lib/components/billing/alerts/missingPaymentMethod.svelte index fd7f4b80ac..66ee47611f 100644 --- a/src/lib/components/billing/alerts/missingPaymentMethod.svelte +++ b/src/lib/components/billing/alerts/missingPaymentMethod.svelte @@ -5,9 +5,32 @@ import { HeaderAlert } from '$lib/layout'; import { hideBillingHeaderRoutes } from '$lib/stores/billing'; import { orgMissingPaymentMethod } from '$routes/(console)/store'; + + // exists + $: hasOrgBillingContext = !!$orgMissingPaymentMethod; + + // needs any methods + $: requiresPaymentMethod = + hasOrgBillingContext && $orgMissingPaymentMethod.billingPlanDetails.requiresPaymentMethod; + + // has any methods + $: hasAnyPaymentMethod = + hasOrgBillingContext && + (!!$orgMissingPaymentMethod.paymentMethodId || + !!$orgMissingPaymentMethod.backupPaymentMethodId); + + // is url excluded + $: isBillingHeaderHidden = hideBillingHeaderRoutes.includes(page.url.pathname); + + // should show header + $: shouldShowBillingHeader = + hasOrgBillingContext && + requiresPaymentMethod && + !hasAnyPaymentMethod && + !isBillingHeaderHidden; -{#if $orgMissingPaymentMethod.billingPlanDetails.requiresPaymentMethod && !$orgMissingPaymentMethod.paymentMethodId && !$orgMissingPaymentMethod.backupPaymentMethodId && !hideBillingHeaderRoutes.includes(page.url.pathname)} +{#if shouldShowBillingHeader} From 8fdda4449d865d6ee951fda6135f0df21debfd8d Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 11:41:16 +0530 Subject: [PATCH 52/79] update: remove conditional guards on `billingPlanDetails`. --- src/lib/components/support.svelte | 4 ++-- src/lib/layout/containerButton.svelte | 2 +- src/lib/layout/containerHeader.svelte | 2 +- .../(console)/organization-[organization]/header.svelte | 2 +- .../organization-[organization]/members/+page.svelte | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/components/support.svelte b/src/lib/components/support.svelte index 50ab1f3454..ef80269c5d 100644 --- a/src/lib/components/support.svelte +++ b/src/lib/components/support.svelte @@ -20,12 +20,12 @@ $: hasPremiumSupport = $currentPlan?.premiumSupport ?? allOrgsHavePremiumSupport ?? false; $: allOrgsHavePremiumSupport = $organizationList.teams.every( - (team) => (team as Models.Organization).billingPlanDetails?.premiumSupport + (team) => (team as Models.Organization).billingPlanDetails.premiumSupport ); // there can only be one free organization $: freeOrganization = $organizationList.teams.find( - (team) => !(team as Models.Organization).billingPlanDetails?.premiumSupport + (team) => !(team as Models.Organization).billingPlanDetails.premiumSupport ); $: upgradeURL = `${base}/organization-${freeOrganization?.$id}/change-plan`; diff --git a/src/lib/layout/containerButton.svelte b/src/lib/layout/containerButton.svelte index 516eead977..c331c2eb74 100644 --- a/src/lib/layout/containerButton.svelte +++ b/src/lib/layout/containerButton.svelte @@ -9,7 +9,7 @@ $organization?.billingPlanDetails.group === BillingPlanGroup.Starter ? `Upgrade to add more ${title.toLocaleLowerCase()}` : `You've reached the ${title.toLocaleLowerCase()} limit for the ${ - $organization?.billingPlanDetails?.name + $organization?.billingPlanDetails.name } plan`; export let disabled: boolean; diff --git a/src/lib/layout/containerHeader.svelte b/src/lib/layout/containerHeader.svelte index 15788ca66c..7b55bdb17a 100644 --- a/src/lib/layout/containerHeader.svelte +++ b/src/lib/layout/containerHeader.svelte @@ -60,7 +60,7 @@ }; const dispatch = createEventDispatcher(); - $: planName = $organization?.billingPlanDetails?.name; + $: planName = $organization?.billingPlanDetails.name; // these can be organization level limitations as well. // we need to migrate this sometime later, but soon! $: hasProjectLimitation = checkForProjectLimitation($organization?.billingPlanId, serviceId); diff --git a/src/routes/(console)/organization-[organization]/header.svelte b/src/routes/(console)/organization-[organization]/header.svelte index 96d8bd446b..2cc67bff3c 100644 --- a/src/routes/(console)/organization-[organization]/header.svelte +++ b/src/routes/(console)/organization-[organization]/header.svelte @@ -155,7 +155,7 @@ {!organization?.billingPlanDetails.addons.seats.supported ? 'Upgrade to add more members' : `You've reached the members limit for the ${ - organization?.billingPlanDetails?.name + organization?.billingPlanDetails.name } plan`}
diff --git a/src/routes/(console)/organization-[organization]/members/+page.svelte b/src/routes/(console)/organization-[organization]/members/+page.svelte index 5882c78eea..bf340ab13c 100644 --- a/src/routes/(console)/organization-[organization]/members/+page.svelte +++ b/src/routes/(console)/organization-[organization]/members/+page.svelte @@ -90,7 +90,7 @@
{!supportsMembers ? 'Upgrade to add more members' - : `You've reached the members limit for the ${$organization?.billingPlanDetails?.name} plan`} + : `You've reached the members limit for the ${$organization?.billingPlanDetails.name} plan`}
From 085cd2334455fcc3a1c7771f8989d388fa4fea43 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 11:41:51 +0530 Subject: [PATCH 53/79] update: use explicit cloud check. --- src/lib/layout/shell.svelte | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index c1034864dd..1ce8a81379 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -153,14 +153,19 @@ }) .toString(), - organizations: $organizationList.teams.map((org) => { - const billingPlan = org['billingPlanDetails'] as Models.BillingPlan; + organizations: $organizationList.teams.map((team) => { + let billingPlan: Models.BillingPlan | null = null; + + if (isCloud) { + billingPlan = (team as Models.Organization).billingPlanDetails; + } + return { - name: org.name, - $id: org.$id, - isSelected: $organization?.$id === org.$id, + name: team.name, + $id: team.$id, + isSelected: $organization?.$id === team.$id, tierName: isCloud ? billingPlan.name : null, - showUpgrade: billingPlan.group === BillingPlanGroup.Starter + showUpgrade: isCloud ? billingPlan.group === BillingPlanGroup.Starter : false }; }), From 945ae62f4765f0c900bf3fada680020a37221632 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 11:50:06 +0530 Subject: [PATCH 54/79] address comments. --- src/routes/(console)/account/organizations/+page.svelte | 2 +- src/routes/(console)/apply-credit/+page.svelte | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/routes/(console)/account/organizations/+page.svelte b/src/routes/(console)/account/organizations/+page.svelte index b7582b6643..2ed7d57dc3 100644 --- a/src/routes/(console)/account/organizations/+page.svelte +++ b/src/routes/(console)/account/organizations/+page.svelte @@ -83,7 +83,7 @@ function isCloudOrg( data: Partial> | Models.Organization ): data is Models.Organization { - return isCloud && 'billingPlan' in data; + return isCloud && 'billingPlanId' in data; } function createOrg() { diff --git a/src/routes/(console)/apply-credit/+page.svelte b/src/routes/(console)/apply-credit/+page.svelte index e59f094fd7..e5292fd3e8 100644 --- a/src/routes/(console)/apply-credit/+page.svelte +++ b/src/routes/(console)/apply-credit/+page.svelte @@ -91,6 +91,7 @@ if (page.url.searchParams.has('org')) { selectedOrgId = page.url.searchParams.get('org'); + tempOrgId = selectedOrgId; canSelectOrg = false; } @@ -104,8 +105,12 @@ if (page.url.searchParams.has('type')) { const type = page.url.searchParams.get('type'); if (type === 'payment_confirmed') { - const organizationId = page.url.searchParams.get('id'); - collaborators = page.url.searchParams.get('invites').split(','); + const organizationId = + page.url.searchParams.get('org') || page.url.searchParams.get('id'); + const invites = page.url.searchParams.get('invites'); + if (invites) { + collaborators = invites.split(','); + } await sdk.forConsole.organizations.validatePayment({ organizationId, From 66577cc1b387f7f62cd865f9e758f2b6deb97475 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 11:55:18 +0530 Subject: [PATCH 55/79] address comment. --- src/routes/(console)/apply-credit/+page.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/routes/(console)/apply-credit/+page.svelte b/src/routes/(console)/apply-credit/+page.svelte index e5292fd3e8..e2ad497d54 100644 --- a/src/routes/(console)/apply-credit/+page.svelte +++ b/src/routes/(console)/apply-credit/+page.svelte @@ -286,9 +286,7 @@ } /* check if payment method selection is needed */ - $: needsPaymentMethod = - selectedOrgId && - (!selectedOrg?.billingPlanDetails.requiresPaymentMethod || !selectedOrg?.paymentMethodId); + $: needsPaymentMethod = selectedOrgId && selectedOrg?.billingPlanDetails.requiresPaymentMethod; /* check if coupon code input should be shown */ $: needsCouponInput = !data?.couponData?.code && selectedOrgId; From 33fedf7588f972ffaf7468b288ed9e8902af6e99 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 12:13:08 +0530 Subject: [PATCH 56/79] address comments. --- .../billing/deleteOrgPayment.svelte | 4 ++-- .../change-plan/+page.svelte | 5 ++++- .../createProjectCloud.svelte | 15 +++++++++------ .../organization-[organization]/header.svelte | 1 - .../usage/[[invoice]]/totalMembers.svelte | 5 ++--- .../settings/smtp/+page.svelte | 2 +- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte b/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte index 3f357d9d97..78570ba89d 100644 --- a/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte +++ b/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte @@ -16,7 +16,7 @@ let error: string; async function removeDefaultMethod() { - if ($currentPlan.requiresPaymentMethod && !hasOtherMethod) return; + if ($currentPlan?.requiresPaymentMethod && !hasOtherMethod) return; try { await sdk.forConsole.organizations.deleteDefaultPaymentMethod({ @@ -38,7 +38,7 @@ } async function removeBackupMethod() { - if ($currentPlan.requiresPaymentMethod && !hasOtherMethod) return; + if ($currentPlan?.requiresPaymentMethod && !hasOtherMethod) return; showDelete = false; try { diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index 394fff5ae4..9c77997c4f 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -470,7 +470,10 @@ - {#if selectedPlan.group !== BillingPlanGroup.Starter && data.organization.billingPlanId !== selectedPlan.group && !data.organization.billingPlanDetails.selfService} + {@const isStarter = selectedPlan.group === BillingPlanGroup.Starter} + {@const isSelfService = data.organization.billingPlanDetails.selfService} + {@const isSameGroup = data.organization.billingPlanDetails.group === selectedPlan.group} + {#if !isStarter && !isSameGroup && !isSelfService} { error = null; - projectId = ID.unique(); projectName = 'New project'; projectRegion = Region.Fra; showCreateProjectCloud = false; + + // reset with a new ID + initialProjectId = ID.unique(); + projectId = initialProjectId; }); @@ -99,7 +102,7 @@ diff --git a/src/routes/(console)/organization-[organization]/header.svelte b/src/routes/(console)/organization-[organization]/header.svelte index 2cc67bff3c..2062f153ae 100644 --- a/src/routes/(console)/organization-[organization]/header.svelte +++ b/src/routes/(console)/organization-[organization]/header.svelte @@ -151,7 +151,6 @@
- {!organization?.billingPlanDetails.addons.seats.supported ? 'Upgrade to add more members' : `You've reached the members limit for the ${ diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte index 570e5f7003..a92b9abcb1 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/totalMembers.svelte @@ -11,9 +11,8 @@ export let members: Models.MembershipList; $: total = members?.total ?? 0; - $: billingPlan = $organization?.billingPlanDetails; - - $: organizationMembersSupported = !billingPlan.addons.seats.supported; /* false on free */ + $: organizationMembersSupported = + !$organization?.billingPlanDetails.addons.seats.supported; /* false on free */ diff --git a/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte index 1b92228cb9..e118b7b150 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte @@ -126,7 +126,7 @@ {#if !$currentPlan.customSmtp} + title="Custom SMTP is a paid plan feature. Upgrade to enable custom SMTP server.">
@@ -68,7 +68,7 @@ title="Backups" buttonText="Manual backup" buttonType="secondary" - buttonDisabled={true} /> + buttonDisabled />
From 0e921a63dfa2ab7625ef39ad87f039d613f7d26f Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 13:27:48 +0530 Subject: [PATCH 59/79] update: better params for short-circuiting. --- src/lib/stores/billing.ts | 15 ++++++++++++--- .../databases/+page.svelte | 4 +--- .../functions/+page.svelte | 6 +----- .../functions/templates/+page.svelte | 6 +----- .../templates/template-[template]/+page.svelte | 3 +-- .../project-[region]-[project]/sites/+page.svelte | 3 +-- 6 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index cbce2be033..65c2f3d770 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -300,12 +300,21 @@ export function checkForProjectLimitation(plan: string, id: PlanServices) { } } -export function isServiceLimited(serviceId: PlanServices, plan: string, total: number) { +export function isServiceLimited( + serviceId: PlanServices, + organization: Models.Organization, + total: number +) { + // total validity if (!total) return false; - if (!plan) return false; + + // org and plan validity! + if (!organization) return false; + if (!('billingPlanId' in organization)) return false; + const limit = getServiceLimit(serviceId) || Infinity; const isLimited = limit !== 0 && limit < Infinity; - const hasUsageFees = checkForUsageFees(plan, serviceId); + const hasUsageFees = checkForUsageFees(organization.billingPlanId, serviceId); return isLimited && total >= limit && !hasUsageFees; } diff --git a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte index cbfdbbdf19..0b154fb4ad 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte @@ -25,9 +25,7 @@ let showCreate = $state(false); - const isLimited = $derived( - isServiceLimited('databases', $organization?.billingPlanId, data.databases.total) - ); + const isLimited = $derived(isServiceLimited('databases', $organization, data.databases.total)); async function handleCreate(event: CustomEvent) { showCreate = false; diff --git a/src/routes/(console)/project-[region]-[project]/functions/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/+page.svelte index 412b6902ba..95f86fe6de 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/+page.svelte @@ -68,11 +68,7 @@ keys: ['c'], disabled: $wizard.show || - isServiceLimited( - 'functions', - $organization?.billingPlanId, - data.functions?.total - ) || + isServiceLimited('functions', $organization, data.functions?.total) || !$canWriteFunctions, icon: IconPlus, group: 'functions' diff --git a/src/routes/(console)/project-[region]-[project]/functions/templates/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/templates/+page.svelte index 418bae9436..2ddb9f30d6 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/templates/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/templates/+page.svelte @@ -79,11 +79,7 @@ .some((param) => param.toLowerCase() === useCase.toLowerCase()); }; - $: buttonDisabled = isServiceLimited( - 'functions', - $organization?.billingPlanId, - $functionsList?.total ?? 0 - ); + $: buttonDisabled = isServiceLimited('functions', $organization, $functionsList?.total ?? 0); diff --git a/src/routes/(console)/project-[region]-[project]/functions/templates/template-[template]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/templates/template-[template]/+page.svelte index 337e6861cb..96aa113950 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/templates/template-[template]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/templates/template-[template]/+page.svelte @@ -26,8 +26,7 @@ import { IconExternalLink } from '@appwrite.io/pink-icons-svelte'; $: buttonDisabled = - isCloud && - isServiceLimited('functions', $organization?.billingPlanId, $functionsList?.total); + isCloud && isServiceLimited('functions', $organization, $functionsList?.total); diff --git a/src/routes/(console)/project-[region]-[project]/sites/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/+page.svelte index 155baaac17..e9ffd7c38d 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/+page.svelte @@ -40,8 +40,7 @@ }, keys: ['c'], disabled: - isServiceLimited('sites', $organization?.billingPlanId, data.siteList?.total) || - !$canWriteSites, + isServiceLimited('sites', $organization, data.siteList?.total) || !$canWriteSites, icon: IconPlus, group: 'sites' } From 88333cdc5938d0c135da62165abc8e99f3457629 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 14:21:37 +0530 Subject: [PATCH 60/79] fix: tests. --- e2e/steps/pro-project.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/steps/pro-project.ts b/e2e/steps/pro-project.ts index 7500b3f74b..53208c2953 100644 --- a/e2e/steps/pro-project.ts +++ b/e2e/steps/pro-project.ts @@ -18,10 +18,10 @@ export async function enterCreditCard(page: Page) { await dialog.waitFor({ state: 'visible' }); await page.getByPlaceholder('cardholder').fill('Test User'); const stripe = page.locator('[title="Secure payment input frame"]').nth(0).contentFrame(); - await stripe.locator('id=Field-numberInput').fill('4242424242424242'); - await stripe.locator('id=Field-expiryInput').fill('1250'); - await stripe.locator('id=Field-cvcInput').fill('123'); - await stripe.locator('id=Field-countryInput').selectOption('DE'); + await stripe.locator('id=payment-numberInput').fill('4242424242424242'); + await stripe.locator('id=payment-expiryInput').fill('1250'); + await stripe.locator('id=payment-cvcInput').fill('123'); + await stripe.locator('id=payment-countryInput').selectOption('DE'); await dialog.getByRole('button', { name: 'Add', exact: true }).click(); await page.locator('id=state-picker').click(); // open dropdown await page.getByRole('option', { name: 'Alabama' }).click(); From b458be44eea65588c58ae1cae4f20eced06b7471 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 17:25:23 +0530 Subject: [PATCH 61/79] fix: regions endpoint call. --- src/routes/(console)/regions.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/routes/(console)/regions.ts b/src/routes/(console)/regions.ts index d4c330d6b7..9c411a82f0 100644 --- a/src/routes/(console)/regions.ts +++ b/src/routes/(console)/regions.ts @@ -21,8 +21,7 @@ export async function loadAvailableRegions(orgId: string, force: boolean = false return; } - // TODO: @itznotabug, fix with latest SDK. - const availableRegions = await sdk.forConsole.console.getRegions({ + const availableRegions = await sdk.forConsole.organizations.listRegions({ organizationId: orgId }); From 2bd17e8ee81be99d7d74c18f08d1b204c4967ebe Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 17:26:33 +0530 Subject: [PATCH 62/79] remove: legacy todos. --- src/lib/stores/billing.ts | 2 -- .../settings/migrations/(import)/importReport.svelte | 1 - 2 files changed, 3 deletions(-) diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 65c2f3d770..1942b6cb74 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -145,7 +145,6 @@ export function getNextTierBillingPlan(billingPlanId: string): Models.BillingPla const plans = get(plansInfo); for (const [, plan] of plans) { - // TODO: @itznotabug, check for group maybe? if (plan.order === currentOrder + 1) { return plan; } @@ -423,7 +422,6 @@ export async function checkForUsageLimit(organization: Models.Organization) { } } - // TODO: @itznotabug - check with @abnegate, what do we do here? this is billing! const { bandwidth, executions, storage, users } = organization?.billingLimits ?? {}; const resources = [ { value: bandwidth, name: 'bandwidth' }, diff --git a/src/routes/(console)/project-[region]-[project]/settings/migrations/(import)/importReport.svelte b/src/routes/(console)/project-[region]-[project]/settings/migrations/(import)/importReport.svelte index 38def3870d..99e62c32f7 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/migrations/(import)/importReport.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/migrations/(import)/importReport.svelte @@ -18,7 +18,6 @@ const dispatch = createEventDispatcher(); - // TODO: @itznotabug - check what needs to be changed here. we might need to do dual stuff to manage backwards compat? const labelMap = { users: { root: 'Users', teams: 'Include teams' }, databases: { root: 'Databases', rows: 'Include rows' }, From ef14ea37aee35ced32e9bf947c71a59d3847b551 Mon Sep 17 00:00:00 2001 From: Darshan Date: Tue, 27 Jan 2026 18:15:21 +0530 Subject: [PATCH 63/79] address: todo. --- .../organization-[organization]/billing/planSummary.svelte | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index bc813cd057..e6cd4163a6 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -351,9 +351,7 @@ 'screenshots-generated', 'Screenshots generated', getResource(resources, 'screenshotsGenerated'), - currentPlan?.[ - 'screenshotsGenerated' - ] /* TODO: @itznotabug - needs correct SDK */ + currentPlan?.screenshotsGenerated ), createResourceRow( 'gb-hours', From 8285a1f296f66384e520e81e8fdd91549dc47b80 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 28 Jan 2026 12:04:30 +0530 Subject: [PATCH 64/79] remove: `upgradeURL` derived store err. --- src/lib/components/backupDatabaseAlert.svelte | 6 ++- .../billing/alerts/limitReached.svelte | 4 +- .../billing/alerts/projectsLimit.svelte | 4 +- .../components/billing/emptyCardCloud.svelte | 6 ++- src/lib/components/bottomModalAlert.svelte | 5 +- src/lib/components/cardContainer.svelte | 6 +-- src/lib/components/cardPlanLimit.svelte | 16 ++++-- src/lib/components/roles/upgrade.svelte | 8 +-- src/lib/elements/table/body.svelte | 52 ------------------- src/lib/elements/table/cell.svelte | 24 --------- src/lib/elements/table/cellAvatar.svelte | 13 ----- src/lib/elements/table/cellButton.svelte | 26 ---------- src/lib/elements/table/cellCheck.svelte | 35 ------------- src/lib/elements/table/cellHead.svelte | 17 ------ src/lib/elements/table/cellHeadCheck.svelte | 32 ------------ src/lib/elements/table/cellLink.svelte | 17 ------ src/lib/elements/table/cellText.svelte | 16 ------ src/lib/elements/table/footer.svelte | 5 -- src/lib/elements/table/header.svelte | 5 -- src/lib/elements/table/index.ts | 16 ------ src/lib/elements/table/row.svelte | 3 -- src/lib/elements/table/rowButton.svelte | 7 --- src/lib/elements/table/rowLink.svelte | 7 --- src/lib/elements/table/table.svelte | 29 ----------- src/lib/elements/table/tableList.svelte | 9 ---- src/lib/elements/table/tableScroll.svelte | 2 +- src/lib/layout/containerHeader.svelte | 11 ++-- src/lib/stores/billing.ts | 38 +++++++++++--- .../organization-[organization]/+page.svelte | 6 +-- .../billing/+page.svelte | 12 +++-- .../billing/availableCredit.svelte | 4 +- .../billing/budgetAlert.svelte | 4 +- .../billing/budgetCap.svelte | 4 +- .../billing/planSummary.svelte | 6 +-- .../billing/planSummaryOld.svelte | 6 +-- .../usage/[[invoice]]/+page.svelte | 8 +-- .../usage/[[invoice]]/totalMembers.svelte | 13 +++-- .../auth/security/updateMockNumbers.svelte | 6 ++- .../auth/templates/emailSignature.svelte | 5 +- .../databases/create.svelte | 5 +- .../backups/containerHeader.svelte | 7 +-- .../backups/createPolicy.svelte | 7 +-- .../backups/upgradeCard.svelte | 15 ++++-- .../table-[table]/columns/string.svelte | 6 ++- .../(components)/details.svelte | 6 +-- .../settings/updateResourceLimits.svelte | 4 +- .../settings/smtp/+page.svelte | 4 +- .../settings/usage/[[invoice]]/+page.svelte | 4 +- .../settings/updateResourceLimits.svelte | 4 +- .../settings/updateMaxFileSize.svelte | 4 +- 50 files changed, 154 insertions(+), 405 deletions(-) delete mode 100644 src/lib/elements/table/body.svelte delete mode 100644 src/lib/elements/table/cell.svelte delete mode 100644 src/lib/elements/table/cellAvatar.svelte delete mode 100644 src/lib/elements/table/cellButton.svelte delete mode 100644 src/lib/elements/table/cellCheck.svelte delete mode 100644 src/lib/elements/table/cellHead.svelte delete mode 100644 src/lib/elements/table/cellHeadCheck.svelte delete mode 100644 src/lib/elements/table/cellLink.svelte delete mode 100644 src/lib/elements/table/cellText.svelte delete mode 100644 src/lib/elements/table/footer.svelte delete mode 100644 src/lib/elements/table/header.svelte delete mode 100644 src/lib/elements/table/row.svelte delete mode 100644 src/lib/elements/table/rowButton.svelte delete mode 100644 src/lib/elements/table/rowLink.svelte delete mode 100644 src/lib/elements/table/table.svelte delete mode 100644 src/lib/elements/table/tableList.svelte diff --git a/src/lib/components/backupDatabaseAlert.svelte b/src/lib/components/backupDatabaseAlert.svelte index 46b44a41e8..cc7e2f3263 100644 --- a/src/lib/components/backupDatabaseAlert.svelte +++ b/src/lib/components/backupDatabaseAlert.svelte @@ -4,7 +4,7 @@ import { organization } from '$lib/stores/organization'; import { HeaderAlert } from '$lib/layout'; import { isCloud } from '$lib/system'; - import { upgradeURL } from '$lib/stores/billing'; + import { getChangePlanUrl } from '$lib/stores/billing'; import { hideNotification } from '$lib/helpers/notifications'; import { backupsBannerId, showPolicyAlert } from '$lib/stores/database'; import { IconX } from '@appwrite.io/pink-icons-svelte'; @@ -24,7 +24,9 @@ : 'Protect your data by quickly adding a backup policy'} {@const ctaText = !areBackupsAvailable ? 'Upgrade plan' : 'Create policy'} - {@const ctaURL = !areBackupsAvailable ? $upgradeURL : `${page.url.pathname}/backups`} + {@const ctaURL = !areBackupsAvailable + ? getChangePlanUrl($organization.$id) + : `${page.url.pathname}/backups`} {subtitle} diff --git a/src/lib/components/billing/alerts/limitReached.svelte b/src/lib/components/billing/alerts/limitReached.svelte index cb6e934b8d..d0944f801d 100644 --- a/src/lib/components/billing/alerts/limitReached.svelte +++ b/src/lib/components/billing/alerts/limitReached.svelte @@ -4,7 +4,7 @@ import { Click, trackEvent } from '$lib/actions/analytics'; import { Button } from '$lib/elements/forms'; import { HeaderAlert } from '$lib/layout'; - import { hideBillingHeaderRoutes, readOnly, upgradeURL } from '$lib/stores/billing'; + import { hideBillingHeaderRoutes, readOnly, getChangePlanUrl } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; @@ -27,7 +27,7 @@ {/if} diff --git a/src/lib/components/roles/upgrade.svelte b/src/lib/components/roles/upgrade.svelte index e1702f53d7..fbef983a4e 100644 --- a/src/lib/components/roles/upgrade.svelte +++ b/src/lib/components/roles/upgrade.svelte @@ -1,7 +1,7 @@ - -
- -
-{#if isCloud && limitReached && service} - - - - - Upgrade your plan to add {name} to your organization - - - - - -{/if} diff --git a/src/lib/elements/table/cell.svelte b/src/lib/elements/table/cell.svelte deleted file mode 100644 index a35998e690..0000000000 --- a/src/lib/elements/table/cell.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - -
- -
diff --git a/src/lib/elements/table/cellAvatar.svelte b/src/lib/elements/table/cellAvatar.svelte deleted file mode 100644 index 3d131be3fc..0000000000 --- a/src/lib/elements/table/cellAvatar.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - - -
- -
-
diff --git a/src/lib/elements/table/cellButton.svelte b/src/lib/elements/table/cellButton.svelte deleted file mode 100644 index b6a2277e02..0000000000 --- a/src/lib/elements/table/cellButton.svelte +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/src/lib/elements/table/cellCheck.svelte b/src/lib/elements/table/cellCheck.svelte deleted file mode 100644 index db1c8ab9c3..0000000000 --- a/src/lib/elements/table/cellCheck.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - - - - diff --git a/src/lib/elements/table/cellHead.svelte b/src/lib/elements/table/cellHead.svelte deleted file mode 100644 index d8bb55ca48..0000000000 --- a/src/lib/elements/table/cellHead.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - -
- - - -
diff --git a/src/lib/elements/table/cellHeadCheck.svelte b/src/lib/elements/table/cellHeadCheck.svelte deleted file mode 100644 index abff61e0fa..0000000000 --- a/src/lib/elements/table/cellHeadCheck.svelte +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/src/lib/elements/table/cellLink.svelte b/src/lib/elements/table/cellLink.svelte deleted file mode 100644 index 83ee17483c..0000000000 --- a/src/lib/elements/table/cellLink.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - -
- -
diff --git a/src/lib/elements/table/cellText.svelte b/src/lib/elements/table/cellText.svelte deleted file mode 100644 index 9bcd2262bc..0000000000 --- a/src/lib/elements/table/cellText.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/src/lib/elements/table/footer.svelte b/src/lib/elements/table/footer.svelte deleted file mode 100644 index d8f058c6b3..0000000000 --- a/src/lib/elements/table/footer.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/lib/elements/table/header.svelte b/src/lib/elements/table/header.svelte deleted file mode 100644 index bad3f122b4..0000000000 --- a/src/lib/elements/table/header.svelte +++ /dev/null @@ -1,5 +0,0 @@ -
-
- -
-
diff --git a/src/lib/elements/table/index.ts b/src/lib/elements/table/index.ts index 50bda1812d..b347931b7f 100644 --- a/src/lib/elements/table/index.ts +++ b/src/lib/elements/table/index.ts @@ -1,17 +1 @@ -export { default as Table } from './table.svelte'; export { default as TableScroll } from './tableScroll.svelte'; -export { default as TableList } from './tableList.svelte'; -export { default as TableBody } from './body.svelte'; -export { default as TableHeader } from './header.svelte'; -export { default as TableFooter } from './footer.svelte'; -export { default as TableRow } from './row.svelte'; -export { default as TableRowLink } from './rowLink.svelte'; -export { default as TableRowButton } from './rowButton.svelte'; -export { default as TableCell } from './cell.svelte'; -export { default as TableCellButton } from './cellButton.svelte'; -export { default as TableCellHead } from './cellHead.svelte'; -export { default as TableCellHeadCheck } from './cellHeadCheck.svelte'; -export { default as TableCellLink } from './cellLink.svelte'; -export { default as TableCellAvatar } from './cellAvatar.svelte'; -export { default as TableCellText } from './cellText.svelte'; -export { default as TableCellCheck } from './cellCheck.svelte'; diff --git a/src/lib/elements/table/row.svelte b/src/lib/elements/table/row.svelte deleted file mode 100644 index 3a9808d967..0000000000 --- a/src/lib/elements/table/row.svelte +++ /dev/null @@ -1,3 +0,0 @@ -
- -
diff --git a/src/lib/elements/table/rowButton.svelte b/src/lib/elements/table/rowButton.svelte deleted file mode 100644 index 9667a2ade3..0000000000 --- a/src/lib/elements/table/rowButton.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/src/lib/elements/table/rowLink.svelte b/src/lib/elements/table/rowLink.svelte deleted file mode 100644 index 7a8249bc77..0000000000 --- a/src/lib/elements/table/rowLink.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/src/lib/elements/table/table.svelte b/src/lib/elements/table/table.svelte deleted file mode 100644 index 179d411ce1..0000000000 --- a/src/lib/elements/table/table.svelte +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - diff --git a/src/lib/elements/table/tableList.svelte b/src/lib/elements/table/tableList.svelte deleted file mode 100644 index 7e2b4fe4b1..0000000000 --- a/src/lib/elements/table/tableList.svelte +++ /dev/null @@ -1,9 +0,0 @@ -
-
-
-
    - -
-
-
-
diff --git a/src/lib/elements/table/tableScroll.svelte b/src/lib/elements/table/tableScroll.svelte index 91a7c80181..5c5b458bf0 100644 --- a/src/lib/elements/table/tableScroll.svelte +++ b/src/lib/elements/table/tableScroll.svelte @@ -12,7 +12,7 @@ let isOverflowing = false; - const hasOverflow: Action = (node) => { + const hasOverflow: Action = (node: Element) => { const observer = new ResizeObserver((entries) => { for (const entry of entries) { let overflowing = false; diff --git a/src/lib/layout/containerHeader.svelte b/src/lib/layout/containerHeader.svelte index 7b55bdb17a..b162be4044 100644 --- a/src/lib/layout/containerHeader.svelte +++ b/src/lib/layout/containerHeader.svelte @@ -10,7 +10,7 @@ getServiceLimit, readOnly, showUsageRatesModal, - upgradeURL, + getChangePlanUrl, type PlanServices, canUpgrade } from '$lib/stores/billing'; @@ -56,8 +56,9 @@ //TODO: refactor this to be a string const upgradeMethod = () => { showDropdown = false; - goto($upgradeURL); + goto(getChangePlanUrl($organization?.$id)); }; + const dispatch = createEventDispatcher(); $: planName = $organization?.billingPlanDetails.name; @@ -117,7 +118,7 @@ You've reached the {services} limit for the {planName} plan. Upgrade your organization for additional resources. @@ -161,7 +162,7 @@ You are limited to {limit} {title.toLocaleLowerCase()} per project on the {planName} plan. {#if canUpgrade($organization.billingPlanId)}Upgrade @@ -181,7 +182,7 @@ You are limited to {limit} {title.toLocaleLowerCase()} per organization on the {planName} plan. {#if canUpgrade($organization.billingPlanId)} - Upgrade + Upgrade for additional {title.toLocaleLowerCase()}. {/if}

diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 1942b6cb74..0b7d01a882 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -1,8 +1,8 @@ -import { browser } from '$app/environment'; -import { goto } from '$app/navigation'; import { resolve } from '$app/paths'; -import { Click, trackEvent } from '$lib/actions/analytics'; import { page } from '$app/stores'; +import { goto } from '$app/navigation'; +import { browser } from '$app/environment'; +import { Click, trackEvent } from '$lib/actions/analytics'; import LimitReached from '$lib/components/billing/alerts/limitReached.svelte'; import MarkedForDeletion from '$lib/components/billing/alerts/markedForDeletion.svelte'; import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentMethod.svelte'; @@ -650,11 +650,33 @@ export async function checkForNewDevUpgradePro(org: Models.Organization) { importance: 1 }); } -export const upgradeURL = derived(page, ($page) => { - return resolve('/(console)/organization-[organization]/change-plan', { - organization: $page.data?.organization?.$id - }); -}); + +export function getChangePlanUrl(organizationId?: string | null | undefined): string { + let orgId = organizationId || null; + + if (!orgId) { + try { + const pageState = get(page); + + const fromUrl = pageState?.params?.organization?.trim?.() || null; + const fromOrgData = pageState?.data?.organization?.$id?.trim?.() || null; + const fromProjectData = pageState?.data?.project?.teamId?.trim?.() || null; + + orgId = fromUrl ?? fromProjectData ?? fromOrgData; + } catch { + /* ignore */ + } + } + + if (orgId) { + return resolve('/(console)/organization-[organization]/change-plan', { + organization: orgId + }); + } else { + /* fallback to not crash anything */ + return resolve('/'); + } +} export const hideBillingHeaderRoutes = [resolve('/account'), resolve('/create-organization')]; diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index 28d350a059..3fd94eb209 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -18,7 +18,7 @@ } from '$lib/components'; import { trackEvent, Click } from '$lib/actions/analytics'; import { type Models } from '@appwrite.io/console'; - import { getServiceLimit, readOnly, upgradeURL } from '$lib/stores/billing'; + import { getServiceLimit, readOnly, getChangePlanUrl } from '$lib/stores/billing'; import { hideNotification, shouldShowNotification } from '$lib/helpers/notifications'; import { onMount, type ComponentType } from 'svelte'; import { canWriteProjects } from '$lib/stores/roles'; @@ -206,7 +206,7 @@ +
{:else} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/containerHeader.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/containerHeader.svelte index 1ee5a6e2f1..5e2cae01d6 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/containerHeader.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/containerHeader.svelte @@ -1,10 +1,11 @@
@@ -77,7 +80,9 @@ @@ -85,7 +90,9 @@
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/string.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/string.svelte index 84ef6f7060..c3b3ed8e7f 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/string.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/string.svelte @@ -40,12 +40,13 @@ {#if $canWriteWebhooks} - + +
+ +
+ + You have reached the maximum number of webhooks for your plan. + +
{/if}
From d6a5e73b6d3e986ce28f713aeb663d3ecc32f30d Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 28 Jan 2026 13:28:01 +0530 Subject: [PATCH 67/79] fix: bad bug with stores. --- .../settings/smtp/+page.svelte | 127 +++++++++--------- 1 file changed, 65 insertions(+), 62 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte index dbcf482070..dac4c8244a 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte @@ -2,7 +2,6 @@ import { CardGrid } from '$lib/components'; import { Button, Form, InputText, InputEmail } from '$lib/elements/forms'; import { Container } from '$lib/layout'; - import { project } from '../../store'; import InputPassword from '$lib/elements/forms/inputPassword.svelte'; import { sdk } from '$lib/stores/sdk'; import { invalidate } from '$app/navigation'; @@ -10,24 +9,32 @@ import { addNotification } from '$lib/stores/notifications'; import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics'; import InputNumber from '$lib/elements/forms/inputNumber.svelte'; - import { base } from '$app/paths'; + import { resolve } from '$app/paths'; import deepEqual from 'deep-equal'; - import { onMount } from 'svelte'; import { currentPlan } from '$lib/stores/organization'; import { type SMTPSecure } from '@appwrite.io/console'; import InputSelect from '$lib/elements/forms/inputSelect.svelte'; import { getChangePlanUrl } from '$lib/stores/billing'; import { Link, Selector, Alert } from '@appwrite.io/pink-svelte'; + import type { PageProps } from './$types'; - let enabled = false; - let senderName: string; - let senderEmail: string; - let replyTo: string; - let host: string; - let port: number; - let username: string; - let password: string; - let secure: string; + const { data }: PageProps = $props(); + + const { project } = data; + + let enabled: boolean = $state(false); + + let replyTo: string = $state(null); + let senderName: string = $state(null); + let senderEmail: string = $state(null); + + let host: string = $state(null); + let port: number = $state(null); + + let username: string = $state(null); + let password: string = $state(null); + + let secure: string = $state(null); const options = [ { value: 'tls', label: 'TLS' }, @@ -35,33 +42,27 @@ { value: '', label: 'None' } ]; - onMount(() => { - enabled = $project.smtpEnabled ?? false; - senderName = $project.smtpSenderName; - senderEmail = $project.smtpSenderEmail; - replyTo = $project.smtpReplyTo; - host = $project.smtpHost; - port = $project.smtpPort; - username = $project.smtpUsername; - password = $project.smtpPassword; - secure = $project.smtpSecure === 'tls' ? 'tls' : $project.smtpSecure === 'ssl' ? 'ssl' : ''; + const isButtonDisabled = $derived.by(() => { + return deepEqual( + { enabled, senderName, senderEmail, replyTo, host, port, username, password, secure }, + { + enabled: project.smtpEnabled, + senderName: project.smtpSenderName, + senderEmail: project.smtpSenderEmail, + replyTo: project.smtpReplyTo, + host: project.smtpHost, + port: project.smtpPort, + username: project.smtpUsername, + password: project.smtpPassword, + secure: project.smtpSecure + } + ) }); async function updateSmtp() { try { - if (!enabled) { - enabled = false; - senderName = undefined; - senderEmail = undefined; - replyTo = undefined; - host = undefined; - port = undefined; - username = undefined; - password = undefined; - } - await sdk.forConsole.projects.updateSMTP({ - projectId: $project.$id, + projectId: project.$id, enabled, senderName: senderName || undefined, senderEmail: senderEmail || undefined, @@ -76,7 +77,7 @@ invalidate(Dependencies.PROJECT); addNotification({ type: 'success', - message: 'SMTP server has been ' + (enabled ? 'enabled.' : 'disabled.') + message: `SMTP server has been ${enabled ? 'enabled.' : 'disabled.'}` }); trackEvent(Submit.ProjectUpdateSMTP); } catch (error) { @@ -88,31 +89,30 @@ } } - $: isButtonDisabled = deepEqual( - { enabled, senderName, senderEmail, replyTo, host, port, username, password, secure }, - { - enabled: $project.smtpEnabled, - senderName: $project.smtpSenderName, - senderEmail: $project.smtpSenderEmail, - replyTo: $project.smtpReplyTo, - host: $project.smtpHost, - port: $project.smtpPort, - username: $project.smtpUsername, - password: $project.smtpPassword, - secure: $project.smtpSecure - } - ); + $effect(() => { + enabled = project.smtpEnabled ?? false; + senderName = project.smtpSenderName; + senderEmail = project.smtpSenderEmail; + replyTo = project.smtpReplyTo; + host = project.smtpHost; + port = project.smtpPort; + username = project.smtpUsername; + password = project.smtpPassword; + secure = project.smtpSecure === 'tls' ? 'tls' : project.smtpSecure === 'ssl' ? 'ssl' : ''; + }); - $: if (!enabled) { - senderName = undefined; - senderEmail = undefined; - replyTo = undefined; - host = undefined; - port = undefined; - username = undefined; - password = undefined; - secure = undefined; - } + $effect(() => { + if (!enabled) { + senderName = undefined; + senderEmail = undefined; + replyTo = undefined; + host = undefined; + port = undefined; + username = undefined; + password = undefined; + secure = undefined; + } + }); @@ -120,8 +120,11 @@ SMTP server You can customize the email service by providing your own SMTP server. View your email templates - here + here {#if !$currentPlan.customSmtp} diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/updateMockNumbers.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/updateMockNumbers.svelte index 8ec2d2e6d7..88c2e91fdf 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/security/updateMockNumbers.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/security/updateMockNumbers.svelte @@ -1,10 +1,8 @@ @@ -138,7 +148,7 @@ fullWidth external={isSelfHosted} href={isCloud - ? getChangePlanUrl($project.teamId) + ? getChangePlanUrl(project.teamId) : 'https://cloud.appwrite.io/register'} on:click={() => { trackEvent(Click.CloudSignupClick, { diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionLength.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionLength.svelte index 807adba3fe..de62ad3845 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionLength.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionLength.svelte @@ -7,11 +7,15 @@ import { createTimeUnitPair } from '$lib/helpers/unit'; import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; - import { project as projectStore } from '../../store'; import { Layout } from '@appwrite.io/pink-svelte'; - import { page } from '$app/state'; + import type { Models } from '@appwrite.io/console'; + + let { + project + }: { + project: Models.Project; + } = $props(); - const project = $derived($projectStore ?? page.data?.project); const { value, unit, baseValue, units } = $derived(createTimeUnitPair(project?.authDuration)); const options = $derived(units.map((v) => ({ label: v.name, value: v.name }))); diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionsLimit.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionsLimit.svelte index eb0bb97c89..1c6f0bb904 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionsLimit.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionsLimit.svelte @@ -7,14 +7,20 @@ import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; import { Typography } from '@appwrite.io/pink-svelte'; - import { project } from '../../store'; + import type { Models } from '@appwrite.io/console'; - let maxSessions = $project?.authSessionsLimit; + let { + project + }: { + project: Models.Project; + } = $props(); + + let maxSessions = $state(project?.authSessionsLimit); async function updateSessionsLimit() { try { await sdk.forConsole.projects.updateAuthSessionsLimit({ - projectId: $project.$id, + projectId: project.$id, limit: maxSessions }); await invalidate(Dependencies.PROJECT); @@ -49,7 +55,7 @@ bind:value={maxSessions} /> - + diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/updateUsersLimit.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/updateUsersLimit.svelte index a319a1a08d..5296362cb2 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/security/updateUsersLimit.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/security/updateUsersLimit.svelte @@ -7,29 +7,32 @@ import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; import { Layout, Selector, Input, Badge } from '@appwrite.io/pink-svelte'; - import { project } from '../../store'; import { tick } from 'svelte'; + import type { Models } from '@appwrite.io/console'; - let value = $project?.authLimit !== 0 ? 'limited' : 'unlimited'; + let { + project + }: { + project: Models.Project; + } = $props(); - $: isLimited = value === 'limited'; - let newLimit = isLimited ? $project?.authLimit : 100; + let maxUsersInputField: HTMLInputElement | null = $state(null); - $: btnDisabled = (function isBtnDisabled() { - if ( - (!isLimited && $project?.authLimit === 0) || - (isLimited && $project?.authLimit === newLimit) - ) { - return true; - } + let value = $state(project?.authLimit !== 0 ? 'limited' : 'unlimited'); + let newLimit = $state(project?.authLimit !== 0 ? project?.authLimit : 100); - return false; - })(); + const isLimited = $derived(value === 'limited'); + const btnDisabled = $derived.by(() => { + return ( + (!isLimited && project?.authLimit === 0) || + (isLimited && project?.authLimit === newLimit) + ); + }); async function updateLimit() { try { await sdk.forConsole.projects.updateAuthLimit({ - projectId: $project?.$id, + projectId: project?.$id, limit: isLimited ? newLimit : 0 }); await invalidate(Dependencies.PROJECT); @@ -47,13 +50,13 @@ } } - let maxUsersInputField: HTMLInputElement | null = null; - - $: if (isLimited && maxUsersInputField) { - tick().then(() => { - maxUsersInputField.focus(); - }); - } + $effect(() => { + if (isLimited && maxUsersInputField) { + tick().then(() => { + maxUsersInputField.focus(); + }); + } + }); diff --git a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte index 28a3896a12..5850c6ce73 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/settings/+page.svelte @@ -1,5 +1,4 @@ -{#if $authMethods && $project} +{#if $authMethods && project} @@ -73,7 +75,7 @@ OAuth2 Providers
    - {#each $project.oAuthProviders + {#each project.oAuthProviders .filter((p) => p.name !== 'Mock') .sort( (a, b) => (a.enabled === b.enabled ? 0 : a.enabled ? -1 : 1) ) as provider} {@const oAuthProvider = oAuthProviders[provider.key]} @@ -116,9 +118,9 @@ {/if} {#if selectedProvider && showProvider} - {@const oAuthProvider = oAuthProviders[selectedProvider.key]} - { diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/+page.svelte index da7178906c..446975e3e9 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/+page.svelte @@ -58,11 +58,12 @@ import { isCloud } from '$lib/system'; import { Accordion, Alert, Badge, Layout, Link, Typography } from '@appwrite.io/pink-svelte'; import { page } from '$app/state'; + import type { PageProps } from './$types'; - export let data; + let { data }: PageProps = $props(); - let templateType = null; - let isTemplateLoading = false; + let templateType = $state(null); + let isTemplateLoading = $state(false); let openStates = Object.fromEntries(templates.map(({ key }) => [key, false])); loadTemplateFor(EmailTemplateType.Verification); @@ -128,9 +129,11 @@ on:toggle={(event) => event.detail && toggleAccordion(section.key)}> {section.description} - + {@const SectionComponent = section.component} + {/each} @@ -144,7 +147,8 @@ + {#if isCloud && $currentPlan.emailBranding} - + {/if} diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/email2FATemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/email2FATemplate.svelte index 536a1aa282..932bd7e2c2 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/email2FATemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/email2FATemplate.svelte @@ -2,19 +2,19 @@ import EmailTemplate from './emailTemplate.svelte'; import LocaleOptions from './localeOptions.svelte'; import { loadEmailTemplate } from './+page.svelte'; - import { page } from '$app/state'; import { baseEmailTemplate, emailTemplate } from './store'; import { addNotification } from '$lib/stores/notifications'; import { Id } from '$lib/components'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Layout, Card } from '@appwrite.io/pink-svelte'; - import { EmailTemplateLocale, EmailTemplateType } from '@appwrite.io/console'; + import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console'; export let loading = false; + export let project: Models.Project; + export let localeCodes: Models.LocaleCode[]; let isUpdating = false; let locale = EmailTemplateLocale.En; - const projectId = page.params.project; async function onLocaleChange() { const timeout = setTimeout(() => { @@ -22,7 +22,7 @@ }, 1000); try { const template = await loadEmailTemplate( - projectId, + project.$id, EmailTemplateType.Mfachallenge, locale ); @@ -44,8 +44,8 @@ - - + + {'{{user}}'} {'{{project}}'} {'{{otp}}'} diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailInviteTemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailInviteTemplate.svelte index f723da9cd1..38ec9c54cd 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailInviteTemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailInviteTemplate.svelte @@ -2,19 +2,19 @@ import EmailTemplate from './emailTemplate.svelte'; import LocaleOptions from './localeOptions.svelte'; import { loadEmailTemplate } from './+page.svelte'; - import { page } from '$app/state'; import { baseEmailTemplate, emailTemplate } from './store'; import { addNotification } from '$lib/stores/notifications'; import { Id } from '$lib/components'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Layout, Card } from '@appwrite.io/pink-svelte'; - import { EmailTemplateLocale, EmailTemplateType } from '@appwrite.io/console'; + import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console'; export let loading = false; + export let project: Models.Project; + export let localeCodes: Models.LocaleCode[]; let isUpdating = false; let locale = EmailTemplateLocale.En; - const projectId = page.params.project; async function onLocaleChange() { const timeout = setTimeout(() => { @@ -22,7 +22,7 @@ }, 1000); try { const template = await loadEmailTemplate( - projectId, + project.$id, EmailTemplateType.Invitation, locale ); @@ -44,8 +44,9 @@ - - + + + {'{{team}}'} {'{{user}}'} {'{{project}}'} diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailMagicUrlTemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailMagicUrlTemplate.svelte index 1e98218db0..ac962841ce 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailMagicUrlTemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailMagicUrlTemplate.svelte @@ -3,18 +3,18 @@ import EmailTemplate from './emailTemplate.svelte'; import LocaleOptions from './localeOptions.svelte'; import { baseEmailTemplate, emailTemplate } from './store'; - import { page } from '$app/state'; import { addNotification } from '$lib/stores/notifications'; import { Id } from '$lib/components'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Layout, Card } from '@appwrite.io/pink-svelte'; - import { EmailTemplateLocale, EmailTemplateType } from '@appwrite.io/console'; + import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console'; export let loading = false; + export let project: Models.Project; + export let localeCodes: Models.LocaleCode[]; let isUpdating = false; let locale = EmailTemplateLocale.En; - const projectId = page.params.project; async function onLocaleChange() { const timeout = setTimeout(() => { @@ -22,7 +22,7 @@ }, 1000); try { const template = await loadEmailTemplate( - projectId, + project.$id, EmailTemplateType.Magicsession, locale ); @@ -45,8 +45,8 @@ - - + + {'{{user}}'} {'{{project}}'} {'{{redirect}}'} diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailOtpSessionTemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailOtpSessionTemplate.svelte index 07e6b1bc63..1ec2f24bd2 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailOtpSessionTemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailOtpSessionTemplate.svelte @@ -3,18 +3,18 @@ import EmailTemplate from './emailTemplate.svelte'; import LocaleOptions from './localeOptions.svelte'; import { baseEmailTemplate, emailTemplate } from './store'; - import { page } from '$app/state'; import { addNotification } from '$lib/stores/notifications'; import { Id } from '$lib/components'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Layout, Card } from '@appwrite.io/pink-svelte'; - import { EmailTemplateLocale, EmailTemplateType } from '@appwrite.io/console'; + import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console'; export let loading = false; + export let project: Models.Project; + export let localeCodes: Models.LocaleCode[]; let isUpdating = false; let locale = EmailTemplateLocale.En; - const projectId = page.params.project; async function onLocaleChange() { const timeout = setTimeout(() => { @@ -22,7 +22,7 @@ }, 1000); try { const template = await loadEmailTemplate( - projectId, + project.$id, EmailTemplateType.Otpsession, locale ); @@ -45,8 +45,8 @@ - - + + {'{{user}}'} {'{{project}}'} {'{{otp}}'} diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailRecoveryTemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailRecoveryTemplate.svelte index 23597970ff..b49a333e17 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailRecoveryTemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailRecoveryTemplate.svelte @@ -3,25 +3,29 @@ import LocaleOptions from './localeOptions.svelte'; import { baseEmailTemplate, emailTemplate } from './store'; import { loadEmailTemplate } from './+page.svelte'; - import { page } from '$app/state'; import { addNotification } from '$lib/stores/notifications'; import { Id } from '$lib/components'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Layout, Card } from '@appwrite.io/pink-svelte'; - import { EmailTemplateLocale, EmailTemplateType } from '@appwrite.io/console'; + import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console'; export let loading = false; + export let project: Models.Project; + export let localeCodes: Models.LocaleCode[]; let isUpdating = false; let locale = EmailTemplateLocale.En; - const projectId = page.params.project; async function onLocaleChange() { const timeout = setTimeout(() => { isUpdating = true; }, 1000); try { - const template = await loadEmailTemplate(projectId, EmailTemplateType.Recovery, locale); + const template = await loadEmailTemplate( + project.$id, + EmailTemplateType.Recovery, + locale + ); emailTemplate.set(template); $baseEmailTemplate = { ...$emailTemplate }; trackEvent(Submit.EmailChangeLocale, { locale, type: EmailTemplateType.Recovery }); @@ -40,8 +44,8 @@ - - + + {'{{user}}'} {'{{project}}'} {'{{redirect}}'} diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailSessionAlertTemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailSessionAlertTemplate.svelte index ebe9075501..ecd1cbb282 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailSessionAlertTemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailSessionAlertTemplate.svelte @@ -2,19 +2,19 @@ import EmailTemplate from './emailTemplate.svelte'; import LocaleOptions from './localeOptions.svelte'; import { loadEmailTemplate } from './+page.svelte'; - import { page } from '$app/state'; import { baseEmailTemplate, emailTemplate } from './store'; import { addNotification } from '$lib/stores/notifications'; import { Id } from '$lib/components'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Layout, Card } from '@appwrite.io/pink-svelte'; - import { EmailTemplateLocale, EmailTemplateType } from '@appwrite.io/console'; + import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console'; export let loading = false; + export let project: Models.Project; + export let localeCodes: Models.LocaleCode[]; let isUpdating = false; let locale = EmailTemplateLocale.En; - const projectId = page.params.project; async function onLocaleChange() { const timeout = setTimeout(() => { @@ -22,7 +22,7 @@ }, 1000); try { const template = await loadEmailTemplate( - projectId, + project.$id, EmailTemplateType.Sessionalert, locale ); @@ -44,8 +44,8 @@ - - + + {'{{user}}'} {'{{project}}'} {'{{device}}'} diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailSignature.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailSignature.svelte index 59030bb715..2afc6261a3 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailSignature.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailSignature.svelte @@ -8,7 +8,13 @@ import EmailLight from './email-footer-light.png'; import EmailMobileDark from './email-footer-mobile-dark.png'; import EmailMobileLight from './email-footer-mobile-light.png'; - import { project } from '$routes/(console)/project-[region]-[project]/store'; + import type { Models } from '@appwrite.io/console'; + + const { + project + }: { + project: Models.Project; + } = $props(); @@ -55,7 +61,7 @@ class="u-margin-block-start-32" secondary fullWidth - href={getChangePlanUrl($project.teamId)} + href={getChangePlanUrl(project.teamId)} on:click={() => { trackEvent(Click.OrganizationClickUpgrade, { from: 'button', diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailTemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailTemplate.svelte index 0e5757047e..cd5bb6958e 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailTemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailTemplate.svelte @@ -2,21 +2,37 @@ import { Button, Form, InputEmail, InputText, InputTextarea } from '$lib/elements/forms'; import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; - import { project } from '../../store'; import ResetEmail from './resetEmail.svelte'; import { baseEmailTemplate, emailTemplate } from './store'; import deepEqual from 'deep-equal'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import type { EmailTemplateLocale, EmailTemplateType } from '@appwrite.io/console'; + import { + type EmailTemplateLocale, + type EmailTemplateType, + type Models + } from '@appwrite.io/console'; import { Icon, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte'; import { IconInfo } from '@appwrite.io/pink-icons-svelte'; import TemplateSkeleton from './templateSkeleton.svelte'; + import type { Snippet } from 'svelte'; - export let loading = false; - export let isUpdating = false; + let { + loading = false, + isUpdating = false, + project, + children = null + }: { + loading: boolean; + isUpdating: boolean; + project: Models.Project; + children: Snippet; + } = $props(); - let openResetModal = false; - let eventType = Submit.EmailUpdateInviteTemplate; + let openResetModal = $state(false); + let eventType = $state(Submit.EmailUpdateInviteTemplate); + + const isSmtpEnabled = $derived(project?.smtpEnabled); + const isButtonDisabled = $derived(deepEqual($emailTemplate, $baseEmailTemplate)); async function saveEmailTemplate() { if (!$emailTemplate.locale) { @@ -51,7 +67,7 @@ } // TODO: fix TemplateType and TemplateLocale typing once SDK is updated await sdk.forConsole.projects.updateEmailTemplate({ - projectId: $project.$id, + projectId: project.$id, type: $emailTemplate.type as EmailTemplateType, locale: $emailTemplate.locale as EmailTemplateLocale, subject: $emailTemplate.subject || undefined, @@ -80,9 +96,6 @@ }); } } - - $: isSmtpEnabled = $project?.smtpEnabled; - $: isButtonDisabled = deepEqual($emailTemplate, $baseEmailTemplate);
    @@ -111,7 +124,7 @@ label="Reply to" placeholder="noreply@appwrite.io" /> - {#if $$slots.default} + {#if children}

    Click to copy variables for the fields below. Learn more here.

    + - + {@render children()} {/if} @@ -154,4 +168,4 @@
    - + diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/emailVerificationTemplate.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/emailVerificationTemplate.svelte index 54f257fe4d..4f5f9d9532 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/emailVerificationTemplate.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/emailVerificationTemplate.svelte @@ -3,18 +3,18 @@ import LocaleOptions from './localeOptions.svelte'; import { loadEmailTemplate } from './+page.svelte'; import { baseEmailTemplate, emailTemplate } from './store'; - import { page } from '$app/state'; import { Id } from '$lib/components'; import { addNotification } from '$lib/stores/notifications'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Layout, Card } from '@appwrite.io/pink-svelte'; - import { EmailTemplateLocale, EmailTemplateType } from '@appwrite.io/console'; + import { EmailTemplateLocale, EmailTemplateType, type Models } from '@appwrite.io/console'; export let loading = false; + export let project: Models.Project; + export let localeCodes: Models.LocaleCode[]; let isUpdating = false; let locale = EmailTemplateLocale.En; - const projectId = page.params.project; async function onLocaleChange() { const timeout = setTimeout(() => { @@ -22,7 +22,7 @@ }, 1000); try { const template = await loadEmailTemplate( - projectId, + project.$id, EmailTemplateType.Verification, locale ); @@ -44,8 +44,8 @@ - - + + {'{{user}}'} {'{{project}}'} {'{{redirect}}'} diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/localeOptions.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/localeOptions.svelte index 4c4c334caf..08613e1591 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/localeOptions.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/localeOptions.svelte @@ -1,10 +1,11 @@ @@ -20,5 +23,5 @@ - + diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/dangerZone.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/dangerZone.svelte index 30ecb4d7a5..8a92d9fe41 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/dangerZone.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/dangerZone.svelte @@ -1,24 +1,29 @@ @@ -52,7 +57,7 @@ ? [$user.email, $user.phone].join(',') : $user.email || $user.phone}

    -

    Last activity: {accessedAt ? toLocaleDate(accessedAt) : 'never'}

    +

    Last activity: {$user.accessedAt ? toLocaleDate($user.accessedAt) : 'never'}

    @@ -61,4 +66,4 @@
    - + diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/deleteUser.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/deleteUser.svelte index 043cd64ac5..8f9ef79b0e 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/deleteUser.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/deleteUser.svelte @@ -1,29 +1,34 @@ diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/dangerZone.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/dangerZone.svelte index 8a92d9fe41..1518c2e57b 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/dangerZone.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/dangerZone.svelte @@ -7,10 +7,12 @@ export const promptDeleteUser = (id: string) => { showDelete.set(true); - goto(resolveRoute('/(console)/project-[region]-[project]/auth/user-[user]', { - ...page.params, - user: id - })); + goto( + resolveRoute('/(console)/project-[region]-[project]/auth/user-[user]', { + ...page.params, + user: id + }) + ); }; diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/deleteUser.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/deleteUser.svelte index 8f9ef79b0e..ffd2d48547 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/deleteUser.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/deleteUser.svelte @@ -10,8 +10,8 @@ import { Submit, trackEvent, trackError } from '$lib/actions/analytics'; export let showDelete = false; - export let user: Models.User; export let project: Models.Project; + export let user: Models.User>; let error: string; const deleteUser = async () => { @@ -25,10 +25,11 @@ message: `${user.name ? user.name : 'User'} has been deleted` }); trackEvent(Submit.UserDelete); - await goto(resolveRoute('/(console)/project-[region]-[project]/auth', { - ...page.params - })); - + await goto( + resolveRoute('/(console)/project-[region]-[project]/auth', { + ...page.params + }) + ); } catch (e) { error = e.message; trackError(e, Submit.UserDelete); diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/updatePrefs.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/updatePrefs.svelte index 0e9940292e..4c0c0a5869 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/updatePrefs.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/updatePrefs.svelte @@ -97,9 +97,11 @@
    + {:else} { $showCreateBackup = true; @@ -227,7 +229,7 @@
    {:else}
    - +
    {/if}
@@ -238,7 +240,11 @@ onSubmit={createPolicies} bind:show={$showCreatePolicy} bind:error={policyCreateError}> - + diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/containerHeader.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/containerHeader.svelte index 5e2cae01d6..11e2c3fa85 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/containerHeader.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/containerHeader.svelte @@ -4,11 +4,12 @@ import { Button } from '$lib/elements/forms'; import { getChangePlanUrl } from '$lib/stores/billing'; import { IconInfo, IconPlus } from '@appwrite.io/pink-icons-svelte'; - import { project } from '$routes/(console)/project-[region]-[project]/store'; import { Badge, Icon, Layout, Tag, Typography } from '@appwrite.io/pink-svelte'; + import type { Models } from '@appwrite.io/console'; export let isFlex = true; export let title: string; + export let project: Models.Project; export let buttonText: string = null; export let policiesCreated: number = 0; @@ -22,8 +23,8 @@
{ showDropdown = !showDropdown; - goto(getChangePlanUrl($project.teamId)); + goto(getChangePlanUrl(project.teamId)); }}>Upgrade your plan to add customized backup policies. diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/createPolicy.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/createPolicy.svelte index 8fe6baa085..b6950baf4a 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/createPolicy.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/createPolicy.svelte @@ -10,7 +10,7 @@ InputTime, Label } from '$lib/elements/forms'; - import { ID } from '@appwrite.io/console'; + import { ID, type Models } from '@appwrite.io/console'; import { capitalize } from '$lib/helpers/string'; import { backupRetainingOptions, customRetainingOptions } from '../store'; import { presetPolicies, showCreatePolicy } from './store'; @@ -26,12 +26,12 @@ import { isSmallViewport } from '$lib/stores/viewport'; import { goto } from '$app/navigation'; import { getChangePlanUrl } from '$lib/stores/billing'; - import { project } from '$routes/(console)/project-[region]-[project]/store'; export let isShowing: boolean; export let isFromBackupsTab: boolean = false; export let title: string | undefined = undefined; export let subtitle: string | undefined = undefined; + export let project: Models.Project; export let totalPolicies: UserBackupPolicy[] = []; @@ -214,7 +214,7 @@ on:click={() => { isShowing = false; $showCreatePolicy = false; - goto(getChangePlanUrl($project.teamId)); + goto(getChangePlanUrl(project.teamId)); }}>Upgrade your plan to add customized backup policies. @@ -230,7 +230,7 @@ { isShowing = false; - goto(getChangePlanUrl($project.teamId)); + goto(getChangePlanUrl(project.teamId)); }}>Upgrade your plan to add customized backup policies. diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/locked.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/locked.svelte index bf12af386c..c456184aac 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/locked.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/locked.svelte @@ -11,15 +11,23 @@ import LockedBackupsDarkTablet from '$lib/images/backups/empty/backups-tablet-dark.png'; import LockedBackupsLightTablet from '$lib/images/backups/empty/backups-tablet-light.png'; + import type { Models } from '@appwrite.io/console'; + + let { + project + }: { + project: Models.Project; + } = $props();
- +
+
@@ -81,7 +81,7 @@ @@ -91,7 +91,7 @@ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte index e56f274dce..8bf9dee80a 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.svelte @@ -42,7 +42,6 @@ import CreateColumn from './createColumn.svelte'; import { CreateColumnPanel } from '$lib/commandCenter/panels'; import { showCreateEntity } from '../store'; - import { project } from '../../../store'; import { page } from '$app/state'; import { canWriteTables } from '$lib/stores/roles'; import { @@ -298,8 +297,13 @@ columnCreationHandler = handler; + const project = { + id: page.params.project, + region: page.params.region + }; + columns = await generateFields( - $project, + project, page.params.database, page.params.table, databaseType diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/string.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/string.svelte index c3b3ed8e7f..67ccf95f1d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/string.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/string.svelte @@ -46,7 +46,6 @@ import { ActionMenu, Selector } from '@appwrite.io/pink-svelte'; import RequiredArrayCheckboxes from './requiredArrayCheckboxes.svelte'; import { InputNumber, InputText, InputTextarea } from '$lib/elements/forms'; - import { project } from '$routes/(console)/project-[region]-[project]/store'; import { Popover, Layout, Tag, Typography, Link } from '@appwrite.io/pink-svelte'; export let data: Partial = { @@ -60,6 +59,8 @@ export let disabled = false; export let autoIncreaseSize = false; + const organizationId = page.data?.organization?.$id ?? page.data?.project?.$id; + let savedDefault = data.default; function handleDefaultState(hideDefault: boolean) { @@ -161,7 +162,7 @@ - Available on Pro plan. Upgrade to enable encrypted columns. From 7a3af231169ae08b2c4d85a2b1e75a774833bc11 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 28 Jan 2026 14:59:13 +0530 Subject: [PATCH 71/79] fix: more fixes on functions. --- .../template-[template]/+page.svelte | 10 +++++++-- .../template-[template]/configuration.svelte | 6 +++--- .../function-[function]/+layout.svelte | 21 +++++++++---------- .../domains/add-domain/+page.svelte | 16 ++++++++------ .../executions/+page.svelte | 5 ++--- .../settings/executeFunction.svelte | 19 ++++++++++------- 6 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte index 98c8bf7ea4..421a217d8f 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte @@ -242,7 +242,10 @@ repositoryId={selectedRepository} /> {#if data.template.variables?.length} - + {/if} {:else} @@ -328,7 +331,10 @@ {/if} {:else if data.template.variables?.length} - + {/if} {/if} diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/configuration.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/configuration.svelte index cd21e5838d..94fdf57c8e 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/configuration.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/configuration.svelte @@ -21,8 +21,8 @@ import type { Component } from 'svelte'; import { getApiEndpoint } from '$lib/stores/sdk'; import { page } from '$app/state'; - import { project } from '$routes/(console)/project-[region]-[project]/store'; + export let project: Models.Project; export let variables: Partial[] = []; export let templateVariables: Models.TemplateVariable[] = []; @@ -47,8 +47,8 @@ variable.value = page.params.project; variable.placeholder = page.params.project; } else if (variable.value === '{projectName}') { - variable.value = $project.name; - variable.placeholder = $project.name; + variable.value = project.name; + variable.placeholder = project.name; } else return variable; }); variables = [...variables]; diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/+layout.svelte index aca7e790ee..24aadfb532 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/+layout.svelte @@ -6,7 +6,6 @@ import { invalidate, goto } from '$app/navigation'; import { registerCommands } from '$lib/commandCenter'; import { func, showCreateDeployment } from './store'; - import { project } from '../../store'; import type { Models } from '@appwrite.io/console'; import { base } from '$app/paths'; import { canWriteFunctions } from '$lib/stores/roles'; @@ -52,7 +51,7 @@ async callback() { if (!page.url.pathname.endsWith($func.$id)) { await goto( - `${base}/project-${$project.region}-${$project.$id}/functions/function-${$func.$id}` + `${base}/project-${page.params.region}-${page.params.project}/functions/function-${$func.$id}` ); } showCreateDeployment.set(true); @@ -66,7 +65,7 @@ label: 'Permissions', async callback() { await goto( - `${base}/project-${$project.region}-${$project.$id}/functions/function-${$func.$id}/settings#permissions` + `${base}/project-${page.params.region}-${page.params.project}/functions/function-${$func.$id}/settings#permissions` ); scrollBy({ top: -100 }); }, @@ -78,7 +77,7 @@ label: 'Events', async callback() { await goto( - `${base}/project-${$project.region}-${$project.$id}/functions/function-${$func.$id}/settings#events` + `${base}/project-${page.params.region}-${page.params.project}/functions/function-${$func.$id}/settings#events` ); scrollBy({ top: -100 }); }, @@ -90,7 +89,7 @@ label: 'Variables', async callback() { await goto( - `${base}/project-${$project.region}-${$project.$id}/functions/function-${$func.$id}/settings#variables` + `${base}/project-${page.params.region}-${page.params.project}/functions/function-${$func.$id}/settings#variables` ); }, icon: IconList, @@ -101,7 +100,7 @@ label: 'Timeout', callback() { goto( - `${base}/project-${$project.region}-${$project.$id}/functions/function-${$func.$id}/settings#timeout` + `${base}/project-${page.params.region}-${page.params.project}/functions/function-${$func.$id}/settings#timeout` ); }, icon: IconXCircle, @@ -112,7 +111,7 @@ label: 'Schedule', async callback() { await goto( - `${base}/project-${$project.region}-${$project.$id}/functions/function-${$func.$id}/settings#schedule` + `${base}/project-${page.params.region}-${page.params.project}/functions/function-${$func.$id}/settings#schedule` ); scrollBy({ top: -100 }); }, @@ -124,7 +123,7 @@ label: 'Go to deployments', callback() { goto( - `${base}/project-${$project.region}-${$project.$id}/functions/function-${$func.$id}` + `${base}/project-${page.params.region}-${page.params.project}/functions/function-${$func.$id}` ); }, keys: ['g', 'd'], @@ -136,7 +135,7 @@ label: 'Go to usage', callback() { goto( - `${base}/project-${$project.region}-${$project.$id}/functions/function-${$func.$id}/usage` + `${base}/project-${page.params.region}-${page.params.project}/functions/function-${$func.$id}/usage` ); }, keys: ['g', 'u'], @@ -148,7 +147,7 @@ label: 'Go to executions', callback() { goto( - `${base}/project-${$project.region}-${$project.$id}/functions/function-${$func.$id}/executions` + `${base}/project-${page.params.region}-${page.params.project}/functions/function-${$func.$id}/executions` ); }, keys: ['g', 'e'], @@ -160,7 +159,7 @@ label: 'Go to settings', callback() { goto( - `${base}/project-${$project.region}-${$project.$id}/functions/function-${$func.$id}/settings` + `${base}/project-${page.params.region}-${page.params.project}/functions/function-${$func.$id}/settings` ); }, keys: ['g', 's'], diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/add-domain/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/add-domain/+page.svelte index 6a9d3183aa..add73ffdde 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/add-domain/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/add-domain/+page.svelte @@ -1,5 +1,4 @@ @@ -39,10 +48,6 @@ - + From de14d181379bb04897710da2e237cc7f576a3e6f Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 28 Jan 2026 15:01:47 +0530 Subject: [PATCH 72/79] fix: more store fixes. --- .../function-[function]/settings/updateResourceLimits.svelte | 1 + .../settings/usage/[[invoice]]/+page.svelte | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/settings/updateResourceLimits.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/settings/updateResourceLimits.svelte index 021902a711..fce7e945d7 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/settings/updateResourceLimits.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/settings/updateResourceLimits.svelte @@ -17,6 +17,7 @@ export let func: Models.Function; export let specs: Models.SpecificationList; + let specification = func.specification; let originalSpecification = func.specification; $: originalSpecification = func.specification; diff --git a/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte index 962901f18f..830c933754 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte @@ -61,7 +61,7 @@ {#if planHasGroup(currentBillingPlan, BillingPlanGroup.Starter)} - {/if} @@ -86,7 +86,7 @@

If you exceed the limits of the {currentBillingPlan.name} plan, services for your projects may be disrupted. - Upgrade for greater capacity.

From ad909fdf026dd151d19d3950a951bc0cdc07eba0 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 28 Jan 2026 15:24:09 +0530 Subject: [PATCH 73/79] fix: state. --- .../project-[region]-[project]/auth/templates/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/project-[region]-[project]/auth/templates/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/templates/+page.svelte index da54773df1..72002ae96b 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/templates/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/templates/+page.svelte @@ -64,7 +64,7 @@ let templateType = $state(null); let isTemplateLoading = $state(false); - let openStates = Object.fromEntries(templates.map(({ key }) => [key, false])); + let openStates = $state(Object.fromEntries(templates.map(({ key }) => [key, false]))); loadTemplateFor(EmailTemplateType.Verification); From 4b33c8527c01f62cb5d16023c49d46fc0cf8c41a Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 28 Jan 2026 16:01:52 +0530 Subject: [PATCH 74/79] fix: deep equals check. update: address comments. --- .../billing/alerts/projectsLimit.svelte | 2 +- src/lib/stores/billing.ts | 9 ++++ .../organization-[organization]/+page.svelte | 2 +- .../settings/smtp/+page.svelte | 47 ++++++++++++------- .../settings/usage/[[invoice]]/+page.svelte | 4 +- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/src/lib/components/billing/alerts/projectsLimit.svelte b/src/lib/components/billing/alerts/projectsLimit.svelte index 6ffe1a22c7..493e445107 100644 --- a/src/lib/components/billing/alerts/projectsLimit.svelte +++ b/src/lib/components/billing/alerts/projectsLimit.svelte @@ -22,7 +22,7 @@ -{#if $currentPlan && $currentPlan.projects > 0 && !hideBillingHeaderRoutes.includes(page.url.pathname)} +{#if organizationId && $currentPlan && $currentPlan.projects > 0 && !hideBillingHeaderRoutes.includes(page.url.pathname)} diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 0b7d01a882..baf663114b 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -141,6 +141,11 @@ export function billingIdToPlan(billingId: string): Models.BillingPlan | null { export function getNextTierBillingPlan(billingPlanId: string): Models.BillingPlan { const currentPlanData = billingIdToPlan(billingPlanId); + if (!currentPlanData) { + /* should never happen but safety! */ + return getBasePlanFromGroup(BillingPlanGroup.Pro); + } + const currentOrder = currentPlanData.order; const plans = get(plansInfo); @@ -155,6 +160,10 @@ export function getNextTierBillingPlan(billingPlanId: string): Models.BillingPla export function getPreviousTierBillingPlan(billingPlanId: string): Models.BillingPlan { const currentPlanData = billingIdToPlan(billingPlanId); + if (!currentPlanData) { + /* should never happen but safety! */ + return getBasePlanFromGroup(BillingPlanGroup.Starter); + } const currentOrder = currentPlanData.order; const plans = get(plansInfo); diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte index 3fd94eb209..947cdedcaa 100644 --- a/src/routes/(console)/organization-[organization]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/+page.svelte @@ -219,7 +219,7 @@ {/if} - {#if isCloud && data.currentPlan.projects !== 0 && projectsToArchive.length === 0 && !freePlanAlertDismissed} + {#if isCloud && data.currentPlan?.projects !== 0 && projectsToArchive.length === 0 && !freePlanAlertDismissed} Your Free plan includes up to 2 projects and limited resources. Upgrade to unlock diff --git a/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte index a3ff41e3ac..752c01aac2 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/smtp/+page.svelte @@ -17,6 +17,7 @@ import { getChangePlanUrl } from '$lib/stores/billing'; import { Link, Selector, Alert } from '@appwrite.io/pink-svelte'; import type { PageProps } from './$types'; + import { isCloud } from '$lib/system'; const { data }: PageProps = $props(); @@ -24,17 +25,17 @@ let enabled: boolean = $state(false); - let replyTo: string = $state(null); - let senderName: string = $state(null); - let senderEmail: string = $state(null); + let replyTo: string = $state(''); + let senderName: string = $state(''); + let senderEmail: string = $state(''); - let host: string = $state(null); + let host: string = $state(''); let port: number = $state(null); - let username: string = $state(null); - let password: string = $state(null); + let username: string = $state(''); + let password: string = $state(''); - let secure: string = $state(null); + let secure: string = $state(''); const options = [ { value: 'tls', label: 'TLS' }, @@ -44,7 +45,17 @@ const isButtonDisabled = $derived.by(() => { return deepEqual( - { enabled, senderName, senderEmail, replyTo, host, port, username, password, secure }, + { + enabled, + senderName, + senderEmail, + replyTo, + host, + port: port ?? '', + username, + password, + secure + }, { enabled: project.smtpEnabled, senderName: project.smtpSenderName, @@ -103,14 +114,14 @@ $effect(() => { if (!enabled) { - senderName = undefined; - senderEmail = undefined; - replyTo = undefined; - host = undefined; - port = undefined; - username = undefined; - password = undefined; - secure = undefined; + senderName = ''; + senderEmail = ''; + replyTo = ''; + host = ''; + port = null; + username = ''; + password = ''; + secure = ''; } }); @@ -126,7 +137,7 @@ region: project.region })}>here - {#if !$currentPlan.customSmtp} + {#if isCloud && !$currentPlan.customSmtp} @@ -199,7 +210,7 @@ {/if} - diff --git a/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte index 830c933754..521d08acda 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte @@ -51,8 +51,8 @@ } ]; - const currentPlanId = data?.currentInvoice?.plan ?? $organization?.billingPlanId; - const currentBillingPlan = billingIdToPlan(currentPlanId); + $: currentPlanId = data?.currentInvoice?.plan ?? $organization?.billingPlanId; + $: currentBillingPlan = billingIdToPlan(currentPlanId); From 60e6dee3c0026fd4a94dbf76a6d5af65b9fd8a78 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 28 Jan 2026 17:16:18 +0530 Subject: [PATCH 75/79] fix: disabled state missing on platforms create. --- .../overview/platforms/action.svelte | 96 +++++++++++-------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte b/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte index 0f3c96b173..b20a72ab18 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte @@ -1,7 +1,7 @@ - {#if $canWritePlatforms} - - {#if $canWritePlatforms} - - {/if} -
- - addPlatform(Platform.Web)} - leadingIcon={IconCode}> - Web - - addPlatform(Platform.Flutter)} - leadingIcon={IconFlutter}> - Flutter - - addPlatform(Platform.Android)} - leadingIcon={IconAndroid}> - Android - - addPlatform(Platform.Apple)} - leadingIcon={IconApple}> - Apple - - addPlatform(Platform.ReactNative)} - leadingIcon={IconReact}> - React Native - - -
-
+ {#if isCloud && $currentPlan?.platforms >= page.data.platforms.total} + +
+ +
+ + + You have reached the maximum number of platforms for your plan in a project. + +
+ {:else} + + {#if $canWritePlatforms} + + {/if} +
+ + addPlatform(Platform.Web)} + leadingIcon={IconCode}> + Web + + addPlatform(Platform.Flutter)} + leadingIcon={IconFlutter}> + Flutter + + addPlatform(Platform.Android)} + leadingIcon={IconAndroid}> + Android + + addPlatform(Platform.Apple)} + leadingIcon={IconApple}> + Apple + + addPlatform(Platform.ReactNative)} + leadingIcon={IconReact}> + React Native + + +
+
+ {/if} {/if} From 0b3b55622982fd71ed74cacad6030564896181d1 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 28 Jan 2026 17:28:19 +0530 Subject: [PATCH 76/79] fix: disabled state missing on functions create. --- .../functions/+page.svelte | 97 ++++++++++++------- 1 file changed, 61 insertions(+), 36 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/functions/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/+page.svelte index 95f86fe6de..b458dcd6e5 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/+page.svelte @@ -1,5 +1,4 @@ - + +
+ +
+ + You have reached the maximum number of functions for your plan. + +
{#if data.functions.total} @@ -95,11 +116,16 @@ event="functions" total={data.functions.total} service="functions" - on:click={() => - goto(`${base}/project-${page.params.region}-${project}/functions/create-function`)}> + on:click={() => goto(createFunctionsUrl)}> {#each data.functions.functions as func (func.$id)} + href={resolveRoute( + '/(console)/project-[region]-[project]/functions/function-[function]', + { + ...page.params, + function: func.$id + } + )}> @@ -135,7 +161,9 @@ @@ -145,9 +173,6 @@ allowCreate={$canWriteFunctions} href="https://appwrite.io/docs/products/functions" target="function" - on:click={() => - goto( - `${base}/project-${page.params.region}-${project}/functions/create-function` - )} /> + on:click={() => goto(createFunctionsUrl)} /> {/if}
From c4597d2b19c06ce0b1dcf630c1d4adadf951bcbf Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 28 Jan 2026 17:37:54 +0530 Subject: [PATCH 77/79] fix: stores issue, again in some places. --- .../function-[function]/settings/+page.svelte | 1 + .../settings/+page.svelte | 3 ++- .../sites/site-[site]/settings/+page.svelte | 1 + .../updateVariables.svelte | 25 +++++++++++++------ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/settings/+page.svelte index 7d7e9e4685..3c69a5099b 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/settings/+page.svelte @@ -105,6 +105,7 @@ isGlobal={false} globalVariableList={data.globalVariables} variableList={data.variables} + project={data.project} analyticsSource="function_settings" /> diff --git a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte index 8c427cc23f..8e37b72f9b 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte @@ -94,8 +94,9 @@ {sdkCreateVariable} {sdkUpdateVariable} {sdkDeleteVariable} - isGlobal={true} + isGlobal variableList={data.variables} + project={data.project} analyticsSource="project_settings" /> diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/settings/+page.svelte index f672e24607..fc9763be71 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/settings/+page.svelte @@ -75,6 +75,7 @@ isGlobal={false} globalVariableList={data.globalVariables} variableList={data.variables} + project={data.project} product="site" analyticsSource="site_settings" /> {#if isCloud} diff --git a/src/routes/(console)/project-[region]-[project]/updateVariables.svelte b/src/routes/(console)/project-[region]-[project]/updateVariables.svelte index 86f24b136d..91948168ec 100644 --- a/src/routes/(console)/project-[region]-[project]/updateVariables.svelte +++ b/src/routes/(console)/project-[region]-[project]/updateVariables.svelte @@ -8,7 +8,6 @@ import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { Dependencies } from '$lib/constants'; import { addNotification } from '$lib/stores/notifications'; - import { project } from '$routes/(console)/project-[region]-[project]/store'; import PromoteVariableModal from './promoteVariableModal.svelte'; import CreateVariable from './createVariableModal.svelte'; import RawVariableEditor from './rawVariableEditor.svelte'; @@ -39,7 +38,9 @@ import UpdateVariablesModal from './updateVariablesModal.svelte'; import SecretVariableModal from './secretVariableModal.svelte'; import { Confirm } from '$lib/components'; + import { resolveRoute, withPath } from '$lib/stores/navigation'; + export let project: Models.Project; export let variableList: Models.VariableList; export let globalVariableList: Models.VariableList | undefined = undefined; export let analyticsSource = ''; @@ -80,7 +81,7 @@ showVariablesModal = false; addNotification({ type: 'success', - message: `${$project.name} ${ + message: `${project.name} ${ isGlobal ? 'global variable' : 'variable' } has been created.` }); @@ -102,7 +103,7 @@ showVariablesModal = false; addNotification({ type: 'success', - message: `${$project.name} ${ + message: `${project.name} ${ isGlobal ? 'global variable' : 'variable' } has been updated.` }); @@ -123,7 +124,7 @@ showVariablesModal = false; addNotification({ type: 'success', - message: `${$project.name} ${ + message: `${project.name} ${ isGlobal ? 'global variable' : 'variable' } has been marked as secret.` }); @@ -144,7 +145,7 @@ selectedVar = null; addNotification({ type: 'success', - message: `${$project.name} ${ + message: `${project.name} ${ isGlobal ? 'global variable' : 'variable' } has been deleted.` }); @@ -274,7 +275,12 @@ {:else} Set the environment variables or secret keys that will be passed to your {product}. Global variables can be found in + href={withPath( + resolveRoute('/(console)/project-[region]-[project]/settings', { + ...page.params + }), + '#variables' + )}> project settings. {/if} @@ -324,7 +330,12 @@ {/if} a naming conflict with a global variable. View global variables in project settings Date: Wed, 28 Jan 2026 17:43:18 +0530 Subject: [PATCH 78/79] fix: stores issue, again in some places. --- .../overview/platforms/action.svelte | 8 ++-- .../storage/+page.svelte | 39 +++++++++++++------ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte b/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte index b20a72ab18..2337428546 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/platforms/action.svelte @@ -11,13 +11,15 @@ IconPlus, IconReact } from '@appwrite.io/pink-icons-svelte'; - import { isCloud } from '$lib/system'; - import { currentPlan } from '$lib/stores/organization'; + import { isServiceLimited } from '$lib/stores/billing'; + import { organization } from '$lib/stores/organization'; import { page } from '$app/state'; + + $: isLimited = isServiceLimited('platforms', $organization, page.data.platforms.total); {#if $canWritePlatforms} - {#if isCloud && $currentPlan?.platforms >= page.data.platforms.total} + {#if isLimited}
+ +
+ +
+ + You have reached the maximum number of buckets for your plan. + +
{/if} From c8962485901675180d4f864b45d9e59596a1757d Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 28 Jan 2026 18:01:46 +0530 Subject: [PATCH 79/79] address comments. --- .../functions/+page.svelte | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/routes/(console)/project-[region]-[project]/functions/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/+page.svelte index b458dcd6e5..6d8177c1e0 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/+page.svelte @@ -33,12 +33,11 @@ let offset = $state(0); - const createFunctionsUrl = resolveRoute( - '/(console)/project-[region]-[project]/functions/create-function', - { + const createFunctionsUrl = $derived.by(() => { + return resolveRoute('/(console)/project-[region]-[project]/functions/create-function', { ...page.params - } - ); + }); + }); const isLimited = $derived(isServiceLimited('functions', $organization, data.functions.total)); @@ -57,7 +56,13 @@ template } ); - await goto(withPath(templateUrl, `?templateConfig=${templateConfig}`)); + + if (!templateConfig) { + await goto(templateUrl); + } else { + await goto(withPath(templateUrl, `?templateConfig=${templateConfig}`)); + } + break; } case 'cover':