From aadbf05f08e3d02e5142924087b62d6661cab735 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 26 Nov 2025 19:38:00 +0530 Subject: [PATCH 01/14] update: billing stuff. --- .../billing/planComparisonBox.svelte | 189 ++++++++++-------- .../components/billing/planSelection.svelte | 37 +++- .../components/organizationUsageLimits.svelte | 25 ++- src/lib/helpers/billing.ts | 10 + src/lib/profiles/index.svelte.ts | 3 + src/lib/sdk/billing.ts | 20 +- src/lib/stores/billing.ts | 7 +- src/routes/(console)/+layout.ts | 2 +- .../(console)/apply-credit/+page.svelte | 2 +- .../create-organization/+page.svelte | 2 + .../change-plan/+page.svelte | 31 ++- .../change-plan/+page.ts | 5 +- 12 files changed, 195 insertions(+), 138 deletions(-) diff --git a/src/lib/components/billing/planComparisonBox.svelte b/src/lib/components/billing/planComparisonBox.svelte index 4379cc6f8..f4c430e00 100644 --- a/src/lib/components/billing/planComparisonBox.svelte +++ b/src/lib/components/billing/planComparisonBox.svelte @@ -1,103 +1,130 @@ - {#each visibleTiers as tier} + {#each visiblePlans as plan} (selectedTab = tier)}> - {tierToPlan(tier).name} + active={selectedTab === plan.$id} + on:click={() => (selectedTab = plan.$id)}> + {getCleanPlanName(plan)} {/each} - {plan.name} plan - {#if selectedTab === BillingPlan.FREE} - {#if downgrade} -
    -
  • - - - Limited to {plan.databases} Database, {plan.buckets} Buckets, {plan.functions} - Functions per project - -
  • -
  • - - Limited to 1 organization member -
  • -
  • - - - {plan.bandwidth}GB bandwidth - -
  • -
  • - - - {plan.storage}GB storage - -
  • -
  • - - - {formatNum(plan.executions)} executions - -
  • -
- {:else} -
    -
  • - Limited to {plan.databases} Database, {plan.buckets} Buckets, {plan.functions} - Functions per project -
  • -
  • Limited to 1 organization member
  • -
  • - Limited to {plan.bandwidth}GB bandwidth -
  • -
  • - Limited to {plan.storage}GB storage -
  • -
  • - Limited to {formatNum(plan.executions)} executions -
  • -
- {/if} - {:else if selectedTab === BillingPlan.PRO} - Everything in the Free plan, plus: -
    -
  • Unlimited databases, buckets, functions
  • -
  • Unlimited seats
  • -
  • {plan.bandwidth}GB bandwidth
  • -
  • {plan.storage}GB storage
  • -
  • {formatNum(plan.executions)} executions
  • -
  • Email support
  • -
- {:else if selectedTab === BillingPlan.SCALE} - Everything in the Pro plan, plus: -
    -
  • Unlimited seats
  • -
  • Organization roles
  • -
  • SOC-2, HIPAA compliance
  • -
  • SSO Coming soon
  • -
  • Priority support
  • -
+ {getCleanPlanName(currentPlan)} plan + + {#if resolvedProfile.platform === ProfileMode.CONSOLE} + {@render appwritePlanView(currentPlan)} + {:else} + {@render imaginePlanView(currentPlan)} {/if}
+ +{#snippet appwritePlanView()} + {#if isFreePlan(selectedTab)} + {#if downgrade} + + {:else} + + {/if} + {:else if isProPlan(selectedTab)} + Everything in the Free plan, plus: + + {:else if selectedTab === BillingPlan.SCALE} + Everything in the Pro plan, plus: + + {/if} +{/snippet} + +{#snippet imaginePlanView()} + + {currentPlan?.chatMessages} daily messages + +{/snippet} diff --git a/src/lib/components/billing/planSelection.svelte b/src/lib/components/billing/planSelection.svelte index 0e194f9b6..d3ee16e2d 100644 --- a/src/lib/components/billing/planSelection.svelte +++ b/src/lib/components/billing/planSelection.svelte @@ -6,17 +6,27 @@ import { LabelCard } from '..'; import type { Plan } from '$lib/sdk/billing'; import { page } from '$app/state'; + import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte'; - export let billingPlan: BillingPlan; export let isNewOrg = false; export let selfService = true; export let anyOrgFree = false; + export let billingPlan: BillingPlan; $: plans = Object.values(page.data.plans.plans) as Plan[]; $: currentPlanInList = plans.some((plan) => plan.$id === $currentPlan?.$id); // experiment to remove scale plan temporarily $: plansWithoutScale = plans.filter((plan) => plan.$id != BillingPlan.SCALE); + + function message(plan: Plan): string { + const price = formatCurrency(plan?.price ?? 0); + if (resolvedProfile.id === ProfileMode.STUDIO) { + return `${plan.chatMessages} daily messages for ${price} per month + usage`; + } else { + return `${price} per month + usage`; + } + } @@ -33,16 +43,21 @@ {/if} - - {plan.desc} - - - {@const isZeroPrice = (plan.price ?? 0) <= 0} - {@const price = formatCurrency(plan.price ?? 0)} - {isZeroPrice ? price : `${price} per month + usage`} - + + + + {plan.desc} + + + + {@const isZeroPrice = (plan.price ?? 0) <= 0} + {@const price = formatCurrency(plan.price ?? 0)} + {isZeroPrice ? price : message(plan)} + + {/each} + {#if $currentPlan && !currentPlanInList} {/if} + {$currentPlan.desc} + {@const isZeroPrice = ($currentPlan?.price ?? 0) <= 0} {@const price = formatCurrency($currentPlan?.price ?? 0)} - {isZeroPrice ? price : `${price} per month + usage`} + {isZeroPrice ? price : message($currentPlan)} {/if} diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index dac1cbb3d..3de0fdeb7 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -13,6 +13,7 @@ import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date'; import { organization, type Organization } from '$lib/stores/organization'; import type { Models } from '@appwrite.io/console'; + import { resolvedProfile } from '$lib/profiles/index.svelte'; // Props type Props = { @@ -30,37 +31,37 @@ let showSelectionReminder = $state(false); // Derived state using runes - let freePlanLimits = $derived({ - projects: $plansInfo?.get(BillingPlan.FREE)?.projects, - members: getServiceLimit('members', BillingPlan.FREE), - storage: getServiceLimit('storage', BillingPlan.FREE) + const freePlanLimits = $derived({ + projects: $plansInfo?.get(resolvedProfile.freeTier)?.projects, + members: getServiceLimit('members', resolvedProfile.freeTier), + storage: getServiceLimit('storage', resolvedProfile.freeTier) }); // When preparing to downgrade to Free, enforce Free plan limit locally (2) - let allowedProjectsToKeep = $derived(freePlanLimits.projects); + const allowedProjectsToKeep = $derived(freePlanLimits.projects); - let currentUsage = $derived({ + const currentUsage = $derived({ projects: projects?.length || 0, members: members?.length || 0, storage: storageUsage || 0 }); - let storageUsageGB = $derived(storageUsage / (1024 * 1024 * 1024)); + const storageUsageGB = $derived(storageUsage / (1024 * 1024 * 1024)); - let isLimitExceeded = $derived({ + const isLimitExceeded = $derived({ projects: currentUsage.projects > freePlanLimits.projects, members: currentUsage.members > freePlanLimits.members, storage: storageUsageGB > freePlanLimits.storage }); - let excessUsage = $derived({ + const excessUsage = $derived({ projects: Math.max(0, currentUsage.projects), members: Math.max(0, currentUsage.members - freePlanLimits.members), storage: Math.max(0, storageUsageGB - freePlanLimits.storage) }); // projects that would be archived with the current selection - let projectsToArchive = $derived( + const projectsToArchive = $derived( projects.filter((project) => !selectedProjects.includes(project.$id)) ); @@ -113,7 +114,9 @@ error = `You must select exactly ${allowedProjectsToKeep} projects to keep.`; return; } - // Keep selection locally; parent flow will apply after plan change + + // Keep selection locally; + // parent flow will apply after plan change showSelectProject = false; showSelectionReminder = false; addNotification({ type: 'success', message: `Projects selected for archiving` }); diff --git a/src/lib/helpers/billing.ts b/src/lib/helpers/billing.ts index 0ade94986..41f9074e5 100644 --- a/src/lib/helpers/billing.ts +++ b/src/lib/helpers/billing.ts @@ -9,3 +9,13 @@ export function isFreePlan(plan: BillingPlan | string): boolean { return false; } } + +export function isProPlan(plan: BillingPlan | string): boolean { + switch (plan) { + case BillingPlan.Imaginepro: + case BillingPlan.Tier1: + return true; + default: + return false; + } +} diff --git a/src/lib/profiles/index.svelte.ts b/src/lib/profiles/index.svelte.ts index 764e17cf6..aed8fa667 100644 --- a/src/lib/profiles/index.svelte.ts +++ b/src/lib/profiles/index.svelte.ts @@ -44,6 +44,7 @@ export type Profile = { sites: boolean; settings: boolean; }; + website: string; showOrgInBreadcrumbs: boolean; minimalOrgHeader: boolean; getProjectRoute: (params: { region: string; project: string }) => ResolvedPathname; @@ -85,6 +86,7 @@ export const base: Profile = { sites: true, settings: true }, + website: 'https://appwrite.io', showOrgInBreadcrumbs: true, minimalOrgHeader: false, getProjectRoute({ region, project }) { @@ -128,6 +130,7 @@ export const studio: Profile = { sites: false, settings: true }, + website: 'https://imagine.dev', showOrgInBreadcrumbs: false, minimalOrgHeader: true, getProjectRoute({ region, project }) { diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index 6e0c69802..d3c8a885c 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -2,6 +2,7 @@ import type { Tier } from '$lib/stores/billing'; import type { Campaign } from '$lib/stores/campaigns'; import type { Client, Models, Platform } from '@appwrite.io/console'; import type { PaymentMethod } from '@stripe/stripe-js'; +import { resolvedProfile } from '$lib/profiles/index.svelte'; import type { Organization, OrganizationError, OrganizationList } from '../stores/organization'; export type PaymentMethodData = { @@ -431,6 +432,8 @@ export type Plan = { buildSize: number; // in MB deploymentSize: number; // in MB usagePerProject: boolean; + // imagine specifics + chatMessages: number; // daily }; export type PlanList = { @@ -581,7 +584,8 @@ export class Billing { const path = `/console/plans`; const uri = new URL(this.client.config.endpoint + path); const params = { - queries + queries, + platform: resolvedProfile.platform.toLowerCase() }; return await this.client.call( 'get', @@ -1428,18 +1432,4 @@ export class Billing { params ); } - - async getPlansInfo(): Promise { - const path = `/console/plans`; - const params = {}; - const uri = new URL(this.client.config.endpoint + path); - return await this.client.call( - 'GET', - uri, - { - 'content-type': 'application/json' - }, - params - ); - } } diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 89a547377..f546e0da4 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -42,6 +42,7 @@ import TeamReadonlyAlert from '$routes/(console)/organization-[organization]/tea import ProjectsLimit from '$lib/components/billing/alerts/projectsLimit.svelte'; import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte'; import { isFreePlan } from '$lib/helpers/billing'; +import { BillingPlan as CloudSdkBillingPlan } from '@appwrite.io/console'; export type Tier = 'tier-0' | 'tier-1' | 'tier-2' | 'auto-1' | 'cont-1' | 'ent-1'; @@ -153,7 +154,11 @@ export type PlanServices = | 'authPhone' | 'imageTransformations'; -export function getServiceLimit(serviceId: PlanServices, tier: Tier = null, plan?: Plan): number { +export function getServiceLimit( + serviceId: PlanServices, + tier: Tier | CloudSdkBillingPlan = null, + plan?: Plan +): number { if (!isCloud) return 0; if (!serviceId) return 0; diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index 3cf1478a3..b3523785d 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -16,7 +16,7 @@ export const load: LayoutLoad = async ({ depends, parent }) => { const { endpoint, project } = sdk.forConsole.client.config; const [preferences, plansArray, versionData, consoleVariables] = await Promise.all([ sdk.forConsole.account.getPrefs(), - isCloud ? sdk.forConsole.billing.getPlansInfo() : null, + isCloud ? sdk.forConsole.billing.listPlans() : null, fetch(`${endpoint}/health/version`, { headers: { 'X-Appwrite-Project': project } }).then((response) => response.json() as { version?: string }), diff --git a/src/routes/(console)/apply-credit/+page.svelte b/src/routes/(console)/apply-credit/+page.svelte index b8bc37a60..512cbe5b2 100644 --- a/src/routes/(console)/apply-credit/+page.svelte +++ b/src/routes/(console)/apply-credit/+page.svelte @@ -150,7 +150,7 @@ selectedOrg.$id, billingPlan, paymentMethodId, - null, + undefined, couponData.code ? couponData.code : null, collaborators ); diff --git a/src/routes/(console)/create-organization/+page.svelte b/src/routes/(console)/create-organization/+page.svelte index d6466a9a2..0de98c724 100644 --- a/src/routes/(console)/create-organization/+page.svelte +++ b/src/routes/(console)/create-organization/+page.svelte @@ -259,6 +259,8 @@ @@ -368,7 +367,7 @@ extraMembers * ($plansInfo?.get(selectedPlan)?.addons?.seats?.price ?? 0) )} - {#if selectedPlan === BillingPlan.PRO} + {#if isProPlan(selectedPlan)} Your monthly payments will be adjusted for the Pro plan diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.ts b/src/routes/(console)/organization-[organization]/change-plan/+page.ts index 51a8515cc..7d5e50880 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.ts +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.ts @@ -3,17 +3,18 @@ import type { Organization } from '$lib/stores/organization'; import { BillingPlan, Dependencies } from '$lib/constants'; import { sdk } from '$lib/stores/sdk'; import { isFreePlan } from '$lib/helpers/billing'; +import type { PlanList } from '$lib/sdk/billing'; export const load: PageLoad = async ({ depends, parent }) => { const { members, currentPlan, organizations } = await parent(); depends(Dependencies.UPGRADE_PLAN); - let plans; + let plans: PlanList; try { plans = await sdk.forConsole.billing.listPlans(); } catch (error) { console.error('Failed to load billing plans:', error); - plans = { plans: {} }; + plans = { total: 0, plans: [] }; } let plan: BillingPlan; From 48ea08247db4db578e694ba336a7910c045a3791 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 26 Nov 2025 20:02:48 +0530 Subject: [PATCH 02/14] update: use website. --- src/lib/components/consent.svelte | 2 +- src/lib/components/support.svelte | 2 +- src/lib/layout/footer.svelte | 8 ++++---- src/routes/(console)/+layout.svelte | 7 ++++--- src/routes/(console)/create-organization/+page.svelte | 2 +- src/routes/(console)/createOrganization.svelte | 2 +- .../usage/[[invoice]]/+page.svelte | 5 +++-- .../project-[region]-[project]/overview/onboard.svelte | 4 ++-- .../settings/updateInstallations.svelte | 2 +- src/routes/(public)/(guest)/register/+page.svelte | 4 ++-- .../(public)/(guest)/register/invite/[slug]/+page.svelte | 4 ++-- src/routes/(public)/invite/+page.svelte | 4 ++-- 12 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/lib/components/consent.svelte b/src/lib/components/consent.svelte index f5ae5293b..f72eb994a 100644 --- a/src/lib/components/consent.svelte +++ b/src/lib/components/consent.svelte @@ -118,7 +118,7 @@ - + diff --git a/src/lib/components/support.svelte b/src/lib/components/support.svelte index b25b77c92..514fde3a4 100644 --- a/src/lib/components/support.svelte +++ b/src/lib/components/support.svelte @@ -55,7 +55,7 @@ cta: 'Discord', showSupport: false, label: 'Community support', - link: 'https://appwrite.io/discord', + link: `${resolvedProfile.website}/discord`, description: 'Get support from our community through Discord' }, ...(!resolvedProfile.showGithubIssueSupport diff --git a/src/lib/layout/footer.svelte b/src/lib/layout/footer.svelte index 9ec655313..4feee473d 100644 --- a/src/lib/layout/footer.svelte +++ b/src/lib/layout/footer.svelte @@ -99,7 +99,7 @@ Docs @@ -110,7 +110,7 @@ Terms @@ -121,7 +121,7 @@ Privacy @@ -133,7 +133,7 @@ Cookies diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index ce35cc1d1..9dc556509 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -58,6 +58,7 @@ import type { LayoutData } from './$types'; import Studio from '$lib/studio/studio.svelte'; import { isFreePlan } from '$lib/helpers/billing'; + import { resolvedProfile } from '$lib/profiles/index.svelte'; export let data: LayoutData; @@ -110,7 +111,7 @@ { label: 'Open documentation', callback: () => { - window.open('https://appwrite.io/docs', '_blank'); + window.open(`${resolvedProfile.website}/docs`, '_blank'); }, group: 'help', icon: IconBookOpen @@ -118,7 +119,7 @@ { label: 'Contact support', callback: () => { - window.open('https://appwrite.io/discord', '_blank'); + window.open(`${resolvedProfile.website}/discord`, '_blank'); }, group: 'help', icon: IconQuestionMarkCircle @@ -134,7 +135,7 @@ { label: 'Join Discord community', callback: () => { - window.open('https://appwrite.io/discord', '_blank'); + window.open('${resolvedProfile.website}/discord', '_blank'); }, group: 'help', icon: IconDiscord diff --git a/src/routes/(console)/create-organization/+page.svelte b/src/routes/(console)/create-organization/+page.svelte index 0de98c724..1c1092f73 100644 --- a/src/routes/(console)/create-organization/+page.svelte +++ b/src/routes/(console)/create-organization/+page.svelte @@ -199,7 +199,7 @@ For more details on our plans, visit our pricing page. diff --git a/src/routes/(console)/createOrganization.svelte b/src/routes/(console)/createOrganization.svelte index f4b91181f..a46cc11de 100644 --- a/src/routes/(console)/createOrganization.svelte +++ b/src/routes/(console)/createOrganization.svelte @@ -51,7 +51,7 @@ will need to either upgrade to the Pro plan, transfer your projects to a Pro organization, or migrate to self-hosting. - + {/if} diff --git a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte index 706a51102..856098784 100644 --- a/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte +++ b/src/routes/(console)/organization-[organization]/usage/[[invoice]]/+page.svelte @@ -25,6 +25,7 @@ import { onMount } from 'svelte'; import type { UsageProjectInfo } from '../../store'; import { isFreePlan } from '$lib/helpers/billing'; + import { resolvedProfile } from '$lib/profiles/index.svelte'; export let data; @@ -83,7 +84,7 @@ ($showUsageRatesModal = true)}>Learn more {:else} Learn more @@ -98,7 +99,7 @@ ($showUsageRatesModal = true)}>Learn more {:else} Learn more diff --git a/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte b/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte index c2db43d7b..51e6cfed7 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/onboard.svelte @@ -565,7 +565,7 @@ color="--fgcolor-neutral-secondary"> Deploy the {resolvedProfile.platform} MCP server with a single click, or view the docs for instructions. @@ -598,7 +598,7 @@ { trackEvent(Click.OnboardingDiscordClick); diff --git a/src/routes/(console)/project-[region]-[project]/settings/updateInstallations.svelte b/src/routes/(console)/project-[region]-[project]/settings/updateInstallations.svelte index 32d773bd1..0ae0db0f3 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/updateInstallations.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/updateInstallations.svelte @@ -212,7 +212,7 @@ Deploy the {resolvedProfile.platform} MCP server with a single click, or view the docs for instructions. diff --git a/src/routes/(public)/(guest)/register/+page.svelte b/src/routes/(public)/(guest)/register/+page.svelte index 8c91e9eef..5786dbf14 100644 --- a/src/routes/(public)/(guest)/register/+page.svelte +++ b/src/routes/(public)/(guest)/register/+page.svelte @@ -149,13 +149,13 @@ bind:value={pass} /> By registering, you agree that you have read, understand, and acknowledge our Privacy Policy and accept our General Terms of Use. diff --git a/src/routes/(public)/(guest)/register/invite/[slug]/+page.svelte b/src/routes/(public)/(guest)/register/invite/[slug]/+page.svelte index b640b1f18..c255bcd1c 100644 --- a/src/routes/(public)/(guest)/register/invite/[slug]/+page.svelte +++ b/src/routes/(public)/(guest)/register/invite/[slug]/+page.svelte @@ -134,14 +134,14 @@ By registering, you agree that you have read, understand, and acknowledge our Privacy Policy and accept our General Terms of Use. diff --git a/src/routes/(public)/invite/+page.svelte b/src/routes/(public)/invite/+page.svelte index 050b9ebef..514630eb5 100644 --- a/src/routes/(public)/invite/+page.svelte +++ b/src/routes/(public)/invite/+page.svelte @@ -81,12 +81,12 @@ label="terms" showLabel={false}> By accepting the invitation, you agree to the Terms and Conditions and Privacy Policy Date: Wed, 26 Nov 2025 20:05:11 +0530 Subject: [PATCH 03/14] lint. --- src/lib/components/billing/planComparisonBox.svelte | 4 ++-- src/lib/components/billing/planSelection.svelte | 2 +- src/lib/components/organizationUsageLimits.svelte | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/components/billing/planComparisonBox.svelte b/src/lib/components/billing/planComparisonBox.svelte index f4c430e00..941a1d8e6 100644 --- a/src/lib/components/billing/planComparisonBox.svelte +++ b/src/lib/components/billing/planComparisonBox.svelte @@ -42,9 +42,9 @@ {getCleanPlanName(currentPlan)} plan {#if resolvedProfile.platform === ProfileMode.CONSOLE} - {@render appwritePlanView(currentPlan)} + {@render appwritePlanView()} {:else} - {@render imaginePlanView(currentPlan)} + {@render imaginePlanView()} {/if} diff --git a/src/lib/components/billing/planSelection.svelte b/src/lib/components/billing/planSelection.svelte index d3ee16e2d..6eea41c35 100644 --- a/src/lib/components/billing/planSelection.svelte +++ b/src/lib/components/billing/planSelection.svelte @@ -45,7 +45,7 @@ - + {plan.desc} diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index 3de0fdeb7..fb2a85d6b 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -1,7 +1,6 @@ @@ -125,6 +125,6 @@ {#snippet imaginePlanView()} - {currentPlan?.chatMessages} daily messages + {currentPlan?.limits?.credits} daily messages {/snippet} diff --git a/src/lib/components/billing/planSelection.svelte b/src/lib/components/billing/planSelection.svelte index 6eea41c35..670bee512 100644 --- a/src/lib/components/billing/planSelection.svelte +++ b/src/lib/components/billing/planSelection.svelte @@ -8,6 +8,7 @@ import { page } from '$app/state'; import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte'; + export let disabled = false; export let isNewOrg = false; export let selfService = true; export let anyOrgFree = false; @@ -22,7 +23,7 @@ function message(plan: Plan): string { const price = formatCurrency(plan?.price ?? 0); if (resolvedProfile.id === ProfileMode.STUDIO) { - return `${plan.chatMessages} daily messages for ${price} per month + usage`; + return `${plan.limits?.credits} daily messages for ${price} per month + usage`; } else { return `${price} per month + usage`; } @@ -34,7 +35,7 @@ diff --git a/src/lib/helpers/billing.ts b/src/lib/helpers/billing.ts index 41f9074e5..6160a829a 100644 --- a/src/lib/helpers/billing.ts +++ b/src/lib/helpers/billing.ts @@ -2,7 +2,7 @@ import { BillingPlan } from '@appwrite.io/console'; export function isFreePlan(plan: BillingPlan | string): boolean { switch (plan) { - case BillingPlan.Imaginebasic: + case BillingPlan.Imaginetier0: case BillingPlan.Tier0: return true; default: @@ -11,11 +11,5 @@ export function isFreePlan(plan: BillingPlan | string): boolean { } export function isProPlan(plan: BillingPlan | string): boolean { - switch (plan) { - case BillingPlan.Imaginepro: - case BillingPlan.Tier1: - return true; - default: - return false; - } + return !isFreePlan(plan); } diff --git a/src/lib/profiles/index.svelte.ts b/src/lib/profiles/index.svelte.ts index aed8fa667..c09a37f83 100644 --- a/src/lib/profiles/index.svelte.ts +++ b/src/lib/profiles/index.svelte.ts @@ -101,7 +101,7 @@ export const studio: Profile = { id: ProfileMode.STUDIO, platform: 'Imagine', organizationPlatform: Platform.Imagine, - freeTier: BillingPlan.Imaginebasic, + freeTier: BillingPlan.Imaginetier0, logo: { src: { dark: asset('/images/imagine-logo-dark.svg'), diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index d3c8a885c..37debf8dd 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -412,6 +412,9 @@ export type Plan = { databasesWrites: AdditionalResource; GBHours: AdditionalResource; imageTransformations: AdditionalResource; + + // imagine specific + credits: AdditionalResource; }; addons: { seats: PlanAddon; @@ -432,8 +435,9 @@ export type Plan = { buildSize: number; // in MB deploymentSize: number; // in MB usagePerProject: boolean; - // imagine specifics - chatMessages: number; // daily + limits: { + credits?: number + } }; export type PlanList = { diff --git a/src/routes/(console)/create-organization/+page.svelte b/src/routes/(console)/create-organization/+page.svelte index 1c1092f73..d4219be5f 100644 --- a/src/routes/(console)/create-organization/+page.svelte +++ b/src/routes/(console)/create-organization/+page.svelte @@ -205,7 +205,11 @@ >. - + {#if !isFreePlan(selectedPlan)} diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index 12a8f256f..dcf0ce7ea 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -250,6 +250,7 @@ !data?.members?.memberships?.find((m) => m.userEmail === collaborator) ); } + const org = await sdk.forConsole.billing.updatePlan( data.organization.$id, selectedPlan, @@ -310,6 +311,8 @@ } } + $: console.log($plansInfo.get(selectedPlan)); + $: isUpgrade = $plansInfo.get(selectedPlan)?.order > $currentPlan?.order; $: isDowngrade = $plansInfo.get(selectedPlan)?.order < $currentPlan?.order; $: isButtonDisabled = @@ -342,7 +345,11 @@ {/if} - + {#if isDowngrade && isFreePlan(selectedPlan) && data.hasFreeOrgs} Date: Mon, 1 Dec 2025 17:41:19 +0530 Subject: [PATCH 07/14] update: billing layout and downgrades. --- package.json | 2 +- pnpm-lock.yaml | 10 +- .../billing/planComparisonBox.svelte | 94 +++++++++-- .../components/billing/planSelection.svelte | 149 +++++++++++++++--- .../components/organizationUsageLimits.svelte | 4 +- src/lib/helpers/billing.ts | 2 +- src/lib/sdk/billing.ts | 8 +- src/lib/stores/billing.ts | 4 +- .../create-organization/+page.svelte | 3 +- .../change-plan/+page.svelte | 44 ++++-- 10 files changed, 255 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index bef52fab1..d9a7d1b52 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@406d8be", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@5b26bb8", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9201f828e..c7500bd2b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.43.6)(zod@3.24.3) '@appwrite.io/console': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@406d8be - version: https://pkg.vc/-/@appwrite/@appwrite.io/console@406d8be + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42 + version: https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42 '@appwrite.io/pink-icons': specifier: 0.25.0 version: 0.25.0 @@ -278,8 +278,8 @@ packages: '@apm-js-collab/tracing-hooks@0.3.1': resolution: {integrity: sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==} - '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@406d8be': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@406d8be} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42} version: 1.10.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -3667,7 +3667,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@406d8be': {} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.43.6)': dependencies: diff --git a/src/lib/components/billing/planComparisonBox.svelte b/src/lib/components/billing/planComparisonBox.svelte index b8a1df636..6aef44666 100644 --- a/src/lib/components/billing/planComparisonBox.svelte +++ b/src/lib/components/billing/planComparisonBox.svelte @@ -3,7 +3,7 @@ import { BillingPlan } from '$lib/constants'; import type { Plan } from '$lib/sdk/billing'; import { formatNum } from '$lib/helpers/string'; - import { isFreePlan, isProPlan } from '$lib/helpers/billing'; + import { isFreePlan, isPaidPlan } from '$lib/helpers/billing'; import { plansInfo, type Tier } from '$lib/stores/billing'; import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte'; import { Card, Layout, Tabs, Typography } from '@appwrite.io/pink-svelte'; @@ -21,15 +21,34 @@ return page.data.plans.plans.filter((plan: Plan) => plan.$id !== BillingPlan.SCALE); }); + const uniquePlans = $derived.by(() => { + const map = new Map(visiblePlans.map((p) => [p.group ?? p.$id, p])); + + return [...map.values()]; + }); + + const groupPlans = $derived.by(() => { + const selected = visiblePlans.find((p: Plan) => p.$id === selectedTab); + if (!selected) return []; + + const groupKey = selected.group ?? selected.$id; + return visiblePlans.filter((p: Plan) => (p.group ?? p.$id) === groupKey); + }); + function getCleanPlanName(plan: Plan) { return plan?.name?.replace(resolvedProfile.platform, ''); } + + function pluralize(count: number, singular: string, plural?: string) { + if (count === 1) return singular; + return plural ?? `${singular}s`; + } - {#each visiblePlans as plan} + {#each uniquePlans as plan} {getCleanPlanName(currentPlan)} plan - {#if resolvedProfile.platform === ProfileMode.CONSOLE} + {#if resolvedProfile.id === ProfileMode.CONSOLE} {@render appwritePlanView()} {:else} {@render imaginePlanView()} @@ -56,8 +75,10 @@
  • - Limited to {currentPlan.databases} Database, {currentPlan.buckets} Buckets, {currentPlan.functions} - Functions per project + Limited to {currentPlan.databases} + {pluralize(currentPlan.databases, 'Database')}, {currentPlan.buckets} + {pluralize(currentPlan.buckets, 'Bucket')}, {currentPlan.functions} + {pluralize(currentPlan.functions, 'Function')} per project
  • @@ -86,8 +107,10 @@ {:else}
    • - Limited to {currentPlan.databases} Database, {currentPlan.buckets} Buckets, {currentPlan.functions} - Functions per project + Limited to {currentPlan.databases} + {pluralize(currentPlan.databases, 'Database')}, {currentPlan.buckets} + {pluralize(currentPlan.buckets, 'Bucket')}, {currentPlan.functions} + {pluralize(currentPlan.functions, 'Function')} per project
    • Limited to 1 organization member
    • @@ -101,7 +124,7 @@
    {/if} - {:else if isProPlan(selectedTab)} + {:else if isPaidPlan(selectedTab)} Everything in the Free plan, plus:
    • Unlimited databases, buckets, functions
    • @@ -124,7 +147,56 @@ {/snippet} {#snippet imaginePlanView()} - - {currentPlan?.limits?.credits} daily messages - + {#if isFreePlan(selectedTab)} + {#if downgrade} +
        +
      • + + + Limited to {currentPlan?.limits?.dailyCredits ?? + currentPlan?.limits?.credits} daily credits per project + +
      • +
      • + + + Limited to {currentPlan.databases} + {pluralize(currentPlan.databases, 'Database')}, {currentPlan.buckets} + {pluralize(currentPlan.buckets, 'Bucket')}, {currentPlan.functions} + {pluralize(currentPlan.functions, 'Function')} per project + +
      • +
      • + + Limited to 1 organization member +
      • +
      + {:else} +
        +
      • + Limited to {currentPlan?.limits?.dailyCredits ?? currentPlan?.limits?.credits} daily + credits per project +
      • +
      • + Limited to {currentPlan.databases} + {pluralize(currentPlan.databases, 'Database')}, {currentPlan.buckets} + {pluralize(currentPlan.buckets, 'Bucket')}, {currentPlan.functions} + {pluralize(currentPlan.functions, 'Function')} per project +
      • +
      • Limited to 1 organization member
      • +
      + {/if} + {:else if isPaidPlan(selectedTab)} + Everything in the Starter plan, plus: +
        + {#if groupPlans.length > 1} +
      • Credits based on the plan selected
      • + {:else} +
      • {currentPlan?.limits?.credits?.toLocaleString()} credits per month
      • + {/if} +
      • Unlimited databases, buckets, functions
      • +
      • Unlimited seats
      • +
      • Email support
      • +
      + {/if} {/snippet} diff --git a/src/lib/components/billing/planSelection.svelte b/src/lib/components/billing/planSelection.svelte index 670bee512..dea51031d 100644 --- a/src/lib/components/billing/planSelection.svelte +++ b/src/lib/components/billing/planSelection.svelte @@ -7,6 +7,7 @@ import type { Plan } from '$lib/sdk/billing'; import { page } from '$app/state'; import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte'; + import { InputSelect } from '$lib/elements/forms'; export let disabled = false; export let isNewOrg = false; @@ -14,47 +15,151 @@ export let anyOrgFree = false; export let billingPlan: BillingPlan; - $: plans = Object.values(page.data.plans.plans) as Plan[]; - $: currentPlanInList = plans.some((plan) => plan.$id === $currentPlan?.$id); + let selectedTiers: Record = {}; - // experiment to remove scale plan temporarily - $: plansWithoutScale = plans.filter((plan) => plan.$id != BillingPlan.SCALE); + type PlanGroup = { + key: string; + plans: Plan[]; + isGrouped: boolean; + tierOptions?: { value: string; label: string; badge?: string }[]; + }; + + // filter, group, and sort plans + $: groupedPlans = (() => { + const plans = Object.values(page.data.plans.plans) as Plan[]; + const groups = new Map(); + + // Filter and group in one pass + for (const plan of plans) { + if (plan.$id === BillingPlan.SCALE) continue; + + const key = plan.group ?? plan.$id; + const existing = groups.get(key); + if (existing) { + existing.push(plan); + } else { + groups.set(key, [plan]); + } + } + + // convert to array and process groups + return Array.from(groups.entries()) + .map(([key, groupPlans]) => { + const isGrouped = groupPlans.length > 1; + const group: PlanGroup = { key, plans: groupPlans, isGrouped }; + + // pre-compute tier options for grouped plans + if (isGrouped) { + group.tierOptions = groupPlans.map((plan) => ({ + value: plan.$id, + label: getTierLabel(plan), + badge: $organization?.billingPlan === plan.$id ? 'Current' : undefined + })); + } + + return group; + }) + .sort((a, b) => (a.plans[0]?.order ?? 0) - (b.plans[0]?.order ?? 0)); + })(); + + $: currentPlanInList = Object.values(page.data.plans.plans).some( + (plan: Plan) => plan.$id === $currentPlan?.$id + ); + + $: { + for (const group of groupedPlans) { + if (group.isGrouped) { + // check if billingPlan is in this group + const selectedGroupPlan = group.plans.find((p) => p.$id === billingPlan); + if (selectedGroupPlan) { + selectedTiers[group.key] = selectedGroupPlan.$id; + } else { + // If billingPlan not in group, check organization's current plan + const orgCurrentPlan = group.plans.find( + (p) => p.$id === $organization?.billingPlan + ); + if (orgCurrentPlan) { + selectedTiers[group.key] = orgCurrentPlan.$id; + } else if (!selectedTiers[group.key]) { + // default to first plan in group + selectedTiers[group.key] = group.plans[0].$id; + } + } + } + } + } + + function handleTierChange(group: PlanGroup) { + billingPlan = selectedTiers[group.key] as BillingPlan; + } function message(plan: Plan): string { const price = formatCurrency(plan?.price ?? 0); if (resolvedProfile.id === ProfileMode.STUDIO) { - return `${plan.limits?.credits} daily messages for ${price} per month + usage`; - } else { - return `${price} per month + usage`; + if (plan.limits?.dailyCredits) { + return `${plan.limits.dailyCredits.toLocaleString()} daily credits for ${price} per month`; + } else if (plan.limits?.credits) { + return `${plan.limits.credits.toLocaleString()} credits per month for ${price}`; + } } + return `${price} per month`; + } + + function getTierLabel(plan: Plan): string { + const price = formatCurrency(plan?.price ?? 0); + if (resolvedProfile.id === ProfileMode.STUDIO) { + if (plan.limits?.dailyCredits) { + return `${plan.limits.dailyCredits.toLocaleString()} daily credits for ${price} per month`; + } else if (plan.limits?.credits) { + return `${plan.limits.credits.toLocaleString()} credits per month for ${price}`; + } + } + return `${price} per month`; } - {#each plansWithoutScale as plan} + {#each groupedPlans as group (group.key)} + {@const basePlan = group.plans[0]} + disabled={!selfService || (basePlan.$id === BillingPlan.FREE && anyOrgFree) || disabled} + tooltipShow={basePlan.$id === BillingPlan.FREE && anyOrgFree} + value={group.isGrouped ? selectedTiers[group.key] || basePlan.$id : basePlan.$id} + title={basePlan.name}> - {#if $organization?.billingPlan === plan.$id && !isNewOrg} + {#if group.plans.some((plan) => $organization?.billingPlan === plan.$id) && !isNewOrg} {/if} - - - {plan.desc} - + + + + {basePlan.desc} + - - {@const isZeroPrice = (plan.price ?? 0) <= 0} - {@const price = formatCurrency(plan.price ?? 0)} - {isZeroPrice ? price : message(plan)} - + {#if !group.isGrouped} + + {@const isZeroPrice = (basePlan.price ?? 0) <= 0} + {@const price = formatCurrency(basePlan.price ?? 0)} + {#if resolvedProfile.id === ProfileMode.STUDIO} + {message(basePlan)} + {:else} + {isZeroPrice ? price : message(basePlan)} + {/if} + + {/if} + + + {#if group.isGrouped} + handleTierChange(group)} + options={group.tierOptions} /> + {/if} {/each} diff --git a/src/lib/components/organizationUsageLimits.svelte b/src/lib/components/organizationUsageLimits.svelte index fb2a85d6b..27b97bc96 100644 --- a/src/lib/components/organizationUsageLimits.svelte +++ b/src/lib/components/organizationUsageLimits.svelte @@ -321,7 +321,7 @@ -webkit-overflow-scrolling: touch; scrollbar-width: thin; position: relative; - z-index: 2; + /*z-index: 2;*/ } /* Small viewport optimizations */ @@ -329,7 +329,7 @@ .responsive-table { margin-inline: -1rem; padding-inline: 1rem; - z-index: 5; + /*z-index: 5;*/ } .responsive-table :global(td), diff --git a/src/lib/helpers/billing.ts b/src/lib/helpers/billing.ts index 6160a829a..7833859c0 100644 --- a/src/lib/helpers/billing.ts +++ b/src/lib/helpers/billing.ts @@ -10,6 +10,6 @@ export function isFreePlan(plan: BillingPlan | string): boolean { } } -export function isProPlan(plan: BillingPlan | string): boolean { +export function isPaidPlan(plan: BillingPlan | string): boolean { return !isFreePlan(plan); } diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index 37debf8dd..ce8bf97df 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -1,6 +1,6 @@ import type { Tier } from '$lib/stores/billing'; import type { Campaign } from '$lib/stores/campaigns'; -import type { Client, Models, Platform } from '@appwrite.io/console'; +import { BillingPlanGroup, type Client, type Models, type Platform } from '@appwrite.io/console'; import type { PaymentMethod } from '@stripe/stripe-js'; import { resolvedProfile } from '$lib/profiles/index.svelte'; import type { Organization, OrganizationError, OrganizationList } from '../stores/organization'; @@ -436,8 +436,10 @@ export type Plan = { deploymentSize: number; // in MB usagePerProject: boolean; limits: { - credits?: number - } + credits?: number; + dailyCredits?: number; + }; + group: null | BillingPlanGroup; }; export type PlanList = { diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index f546e0da4..82bab5b4d 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -41,7 +41,7 @@ import BudgetLimitAlert from '$routes/(console)/organization-[organization]/budg import TeamReadonlyAlert from '$routes/(console)/organization-[organization]/teamReadonlyAlert.svelte'; import ProjectsLimit from '$lib/components/billing/alerts/projectsLimit.svelte'; import { ProfileMode, resolvedProfile } from '$lib/profiles/index.svelte'; -import { isFreePlan } from '$lib/helpers/billing'; +import { isFreePlan, isPaidPlan } from '$lib/helpers/billing'; import { BillingPlan as CloudSdkBillingPlan } from '@appwrite.io/console'; export type Tier = 'tier-0' | 'tier-1' | 'tier-2' | 'auto-1' | 'cont-1' | 'ent-1'; @@ -271,7 +271,7 @@ export function checkForProjectLimitation(id: PlanServices) { // Members are no longer limited on Pro and Scale plans (unlimited seats) if (id === 'members') { const currentTier = get(organization)?.billingPlan; - if (currentTier === BillingPlan.PRO || currentTier === BillingPlan.SCALE) { + if (isPaidPlan(currentTier)) { return false; // No project limitation for members on Pro/Scale plans } } diff --git a/src/routes/(console)/create-organization/+page.svelte b/src/routes/(console)/create-organization/+page.svelte index d4219be5f..51fc739ee 100644 --- a/src/routes/(console)/create-organization/+page.svelte +++ b/src/routes/(console)/create-organization/+page.svelte @@ -208,8 +208,7 @@ + bind:billingPlan={selectedPlan} /> {#if !isFreePlan(selectedPlan)} diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index dcf0ce7ea..e79689605 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -38,7 +38,7 @@ import type { Models } from '@appwrite.io/console'; import { toLocaleDate } from '$lib/helpers/date'; import { resolvedProfile } from '$lib/profiles/index.svelte'; - import { isFreePlan, isProPlan } from '$lib/helpers/billing.js'; + import { isFreePlan, isPaidPlan } from '$lib/helpers/billing.js'; export let data; @@ -311,13 +311,17 @@ } } - $: console.log($plansInfo.get(selectedPlan)); - $: isUpgrade = $plansInfo.get(selectedPlan)?.order > $currentPlan?.order; $: isDowngrade = $plansInfo.get(selectedPlan)?.order < $currentPlan?.order; $: isButtonDisabled = $organization?.billingPlan === selectedPlan || (isDowngrade && isFreePlan(selectedPlan) && data.hasFreeOrgs); + + // reactive vars for same group downgrades + $: selectedPlanGroup = $plansInfo?.get(selectedPlan)?.group; + $: currentPlanGroup = $plansInfo?.get($organization?.billingPlan)?.group; + $: isSameGroupDowngrade = + currentPlanGroup && selectedPlanGroup && currentPlanGroup === selectedPlanGroup; @@ -348,8 +352,7 @@ + bind:billingPlan={selectedPlan} /> {#if isDowngrade && isFreePlan(selectedPlan) && data.hasFreeOrgs} Your monthly payments will be adjusted for the Pro plan - After switching plans, - you will be charged {price} monthly for {extraMembers} team members. - This will be reflected in your next invoice. + {#if isSameGroupDowngrade} + After switching plans, + you will be charged {price} now. + This will be reflected in your next invoice. + {:else} + After switching plans, + you will be charged {price} monthly for {extraMembers} team + members. + This will be reflected in your next invoice. + {/if} {:else if isFreePlan(selectedPlan)} {/if} - + {#if !isSameGroupDowngrade} + + {/if} {/if} From 6f1221a70b387b26225f3eaa9f16e8d4bd077939 Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 1 Dec 2025 17:59:20 +0530 Subject: [PATCH 08/14] update: usage copy. --- src/lib/components/billing/planSelection.svelte | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/components/billing/planSelection.svelte b/src/lib/components/billing/planSelection.svelte index dea51031d..5bcee34ef 100644 --- a/src/lib/components/billing/planSelection.svelte +++ b/src/lib/components/billing/planSelection.svelte @@ -187,4 +187,8 @@ {/if} + + + Additional charges apply when you exceed plan limits. + From 5f55f3ad661cdf15b5fac4b40ee01fd59d910ea7 Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 1 Dec 2025 18:20:14 +0530 Subject: [PATCH 09/14] fix: tests, other sdk updates/fixes. --- .../components/billing/planComparisonBox.svelte | 12 ++++++++---- .../create-function/deploy/+page.svelte | 12 ++++++------ .../repository-[repository]/+page.svelte | 4 ++-- .../template-[template]/+page.svelte | 5 +++-- .../(modals)/createGit.svelte | 6 +++--- .../sites/create-site/deploy/+page.svelte | 14 ++++++++++---- .../repository-[repository]/+page.svelte | 8 ++++---- .../templates/template-[template]/+page.svelte | 16 ++++++++++++---- .../deployments/createGitDeploymentModal.svelte | 6 +++--- 9 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/lib/components/billing/planComparisonBox.svelte b/src/lib/components/billing/planComparisonBox.svelte index 6aef44666..c62516f97 100644 --- a/src/lib/components/billing/planComparisonBox.svelte +++ b/src/lib/components/billing/planComparisonBox.svelte @@ -16,12 +16,12 @@ let selectedTab: Tier = $state(resolvedProfile.freeTier as Tier); - const currentPlan = $derived($plansInfo.get(selectedTab)); - const visiblePlans = $derived.by(() => { + const currentPlan: Plan = $derived($plansInfo.get(selectedTab)); + const visiblePlans: Plan[] = $derived.by(() => { return page.data.plans.plans.filter((plan: Plan) => plan.$id !== BillingPlan.SCALE); }); - const uniquePlans = $derived.by(() => { + const uniquePlans: Plan[] = $derived.by(() => { const map = new Map(visiblePlans.map((p) => [p.group ?? p.$id, p])); return [...map.values()]; @@ -43,6 +43,10 @@ if (count === 1) return singular; return plural ?? `${singular}s`; } + + function selectPlan(plan: string) { + selectedTab = plan as Tier; + } @@ -52,7 +56,7 @@ (selectedTab = plan.$id)}> + on:click={() => selectPlan(plan.$id)}> {getCleanPlanName(plan)} {/each} diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte index 37f06d174..7f0155759 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte @@ -3,7 +3,7 @@ import { base } from '$app/paths'; import { page } from '$app/state'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { Card } from '$lib/components'; + import { Card, CustomId } from '$lib/components'; import { Button, Form } from '$lib/elements/forms'; import { Wizard } from '$lib/layout'; import { addNotification } from '$lib/stores/notifications'; @@ -11,8 +11,7 @@ import { Fieldset, Layout, Icon, Input, Tag } from '@appwrite.io/pink-svelte'; import { IconGithub, IconPencil } from '@appwrite.io/pink-icons-svelte'; import { onMount } from 'svelte'; - import { ID, Runtime } from '@appwrite.io/console'; - import { CustomId } from '$lib/components'; + import { ID, Runtime, TemplateReferenceType } from '@appwrite.io/console'; import { getIconFromRuntime } from '$lib/stores/runtimes'; import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import { iconPath } from '$lib/stores/app'; @@ -32,8 +31,8 @@ let showCustomId = $state(false); let isSubmitting = $state(writable(false)); - let id = $state(ID.unique()); - let name = $state(data.repository.name); + let id: string = $state(ID.unique()); + let name: string = $state(data.repository.name); let execute = $state(true); let entrypoint = $state(''); @@ -135,7 +134,8 @@ repository: data.repository.name, owner: data.repository.owner, rootDirectory: rootDir || '.', - version: latestTag ?? '1.0.0', + type: TemplateReferenceType.Tag, + reference: latestTag ?? '1.0.0', activate: true }); diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte index b758c2176..5f6a985e7 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/repository-[repository]/+page.svelte @@ -10,7 +10,7 @@ import { installation, repository } from '$lib/stores/vcs'; import { Layout } from '@appwrite.io/pink-svelte'; import { writable } from 'svelte/store'; - import { ID, Runtime, VCSDeploymentType, VCSDetectionType } from '@appwrite.io/console'; + import { ID, Runtime, VCSDetectionType, VCSReferenceType } from '@appwrite.io/console'; import type { Models } from '@appwrite.io/console'; import { onMount } from 'svelte'; import Details from '../(components)/details.svelte'; @@ -133,7 +133,7 @@ .forProject(page.params.region, page.params.project) .functions.createVcsDeployment({ functionId: func.$id, - type: VCSDeploymentType.Branch, + type: VCSReferenceType.Branch, reference: branch, activate: true }); 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 0f09e10b7..9d3bc27b8 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 @@ -15,7 +15,7 @@ import { writable } from 'svelte/store'; import ProductionBranch from '$lib/components/git/productionBranchFieldset.svelte'; import Configuration from './configuration.svelte'; - import { ID, Runtime, type Models } from '@appwrite.io/console'; + import { ID, Runtime, type Models, TemplateReferenceType } from '@appwrite.io/console'; import { ConnectBehaviour, NewRepository, @@ -180,7 +180,8 @@ repository: data.template.providerRepositoryId || undefined, owner: data.template.providerOwner || undefined, rootDirectory: rt?.providerRootDirectory || undefined, - version: data.template.providerVersion || undefined, + type: TemplateReferenceType.Tag, + reference: data.template.providerVersion || undefined, activate: true }); diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createGit.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createGit.svelte index a65ae194f..24a05f760 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createGit.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createGit.svelte @@ -9,7 +9,7 @@ import { addNotification } from '$lib/stores/notifications'; import { sdk } from '$lib/stores/sdk'; import { installation, repository, sortBranches } from '$lib/stores/vcs'; - import { Runtime, VCSDeploymentType, type Models } from '@appwrite.io/console'; + import { Runtime, VCSReferenceType, type Models } from '@appwrite.io/console'; import { IconGithub } from '@appwrite.io/pink-icons-svelte'; import { Icon, Input, Layout, Skeleton, Typography } from '@appwrite.io/pink-svelte'; import { func } from '../store'; @@ -98,7 +98,7 @@ .forProject(page.params.region, page.params.project) .functions.createVcsDeployment({ functionId: $func.$id, - type: VCSDeploymentType.Commit, + type: VCSReferenceType.Commit, reference: commit, activate }); @@ -107,7 +107,7 @@ .forProject(page.params.region, page.params.project) .functions.createVcsDeployment({ functionId: $func.$id, - type: VCSDeploymentType.Branch, + type: VCSReferenceType.Branch, reference: branch, activate }); diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte index 5e2aa0f55..3473bb8ff 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploy/+page.svelte @@ -3,7 +3,7 @@ import { base } from '$app/paths'; import { page } from '$app/state'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { Card } from '$lib/components'; + import { Card, CustomId } from '$lib/components'; import { Button, Form } from '$lib/elements/forms'; import { Wizard } from '$lib/layout'; import { addNotification } from '$lib/stores/notifications'; @@ -12,8 +12,13 @@ import { IconGithub, IconPencil } from '@appwrite.io/pink-icons-svelte'; import { onMount } from 'svelte'; import Domain from '../domain.svelte'; - import { Adapter, BuildRuntime, Framework, ID } from '@appwrite.io/console'; - import { CustomId } from '$lib/components'; + import { + Adapter, + BuildRuntime, + Framework, + ID, + TemplateReferenceType + } from '@appwrite.io/console'; import { getFrameworkIcon } from '$lib/stores/sites'; import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store'; import { iconPath } from '$lib/stores/app'; @@ -173,7 +178,8 @@ repository: data.repository.name, owner: data.repository.owner, rootDirectory: rootDir || '.', - version: latestTag ?? '1.0.0', + type: TemplateReferenceType.Tag, + reference: latestTag ?? '1.0.0', activate: true }); diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte index 5eeadb67f..128b14bd1 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/repositories/repository-[repository]/+page.svelte @@ -19,7 +19,7 @@ BuildRuntime, Framework, ID, - VCSDeploymentType, + VCSReferenceType, VCSDetectionType } from '@appwrite.io/console'; import type { Models } from '@appwrite.io/console'; @@ -98,8 +98,8 @@ let site = await sdk.forProject(page.params.region, page.params.project).sites.create({ siteId: id || ID.unique(), name, - framework: fr, - buildRuntime, + framework: fr as Framework, + buildRuntime: buildRuntime as BuildRuntime, installCommand, buildCommand, outputDirectory, @@ -132,7 +132,7 @@ .forProject(page.params.region, page.params.project) .sites.createVcsDeployment({ siteId: site.$id, - type: VCSDeploymentType.Branch, + type: VCSReferenceType.Branch, reference: branch, activate: true }); diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/templates/template-[template]/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/templates/template-[template]/+page.svelte index 14d6a8a80..8fce2adbf 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/create-site/templates/template-[template]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/templates/template-[template]/+page.svelte @@ -24,7 +24,14 @@ import Details from '../../details.svelte'; import Configuration from './configuration.svelte'; import Aside from '../../aside.svelte'; - import { Adapter, BuildRuntime, Framework, ID, type Models } from '@appwrite.io/console'; + import { + Adapter, + BuildRuntime, + Framework, + ID, + type Models, + TemplateReferenceType + } from '@appwrite.io/console'; import { ConnectBehaviour, NewRepository, @@ -120,8 +127,8 @@ .sites.create({ siteId: id || ID.unique(), name, - framework: fr, - buildRuntime, + framework: fr as Framework, + buildRuntime: buildRuntime as BuildRuntime, installCommand: framework.installCommand, buildCommand: framework.buildCommand, outputDirectory: framework.outputDirectory, @@ -160,7 +167,8 @@ repository: data.template.providerRepositoryId, owner: data.template.providerOwner, rootDirectory: framework.providerRootDirectory, - version: data.template.providerVersion, + type: TemplateReferenceType.Tag, + reference: data.template.providerVersion, activate: true }); diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createGitDeploymentModal.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createGitDeploymentModal.svelte index 5cd9308ee..341eec2aa 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createGitDeploymentModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/createGitDeploymentModal.svelte @@ -14,7 +14,7 @@ Adapter, BuildRuntime, Framework, - VCSDeploymentType, + VCSReferenceType, type Models } from '@appwrite.io/console'; import { IconGithub } from '@appwrite.io/pink-icons-svelte'; @@ -106,7 +106,7 @@ .forProject(page.params.region, page.params.project) .sites.createVcsDeployment({ siteId: site.$id, - type: VCSDeploymentType.Commit, + type: VCSReferenceType.Commit, reference: commit, activate }); @@ -115,7 +115,7 @@ .forProject(page.params.region, page.params.project) .sites.createVcsDeployment({ siteId: site.$id, - type: VCSDeploymentType.Branch, + type: VCSReferenceType.Branch, reference: branch, activate }); From 331f794927a90f5fcc75289442d225b45b5f08db Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 1 Dec 2025 18:22:31 +0530 Subject: [PATCH 10/14] misc. --- .../organization-[organization]/change-plan/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index e79689605..7935b9ba5 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -384,7 +384,7 @@ {#if isSameGroupDowngrade} After switching plans, - you will be charged {price} now. + you will be charged {price}. This will be reflected in your next invoice. {:else} After switching plans, From 6fa9e30d0f87298e4f80ab32fe8b20e9e95c6e5e Mon Sep 17 00:00:00 2001 From: Darshan Date: Mon, 1 Dec 2025 19:29:05 +0530 Subject: [PATCH 11/14] address some comments. --- .../components/billing/planSelection.svelte | 115 +++++++++--------- src/lib/components/support.svelte | 2 +- src/lib/profiles/index.svelte.ts | 3 + 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/lib/components/billing/planSelection.svelte b/src/lib/components/billing/planSelection.svelte index 5bcee34ef..8f16d2d34 100644 --- a/src/lib/components/billing/planSelection.svelte +++ b/src/lib/components/billing/planSelection.svelte @@ -24,12 +24,27 @@ tierOptions?: { value: string; label: string; badge?: string }[]; }; - // filter, group, and sort plans $: groupedPlans = (() => { const plans = Object.values(page.data.plans.plans) as Plan[]; + const groups = filterAndGroupPlans(plans); + return buildPlanGroups(groups); + })(); + + $: currentPlanInList = Object.values(page.data.plans.plans).some( + (plan: Plan) => plan.$id === $currentPlan?.$id + ); + + $: { + for (const group of groupedPlans) { + if (group.isGrouped) { + selectedTiers[group.key] = getDefaultTierForGroup(group); + } + } + } + + function filterAndGroupPlans(plans: Plan[]): Map { const groups = new Map(); - // Filter and group in one pass for (const plan of plans) { if (plan.$id === BillingPlan.SCALE) continue; @@ -42,80 +57,62 @@ } } - // convert to array and process groups + return groups; + } + + function buildPlanGroups(groups: Map): PlanGroup[] { return Array.from(groups.entries()) .map(([key, groupPlans]) => { const isGrouped = groupPlans.length > 1; const group: PlanGroup = { key, plans: groupPlans, isGrouped }; - // pre-compute tier options for grouped plans if (isGrouped) { - group.tierOptions = groupPlans.map((plan) => ({ - value: plan.$id, - label: getTierLabel(plan), - badge: $organization?.billingPlan === plan.$id ? 'Current' : undefined - })); + group.tierOptions = buildTierOptions(groupPlans); } return group; }) .sort((a, b) => (a.plans[0]?.order ?? 0) - (b.plans[0]?.order ?? 0)); - })(); + } - $: currentPlanInList = Object.values(page.data.plans.plans).some( - (plan: Plan) => plan.$id === $currentPlan?.$id - ); + function buildTierOptions(plans: Plan[]): { value: string; label: string; badge?: string }[] { + return plans.map((plan) => ({ + value: plan.$id, + label: getPlanLabel(plan), + badge: $organization?.billingPlan === plan.$id ? 'Current' : undefined + })); + } - $: { - for (const group of groupedPlans) { - if (group.isGrouped) { - // check if billingPlan is in this group - const selectedGroupPlan = group.plans.find((p) => p.$id === billingPlan); - if (selectedGroupPlan) { - selectedTiers[group.key] = selectedGroupPlan.$id; - } else { - // If billingPlan not in group, check organization's current plan - const orgCurrentPlan = group.plans.find( - (p) => p.$id === $organization?.billingPlan - ); - if (orgCurrentPlan) { - selectedTiers[group.key] = orgCurrentPlan.$id; - } else if (!selectedTiers[group.key]) { - // default to first plan in group - selectedTiers[group.key] = group.plans[0].$id; - } - } + function getDefaultTierForGroup(group: PlanGroup): string { + // billingPlan if in group, else org's current plan, else first plan + const selectedGroupPlan = group.plans.find((p) => p.$id === billingPlan); + if (selectedGroupPlan) { + return selectedGroupPlan.$id; + } + + const orgCurrentPlan = group.plans.find((p) => p.$id === $organization?.billingPlan); + if (orgCurrentPlan) { + return orgCurrentPlan.$id; + } + + return selectedTiers[group.key] || group.plans[0].$id; + } + + function getPlanLabel(plan: Plan): string { + const price = formatCurrency(plan?.price ?? 0); + if (resolvedProfile.id === ProfileMode.STUDIO) { + if (plan.limits?.dailyCredits) { + return `${plan.limits.dailyCredits.toLocaleString()} daily credits for ${price} per month`; + } else if (plan.limits?.credits) { + return `${plan.limits.credits.toLocaleString()} credits per month for ${price}`; } } + return `${price} per month`; } function handleTierChange(group: PlanGroup) { billingPlan = selectedTiers[group.key] as BillingPlan; } - - function message(plan: Plan): string { - const price = formatCurrency(plan?.price ?? 0); - if (resolvedProfile.id === ProfileMode.STUDIO) { - if (plan.limits?.dailyCredits) { - return `${plan.limits.dailyCredits.toLocaleString()} daily credits for ${price} per month`; - } else if (plan.limits?.credits) { - return `${plan.limits.credits.toLocaleString()} credits per month for ${price}`; - } - } - return `${price} per month`; - } - - function getTierLabel(plan: Plan): string { - const price = formatCurrency(plan?.price ?? 0); - if (resolvedProfile.id === ProfileMode.STUDIO) { - if (plan.limits?.dailyCredits) { - return `${plan.limits.dailyCredits.toLocaleString()} daily credits for ${price} per month`; - } else if (plan.limits?.credits) { - return `${plan.limits.credits.toLocaleString()} credits per month for ${price}`; - } - } - return `${price} per month`; - } @@ -145,9 +142,9 @@ {@const isZeroPrice = (basePlan.price ?? 0) <= 0} {@const price = formatCurrency(basePlan.price ?? 0)} {#if resolvedProfile.id === ProfileMode.STUDIO} - {message(basePlan)} + {getPlanLabel(basePlan)} {:else} - {isZeroPrice ? price : message(basePlan)} + {isZeroPrice ? price : getPlanLabel(basePlan)} {/if} {/if} @@ -183,7 +180,7 @@ {@const isZeroPrice = ($currentPlan?.price ?? 0) <= 0} {@const price = formatCurrency($currentPlan?.price ?? 0)} - {isZeroPrice ? price : message($currentPlan)} + {isZeroPrice ? price : getPlanLabel($currentPlan)} {/if} diff --git a/src/lib/components/support.svelte b/src/lib/components/support.svelte index 514fde3a4..24e6b67c9 100644 --- a/src/lib/components/support.svelte +++ b/src/lib/components/support.svelte @@ -55,7 +55,7 @@ cta: 'Discord', showSupport: false, label: 'Community support', - link: `${resolvedProfile.website}/discord`, + link: `${resolvedProfile.discord}`, description: 'Get support from our community through Discord' }, ...(!resolvedProfile.showGithubIssueSupport diff --git a/src/lib/profiles/index.svelte.ts b/src/lib/profiles/index.svelte.ts index c09a37f83..5b5b27b0e 100644 --- a/src/lib/profiles/index.svelte.ts +++ b/src/lib/profiles/index.svelte.ts @@ -44,6 +44,7 @@ export type Profile = { sites: boolean; settings: boolean; }; + discord: string; website: string; showOrgInBreadcrumbs: boolean; minimalOrgHeader: boolean; @@ -86,6 +87,7 @@ export const base: Profile = { sites: true, settings: true }, + discord: 'https://appwrite.io/discord', website: 'https://appwrite.io', showOrgInBreadcrumbs: true, minimalOrgHeader: false, @@ -131,6 +133,7 @@ export const studio: Profile = { settings: true }, website: 'https://imagine.dev', + discord: 'https://imagine.dev/discord', showOrgInBreadcrumbs: false, minimalOrgHeader: true, getProjectRoute({ region, project }) { From b0f1c2c918a5c22c63a83ee54952d3dba7fa3881 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 3 Dec 2025 19:30:59 +0530 Subject: [PATCH 12/14] update: the plan comparison. --- .../billing/planComparisonBox.svelte | 62 ++++++++++++------- .../components/billing/planSelection.svelte | 2 +- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/lib/components/billing/planComparisonBox.svelte b/src/lib/components/billing/planComparisonBox.svelte index c62516f97..569aaff8c 100644 --- a/src/lib/components/billing/planComparisonBox.svelte +++ b/src/lib/components/billing/planComparisonBox.svelte @@ -35,6 +35,13 @@ return visiblePlans.filter((p: Plan) => (p.group ?? p.$id) === groupKey); }); + // get the smallest in the group! + const basePaidPlan = $derived.by(() => { + return groupPlans.length > 1 + ? [...groupPlans].sort((a, b) => (a.order ?? 0) - (b.order ?? 0))[0] + : currentPlan; + }); + function getCleanPlanName(plan: Plan) { return plan?.name?.replace(resolvedProfile.platform, ''); } @@ -157,49 +164,56 @@
    • - Limited to {currentPlan?.limits?.dailyCredits ?? - currentPlan?.limits?.credits} daily credits per project + {currentPlan?.limits?.dailyCredits ?? currentPlan?.limits?.credits} credits / + day
    • - - Limited to {currentPlan.databases} - {pluralize(currentPlan.databases, 'Database')}, {currentPlan.buckets} - {pluralize(currentPlan.buckets, 'Bucket')}, {currentPlan.functions} - {pluralize(currentPlan.functions, 'Function')} per project - + {currentPlan.bandwidth}GB bandwidth
    • - Limited to 1 organization member + {currentPlan.storage}GB storage +
    • +
    • + + {currentPlan.databases} + {pluralize(currentPlan.databases, 'database')} per project +
    • +
    • + + Community support
    {:else}
    • - Limited to {currentPlan?.limits?.dailyCredits ?? currentPlan?.limits?.credits} daily - credits per project + {currentPlan?.limits?.dailyCredits ?? currentPlan?.limits?.credits} credits / day
    • +
    • {currentPlan.bandwidth}GB bandwidth
    • +
    • {currentPlan.storage}GB storage
    • - Limited to {currentPlan.databases} - {pluralize(currentPlan.databases, 'Database')}, {currentPlan.buckets} - {pluralize(currentPlan.buckets, 'Bucket')}, {currentPlan.functions} - {pluralize(currentPlan.functions, 'Function')} per project + {currentPlan.databases} + {pluralize(currentPlan.databases, 'database')} per project
    • -
    • Limited to 1 organization member
    • +
    • Community support
    {/if} {:else if isPaidPlan(selectedTab)} - Everything in the Starter plan, plus:
      - {#if groupPlans.length > 1} -
    • Credits based on the plan selected
    • - {:else} -
    • {currentPlan?.limits?.credits?.toLocaleString()} credits per month
    • - {/if} -
    • Unlimited databases, buckets, functions
    • -
    • Unlimited seats
    • +
    • + {basePaidPlan?.limits?.credits?.toLocaleString()} credits / month + {#if basePaidPlan?.usage?.credits} + (${basePaidPlan.usage.credits.price} per {basePaidPlan.usage.credits.value} additional + credits) + {/if} +
    • +
    • {currentPlan.bandwidth / 1000}TB bandwidth
    • +
    • {currentPlan.storage}GB storage
    • +
    • Unlimited databases
    • +
    • Removable {resolvedProfile.platform} branding
    • Email support
    {/if} diff --git a/src/lib/components/billing/planSelection.svelte b/src/lib/components/billing/planSelection.svelte index 8f16d2d34..de40932f5 100644 --- a/src/lib/components/billing/planSelection.svelte +++ b/src/lib/components/billing/planSelection.svelte @@ -185,7 +185,7 @@ {/if} - + Additional charges apply when you exceed plan limits. From b4cfed946c2709e63e480c163bb2b6021f9414d0 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 4 Dec 2025 18:33:18 +0400 Subject: [PATCH 13/14] Update index.svelte.ts --- src/lib/profiles/index.svelte.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/profiles/index.svelte.ts b/src/lib/profiles/index.svelte.ts index adcdb5bfc..c01fd9cb0 100644 --- a/src/lib/profiles/index.svelte.ts +++ b/src/lib/profiles/index.svelte.ts @@ -124,7 +124,7 @@ export const studio: Profile = { logins: [/** temporary */ Logins.EMAIL, Logins.GITHUB, Logins.GOOGLE].filter(Boolean), oauthProviders: { github: OAuthProvider.GithubImagine, - google: OAuthProvider.Google + google: OAuthProvider.GoogleImagine }, css: StudioCss, component: { From 0c95a5c287b4f1561ca1ef6d47bcf6f89ef85e48 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 4 Dec 2025 18:34:54 +0400 Subject: [PATCH 14/14] Update Appwrite console to commit 4838219 --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b3e8f4372..6f73b36df 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@977ca42", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@4838219", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@5b26bb8", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1fa8b76b7..99cc60436 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.24 version: 1.1.24(svelte@5.43.6)(zod@3.24.3) '@appwrite.io/console': - specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42 - version: https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42 + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@4838219 + version: https://pkg.vc/-/@appwrite/@appwrite.io/console@4838219 '@appwrite.io/pink-icons': specifier: 0.25.0 version: 0.25.0 @@ -278,8 +278,8 @@ packages: '@apm-js-collab/tracing-hooks@0.3.1': resolution: {integrity: sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==} - '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@4838219': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@4838219} version: 1.10.0 '@appwrite.io/pink-icons-svelte@2.0.0-RC.1': @@ -3667,7 +3667,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@977ca42': {} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@4838219': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.43.6)': dependencies: