diff --git a/src/lib/components/billing/planExcess.svelte b/src/lib/components/billing/planExcess.svelte index 153b5cb996..d940691deb 100644 --- a/src/lib/components/billing/planExcess.svelte +++ b/src/lib/components/billing/planExcess.svelte @@ -9,137 +9,128 @@ TableScroll } from '$lib/elements/table'; import { Alert } from '$lib/components'; - import { plansInfo, type Tier } from '$lib/stores/billing'; + import { calculateExcess, plansInfo, tierToPlan, type Tier } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; import { toLocaleDate } from '$lib/helpers/date'; import { Button } from '$lib/elements/forms'; import { humanFileSize } from '$lib/helpers/sizeConvertion'; - import { abbreviateNumber, formatCurrency } from '$lib/helpers/numbers'; + import { abbreviateNumber } from '$lib/helpers/numbers'; import { formatNum } from '$lib/helpers/string'; - import { BillingPlan } from '$lib/constants'; + import { onMount } from 'svelte'; + import type { OrganizationUsage } from '$lib/sdk/billing'; + import { sdk } from '$lib/stores/sdk'; - export let excess: { + export let tier: Tier; + + const plan = $plansInfo?.get(tier); + let excess: { bandwidth?: number; storage?: number; users?: number; executions?: number; members?: number; } = null; - export let currentTier: Tier; + let usage: OrganizationUsage = null; + let showExcess = false; - const plan = $plansInfo?.get(currentTier); - const collaboratorPrice = formatCurrency(plan?.addons.member?.price ?? 0); + onMount(async () => { + usage = await sdk.forConsole.billing.listUsage( + $organization.$id, + $organization.billingCurrentInvoiceDate, + new Date().toISOString() + ); + excess = calculateExcess(usage, plan, $organization); + showExcess = Object.values(excess).some((value) => value > 0); + }); - - - {#if currentTier === BillingPlan.STARTER} - Your usage exceeds the {plan.name} plan limits - {:else} - Changing your plan now will result in removal of organization members and more - {/if} - - - {#if excess?.members > 0} - {#if currentTier === BillingPlan.STARTER} - The Starter plan has a limit of one organization member. By proceeding, all but the - creator of the organization admin will be removed. - {:else if currentTier === BillingPlan.PRO} - Additional organization members on the Pro plan cost {collaboratorPrice} per member per billing - period. By proceeding, you acknowledge your fees may increase in your next billing period. - {/if} - {/if} - {#if excess?.bandwidth > 0 || excess?.executions > 0 || excess?.storage > 0 || excess?.users} - Until you reduce your usage, you will be unable to add to the resources listed below in all - projects within your organization. The current billing cycle will end on {toLocaleDate( - $organization.billingCurrentInvoiceDate - )}. Any executions, bandwidth, or messaging usage will be reset at that time. - {/if} - - - {#if currentTier === BillingPlan.STARTER} +{#if showExcess} + + + Your {tierToPlan($organization.billingPlan).name} plan subscription will end on {toLocaleDate( + $organization.billingNextInvoiceDate + )} + + Following payment of your final invoice, your organization will switch to the Starter plan. {#if excess?.members > 0}All + team members except the owner will be removed on that date.{/if} Service disruptions may + occur unless resource usage is reduced. + + - {:else if currentTier === BillingPlan.PRO} - - {/if} - - + + - - - Resource - Free limit - Excess usage - - - {#if excess?.members} - - Organization members - {plan.members} members - -

- - {excess?.members} members -

-
-
- {/if} - {#if excess?.storage} - - Storage - {plan.storage} GB - -

- - {humanFileSize(excess?.storage)} -

-
-
- {/if} - {#if excess?.executions} - - Function executions - - {abbreviateNumber(plan.executions)} executions - - -

- - - {formatNum(excess?.executions)} executions - -

-
-
- {/if} - {#if excess?.users} - - Users - - {abbreviateNumber(plan.users)} users - - -

- - - {formatNum(excess?.users)} users - -

-
-
- {/if} -
-
+ + + Resource + Free limit + Excess usage + + + {#if excess?.members} + + Organization members + {plan.members} members + +

+ + {excess?.members} members +

+
+
+ {/if} + {#if excess?.storage} + + Storage + {plan.storage} GB + +

+ + {humanFileSize(excess?.storage)} +

+
+
+ {/if} + {#if excess?.executions} + + Function executions + + {abbreviateNumber(plan.executions)} executions + + +

+ + + {formatNum(excess?.executions)} executions + +

+
+
+ {/if} + {#if excess?.users} + + Users + + {abbreviateNumber(plan.users)} users + + +

+ + + {formatNum(excess?.users)} users + +

+
+
+ {/if} +
+
+{/if} diff --git a/src/lib/elements/table/tableScroll.svelte b/src/lib/elements/table/tableScroll.svelte index 8c2f43b3a0..40a3fafc1e 100644 --- a/src/lib/elements/table/tableScroll.svelte +++ b/src/lib/elements/table/tableScroll.svelte @@ -7,6 +7,8 @@ export let transparent = false; export let noStyles = false; export let dense = false; + let classes: string = undefined; + export { classes as class }; let isOverflowing = false; @@ -43,7 +45,7 @@ }; -
+
(isOverflowing = v)}> plan.members ? members.total - (plan.members || members.total) : 0; + const membersOverflow = members > plan.members ? members - (plan.members || members) : 0; if (resources.some((r) => r.value >= 100) || membersOverflow > 0) { readOnly.set(true); @@ -369,3 +372,20 @@ export const upgradeURL = derived( ); export const hideBillingHeaderRoutes = ['/console/create-organization', '/console/account']; + +export function calculateExcess(usage: OrganizationUsage, plan: Plan, org: Organization) { + const totBandwidth = usage?.bandwidth?.length > 0 ? last(usage.bandwidth).value : 0; + return { + bandwidth: calculateResourceSurplus(totBandwidth, plan.bandwidth), + storage: calculateResourceSurplus(usage?.storageTotal, plan.storage, 'GB'), + users: calculateResourceSurplus(usage?.usersTotal, plan.users), + executions: calculateResourceSurplus(usage?.executionsTotal, plan.executions, 'GB'), + members: calculateResourceSurplus(org.total, plan.members) + }; +} + +export function calculateResourceSurplus(total: number, limit: number, limitUnit: Size = null) { + if (total === undefined || limit === undefined) return 0; + const realLimit = (limitUnit ? sizeToBytes(limit, limitUnit) : limit) || Infinity; + return total > realLimit ? total - realLimit : 0; +} diff --git a/src/routes/console/organization-[organization]/change-plan/+page.svelte b/src/routes/console/organization-[organization]/change-plan/+page.svelte index 0986933bec..e197c6e489 100644 --- a/src/routes/console/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/console/organization-[organization]/change-plan/+page.svelte @@ -9,6 +9,7 @@ PlanComparisonBox, SelectPaymentMethod } from '$lib/components/billing'; + import PlanExcess from '$lib/components/billing/planExcess.svelte'; import ValidateCreditModal from '$lib/components/billing/validateCreditModal.svelte'; import { BillingPlan, Dependencies, feedbackDowngradeOptions } from '$lib/constants'; import { @@ -235,6 +236,7 @@ } $: isUpgrade = billingPlan > $organization.billingPlan; + $: isDowngrade = billingPlan < $organization.billingPlan; $: freePlan = $plansInfo.get(BillingPlan.STARTER); $: proPlan = $plansInfo.get(BillingPlan.PRO); $: if (billingPlan === BillingPlan.PRO) { @@ -276,8 +278,8 @@ @@ -320,6 +322,9 @@ + {#if isDowngrade} + + {/if} {#if billingPlan === BillingPlan.PRO && $organization.billingPlan !== BillingPlan.PRO} - import { Modal } from '$lib/components'; - import { sizeToBytes } from '$lib/helpers/sizeConvertion'; - import { plansInfo, tierToPlan, upgradeURL } from '$lib/stores/billing'; - import { organization } from '$lib/stores/organization'; - import { onMount } from 'svelte'; - import type { OrganizationUsage } from '$lib/sdk/billing'; - import type { Models } from '@appwrite.io/console'; - import { sdk } from '$lib/stores/sdk'; - import { Button } from '$lib/elements/forms'; - import { goto } from '$app/navigation'; - import { last } from '$lib/helpers/array'; - import { trackEvent } from '$lib/actions/analytics'; - import PlanExcess from '$lib/components/billing/planExcess.svelte'; - - export let show = false; - const plan = $plansInfo?.get($organization.billingPlan); - let usage: OrganizationUsage = null; - let members: Models.MembershipList = null; - let excess: Record = null; - - onMount(async () => { - usage = await sdk.forConsole.billing.listUsage( - $organization.$id, - $organization.billingCurrentInvoiceDate, - new Date().toISOString() - ); - members = await sdk.forConsole.teams.listMemberships($organization.$id); - calculateExcess(); - }); - - function calculateExcess() { - const totBandwidth = usage?.bandwidth?.length > 0 ? last(usage.bandwidth).value : 0; - const totUsers = usage?.users?.length > 0 ? last(usage.users).value : 0; - - excess = { - bandwidth: totBandwidth > plan.bandwidth ? totBandwidth - plan.bandwidth : 0, - storage: - usage?.storageTotal > sizeToBytes(plan.storage, 'GB') - ? usage.storageTotal - sizeToBytes(plan.storage, 'GB') - : 0, - users: totUsers > (plan.users || Infinity) ? totUsers - plan.users : 0, - executions: - usage?.executionsTotal > sizeToBytes(plan.executions, 'GB') - ? usage.executionsTotal - sizeToBytes(plan.executions, 'GB') - : 0, - members: - members?.total > (plan.members || Infinity) - ? members.total - (plan.members || Infinity) - : 0 - }; - } - - $: if (show) calculateExcess(); - - - -

- Usage for {$organization.name} organization has reached the limits of the {tierToPlan( - $organization.billingPlan - ).name} plan. Excess usage fees will apply. -

- - - - -
- -
- - -
-
-
-