Skip to content
Draft
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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@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",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 5 additions & 6 deletions src/lib/components/backupDatabaseAlert.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import { page } from '$app/state';
import { BillingPlan } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { organization } from '$lib/stores/organization';
import { HeaderAlert } from '$lib/layout';
Expand All @@ -18,14 +17,14 @@
</script>

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

<HeaderAlert type="warning" title="Your database has no backup policy">
<svelte:fragment>{subtitle}</svelte:fragment>
Expand All @@ -35,7 +34,7 @@
href={ctaURL}
secondary
fullWidthMobile
event={isFreePlan ? 'backup_banner_upgrade' : 'backup_banner_add'}>
event={!areBackupsAvailable ? 'backup_banner_upgrade' : 'backup_banner_add'}>
<span class="text">{ctaText}</span>
</Button>

Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/backupRestoreBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
17 changes: 5 additions & 12 deletions src/lib/components/billing/alerts/limitReached.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';
</script>

{#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)}
<HeaderAlert
type="error"
title={`${$organization.name} usage has reached the ${billingIdToPlan($organization.billingPlan).name} plan limit`}>
title={`${$organization.name} usage has reached the ${$organization.billingPlanDetails.name} plan limit`}>
<svelte:fragment>
Usage for the <b>{$organization.name}</b> organization has reached the limits of the {billingIdToPlan(
$organization.billingPlan
).name}
Usage for the <b>{$organization.name}</b> organization has reached the limits of the {$organization
.billingPlanDetails.name}
plan. Consider upgrading to increase your resource usage.
</svelte:fragment>
<svelte:fragment slot="buttons">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/state';
import { BillingPlan } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { HeaderAlert } from '$lib/layout';
import { hideBillingHeaderRoutes } from '$lib/stores/billing';
import { orgMissingPaymentMethod } from '$routes/(console)/store';
</script>

{#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)}
<HeaderAlert
type="error"
title={`Payment method required for ${$orgMissingPaymentMethod.name}`}>
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/billing/alerts/newDevUpgradePro.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -23,7 +23,7 @@
}
</script>

{#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')}
<GradientBanner on:close={handleClose}>
<Layout.Stack
gap="m"
Expand Down
7 changes: 4 additions & 3 deletions src/lib/components/billing/estimatedTotalBox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { AppwriteException, BillingPlan, type Models } from '@appwrite.io/console';
import DiscountsApplied from './discountsApplied.svelte';

export let billingPlan: string;
export let billingPlan: Models.BillingPlan;
export let collaborators: string[];
export let couponData: Partial<Models.Coupon>;
export let billingBudget: number;
Expand Down Expand Up @@ -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);
</script>

{#if estimation}
Expand All @@ -106,6 +106,7 @@
{#if couponData?.status === 'active'}
<CreditsApplied bind:couponData {fixedCoupon} />
{/if}

<Divider />
<Layout.Stack direction="row" justifyContent="space-between">
<Typography.Text>Total due</Typography.Text>
Expand Down
3 changes: 1 addition & 2 deletions src/lib/components/billing/planComparisonBox.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script lang="ts">
import { page } from '$app/state';
import { BillingPlan } from '$lib/constants';
import { formatNum } from '$lib/helpers/string';
import { BillingPlanGroup, type Models } from '@appwrite.io/console';
import { Card, Layout, Tabs, Typography } from '@appwrite.io/pink-svelte';
Expand All @@ -17,7 +16,7 @@
const currentPlan: Models.BillingPlan = $derived($plansInfo.get(selectedTab));
const visiblePlans: Array<Models.BillingPlan> = $derived.by(() => {
return page.data.plans.plans.filter(
(plan: Models.BillingPlan) => plan.$id !== BillingPlan.SCALE
(plan: Models.BillingPlan) => plan.group !== BillingPlanGroup.Scale
);
});

Expand Down
51 changes: 35 additions & 16 deletions src/lib/components/billing/planSelection.svelte
Original file line number Diff line number Diff line change
@@ -1,37 +1,56 @@
<script lang="ts">
import { BillingPlan } from '$lib/constants';
import { LabelCard } from '..';
import { page } from '$app/state';
import { formatCurrency } from '$lib/helpers/numbers';
import { currentPlan, organization } from '$lib/stores/organization';
import { BillingPlanGroup, type Models } from '@appwrite.io/console';
import { Badge, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte';
import { LabelCard } from '..';
import { page } from '$app/state';
import type { Models } from '@appwrite.io/console';
import { billingIdToPlan } from '$lib/stores/billing';

export let billingPlan: BillingPlan;
export let isNewOrg = false;
export let selfService = true;
export let anyOrgFree = false;
let {
isNewOrg = false,
selfService = true,
anyOrgFree = false,
selectedBillingPlan = $bindable()
}: {
isNewOrg?: boolean;
selfService?: boolean;
anyOrgFree?: boolean;
selectedBillingPlan: Models.BillingPlan;
} = $props();

$: plans = Object.values(page.data.plans.plans) as Models.BillingPlan[];
$: currentPlanInList = plans.some((plan) => plan.$id === $currentPlan?.$id);
let selectedPlan = $state(selectedBillingPlan.$id);

const plans = $derived(Object.values(page.data.plans.plans) as Models.BillingPlan[]);
const currentPlanInList = $derived(plans.some((plan) => plan.$id === $currentPlan?.$id));

// experiment to remove scale plan temporarily
$: plansWithoutScale = plans.filter((plan) => plan.$id != BillingPlan.SCALE);
const plansWithoutScale = $derived(
plans.filter((plan) => plan.group != BillingPlanGroup.Scale)
);

function shouldShowTooltip(plan: Models.BillingPlan) {
if (plan.$id !== BillingPlan.FREE) return true;
if (plan.group !== BillingPlanGroup.Starter) return true;
else return !anyOrgFree;
}

function shouldDisable(plan: Models.BillingPlan) {
return plan.group === BillingPlanGroup.Starter && anyOrgFree;
}

$effect(() => {
selectedBillingPlan = billingIdToPlan(selectedPlan);
});
</script>

<Layout.Stack>
{#each plansWithoutScale as plan}
<Tooltip disabled={shouldShowTooltip(plan)} maxWidth="fit-content">
<LabelCard
name="plan"
bind:group={billingPlan}
disabled={!selfService || (plan.$id === BillingPlan.FREE && anyOrgFree)}
tooltipShow={plan.$id === BillingPlan.FREE && anyOrgFree}
bind:group={selectedPlan}
disabled={!selfService || shouldDisable(plan)}
tooltipShow={shouldDisable(plan)}
value={plan.$id}
title={plan.name}>
<svelte:fragment slot="action">
Expand All @@ -57,7 +76,7 @@
{#if $currentPlan && !currentPlanInList}
<LabelCard
name="plan"
bind:group={billingPlan}
bind:group={selectedPlan}
value={$currentPlan.$id}
title={$currentPlan.name}>
<svelte:fragment slot="action">
Expand Down
17 changes: 13 additions & 4 deletions src/lib/components/billing/selectPlan.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
<script lang="ts">
import { BillingPlan } from '$lib/constants';
import { formatCurrency } from '$lib/helpers/numbers';
import { plansInfo } from '$lib/stores/billing';
import { organization } from '$lib/stores/organization';
import { LabelCard } from '..';
import { BillingPlanGroup, type Models } from '@appwrite.io/console';

export let billingPlan: string;
export let anyOrgFree = false;
export let isNewOrg = false;
let classes: string = '';
export { classes as class };

function shouldDisable(plan: Models.BillingPlan) {
return plan.group === BillingPlanGroup.Starter && anyOrgFree;
}

function shouldShowTooltip(plan: Models.BillingPlan) {
if (plan.group !== BillingPlanGroup.Starter) return true;
else return !anyOrgFree;
}
</script>

{#if billingPlan}
Expand All @@ -19,10 +28,10 @@
<LabelCard
name="plan"
bind:group={billingPlan}
disabled={(plan.$id === BillingPlan.FREE && anyOrgFree) || !plan.selfService}
disabled={!plan.selfService || shouldDisable(plan)}
value={plan.$id}
tooltipShow={plan.$id === BillingPlan.FREE && anyOrgFree}
tooltipText={plan.$id === BillingPlan.FREE
tooltipShow={shouldShowTooltip(plan)}
tooltipText={plan.group === BillingPlanGroup.Starter
? 'You are limited to 1 Free organization per account.'
: ''}>
<svelte:fragment slot="custom" let:disabled>
Expand Down
24 changes: 10 additions & 14 deletions src/lib/components/billing/usageRates.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand All @@ -27,22 +23,22 @@
};

function getPlanLimit(key: string): number | false {
return plan[key] || false;
return org.billingPlanDetails[key] || false;
}
</script>

<Modal bind:show title="Usage rates">
{#if isFree}
<Typography.Text>
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)}.
</Typography.Text>
{:else if org.billingPlan === BillingPlan.PRO}
{:else if org.billingPlanDetails.group === BillingPlanGroup.Pro}
<Typography.Text>
Usage on the Pro plan will be charged at the end of each billing period at the following
rates. Next billing period: {toLocaleDate(nextDate)}.
</Typography.Text>
{:else if org.billingPlan === BillingPlan.SCALE}
{:else if org.billingPlanDetails.group === BillingPlanGroup.Scale}
<Typography.Text>
Usage on the Scale plan will be charged at the end of each billing period at the
following rates. Next billing period: {toLocaleDate(nextDate)}.
Expand All @@ -56,7 +52,7 @@
<Table.Header.Cell column="limit" {root}>Limit</Table.Header.Cell>
<Table.Header.Cell column="rate" {root}>Rate</Table.Header.Cell>
</svelte:fragment>
{#each Object.values(plan.addons) as addon}
{#each Object.values(org.billingPlanDetails.addons) as addon}
<Table.Row.Base {root}>
<Table.Cell column="resource" {root}>{addon.invoiceDesc}</Table.Cell>
<Table.Cell column="limit" {root}>
Expand All @@ -69,7 +65,7 @@
{/if}
</Table.Row.Base>
{/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}
Expand Down
Loading