Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: downgrade flow excess table #1101

Merged
merged 5 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 103 additions & 112 deletions src/lib/components/billing/planExcess.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
</script>

<Alert type="error">
<svelte:fragment slot="title">
{#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}
</svelte:fragment>

{#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}

<svelte:fragment slot="buttons">
{#if currentTier === BillingPlan.STARTER}
{#if showExcess}
<Alert type="error" {...$$restProps}>
<svelte:fragment slot="title">
Your {tierToPlan($organization.billingPlan).name} plan subscription will end on {toLocaleDate(
$organization.billingNextInvoiceDate
)}
</svelte:fragment>
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.
<!-- Any executions, bandwidth, or messaging usage will be reset at that time. -->
<svelte:fragment slot="buttons">
<Button
text
external
href="https://appwrite.io/docs/advanced/platform/starter#reaching-resource-limits">
Learn more
</Button>
{:else if currentTier === BillingPlan.PRO}
<Button
text
external
href="https://appwrite.io/docs/advanced/platform/pro#reaching-resource-limits">
Learn more
</Button>
{/if}
</svelte:fragment>
</Alert>
</svelte:fragment>
</Alert>

<TableScroll noMargin>
<TableHeader>
<TableCellHead>Resource</TableCellHead>
<TableCellHead>Free limit</TableCellHead>
<TableCellHead>Excess usage</TableCellHead>
</TableHeader>
<TableBody>
{#if excess?.members}
<TableRow>
<TableCellText title="members">Organization members</TableCellText>
<TableCellText title="limit">{plan.members} members</TableCellText>
<TableCell title="excess">
<p class="u-color-text-danger u-flex u-cross-center u-gap-4">
<span class="icon-arrow-up" />
{excess?.members} members
</p>
</TableCell>
</TableRow>
{/if}
{#if excess?.storage}
<TableRow>
<TableCellText title="storage">Storage</TableCellText>
<TableCellText title="limit">{plan.storage} GB</TableCellText>
<TableCell title="excess">
<p class="u-color-text-danger">
<span class="icon-arrow-up" />
{humanFileSize(excess?.storage)}
</p>
</TableCell>
</TableRow>
{/if}
{#if excess?.executions}
<TableRow>
<TableCellText title="executions">Function executions</TableCellText>
<TableCellText title="limit">
{abbreviateNumber(plan.executions)} executions
</TableCellText>
<TableCell title="excess">
<p class="u-color-text-danger">
<span class="icon-arrow-up" />
<span
title={excess?.executions
? excess.executions.toString()
: 'executions'}>
{formatNum(excess?.executions)} executions
</span>
</p>
</TableCell>
</TableRow>
{/if}
{#if excess?.users}
<TableRow>
<TableCellText title="users">Users</TableCellText>
<TableCellText title="limit">
{abbreviateNumber(plan.users)} users
</TableCellText>
<TableCell title="excess">
<p class="u-color-text-danger">
<span class="icon-arrow-up" />
<span title={excess?.users ? excess.users.toString() : 'users'}>
{formatNum(excess?.users)} users
</span>
</p>
</TableCell>
</TableRow>
{/if}
</TableBody>
</TableScroll>
<TableScroll dense noMargin class="u-margin-block-start-16">
<TableHeader>
<TableCellHead>Resource</TableCellHead>
<TableCellHead>Free limit</TableCellHead>
<TableCellHead>Excess usage</TableCellHead>
</TableHeader>
<TableBody>
{#if excess?.members}
<TableRow>
<TableCellText title="members">Organization members</TableCellText>
<TableCellText title="limit">{plan.members} members</TableCellText>
<TableCell title="excess">
<p class="u-color-text-danger u-flex u-cross-center u-gap-4">
<span class="icon-arrow-up" />
{excess?.members} members
</p>
</TableCell>
</TableRow>
{/if}
{#if excess?.storage}
<TableRow>
<TableCellText title="storage">Storage</TableCellText>
<TableCellText title="limit">{plan.storage} GB</TableCellText>
<TableCell title="excess">
<p class="u-color-text-danger">
<span class="icon-arrow-up" />
{humanFileSize(excess?.storage)}
</p>
</TableCell>
</TableRow>
{/if}
{#if excess?.executions}
<TableRow>
<TableCellText title="executions">Function executions</TableCellText>
<TableCellText title="limit">
{abbreviateNumber(plan.executions)} executions
</TableCellText>
<TableCell title="excess">
<p class="u-color-text-danger">
<span class="icon-arrow-up" />
<span
title={excess?.executions
? excess.executions.toString()
: 'executions'}>
{formatNum(excess?.executions)} executions
</span>
</p>
</TableCell>
</TableRow>
{/if}
{#if excess?.users}
<TableRow>
<TableCellText title="users">Users</TableCellText>
<TableCellText title="limit">
{abbreviateNumber(plan.users)} users
</TableCellText>
<TableCell title="excess">
<p class="u-color-text-danger">
<span class="icon-arrow-up" />
<span title={excess?.users ? excess.users.toString() : 'users'}>
{formatNum(excess?.users)} users
</span>
</p>
</TableCell>
</TableRow>
{/if}
</TableBody>
</TableScroll>
{/if}
4 changes: 3 additions & 1 deletion src/lib/elements/table/tableScroll.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -43,7 +45,7 @@
};
</script>

<div class="table-with-scroll" class:u-margin-block-start-32={!noMargin} data-private>
<div class="table-with-scroll {classes}" class:u-margin-block-start-32={!noMargin} data-private>
<div class="table-wrapper" use:hasOverflow={(v) => (isOverflowing = v)}>
<table
class="table"
Expand Down
28 changes: 24 additions & 4 deletions src/lib/stores/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import type {
Invoice,
PaymentList,
PlansMap,
PaymentMethodData
PaymentMethodData,
OrganizationUsage,
Plan
} from '$lib/sdk/billing';
import { isCloud } from '$lib/system';
import { cachedStore } from '$lib/helpers/cache';
Expand All @@ -25,6 +27,8 @@ import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte
import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentMethod.svelte';
import LimitReached from '$lib/components/billing/alerts/limitReached.svelte';
import { trackEvent } from '$lib/actions/analytics';
import { last } from '$lib/helpers/array';
import { sizeToBytes, type Size } from '$lib/helpers/sizeConvertion';

export type Tier = 'tier-0' | 'tier-1' | 'tier-2';

Expand Down Expand Up @@ -198,10 +202,9 @@ export async function checkForUsageLimit(org: Organization) {
{ value: users, name: 'users' }
];

const members = await sdk.forConsole.teams.listMemberships(org.$id);
const members = org.total;
const plan = get(plansInfo)?.get(org.billingPlan);
const membersOverflow =
members?.total > 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);
Expand Down Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -276,8 +278,8 @@
<LabelCard
name="plan"
bind:group={billingPlan}
value="tier-0"
disabled={!!anyOrgFree}
value="tier-0"
tooltipShow={!!anyOrgFree}
tooltipText="You are limited to 1 Free organization per account.">
<svelte:fragment slot="custom" let:disabled>
Expand Down Expand Up @@ -320,6 +322,9 @@
</LabelCard>
</li>
</ul>
{#if isDowngrade}
<PlanExcess tier={BillingPlan.STARTER} class="u-margin-block-start-24" />
{/if}
{#if billingPlan === BillingPlan.PRO && $organization.billingPlan !== BillingPlan.PRO}
<FormList class="u-margin-block-start-16">
<InputTags
Expand Down
Loading
Loading