From 5b0fd9120a90bc29574de8e5eca0c4396e2e8131 Mon Sep 17 00:00:00 2001 From: Darshan Date: Wed, 24 Dec 2025 18:44:25 +0530 Subject: [PATCH 1/2] remove: BillingPlan hardcoded constant. --- package.json | 2 +- pnpm-lock.yaml | 10 +- src/lib/components/backupDatabaseAlert.svelte | 11 +- src/lib/components/backupRestoreBox.svelte | 4 +- .../billing/alerts/limitReached.svelte | 17 +-- .../alerts/missingPaymentMethod.svelte | 3 +- .../billing/alerts/newDevUpgradePro.svelte | 4 +- .../billing/estimatedTotalBox.svelte | 7 +- .../billing/planComparisonBox.svelte | 3 +- .../components/billing/planSelection.svelte | 51 ++++--- src/lib/components/billing/selectPlan.svelte | 17 ++- src/lib/components/billing/usageRates.svelte | 24 ++-- src/lib/components/bottomModalAlert.svelte | 22 +-- src/lib/components/breadcrumbs.svelte | 3 +- src/lib/components/emptyCardImageCloud.svelte | 4 +- .../components/organizationUsageLimits.svelte | 13 +- src/lib/components/roles/upgrade.svelte | 3 +- src/lib/constants.ts | 11 -- src/lib/layout/containerButton.svelte | 8 +- src/lib/layout/containerHeader.svelte | 54 +++++--- src/lib/layout/createProject.svelte | 41 +++--- src/lib/layout/shell.svelte | 12 +- src/lib/stores/billing.ts | 131 ++++++++++++------ src/routes/(console)/+layout.svelte | 4 +- src/routes/(console)/+layout.ts | 3 + .../account/organizations/+page.svelte | 9 +- .../(console)/apply-credit/+page.svelte | 47 ++++--- .../create-organization/+page.svelte | 34 +++-- .../(console)/create-organization/+page.ts | 41 ++++-- .../create-organization/+page.svelte | 19 ++- .../onboarding/create-project/+page.ts | 10 +- .../organization-[organization]/+page.svelte | 14 +- .../billing/+page.svelte | 5 +- .../billing/+page.ts | 8 +- .../billing/availableCredit.svelte | 5 +- .../billing/budgetAlert.svelte | 10 +- .../billing/budgetCap.svelte | 4 +- .../billing/deleteOrgPayment.svelte | 8 +- .../billing/paymentMethods.svelte | 6 +- .../billing/planSummary.svelte | 4 +- .../billing/planSummaryOld.svelte | 11 +- .../budgetLimitAlert.svelte | 3 +- .../change-plan/+page.svelte | 71 +++++----- .../change-plan/+page.ts | 11 +- .../createProjectCloud.svelte | 62 +++++---- .../organization-[organization]/header.svelte | 19 +-- .../members/+page.svelte | 11 +- .../usage/[[invoice]]/+page.svelte | 14 +- .../usage/[[invoice]]/totalMembers.svelte | 18 +-- .../(components)/details.svelte | 13 +- .../settings/updateResourceLimits.svelte | 11 +- .../functions/templates/+page.svelte | 2 +- .../settings/smtp/+page.svelte | 12 +- .../settings/usage/[[invoice]]/+page.svelte | 26 ++-- .../sites/create-site/configuration.svelte | 2 +- .../sites/create-site/templates/+page.svelte | 2 +- .../site-[site]/instantRollbackModal.svelte | 2 +- .../settings/updateResourceLimits.svelte | 12 +- .../settings/updateMaxFileSize.svelte | 9 +- src/routes/(public)/functions/deploy/+page.ts | 6 +- src/routes/(public)/sites/deploy/+page.ts | 8 +- 61 files changed, 556 insertions(+), 455 deletions(-) diff --git a/package.json b/package.json index 50b4029dce..a2e1778fe3 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@7a147b9", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f", "@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 ce01083e0f..9a968c01e9 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@7a147b9 - version: https://pkg.vc/-/@appwrite/@appwrite.io/console@7a147b9 + specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f + version: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f '@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@7a147b9': - resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@7a147b9} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f': + resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f} 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@7a147b9': {} + '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@f21fc7f': {} '@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)': dependencies: diff --git a/src/lib/components/backupDatabaseAlert.svelte b/src/lib/components/backupDatabaseAlert.svelte index 1c2ceed16f..46b44a41e8 100644 --- a/src/lib/components/backupDatabaseAlert.svelte +++ b/src/lib/components/backupDatabaseAlert.svelte @@ -1,6 +1,5 @@ {#if $showPolicyAlert && isCloud && $organization?.$id && page.url.pathname.match(/\/databases\/database-[^/]+$/)} - {@const isFreePlan = $organization?.billingPlan === BillingPlan.FREE} + {@const areBackupsAvailable = $organization?.billingPlanDetails.backupsEnabled} - {@const subtitle = isFreePlan + {@const subtitle = !areBackupsAvailable ? 'Upgrade your plan to ensure your data stays safe and backed up' : 'Protect your data by quickly adding a backup policy'} - {@const ctaText = isFreePlan ? 'Upgrade plan' : 'Create policy'} - {@const ctaURL = isFreePlan ? $upgradeURL : `${page.url.pathname}/backups`} + {@const ctaText = !areBackupsAvailable ? 'Upgrade plan' : 'Create policy'} + {@const ctaURL = !areBackupsAvailable ? $upgradeURL : `${page.url.pathname}/backups`} {subtitle} @@ -35,7 +34,7 @@ href={ctaURL} secondary fullWidthMobile - event={isFreePlan ? 'backup_banner_upgrade' : 'backup_banner_add'}> + event={!areBackupsAvailable ? 'backup_banner_upgrade' : 'backup_banner_add'}> {ctaText} 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 2/2] 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}