Merge pull request #1067 from appwrite/redesign-plan-summary-card

redesign: plan summary card
This commit is contained in:
Torsten Dittmann
2024-05-21 20:13:25 +02:00
committed by GitHub
7 changed files with 179 additions and 141 deletions
+9 -9
View File
@@ -7,8 +7,8 @@
"name": "@appwrite/console",
"dependencies": {
"@appwrite.io/console": "^0.6.2",
"@appwrite.io/pink": "^0.19.0",
"@appwrite.io/pink-icons": "^0.19.0",
"@appwrite.io/pink": "0.20.0",
"@appwrite.io/pink-icons": "0.20.0",
"@popperjs/core": "^2.11.8",
"@sentry/svelte": "^7.66.0",
"@sentry/tracing": "^7.66.0",
@@ -162,19 +162,19 @@
"integrity": "sha512-3qYknuFwhvTN2GnPB8G1w5DgxfVorGtfj4uuEaXbTmMUmjA8fHaW5VkJriuUxCi6/TpxDxqV/hhEkdoXL+5H4w=="
},
"node_modules/@appwrite.io/pink": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/@appwrite.io/pink/-/pink-0.19.0.tgz",
"integrity": "sha512-b5yJCtkqRfikgqpi6rxN0je7FkLdvtSPQ3XNW49yLN7euHHuMr74qj8XvWrvv41ilikanvvUWUD2C8PLvmOy8Q==",
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@appwrite.io/pink/-/pink-0.20.0.tgz",
"integrity": "sha512-17anov7EL+Ic/4fNtCYgG3wA+hoeBXn/j+ZjkfZFfX3g66M8XzVTaQfsAH+EMZ3YjvWuKlHtn+bY4BkGwvMIzQ==",
"dependencies": {
"@appwrite.io/pink-icons": "0.19.0",
"@appwrite.io/pink-icons": "0.20.0",
"normalize.css": "^8.0.1",
"the-new-css-reset": "^1.11.2"
}
},
"node_modules/@appwrite.io/pink-icons": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/@appwrite.io/pink-icons/-/pink-icons-0.19.0.tgz",
"integrity": "sha512-qXzIQrY9OVFUJ7/qCf5oRJqJEJ+ECQpzlU0JiLycbPnu/nbq+pbaFDPma+7cTHsaD8znRC8X+a7P22JNrsN0rg=="
"version": "0.20.0",
"resolved": "https://registry.npmjs.org/@appwrite.io/pink-icons/-/pink-icons-0.20.0.tgz",
"integrity": "sha512-nfDy7qE6FQWVO1eC+3nwGjXkj/Zm2V50rhvyj0/HKq8RyWNWFGX31BZoMSim01vl/N5irBilpkaER6mLPp7oEw=="
},
"node_modules/@babel/code-frame": {
"version": "7.24.2",
+2 -2
View File
@@ -20,8 +20,8 @@
},
"dependencies": {
"@appwrite.io/console": "^0.6.2",
"@appwrite.io/pink": "^0.19.0",
"@appwrite.io/pink-icons": "^0.19.0",
"@appwrite.io/pink": "0.20.0",
"@appwrite.io/pink-icons": "0.20.0",
"@popperjs/core": "^2.11.8",
"@sentry/svelte": "^7.66.0",
"@sentry/tracing": "^7.66.0",
+5 -1
View File
@@ -1,3 +1,7 @@
<ul class="collapsible">
<script lang="ts">
export let fullWidth = false;
</script>
<ul class="collapsible" class:u-width-full-line={fullWidth}>
<slot />
</ul>
+44 -30
View File
@@ -4,40 +4,54 @@
export let withIndentation = false;
export let open = false;
export let disabled = false;
export let noContent = false;
export let isInfo = false;
export let gap = 16;
</script>
<li class="collapsible-item">
<details class="collapsible-wrapper" class:is-disabled={disabled} bind:open>
<!-- svelte-ignore a11y-no-redundant-roles -->
<summary
class="collapsible-button u-position-relative"
on:keyup={clickOnEnter}
on:click
role="button"
tabindex="0">
<slot name="beforetitle" />
<div>
<span class="text" class:u-color-text-disabled={disabled}
><slot name="title" /></span>
{#if $$slots.subtitle}
<span class="collapsible-button-optional" class:u-color-text-disabled={disabled}
><slot name="subtitle" /></span>
{/if}
<li class="collapsible-item" class:is-info={isInfo}>
{#if noContent}
<div class="collapsible-wrapper">
<div class={`collapsible-button u-gap-${gap}`}>
<slot />
</div>
<div class="icon">
<span
class="icon-cheveron-down u-font-size-20"
class:u-color-text-disabled={disabled}
aria-hidden="true" />
</div>
</summary>
<div
class="collapsible-content"
class:u-margin-block-start-8={withIndentation}
class:u-padding-inline-32={withIndentation}>
<slot />
</div>
</details>
{:else}
<details class="collapsible-wrapper" class:is-disabled={disabled} bind:open>
<!-- svelte-ignore a11y-no-redundant-roles -->
<summary
class={`collapsible-button u-position-relative u-gap-${gap}`}
on:keyup={clickOnEnter}
on:click
role="button"
tabindex="0">
<slot name="beforetitle" />
<div>
<span class="text" class:u-color-text-disabled={disabled}>
<slot name="title" /></span>
{#if $$slots.subtitle}
<span
class="collapsible-button-optional"
class:u-color-text-disabled={disabled}><slot name="subtitle" /></span>
{/if}
</div>
<slot name="end">
<div class="icon">
<span
class="icon-cheveron-down u-font-size-20"
class:u-color-text-disabled={disabled}
aria-hidden="true" />
</div>
</slot>
</summary>
<div
class="collapsible-content"
class:u-margin-block-start-8={withIndentation}
class:u-padding-inline-32={withIndentation}>
<slot />
</div>
</details>
{/if}
</li>
<style lang="scss">
+2 -1
View File
@@ -18,6 +18,7 @@
class:u-flex={right}
class:u-main-end={right}
data-title={title}
role="cell">
role="cell"
data-private>
<slot />
</div>
+8 -10
View File
@@ -1,18 +1,16 @@
<script lang="ts">
import { Trim } from '$lib/components';
export let title: string;
import Cell from './cell.svelte';
export let title = '';
export let showOverflow = false;
export let onlyDesktop = false;
export let width: number = null;
export let right = false;
let className = '';
export { className as class };
export let style = '';
</script>
<div
class="table-col"
style:--p-col-width={width?.toString()}
class:is-only-desktop={onlyDesktop}
class:u-overflow-visible={showOverflow}
data-title={title}
role="cell"
data-private>
<Cell {title} {showOverflow} {onlyDesktop} {width} {right} class={className} {style}>
<Trim><slot /></Trim>
</div>
</Cell>
@@ -1,7 +1,6 @@
<script lang="ts">
import { base } from '$app/paths';
import { Box, CardGrid, EyebrowHeading, Heading } from '$lib/components';
import { Pill } from '$lib/elements';
import { CardGrid, Collapsible, CollapsibleItem, Heading } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { toLocaleDate } from '$lib/helpers/date';
import { plansInfo, tierToPlan } from '$lib/stores/billing';
@@ -16,8 +15,10 @@
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { BillingPlan } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
import { tooltip } from '$lib/actions/tooltip';
let currentInvoice: Invoice;
let extraMembers = 0;
const today = new Date();
onMount(async () => {
const invoices = await sdk.forConsole.billing.listInvoices($organization.$id, [
@@ -25,10 +26,13 @@
Query.orderDesc('$createdAt')
]);
currentInvoice = invoices.invoices[0];
const members = await sdk.forConsole.teams.listMemberships($organization.$id, []);
extraMembers = members.total > 1 ? members.total - 1 : 0;
});
$: currentPlan = $plansInfo?.get($organization?.billingPlan);
$: extraUsage = currentInvoice?.amount - currentPlan?.price;
$: extraUsage = (currentInvoice?.amount ?? 0) - (currentPlan?.price ?? 0);
$: extraAddons = currentInvoice?.usage?.length ?? 0;
$: isTrial =
new Date($organization?.billingStartDate).getTime() - today.getTime() > 0 &&
$plansInfo.get($organization.billingPlan)?.trialDays;
@@ -36,102 +40,119 @@
{#if $organization}
<CardGrid>
<Heading tag="h2" size="6">Overview</Heading>
<Heading tag="h2" size="6">Payment estimates</Heading>
<p class="text">
A summary of the current plan. For more information on Appwrite plans, <a
type="button"
class="link"
href="https://appwrite.io/pricing"
target="_blank"
rel="noopener noreferrer">
view our pricing page.
</a>
A breakdown of your estimated upcoming payment for the current billing period. Totals
displayed exclude accumulated credits.
</p>
<svelte:fragment slot="aside">
<Box class="u-flex-vertical u-gap-8">
<div class="u-flex u-main-space-between u-cross-center">
<div class="u-flex u-gap-8 u-cross-center">
<h6 class="body-text-1 u-bold u-trim-1">
{tierToPlan($organization?.billingPlan)?.name} plan
</h6>
{#if $organization?.billingPlan !== BillingPlan.STARTER && isTrial}
<Pill>TRIAL</Pill>
{/if}
<p class="text u-bold">
Billing period: {toLocaleDate($organization?.billingCurrentInvoiceDate)} - {toLocaleDate(
$organization?.billingNextInvoiceDate
)}
</p>
<Collapsible>
<CollapsibleItem noContent>
<span class="body-text-2">
{tierToPlan($organization?.billingPlan)?.name} plan</span>
<div class="body-text-2 u-margin-inline-start-auto">
{isTrial ? formatCurrency(0) : formatCurrency(currentPlan?.price)}
</div>
</CollapsibleItem>
{#if $organization?.billingPlan !== BillingPlan.STARTER && extraUsage}
<CollapsibleItem isInfo gap={8}>
<svelte:fragment slot="beforetitle">
<span class="body-text-2"><b>Add-ons</b></span><span class="inline-tag"
>{extraMembers ? extraAddons + 1 : extraAddons}</span>
<div class="icon">
<span class="icon-cheveron-down" aria-hidden="true"></span>
</div>
</svelte:fragment>
<svelte:fragment slot="end">
<div class="body-text-2 u-margin-inline-start-auto">
<b>
{formatCurrency(extraUsage >= 0 ? extraUsage : 0)}
</b>
</div>
</svelte:fragment>
<p class="text">
{#if !extraUsage}
<span class="text u-color-text-gray">Total to-date:</span>
{/if}
<span class="body-text-1">
{isTrial ? formatCurrency(0) : formatCurrency(currentPlan?.price)}
</span>
</p>
</div>
{#if currentInvoice?.usage?.length && $organization?.billingPlan !== BillingPlan.STARTER && !isTrial}
{@const extraMembers = currentInvoice.usage.find((u) => u.name === 'members')}
{#if extraMembers}
<div class="u-margin-block-start-24">
<EyebrowHeading tag="h6" size={3}>Additional members</EyebrowHeading>
<ul>
<li class="u-flex u-main-space-between u-margin-block-start-8">
<p class="text u-color-text-gray">
<span> {extraMembers.value}</span>
{extraMembers.name}
</p>
<p class="text">{formatCurrency(extraMembers.amount)}</p>
</li>
</ul>
</div>
{/if}
<div class="u-margin-block-start-24">
<EyebrowHeading tag="h6" size={3}>Addons</EyebrowHeading>
<ul>
{#each currentInvoice.usage as excess}
{#if ['storage', 'bandwidth'].includes(excess.name)}
{@const excessValue = humanFileSize(excess.value)}
<li class="u-flex u-main-space-between u-margin-block-start-8">
<p class="text u-color-text-gray">
<span
title={formatNumberWithCommas(excess.value) +
{#if extraMembers}
<li class="u-flex-vertical u-gap-4 u-padding-block-8">
<div class="u-flex u-gap-4">
<h5 class="body-text-2 u-stretch">Additional members</h5>
<div>
{formatCurrency(
extraMembers * currentPlan.addons.member.price
)}
</div>
</div>
<div class="u-flex u-gap-4">
<h5 class="body-text-2 u-stretch u-color-text-offline">
{extraMembers}
</h5>
</div>
</li>
{/if}
{#if currentInvoice?.usage}
{#each currentInvoice.usage as excess, i}
<li
class="u-flex-vertical u-gap-4 {extraMembers
? 'u-padding-block-8'
: 'u-padding-block-start-8'}"
class:u-sep-block-start={i > 0 || extraMembers}>
{#if ['storage', 'bandwidth'].includes(excess.name)}
{@const excessValue = humanFileSize(excess.value)}
<div class="u-flex u-main-space-between">
<h5 class="body-text-2 u-stretch u-capitalize">
{excess.name}
</h5>
{formatCurrency(excess.amount)}
</div>
<h5
class="body-text-2 u-stretch u-color-text-offline"
title={formatNumberWithCommas(excess.value ?? 0) +
'bytes'}>
{excessValue.value}{excessValue.unit}
</span>
{excess.name}
</p>
<p class="text">{formatCurrency(excess.amount)}</p>
{excessValue.value ?? 0}{excessValue.unit}
</h5>
{/if}
{#if ['users', 'executions'].includes(excess.name)}
<div class="u-flex u-main-space-between">
<h5 class="body-text-2 u-stretch u-capitalize">
{excess.name}
</h5>
{formatCurrency(excess.amount)}
</div>
<h5
class="body-text-2 u-stretch u-color-text-offline"
title={formatNumberWithCommas(excess.value)}>
{abbreviateNumber(excess.value)}
</h5>
{/if}
</li>
{/if}
{#if ['users', 'executions'].includes(excess.name)}
<li class="u-flex u-main-space-between u-margin-block-start-8">
<p class="text u-color-text-gray">
<span title={formatNumberWithCommas(excess.value)}
>{abbreviateNumber(excess.value)}</span>
{excess.name}
</p>
<p class="text">{formatCurrency(excess.amount)}</p>
</li>
{/if}
{/each}
<li class="u-flex u-main-space-between u-margin-block-start-16">
<p class="body-text-1 u-bold">Total to-date:</p>
<p class="body-text-1 u-bold">
{formatCurrency(currentInvoice?.amount ?? 0)}
</p>
</li>
{/each}
{/if}
</ul>
</div>
</CollapsibleItem>
{/if}
</Box>
<div class="u-flex u-main-space-between u-cross-center">
<p class="body-text-1 u-bold">Billing period:</p>
<p class="text">
{toLocaleDate($organization?.billingCurrentInvoiceDate)} - {toLocaleDate(
$organization?.billingNextInvoiceDate
)}
</p>
</div>
<CollapsibleItem noContent gap={4}>
<span class="body-text-2">Current total (USD)</span>
<span class="tooltip u-cross-center" aria-label="total info">
<span
class="icon-info"
aria-hidden="true"
use:tooltip={{
content:
'Totals displayed are estimates updated every 24 hours and may not accurately reflect your invoice.'
}}></span>
</span>
<div class="body-text-2 u-margin-inline-start-auto">
{formatCurrency(currentInvoice?.amount ?? 0)}
</div>
</CollapsibleItem>
</Collapsible>
</svelte:fragment>
<svelte:fragment slot="actions">
{#if $organization?.billingPlan === BillingPlan.STARTER}