mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: add comparison box
This commit is contained in:
Generated
+2
-2
@@ -7,8 +7,8 @@
|
||||
"name": "@appwrite/console",
|
||||
"dependencies": {
|
||||
"@appwrite.io/console": "^0.6.1",
|
||||
"@appwrite.io/pink": "0.11.0",
|
||||
"@appwrite.io/pink-icons": "0.11.0",
|
||||
"@appwrite.io/pink": "0.16.0",
|
||||
"@appwrite.io/pink-icons": "0.16.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/svelte": "^7.66.0",
|
||||
"@sentry/tracing": "^7.66.0",
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<script lang="ts">
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { FormList, InputChoice, InputNumber } from '$lib/elements/forms';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import type { Coupon } from '$lib/sdk/billing';
|
||||
import { plansInfo, type Tier } from '$lib/stores/billing';
|
||||
import { Box } from '..';
|
||||
|
||||
export let billingPlan: Tier;
|
||||
export let collaborators: string[];
|
||||
export let couponData: Partial<Coupon>;
|
||||
export let billingBudget: number;
|
||||
|
||||
const today = new Date();
|
||||
const billingPayDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
let budgetEnabled = false;
|
||||
|
||||
$: currentPlan = $plansInfo.get(billingPlan);
|
||||
$: extraSeatsCost = (collaborators?.length ?? 0) * (currentPlan?.addons?.member?.price ?? 0);
|
||||
$: grossCost = currentPlan.price + extraSeatsCost;
|
||||
$: estimatedTotal =
|
||||
couponData?.status === 'active'
|
||||
? grossCost - couponData.credits >= 0
|
||||
? grossCost - couponData.credits
|
||||
: 0
|
||||
: grossCost;
|
||||
</script>
|
||||
|
||||
<Box class="u-margin-block-start-32 u-flex u-flex-vertical u-gap-16" radius="small">
|
||||
<span class="u-flex u-main-space-between">
|
||||
<p class="text">{currentPlan.name} plan</p>
|
||||
<p class="text">{formatCurrency(currentPlan.price)}</p>
|
||||
</span>
|
||||
<span class="u-flex u-main-space-between">
|
||||
<p class="text">Additional seats ({collaborators?.length})</p>
|
||||
<p class="text">
|
||||
{formatCurrency(extraSeatsCost)}
|
||||
</p>
|
||||
</span>
|
||||
{#if couponData?.status === 'active'}
|
||||
<span class="u-flex u-main-space-between">
|
||||
<div class="u-flex u-cross-center u-gap-4">
|
||||
<p class="text">
|
||||
<span class="icon-tag u-color-text-success" aria-hidden="true" />
|
||||
{#if couponData.credits > 100}
|
||||
{couponData.code.toUpperCase()}
|
||||
{:else}
|
||||
<span use:tooltip={{ content: couponData.code.toUpperCase() }}
|
||||
>Credits applied</span>
|
||||
{/if}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-text is-only-icon"
|
||||
style="--button-size:1.5rem;"
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
on:click={() =>
|
||||
(couponData = {
|
||||
code: null,
|
||||
status: null,
|
||||
credits: null
|
||||
})}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{#if couponData.credits > 100}
|
||||
<p class="inline-tag" use:tooltip={{ content: formatCurrency(couponData.credits) }}>
|
||||
Credits applied
|
||||
</p>
|
||||
{:else}
|
||||
<span class="u-color-text-success">-{formatCurrency(couponData.credits)}</span>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="u-sep-block-start" />
|
||||
<span class="u-flex u-main-space-between">
|
||||
<p class="text">Estimated total</p>
|
||||
<p class="text">
|
||||
{formatCurrency(estimatedTotal)}
|
||||
</p>
|
||||
</span>
|
||||
|
||||
{@const trialEndDate = new Date(
|
||||
billingPayDate.getTime() + currentPlan.trialDays * 24 * 60 * 60 * 1000
|
||||
)}
|
||||
<p class="text u-margin-block-start-16">
|
||||
Your payment method will be charged this amount plus usage fees every 30 days {!currentPlan.trialDays
|
||||
? `starting ${toLocaleDate(billingPayDate.toString())}`
|
||||
: ` after your trial period ends on ${toLocaleDate(trialEndDate.toString())}`}.
|
||||
</p>
|
||||
<FormList>
|
||||
<InputChoice
|
||||
type="switchbox"
|
||||
id="budget"
|
||||
label="Enable budget cap"
|
||||
tooltip="If enabled, you will be notified when your spending reaches 75% of the set cap. Update cap alerts in your organization settings."
|
||||
fullWidth
|
||||
bind:value={budgetEnabled}>
|
||||
{#if budgetEnabled}
|
||||
<div class="u-margin-block-start-16">
|
||||
<InputNumber
|
||||
id="budget"
|
||||
label="Budget cap (USD)"
|
||||
placeholder="0"
|
||||
min={0}
|
||||
bind:value={billingBudget} />
|
||||
</div>
|
||||
{/if}
|
||||
</InputChoice>
|
||||
</FormList>
|
||||
</Box>
|
||||
@@ -2,3 +2,5 @@ export { default as PaymentBoxes } from './paymentBoxes.svelte';
|
||||
export { default as CouponInput } from './couponInput.svelte';
|
||||
export { default as SelectPaymentMethod } from './selectPaymentMethod.svelte';
|
||||
export { default as UsageRates } from './usageRates.svelte';
|
||||
export { default as EstimatedTotalBox } from './estimatedTotalBox.svelte';
|
||||
export { default as PlanComparisonBox } from './planComparisonBox.svelte';
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import { formatNum } from '$lib/helpers/string';
|
||||
import { plansInfo } from '$lib/stores/billing';
|
||||
import { Box, SecondaryTabs, SecondaryTabsItem } from '..';
|
||||
|
||||
let selectedTab: 'tier-0' | 'tier-1' = 'tier-0';
|
||||
|
||||
$: plan = $plansInfo.get(selectedTab);
|
||||
</script>
|
||||
|
||||
<Box>
|
||||
<SecondaryTabs stretch>
|
||||
<SecondaryTabsItem
|
||||
disabled={selectedTab === 'tier-0'}
|
||||
on:click={() => (selectedTab = 'tier-0')}>
|
||||
Starter
|
||||
</SecondaryTabsItem>
|
||||
<SecondaryTabsItem
|
||||
disabled={selectedTab === 'tier-1'}
|
||||
on:click={() => (selectedTab = 'tier-1')}>
|
||||
Pro
|
||||
</SecondaryTabsItem>
|
||||
</SecondaryTabs>
|
||||
|
||||
<div class="u-margin-block-start-24">
|
||||
{#if selectedTab === 'tier-0'}
|
||||
<h3 class="u-bold body-text-1">{plan.name} plan</h3>
|
||||
<ul class="list u-margin-block-start-8">
|
||||
<li class="list-item">
|
||||
Limited to {plan.databases} Database, {plan.buckets} Buckets, {plan.functions} Functions
|
||||
per project
|
||||
</li>
|
||||
<li class="list-item">Limited to 1 organization member</li>
|
||||
<li class="list-item">{plan.bandwidth}GB bandwidth</li>
|
||||
<li class="list-item">{plan.storage}GB storage</li>
|
||||
<li class="list-item">{formatNum(plan.executions)} executions</li>
|
||||
</ul>
|
||||
{:else if selectedTab === 'tier-1'}
|
||||
<h3 class="u-bold body-text-1">{plan.name} plan</h3>
|
||||
<ul class="list u-margin-block-start-8">
|
||||
<li class="list-item">Everything in the Starter plan, plus:</li>
|
||||
<li class="list-item">Unlimited databases, buckets, functions</li>
|
||||
<li class="list-item">{plan.bandwidth}GB bandwidth</li>
|
||||
<li class="list-item">{plan.storage}GB storage</li>
|
||||
<li class="list-item">{formatNum(plan.executions)} executions</li>
|
||||
<li class="list-item">Email support</li>
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</Box>
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let large = false;
|
||||
export let stretch = false;
|
||||
let classes: string = undefined;
|
||||
export { classes as class };
|
||||
</script>
|
||||
|
||||
<ul class="secondary-tabs {classes}" class:is-large={large}>
|
||||
<ul class="secondary-tabs {classes}" class:is-large={large} class:is-stretch={stretch}>
|
||||
<slot />
|
||||
</ul>
|
||||
|
||||
@@ -3,22 +3,15 @@
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { Box, LabelCard } from '$lib/components';
|
||||
import { SelectPaymentMethod } from '$lib/components/billing';
|
||||
import { LabelCard } from '$lib/components';
|
||||
import {
|
||||
EstimatedTotalBox,
|
||||
PlanComparisonBox,
|
||||
SelectPaymentMethod
|
||||
} from '$lib/components/billing';
|
||||
import ValidateCreditModal from '$lib/components/billing/validateCreditModal.svelte';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
FormList,
|
||||
InputChoice,
|
||||
InputNumber,
|
||||
InputTags,
|
||||
InputText,
|
||||
Label
|
||||
} from '$lib/elements/forms';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { Button, Form, FormList, InputTags, InputText, Label } from '$lib/elements/forms';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import {
|
||||
WizardSecondaryContainer,
|
||||
@@ -39,9 +32,6 @@
|
||||
(org) => (org as Organization)?.billingPlan === BillingPlan.STARTER
|
||||
);
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$/i;
|
||||
const today = new Date();
|
||||
const billingPayDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
let previousPage: string = `${base}/console`;
|
||||
|
||||
afterNavigate(({ from }) => {
|
||||
@@ -63,7 +53,7 @@
|
||||
credits: null
|
||||
};
|
||||
let taxId: string;
|
||||
let budgetEnabled = false;
|
||||
|
||||
let billingBudget: number;
|
||||
let showCreditModal = false;
|
||||
|
||||
@@ -190,15 +180,6 @@
|
||||
$: if (billingPlan === BillingPlan.PRO) {
|
||||
loadPaymentMethods();
|
||||
}
|
||||
$: currentPlan = $plansInfo.get(billingPlan);
|
||||
$: extraSeatsCost = (collaborators?.length ?? 0) * (currentPlan?.addons?.member?.price ?? 0);
|
||||
$: grossCost = currentPlan.price + extraSeatsCost;
|
||||
$: estimatedTotal =
|
||||
couponData?.status === 'active'
|
||||
? grossCost - couponData.credits >= 0
|
||||
? grossCost - couponData.credits
|
||||
: 0
|
||||
: grossCost;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -293,97 +274,13 @@
|
||||
</Form>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if billingPlan !== BillingPlan.STARTER}
|
||||
<Box class="u-margin-block-start-32 u-flex u-flex-vertical u-gap-16" radius="small">
|
||||
<span class="u-flex u-main-space-between">
|
||||
<p class="text">{currentPlan.name} plan</p>
|
||||
<p class="text">{formatCurrency(currentPlan.price)}</p>
|
||||
</span>
|
||||
<span class="u-flex u-main-space-between">
|
||||
<p class="text">Additional seats ({collaborators?.length})</p>
|
||||
<p class="text">
|
||||
{formatCurrency(extraSeatsCost)}
|
||||
</p>
|
||||
</span>
|
||||
{#if couponData?.status === 'active'}
|
||||
<span class="u-flex u-main-space-between">
|
||||
<div class="u-flex u-cross-center u-gap-4">
|
||||
<p class="text">
|
||||
<span
|
||||
class="icon-tag u-color-text-success"
|
||||
aria-hidden="true" />
|
||||
{#if couponData.credits > 100}
|
||||
{couponData.code.toUpperCase()}
|
||||
{:else}
|
||||
<span
|
||||
use:tooltip={{ content: couponData.code.toUpperCase() }}
|
||||
>Credits applied</span>
|
||||
{/if}
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
class="button is-text is-only-icon"
|
||||
style="--button-size:1.5rem;"
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
on:click={() =>
|
||||
(couponData = {
|
||||
code: null,
|
||||
status: null,
|
||||
credits: null
|
||||
})}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{#if couponData.credits > 100}
|
||||
<p
|
||||
class="inline-tag"
|
||||
use:tooltip={{ content: formatCurrency(couponData.credits) }}>
|
||||
Credits applied
|
||||
</p>
|
||||
{:else}
|
||||
<span class="u-color-text-success"
|
||||
>-{formatCurrency(couponData.credits)}</span>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="u-sep-block-start" />
|
||||
<span class="u-flex u-main-space-between">
|
||||
<p class="text">Estimated total</p>
|
||||
<p class="text">
|
||||
{formatCurrency(estimatedTotal)}
|
||||
</p>
|
||||
</span>
|
||||
|
||||
{@const trialEndDate = new Date(
|
||||
billingPayDate.getTime() + currentPlan.trialDays * 24 * 60 * 60 * 1000
|
||||
)}
|
||||
<p class="text u-margin-block-start-16">
|
||||
Your payment method will be charged this amount plus usage fees every 30
|
||||
days {!currentPlan.trialDays
|
||||
? `starting ${toLocaleDate(billingPayDate.toString())}`
|
||||
: ` after your trial period ends on ${toLocaleDate(trialEndDate.toString())}`}.
|
||||
</p>
|
||||
<FormList>
|
||||
<InputChoice
|
||||
type="switchbox"
|
||||
id="budget"
|
||||
label="Enable budget cap"
|
||||
tooltip="If enabled, you will be notified when your spending reaches 75% of the set cap. Update cap alerts in your organization settings."
|
||||
fullWidth
|
||||
bind:value={budgetEnabled}>
|
||||
{#if budgetEnabled}
|
||||
<div class="u-margin-block-start-16">
|
||||
<InputNumber
|
||||
id="budget"
|
||||
label="Budget cap (USD)"
|
||||
placeholder="0"
|
||||
min={0}
|
||||
bind:value={billingBudget} />
|
||||
</div>
|
||||
{/if}
|
||||
</InputChoice>
|
||||
</FormList>
|
||||
</Box>
|
||||
<EstimatedTotalBox
|
||||
{billingPlan}
|
||||
{collaborators}
|
||||
bind:couponData
|
||||
bind:billingBudget />
|
||||
{:else}
|
||||
<PlanComparisonBox />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</WizardSecondaryContent>
|
||||
|
||||
Reference in New Issue
Block a user