mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Migrate UI components to Pink design library
The commit updates various UI components to use the new Pink design library, replacing older custom components. Key changes include: - Replace custom Alert components with Pink's Alert.Inline - Replace custom Table components with Pink's Table.Root/Cell structure - Update form inputs and selectors to use Pink variants - Remove unnecessary wrapper elements and classes - Adjust layout and typography to match Pink patterns - Clean up redundant styles and markup Focus is on maintaining functionality while adopting the new design system components.
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { Remarkable } from 'remarkable';
|
||||
import Template from './template.svelte';
|
||||
import { Keyboard, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Alert, Keyboard, Layout } from '@appwrite.io/pink-svelte';
|
||||
|
||||
const markdownInstance = new Remarkable();
|
||||
|
||||
import { Alert, AvatarInitials, Code, LoadingDots, SvgIcon } from '$lib/components';
|
||||
import { AvatarInitials, Code, LoadingDots, SvgIcon } from '$lib/components';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { useCompletion } from 'ai/svelte';
|
||||
import { subPanels } from '../subPanels';
|
||||
@@ -218,13 +218,9 @@
|
||||
|
||||
{#if $error}
|
||||
<div style="padding: 1rem; padding-block-end: 0;">
|
||||
<Alert type="error">
|
||||
<span slot="title">Something went wrong</span>
|
||||
<p>
|
||||
An unexpected error occurred while handling your request. Please try again
|
||||
later.
|
||||
</p>
|
||||
</Alert>
|
||||
<Alert.Inline status="error" title="Something went wrong">
|
||||
An unexpected error occurred while handling your request. Please try again later.
|
||||
</Alert.Inline>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -36,42 +36,34 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormList gap={8}>
|
||||
<InputText
|
||||
placeholder="Coupon code"
|
||||
id="code"
|
||||
label="Add credits"
|
||||
{required}
|
||||
hideRequired
|
||||
disabled={couponData?.status === 'active'}
|
||||
bind:value={coupon}>
|
||||
<Button
|
||||
secondary
|
||||
disabled={couponData?.status === 'active' || !coupon}
|
||||
on:click={addCoupon}>
|
||||
Apply
|
||||
</Button>
|
||||
</InputText>
|
||||
{#if couponData?.status === 'error'}
|
||||
<InputText
|
||||
placeholder="Coupon code"
|
||||
id="code"
|
||||
label="Add credits"
|
||||
{required}
|
||||
disabled={couponData?.status === 'active'}
|
||||
bind:value={coupon}>
|
||||
<Button secondary disabled={couponData?.status === 'active' || !coupon} on:click={addCoupon}>
|
||||
Apply
|
||||
</Button>
|
||||
</InputText>
|
||||
{#if couponData?.status === 'error'}
|
||||
<div>
|
||||
<span class="icon-exclamation-circle u-color-text-danger" />
|
||||
<span>
|
||||
{couponData.code.toUpperCase()} is not a valid promo code
|
||||
</span>
|
||||
</div>
|
||||
{:else if couponData?.status === 'active'}
|
||||
<div class="u-flex u-main-space-between u-cross-center">
|
||||
<div>
|
||||
<span class="icon-exclamation-circle u-color-text-danger" />
|
||||
<span>
|
||||
{couponData.code.toUpperCase()} is not a valid promo code
|
||||
</span>
|
||||
<span class="icon-tag u-color-text-success" />
|
||||
<slot data={couponData}>
|
||||
<span>
|
||||
{couponData.code.toUpperCase()} applied (-{formatCurrency(couponData.credits)})
|
||||
</span>
|
||||
</slot>
|
||||
</div>
|
||||
{:else if couponData?.status === 'active'}
|
||||
<div class="u-flex u-main-space-between u-cross-center">
|
||||
<div>
|
||||
<span class="icon-tag u-color-text-success" />
|
||||
<slot data={couponData}>
|
||||
<span>
|
||||
{couponData.code.toUpperCase()} applied (-{formatCurrency(
|
||||
couponData.credits
|
||||
)})
|
||||
</span>
|
||||
</slot>
|
||||
</div>
|
||||
<Button icon text on:click={removeCoupon}><span class="icon-x"></span></Button>
|
||||
</div>
|
||||
{/if}
|
||||
</FormList>
|
||||
<Button icon text on:click={removeCoupon}><span class="icon-x"></span></Button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { tierToPlan, upgradeURL } from '$lib/stores/billing';
|
||||
import { Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Card } from '..';
|
||||
|
||||
export let service: string;
|
||||
@@ -11,14 +12,13 @@
|
||||
|
||||
<Card>
|
||||
<slot>
|
||||
<div class="u-flex u-flex-vertical u-main-center u-cross-center u-gap-8">
|
||||
<h6 class="body-text-1 u-bold u-trim-1">Upgrade to add {service}</h6>
|
||||
<p class="text u-text-center">
|
||||
<Layout.Stack alignItems="center">
|
||||
<Typography.Text variant="m-600">Upgrade to add {service}</Typography.Text>
|
||||
<Typography.Text>
|
||||
Upgrade to a {tierToPlan(BillingPlan.PRO).name} plan to add {service} to your organization
|
||||
</p>
|
||||
</Typography.Text>
|
||||
|
||||
<Button
|
||||
class="u-margin-block-start-16"
|
||||
secondary
|
||||
fullWidthMobile
|
||||
href={$upgradeURL}
|
||||
@@ -30,6 +30,6 @@
|
||||
}}>
|
||||
Upgrade
|
||||
</Button>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</slot>
|
||||
</Card>
|
||||
|
||||
@@ -94,12 +94,10 @@
|
||||
<div bind:this={element}></div>
|
||||
</div>
|
||||
{#if showSetAsDefault}
|
||||
<ul>
|
||||
<InputChoice
|
||||
bind:value={setAsDefault}
|
||||
id="default"
|
||||
label="Set as default payment method for this organization" />
|
||||
</ul>
|
||||
<InputChoice
|
||||
bind:value={setAsDefault}
|
||||
id="default"
|
||||
label="Set as default payment method for this organization" />
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -66,29 +66,25 @@
|
||||
</script>
|
||||
|
||||
<FakeModal bind:show title="Add payment method" bind:error onSubmit={handleSubmit}>
|
||||
<FormList>
|
||||
<slot />
|
||||
<InputText
|
||||
id="name"
|
||||
required
|
||||
autofocus={true}
|
||||
bind:value={name}
|
||||
label="Cardholder name"
|
||||
placeholder="Cardholder name" />
|
||||
<slot />
|
||||
<InputText
|
||||
id="name"
|
||||
required
|
||||
autofocus={true}
|
||||
bind:value={name}
|
||||
label="Cardholder name"
|
||||
placeholder="Cardholder name" />
|
||||
|
||||
<div class="aw-stripe-container" data-private>
|
||||
<Layout.Stack gap="l" alignItems="center" justifyContent="center">
|
||||
{#if isLoading}
|
||||
<Spinner />
|
||||
{/if}
|
||||
<div class="aw-stripe-container" data-private>
|
||||
{#if isLoading}
|
||||
<Spinner />
|
||||
{/if}
|
||||
|
||||
<div class="stripe-element" bind:this={element}>
|
||||
<!-- Stripe will create form elements here -->
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
<div class="stripe-element" bind:this={element}>
|
||||
<!-- Stripe will create form elements here -->
|
||||
</div>
|
||||
<slot name="end"></slot>
|
||||
</FormList>
|
||||
</div>
|
||||
<slot name="end"></slot>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button submit disabled={!name}>Add</Button>
|
||||
@@ -98,7 +94,7 @@
|
||||
<style lang="scss">
|
||||
.aw-stripe-container {
|
||||
display: flex;
|
||||
min-height: 295px;
|
||||
min-height: 245px;
|
||||
|
||||
.stripe-element {
|
||||
width: 100%;
|
||||
|
||||
@@ -2,38 +2,40 @@
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { formatNum } from '$lib/helpers/string';
|
||||
import { plansInfo, tierFree, tierPro, tierScale, type Tier } from '$lib/stores/billing';
|
||||
import { Card, SecondaryTabs, SecondaryTabsItem } from '..';
|
||||
import { Card, Layout, Tabs, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let downgrade = false;
|
||||
|
||||
let selectedTab: Tier = BillingPlan.FREE;
|
||||
export let downgrade = false;
|
||||
|
||||
$: plan = $plansInfo.get(selectedTab);
|
||||
</script>
|
||||
|
||||
<Card style="--card-padding: 1.5rem">
|
||||
<div class="comparison-box">
|
||||
<SecondaryTabs stretch>
|
||||
<SecondaryTabsItem
|
||||
disabled={selectedTab === BillingPlan.FREE}
|
||||
<Card.Base>
|
||||
<Layout.Stack>
|
||||
<Tabs.Root stretch let:root>
|
||||
<Tabs.Item.Button
|
||||
{root}
|
||||
active={selectedTab === BillingPlan.FREE}
|
||||
on:click={() => (selectedTab = BillingPlan.FREE)}>
|
||||
{tierFree.name}
|
||||
</SecondaryTabsItem>
|
||||
<SecondaryTabsItem
|
||||
disabled={selectedTab === BillingPlan.PRO}
|
||||
</Tabs.Item.Button>
|
||||
<Tabs.Item.Button
|
||||
{root}
|
||||
active={selectedTab === BillingPlan.PRO}
|
||||
on:click={() => (selectedTab = BillingPlan.PRO)}>
|
||||
{tierPro.name}
|
||||
</SecondaryTabsItem>
|
||||
<SecondaryTabsItem
|
||||
disabled={selectedTab === BillingPlan.SCALE}
|
||||
</Tabs.Item.Button>
|
||||
<Tabs.Item.Button
|
||||
{root}
|
||||
active={selectedTab === BillingPlan.SCALE}
|
||||
on:click={() => (selectedTab = BillingPlan.SCALE)}>
|
||||
{tierScale.name}
|
||||
</SecondaryTabsItem>
|
||||
</SecondaryTabs>
|
||||
</div>
|
||||
</Tabs.Item.Button>
|
||||
</Tabs.Root>
|
||||
|
||||
<div class="u-margin-block-start-24">
|
||||
<Typography.Text variant="m-600">{plan.name} plan</Typography.Text>
|
||||
{#if selectedTab === BillingPlan.FREE}
|
||||
<h3 class="u-bold body-text-1">{plan.name} plan</h3>
|
||||
{#if downgrade}
|
||||
<ul class="u-margin-block-start-8 list u-gap-4 u-small">
|
||||
<li class="list-item u-gap-4 u-cross-center">
|
||||
@@ -67,37 +69,26 @@
|
||||
</li>
|
||||
</ul>
|
||||
{:else}
|
||||
<ul class="u-margin-block-start-8 un-order-list">
|
||||
<ul class="un-order-list">
|
||||
<li>
|
||||
<span class="text">
|
||||
Limited to {plan.databases} Database, {plan.buckets} Buckets, {plan.functions}
|
||||
Functions per project
|
||||
</span>
|
||||
Limited to {plan.databases} Database, {plan.buckets} Buckets, {plan.functions}
|
||||
Functions per project
|
||||
</li>
|
||||
<li>Limited to 1 organization member</li>
|
||||
<li>
|
||||
Limited to {plan.bandwidth}GB bandwidth
|
||||
</li>
|
||||
<li>
|
||||
<span class="text"> Limited to 1 organization member </span>
|
||||
Limited to {plan.storage}GB storage
|
||||
</li>
|
||||
<li>
|
||||
<span class="text">
|
||||
{plan.bandwidth}GB bandwidth
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="text">
|
||||
{plan.storage}GB storage
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="text">
|
||||
{formatNum(plan.executions)} executions
|
||||
</span>
|
||||
Limited to {formatNum(plan.executions)} executions
|
||||
</li>
|
||||
</ul>
|
||||
{/if}
|
||||
{:else if selectedTab === BillingPlan.PRO}
|
||||
<h3 class="u-bold body-text-1">{plan.name} plan</h3>
|
||||
<p class="u-margin-block-start-8">Everything in the Free plan, plus:</p>
|
||||
<ul class="un-order-list u-margin-inline-start-4">
|
||||
<Typography.Text>Everything in the Free plan, plus:</Typography.Text>
|
||||
<ul class="un-order-list">
|
||||
<li>Unlimited databases, buckets, functions</li>
|
||||
<li>{plan.bandwidth}GB bandwidth</li>
|
||||
<li>{plan.storage}GB storage</li>
|
||||
@@ -105,9 +96,8 @@
|
||||
<li>Email support</li>
|
||||
</ul>
|
||||
{:else if selectedTab === BillingPlan.SCALE}
|
||||
<h3 class="u-bold body-text-1">{plan.name} plan</h3>
|
||||
<p class="u-margin-block-start-8">Everything in the Pro plan, plus:</p>
|
||||
<ul class="un-order-list u-margin-inline-start-4">
|
||||
<Typography.Text>Everything in the Pro plan, plus:</Typography.Text>
|
||||
<ul class="un-order-list">
|
||||
<li>Unlimited seats</li>
|
||||
<li>Organization roles</li>
|
||||
<li>SOC-2, HIPAA compliance</li>
|
||||
@@ -115,29 +105,5 @@
|
||||
<li>Priority support</li>
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<style lang="scss">
|
||||
.comparison-box {
|
||||
border-radius: var(--border-radius-small);
|
||||
background: hsl(var(--color-neutral-5));
|
||||
}
|
||||
:global(.theme-dark) .comparison-box {
|
||||
background: hsl(var(--color-neutral-85));
|
||||
}
|
||||
|
||||
.comparison-box :global(.secondary-tabs-button:where(:disabled)) {
|
||||
background: hsl(var(--color-neutral-0));
|
||||
border: 1px solid hsl(var(--color-neutral-10));
|
||||
}
|
||||
:global(.theme-dark) .comparison-box :global(.secondary-tabs-button:where(:disabled)) {
|
||||
background: hsl(var(--color-neutral-80));
|
||||
border: 1px solid hsl(var(--color-neutral-85));
|
||||
}
|
||||
|
||||
.inline-tag {
|
||||
line-height: 140%;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { Alert } from '$lib/components';
|
||||
import { calculateExcess, plansInfo, tierToPlan, type Tier } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
@@ -20,7 +10,9 @@
|
||||
import type { OrganizationUsage } from '$lib/sdk/billing';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { Alert, Icon, Table, Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import Cell from '$lib/elements/table/cell.svelte';
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let tier: Tier;
|
||||
export let members: number;
|
||||
@@ -48,12 +40,11 @@
|
||||
</script>
|
||||
|
||||
{#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>
|
||||
<Alert.Inline
|
||||
status="error"
|
||||
title={`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 {tierToPlan(
|
||||
BillingPlan.FREE
|
||||
).name} plan. {#if excess?.members > 0}All team members except the owner will be removed on
|
||||
@@ -67,80 +58,78 @@
|
||||
Learn more
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
|
||||
<TableScroll noMargin dense class="u-margin-block-start-16">
|
||||
<TableHeader>
|
||||
<TableCellHead>Resource</TableCellHead>
|
||||
<TableCellHead>Free limit</TableCellHead>
|
||||
<TableCellHead>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Cell>Resource</Table.Header.Cell>
|
||||
<Table.Header.Cell>Free limit</Table.Header.Cell>
|
||||
<Table.Header.Cell>
|
||||
Excess usage <Tooltip
|
||||
><span class="icon-info"></span>
|
||||
><Icon icon={IconInfo} />
|
||||
<span slot="tooltip">Metrics are estimates updated every 24 hours</span>
|
||||
</Tooltip>
|
||||
</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).value}
|
||||
{humanFileSize(excess?.storage).unit}
|
||||
</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>
|
||||
</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
{#if excess?.members}
|
||||
<Table.Row>
|
||||
<Table.Cell>Organization members</Table.Cell>
|
||||
<Table.Cell>{plan.members} members</Table.Cell>
|
||||
<Table.Cell>
|
||||
<p class="u-color-text-danger u-flex u-cross-center u-gap-4">
|
||||
<span class="icon-arrow-up" />
|
||||
{excess?.members} members
|
||||
</p>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
{#if excess?.storage}
|
||||
<Table.Row>
|
||||
<Table.Cell>Storage</Table.Cell>
|
||||
<Table.Cell>{plan.storage} GB</Table.Cell>
|
||||
<Table.Cell>
|
||||
<p class="u-color-text-danger">
|
||||
<span class="icon-arrow-up" />
|
||||
{humanFileSize(excess?.storage).value}
|
||||
{humanFileSize(excess?.storage).unit}
|
||||
</p>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
{#if excess?.executions}
|
||||
<Table.Row>
|
||||
<Table.Cell>Function executions</Table.Cell>
|
||||
<Table.Cell>
|
||||
{abbreviateNumber(plan.executions)} executions
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<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>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
{#if excess?.users}
|
||||
<Table.Row>
|
||||
<Table.Cell>Users</Table.Cell>
|
||||
<Table.Cell>
|
||||
{abbreviateNumber(plan.users)} users
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<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>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Root>
|
||||
{/if}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import { plansInfo, type Tier, tierFree, tierPro, tierScale } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { LabelCard } from '..';
|
||||
|
||||
export let billingPlan: Tier;
|
||||
@@ -17,83 +18,60 @@
|
||||
$: scalePlan = $plansInfo.get(BillingPlan.SCALE);
|
||||
</script>
|
||||
|
||||
{#if billingPlan}
|
||||
<ul class="u-flex u-flex-vertical u-gap-16 u-margin-block-start-8 {classes}">
|
||||
<li>
|
||||
<LabelCard
|
||||
name="plan"
|
||||
bind:group={billingPlan}
|
||||
disabled={anyOrgFree || !selfService}
|
||||
value={BillingPlan.FREE}
|
||||
tooltipShow={anyOrgFree}
|
||||
title={tierFree.name}
|
||||
tooltipText="You are limited to 1 Free organization per account."
|
||||
padding="m">
|
||||
<div
|
||||
class="u-flex u-flex-vertical u-gap-4 u-width-full-line"
|
||||
class:u-opacity-50={anyOrgFree || !selfService}>
|
||||
<h4 class="body-text-2 u-bold">
|
||||
{tierFree.name}
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE && !isNewOrg}
|
||||
<span class="inline-tag">Current plan</span>
|
||||
{/if}
|
||||
</h4>
|
||||
<p class="u-color-text-offline u-small">
|
||||
{tierFree.description}
|
||||
</p>
|
||||
<p>
|
||||
{formatCurrency(freePlan?.price ?? 0)}
|
||||
</p>
|
||||
</div>
|
||||
</LabelCard>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<LabelCard
|
||||
name="plan"
|
||||
disabled={!selfService}
|
||||
bind:group={billingPlan}
|
||||
value={BillingPlan.PRO}
|
||||
title={tierPro.name}
|
||||
padding="m">
|
||||
<div
|
||||
class="u-flex u-flex-vertical u-gap-4 u-width-full-line"
|
||||
class:u-opacity-50={!selfService}>
|
||||
<h4 class="body-text-2 u-bold">
|
||||
{#if $organization?.billingPlan === BillingPlan.PRO && !isNewOrg}
|
||||
<span class="inline-tag">Current plan</span>
|
||||
{/if}
|
||||
</h4>
|
||||
<p class="u-color-text-offline u-small">
|
||||
{tierPro.description}
|
||||
</p>
|
||||
<p>
|
||||
{formatCurrency(proPlan?.price ?? 0)} per member/month + usage
|
||||
</p>
|
||||
</div>
|
||||
</LabelCard>
|
||||
</li>
|
||||
<li>
|
||||
<LabelCard
|
||||
name="plan"
|
||||
bind:group={billingPlan}
|
||||
value={BillingPlan.SCALE}
|
||||
title={tierScale.name}
|
||||
padding="m">
|
||||
<div class="u-flex u-flex-vertical u-gap-4 u-width-full-line">
|
||||
<h4 class="body-text-2 u-bold">
|
||||
{#if $organization?.billingPlan === BillingPlan.SCALE && !isNewOrg}
|
||||
<span class="inline-tag">Current plan</span>
|
||||
{/if}
|
||||
</h4>
|
||||
<p class="u-color-text-offline u-small">
|
||||
{tierScale.description}
|
||||
</p>
|
||||
<p>
|
||||
{formatCurrency(scalePlan?.price ?? 0)} per month + usage
|
||||
</p>
|
||||
</div>
|
||||
</LabelCard>
|
||||
</li>
|
||||
</ul>
|
||||
{/if}
|
||||
<Layout.Stack>
|
||||
<LabelCard
|
||||
name="plan"
|
||||
bind:group={billingPlan}
|
||||
disabled={anyOrgFree || !selfService}
|
||||
value={BillingPlan.FREE}
|
||||
tooltipShow={anyOrgFree}
|
||||
title={tierFree.name}
|
||||
tooltipText="You are limited to 1 Free organization per account.">
|
||||
<svelte:fragment slot="action">
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE && !isNewOrg}
|
||||
<Badge variant="secondary" size="xs" content="Current plan" />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<Typography.Caption variant="400">
|
||||
{tierFree.description}
|
||||
</Typography.Caption>
|
||||
<Typography.Text>
|
||||
{formatCurrency(freePlan?.price ?? 0)}
|
||||
</Typography.Text>
|
||||
</LabelCard>
|
||||
<LabelCard
|
||||
name="plan"
|
||||
disabled={!selfService}
|
||||
bind:group={billingPlan}
|
||||
value={BillingPlan.PRO}
|
||||
title={tierPro.name}>
|
||||
<svelte:fragment slot="action">
|
||||
{#if $organization?.billingPlan === BillingPlan.PRO && !isNewOrg}
|
||||
<Badge variant="secondary" size="xs" content="Current plan" />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<Typography.Caption variant="400">
|
||||
{tierPro.description}
|
||||
</Typography.Caption>
|
||||
<Typography.Text>
|
||||
{formatCurrency(proPlan?.price ?? 0)} per month + usage
|
||||
</Typography.Text>
|
||||
</LabelCard>
|
||||
<LabelCard
|
||||
name="plan"
|
||||
bind:group={billingPlan}
|
||||
value={BillingPlan.SCALE}
|
||||
title={tierScale.name}>
|
||||
<svelte:fragment slot="action">
|
||||
{#if $organization?.billingPlan === BillingPlan.SCALE && !isNewOrg}
|
||||
<Badge variant="secondary" size="xs" content="Current plan" />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<Typography.Caption variant="400">
|
||||
{tierScale.description}
|
||||
</Typography.Caption>
|
||||
<Typography.Text>
|
||||
{formatCurrency(scalePlan?.price ?? 0)} per month + usage
|
||||
</Typography.Text>
|
||||
</LabelCard>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { Button, Helper, InputChoice, InputSelectSearch, InputText } from '$lib/elements/forms';
|
||||
import { Button, InputChoice, InputText } from '$lib/elements/forms';
|
||||
import type { PaymentList, PaymentMethodData } from '$lib/sdk/billing';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { hasStripePublicKey, isCloud } from '$lib/system';
|
||||
import { onMount } from 'svelte';
|
||||
import { Alert, Card, CreditCardBrandImage } from '..';
|
||||
import PaymentModal from './paymentModal.svelte';
|
||||
import { capitalize } from '$lib/helpers/string';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { Alert, Fieldset, Icon, Layout, Selector } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import InputSelect from '$lib/elements/forms/inputSelect.svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
|
||||
export let methods: PaymentList;
|
||||
export let value: string;
|
||||
@@ -16,22 +17,10 @@
|
||||
|
||||
let showTaxId = false;
|
||||
let showPaymentModal = false;
|
||||
let input: HTMLInputElement;
|
||||
let error: string;
|
||||
|
||||
function handleInvalid(event: Event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (input.validity.valueMissing) {
|
||||
error = 'This field is required';
|
||||
return;
|
||||
}
|
||||
error = input.validationMessage;
|
||||
}
|
||||
|
||||
async function cardSaved(event: CustomEvent<PaymentMethodData>) {
|
||||
value = event.detail.$id;
|
||||
methods = await sdk.forConsole.billing.listPaymentMethods();
|
||||
invalidate(Dependencies.UPGRADE_PLAN);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@@ -44,110 +33,64 @@
|
||||
$: selectedPaymentMethod = methods?.paymentMethods?.find((method) => method.$id === value);
|
||||
</script>
|
||||
|
||||
{#if filteredMethods?.length}
|
||||
{#if selectedPaymentMethod?.country?.toLowerCase() === 'in'}
|
||||
<Alert type="warning">
|
||||
<svelte:fragment slot="title">Indian credit or debit card-holders</svelte:fragment>
|
||||
To comply with RBI regulations in India, Appwrite will ask for verification to charge up
|
||||
to $150 USD on your payment method. We will never charge more than the cost of your plan
|
||||
and the resources you use, or your budget cap limit. For higher usage limits, please contact
|
||||
us.
|
||||
</Alert>
|
||||
{/if}
|
||||
<InputSelectSearch
|
||||
id="method"
|
||||
required
|
||||
label="Payment method"
|
||||
placeholder="Select payment method"
|
||||
bind:value
|
||||
options={filteredMethods.map((method) => {
|
||||
return {
|
||||
value: method.$id,
|
||||
label: `${capitalize(method.brand)} ending in ${method.last4}`,
|
||||
data: [method.brand]
|
||||
};
|
||||
})}
|
||||
interactiveOutput
|
||||
let:option={o}>
|
||||
<svelte:fragment slot="output" let:option={o}>
|
||||
<output class="input-text u-cursor-pointer">
|
||||
<span class="u-flex u-gap-16 u-flex-vertical">
|
||||
<span class="u-flex u-gap-16">
|
||||
<span class="u-flex u-cross-center u-gap-8" style="padding-inline:0.25rem">
|
||||
<span>{o.label}</span>
|
||||
<CreditCardBrandImage brand={o.data?.toString()} />
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</output>
|
||||
</svelte:fragment>
|
||||
|
||||
<span class="u-flex u-gap-16 u-flex-vertical">
|
||||
<span class="u-flex u-gap-16">
|
||||
<span class="u-flex u-cross-center u-gap-8" style="padding-inline:0.25rem">
|
||||
<span>{o.label}</span>
|
||||
<CreditCardBrandImage brand={o.data?.toString()} />
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<svelte:fragment slot="listEnd">
|
||||
<Button text on:click={() => (showPaymentModal = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add new payment method
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</InputSelectSearch>
|
||||
{:else}
|
||||
<div>
|
||||
<input
|
||||
bind:this={input}
|
||||
on:invalid={handleInvalid}
|
||||
required
|
||||
class="u-hide"
|
||||
type="text"
|
||||
name="method"
|
||||
id="method" />
|
||||
<Card
|
||||
isDashed
|
||||
style="--p-card-padding:0.75rem; --p-card-bg-color: transparent; --p-card-border-radius: 0.5rem"
|
||||
isTile>
|
||||
<div class="u-flex u-main-space-between u-cross-center">
|
||||
<p>
|
||||
<span class="icon-exclamation-circle"></span>
|
||||
<span class="text">No saved payment methods</span>
|
||||
</p>
|
||||
<Fieldset legend="Payment">
|
||||
<Layout.Stack>
|
||||
{#if filteredMethods?.length}
|
||||
{#if selectedPaymentMethod?.country?.toLowerCase() === 'in'}
|
||||
<Alert.Inline status="warning">
|
||||
<svelte:fragment slot="title"
|
||||
>Indian credit or debit card-holders</svelte:fragment>
|
||||
To comply with RBI regulations in India, Appwrite will ask for verification to charge
|
||||
up to $150 USD on your payment method. We will never charge more than the cost of
|
||||
your plan and the resources you use, or your budget cap limit. For higher usage limits,
|
||||
please contact us.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<InputSelect
|
||||
id="method"
|
||||
required
|
||||
label="Payment method"
|
||||
placeholder="Select payment method"
|
||||
bind:value
|
||||
options={filteredMethods.map((method) => {
|
||||
return {
|
||||
value: method.$id,
|
||||
label: `${capitalize(method.brand)} ending in ${method.last4}`,
|
||||
data: [method.brand]
|
||||
};
|
||||
})} />
|
||||
<div>
|
||||
<Button secondary on:click={() => (showPaymentModal = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add new payment method
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Alert.Inline title="No saved payment methods">
|
||||
<Button slot="actions" secondary on:click={() => (showPaymentModal = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
{#if error}
|
||||
<Helper class="u-position-relative" type="warning">{error}</Helper>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
|
||||
{#if showPaymentModal && isCloud && hasStripePublicKey}
|
||||
<PaymentModal bind:show={showPaymentModal} on:submit={cardSaved}>
|
||||
<svelte:fragment slot="end">
|
||||
<InputChoice
|
||||
type="checkbox"
|
||||
<Selector.Checkbox
|
||||
id="taxIdCheck"
|
||||
label="I'm purchasing as a business"
|
||||
fullWidth
|
||||
bind:value={showTaxId}>
|
||||
{#if showTaxId}
|
||||
<div class="u-margin-block-start-8">
|
||||
<InputText
|
||||
id="taxId"
|
||||
label="Tax ID"
|
||||
autofocus
|
||||
placeholder="Tax ID"
|
||||
bind:value={taxId} />
|
||||
</div>
|
||||
{/if}
|
||||
</InputChoice>
|
||||
bind:checked={showTaxId} />
|
||||
{#if showTaxId}
|
||||
<InputText
|
||||
id="taxId"
|
||||
label="Tax ID"
|
||||
autofocus
|
||||
placeholder="Tax ID"
|
||||
bind:value={taxId} />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</PaymentModal>
|
||||
{/if}
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/elements/table';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { organization, type Organization } from '$lib/stores/organization';
|
||||
import { type Organization } from '$lib/stores/organization';
|
||||
import { plansInfo } from '$lib/stores/billing';
|
||||
import { abbreviateNumber, formatCurrency } from '$lib/helpers/numbers';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Table, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let show = false;
|
||||
export let org: Organization;
|
||||
@@ -22,7 +15,7 @@
|
||||
|
||||
$: nextDate = org?.name
|
||||
? new Date(new Date().getFullYear(), new Date().getMonth() + 1, 1).toString()
|
||||
: $organization?.billingNextInvoiceDate;
|
||||
: org?.billingNextInvoiceDate;
|
||||
|
||||
const planData = [
|
||||
{
|
||||
@@ -52,64 +45,62 @@
|
||||
$: isFree = org.billingPlan === BillingPlan.FREE;
|
||||
</script>
|
||||
|
||||
<Modal bind:show size="big" title="Usage rates">
|
||||
<Modal bind:show title="Usage rates">
|
||||
{#if isFree}
|
||||
Usage on the {$plansInfo?.get(BillingPlan.FREE).name} plan is limited for the following resources.
|
||||
Next billing period: {toLocaleDate(nextDate)}.
|
||||
<Typography.Text>
|
||||
Usage on the {$plansInfo?.get(BillingPlan.FREE).name} plan is limited for the following resources.
|
||||
Next billing period: {toLocaleDate(nextDate)}.
|
||||
</Typography.Text>
|
||||
{:else if org.billingPlan === BillingPlan.PRO}
|
||||
<p>
|
||||
<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)}.
|
||||
</p>
|
||||
</Typography.Text>
|
||||
{:else if org.billingPlan === BillingPlan.SCALE}
|
||||
<p>
|
||||
<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)}.
|
||||
</p>
|
||||
</Typography.Text>
|
||||
{/if}
|
||||
<Table noStyles noMargin>
|
||||
<TableHeader>
|
||||
<TableCellHead>Resource</TableCellHead>
|
||||
<TableCellHead>Limit</TableCellHead>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Cell>Resource</Table.Header.Cell>
|
||||
<Table.Header.Cell>Limit</Table.Header.Cell>
|
||||
{#if !isFree}
|
||||
<TableCellHead>Rate</TableCellHead>
|
||||
<Table.Header.Cell>Rate</Table.Header.Cell>
|
||||
{/if}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each planData as usage}
|
||||
{#if usage['id'] === 'members'}
|
||||
<TableRow>
|
||||
<TableCellText title="resource">{usage.resource}</TableCellText>
|
||||
<TableCellText title="limit">
|
||||
{plan[usage.id] || 'Unlimited'}
|
||||
</TableCellText>
|
||||
{#if !isFree}
|
||||
<TableCellText title="rate">
|
||||
{formatCurrency(plan.addons?.member?.price)}/{usage?.unit}
|
||||
</TableCellText>
|
||||
{/if}
|
||||
</TableRow>
|
||||
{:else}
|
||||
{@const addon = plan.addons[usage.id]}
|
||||
<TableRow>
|
||||
<TableCellText title="resource">{usage.resource}</TableCellText>
|
||||
<TableCellText title="limit">
|
||||
{abbreviateNumber(plan[usage.id])}{usage?.unit}
|
||||
</TableCellText>
|
||||
{#if !isFree}
|
||||
<TableCellText title="rate">
|
||||
{formatCurrency(addon?.price)}/{['MB', 'GB', 'TB'].includes(
|
||||
addon?.unit
|
||||
)
|
||||
? addon?.value
|
||||
: abbreviateNumber(addon?.value, 0)}{usage?.unit}
|
||||
</TableCellText>
|
||||
{/if}
|
||||
</TableRow>
|
||||
{/if}
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</svelte:fragment>
|
||||
{#each planData as usage}
|
||||
{#if usage['id'] === 'members'}
|
||||
<Table.Row>
|
||||
<Table.Cell>{usage.resource}</Table.Cell>
|
||||
<Table.Cell>
|
||||
{plan[usage.id] || 'Unlimited'}
|
||||
</Table.Cell>
|
||||
{#if !isFree}
|
||||
<Table.Cell>
|
||||
{formatCurrency(plan.addons?.member?.price)}/{usage?.unit}
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{@const addon = plan.addons[usage.id]}
|
||||
<Table.Row>
|
||||
<Table.Cell>{usage.resource}</Table.Cell>
|
||||
<Table.Cell>
|
||||
{abbreviateNumber(plan[usage.id])}{usage?.unit}
|
||||
</Table.Cell>
|
||||
{#if !isFree}
|
||||
<Table.Cell>
|
||||
{formatCurrency(addon?.price)}/{['MB', 'GB', 'TB'].includes(addon?.unit)
|
||||
? addon?.value
|
||||
: abbreviateNumber(addon?.value, 0)}{usage?.unit}
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
</Table.Row>
|
||||
{/if}
|
||||
{/each}
|
||||
</Table.Root>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (show = false)}>Close</Button>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button, FormList, InputText } from '$lib/elements/forms';
|
||||
import { Button, InputText } from '$lib/elements/forms';
|
||||
import type { Coupon } from '$lib/sdk/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
export let show = false;
|
||||
let error: string = null;
|
||||
let coupon: string = '';
|
||||
export let couponData: Partial<Coupon> = {
|
||||
code: null,
|
||||
status: null,
|
||||
credits: null
|
||||
};
|
||||
let error: string = null;
|
||||
let coupon: string = '';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
async function addCoupon() {
|
||||
try {
|
||||
@@ -37,15 +37,20 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show title="Add credits" onSubmit={addCoupon} size="big" bind:error>
|
||||
Credits will be applied automatically to your next invoice.
|
||||
<Modal bind:show title="Add credits" onSubmit={addCoupon} bind:error>
|
||||
<svelte:fragment slot="description">
|
||||
Credits will be applied automatically to your next invoice.
|
||||
</svelte:fragment>
|
||||
|
||||
<FormList>
|
||||
<InputText placeholder="Promo code" id="code" label="Add promo code" bind:value={coupon} />
|
||||
</FormList>
|
||||
<InputText
|
||||
required
|
||||
placeholder="Promo code"
|
||||
id="code"
|
||||
label="Add promo code"
|
||||
bind:value={coupon} />
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button submit>Add</Button>
|
||||
<Button submit disabled={coupon === ''}>Add</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
type Props = ComponentProps<Selector>;
|
||||
|
||||
export let group: string;
|
||||
export let value: string | number | boolean;
|
||||
export let value: string;
|
||||
export let tooltipText: string = null;
|
||||
export let tooltipShow = false;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
export let imageRadius: Props['imageRadius'] = 'xxs';
|
||||
export let padding: Props['padding'] = 's';
|
||||
export let variant: Props['variant'] = 'primary';
|
||||
export let name: Props['name'] = undefined;
|
||||
//temporarily unefined
|
||||
export let title: Props['title'] = undefined;
|
||||
export let disabled = false;
|
||||
@@ -27,6 +28,7 @@
|
||||
|
||||
<Tooltip disabled={!tooltipText || !tooltipShow}>
|
||||
<Card.Selector
|
||||
{name}
|
||||
{src}
|
||||
{alt}
|
||||
{padding}
|
||||
@@ -38,10 +40,9 @@
|
||||
title={title ?? slotTitle?.innerText}
|
||||
bind:group>
|
||||
{#if $$slots.default}
|
||||
<p>
|
||||
<slot />
|
||||
</p>
|
||||
<slot />
|
||||
{/if}
|
||||
<slot name="action" slot="action" />
|
||||
</Card.Selector>
|
||||
<span slot="tooltip">{tooltipText}</span>
|
||||
</Tooltip>
|
||||
|
||||
@@ -8,6 +8,7 @@ export enum Dependencies {
|
||||
CREDIT = 'dependency:credit',
|
||||
INVOICES = 'dependency:invoices',
|
||||
ADDRESS = 'dependency:address',
|
||||
UPGRADE_PLAN = 'dependency:upgrade_plan',
|
||||
PAYMENT_METHODS = 'dependency:paymentMethods',
|
||||
ORGANIZATION = 'dependency:organization',
|
||||
MEMBERS = 'dependency:members',
|
||||
|
||||
+49
-66
@@ -3,14 +3,6 @@
|
||||
import { log } from '$lib/stores/logs';
|
||||
import { Alert, Card, Code, Copy, Id, SvgIcon, Tab, Tabs } from '../components';
|
||||
import { calculateTime } from '$lib/helpers/timeConversion';
|
||||
import {
|
||||
TableBody,
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { isCloud } from '$lib/system';
|
||||
@@ -18,7 +10,7 @@
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Table, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let selectedRequest = 'parameters';
|
||||
let selectedResponse = 'logs';
|
||||
@@ -223,26 +215,22 @@
|
||||
</div>
|
||||
{#if selectedRequest === 'parameters'}
|
||||
{#if parameters?.length}
|
||||
<div class="u-margin-block-start-24">
|
||||
<TableScroll noMargin>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Value</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each parameters as param}
|
||||
<TableRow>
|
||||
<TableCellText title="Key">
|
||||
{param.key}
|
||||
</TableCellText>
|
||||
<TableCellText title="Value">
|
||||
{param.value}
|
||||
</TableCellText>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
</div>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Cell>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell>Value</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
{#each parameters as param}
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
{param.key}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{param.value}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
{/if}
|
||||
|
||||
<p class="text u-text-center u-padding-24">
|
||||
@@ -261,26 +249,22 @@
|
||||
</p>
|
||||
{:else if selectedRequest === 'headers'}
|
||||
{#if execution.requestHeaders.length}
|
||||
<div class="u-margin-block-start-24">
|
||||
<TableScroll noMargin>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Value</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each execution.requestHeaders as header}
|
||||
<TableRow>
|
||||
<TableCellText title="Name">
|
||||
{header.name}
|
||||
</TableCellText>
|
||||
<TableCellText title="Value">
|
||||
{header.value}
|
||||
</TableCellText>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
</div>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Cell>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell>Value</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
{#each execution.requestHeaders as header}
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
{header.key}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{header.value}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
{/if}
|
||||
|
||||
<p class="text u-text-center u-padding-16">
|
||||
@@ -379,23 +363,22 @@
|
||||
{/if}
|
||||
{:else if selectedResponse === 'headers'}
|
||||
{#if execution.responseHeaders.length}
|
||||
<TableScroll noMargin>
|
||||
<TableHeader>
|
||||
<TableCellHead>Name</TableCellHead>
|
||||
<TableCellHead>Value</TableCellHead>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each execution.responseHeaders as header}
|
||||
<TableRow>
|
||||
<TableCellText title="Name">
|
||||
{header.name}
|
||||
</TableCellText>
|
||||
<TableCellText title="Value"
|
||||
>{header.value}</TableCellText>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Cell>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell>Value</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
{#each execution.responseHeaders as header}
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
{header.key}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{header.value}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
{/if}
|
||||
<p class="text u-text-center u-padding-16">
|
||||
{execution.responseHeaders?.length
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Alert } from '$lib/components';
|
||||
import { Button, FormList, InputDomain } from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -8,6 +7,7 @@
|
||||
import { func } from '$routes/(console)/project-[project]/functions/function-[function]/store';
|
||||
import { domain } from './store';
|
||||
import { consoleVariables } from '$routes/(console)/store';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let error = null;
|
||||
const isDomainsEnabled = $consoleVariables?._APP_DOMAIN_ENABLED === true;
|
||||
@@ -64,13 +64,10 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if isSelfHosted && !isDomainsEnabled}
|
||||
<Alert class="common-section" type="info">
|
||||
<svelte:fragment slot="title">
|
||||
Adding a domain to a self-hosted instance
|
||||
</svelte:fragment>
|
||||
To add a domain to a locally hosted Appwrite project, you must first configure your server
|
||||
domain.
|
||||
<svelte:fragment slot="buttons">
|
||||
<Alert.Inline status="info" title="Adding a domain to a self-hosted instance">
|
||||
To add a domain to a locally hosted Appwrite project, you must first configure your
|
||||
server domain.
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
href="https://appwrite.io/docs/advanced/self-hosting/functions#git"
|
||||
external
|
||||
@@ -78,6 +75,6 @@
|
||||
Learn more
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
</WizardStep>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Alert, Modal } from '$lib/components';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Button, FormList, InputNumber, InputSelect } from '$lib/elements/forms';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
@@ -7,6 +7,7 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { PaymentMethodData } from '$lib/sdk/billing';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let show = false;
|
||||
export let selectedPaymentMethod: PaymentMethodData;
|
||||
@@ -65,9 +66,7 @@
|
||||
|
||||
<Modal bind:error onSubmit={handleSubmit} bind:show title="Update payment method">
|
||||
{#if selectedPaymentMethod?.expired}
|
||||
<Alert type="error">
|
||||
<svelte:fragment slot="title">This payment method has expired</svelte:fragment>
|
||||
</Alert>
|
||||
<Alert.Inline status="error" title="This payment method has expired" />
|
||||
{/if}
|
||||
<svelte:fragment slot="description">
|
||||
{#if isLinked}
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputTags, InputText } from '$lib/elements/forms';
|
||||
import { Wizard } from '$lib/layout';
|
||||
import type { Coupon, PaymentList } from '$lib/sdk/billing';
|
||||
import type { Coupon } from '$lib/sdk/billing';
|
||||
import { tierToPlan } from '$lib/stores/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Fieldset, Icon, Link, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Fieldset, Icon, Layout, Link, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export let data;
|
||||
@@ -31,9 +31,9 @@
|
||||
let showExitModal = false;
|
||||
let formComponent: Form;
|
||||
let isSubmitting = writable(false);
|
||||
let methods: PaymentList;
|
||||
let name: string;
|
||||
let paymentMethodId: string;
|
||||
let paymentMethodId: string =
|
||||
data.paymentMethods.paymentMethods.find((method) => !!method?.last4)?.$id ?? null;
|
||||
let collaborators: string[] = [];
|
||||
let taxId: string;
|
||||
let billingBudget: number;
|
||||
@@ -43,11 +43,6 @@
|
||||
previousPage = from?.url?.pathname || previousPage;
|
||||
});
|
||||
|
||||
async function loadPaymentMethods() {
|
||||
methods = await sdk.forConsole.billing.listPaymentMethods();
|
||||
paymentMethodId = methods.paymentMethods.find((method) => !!method?.last4)?.$id ?? null;
|
||||
}
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
let org: Organization;
|
||||
@@ -121,10 +116,6 @@
|
||||
trackError(e, Submit.OrganizationCreate);
|
||||
}
|
||||
}
|
||||
|
||||
$: if (selectedPlan !== BillingPlan.FREE) {
|
||||
loadPaymentMethods();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -133,44 +124,49 @@
|
||||
|
||||
<Wizard title="Create organization" href={previousPage} bind:showExitModal confirmExit>
|
||||
<Form bind:this={formComponent} onSubmit={create} bind:isSubmitting>
|
||||
<Fieldset legend="Options">
|
||||
<InputText
|
||||
bind:value={name}
|
||||
label="Organization name"
|
||||
placeholder="Enter organization name"
|
||||
id="name"
|
||||
required />
|
||||
</Fieldset>
|
||||
<Fieldset legend="Select plan">
|
||||
<Typography.Text>
|
||||
For more details on our plans, visit our
|
||||
<Link.Anchor
|
||||
href="https://appwrite.io/pricing"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">pricing page</Link.Anchor
|
||||
>.
|
||||
</Typography.Text>
|
||||
<PlanSelection
|
||||
bind:billingPlan={selectedPlan}
|
||||
anyOrgFree={data.hasFreeOrganizations}
|
||||
isNewOrg />
|
||||
</Fieldset>
|
||||
{#if selectedPlan !== BillingPlan.FREE}
|
||||
<Fieldset legend="Invite members">
|
||||
<InputTags
|
||||
bind:tags={collaborators}
|
||||
label="Invite members by email"
|
||||
placeholder="Enter email address(es)"
|
||||
id="members" />
|
||||
<Layout.Stack gap="xxl">
|
||||
<Fieldset legend="Options">
|
||||
<InputText
|
||||
bind:value={name}
|
||||
label="Organization name"
|
||||
placeholder="Enter organization name"
|
||||
id="name"
|
||||
required />
|
||||
</Fieldset>
|
||||
<SelectPaymentMethod bind:methods bind:value={paymentMethodId} bind:taxId />
|
||||
{#if !selectedCoupon?.code}
|
||||
<Button text on:click={() => (showCreditModal = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add credits
|
||||
</Button>
|
||||
<Fieldset legend="Select plan">
|
||||
<Typography.Text>
|
||||
For more details on our plans, visit our
|
||||
<Link.Anchor
|
||||
href="https://appwrite.io/pricing"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">pricing page</Link.Anchor
|
||||
>.
|
||||
</Typography.Text>
|
||||
<PlanSelection
|
||||
bind:billingPlan={selectedPlan}
|
||||
anyOrgFree={data.hasFreeOrganizations}
|
||||
isNewOrg />
|
||||
</Fieldset>
|
||||
{#if selectedPlan !== BillingPlan.FREE}
|
||||
<SelectPaymentMethod
|
||||
methods={data.paymentMethods}
|
||||
bind:value={paymentMethodId}
|
||||
bind:taxId />
|
||||
<Fieldset legend="Invite members">
|
||||
<InputTags
|
||||
bind:tags={collaborators}
|
||||
label="Invite members by email"
|
||||
placeholder="Enter email address(es)"
|
||||
id="members" />
|
||||
</Fieldset>
|
||||
{#if !selectedCoupon?.code}
|
||||
<Button text on:click={() => (showCreditModal = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add credits
|
||||
</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if selectedPlan !== BillingPlan.FREE}
|
||||
|
||||
@@ -6,7 +6,10 @@ import type { Organization } from '$lib/stores/organization';
|
||||
|
||||
export const load: PageLoad = async ({ url, parent }) => {
|
||||
const { organizations } = await parent();
|
||||
const coupon = await getCoupon(url);
|
||||
const [coupon, paymentMethods] = await Promise.all([
|
||||
getCoupon(url),
|
||||
sdk.forConsole.billing.listPaymentMethods()
|
||||
]);
|
||||
let plan = getPlanFromUrl(url);
|
||||
const hasFreeOrganizations = organizations.teams?.some(
|
||||
(org) => (org as Organization)?.billingPlan === BillingPlan.FREE
|
||||
@@ -19,6 +22,7 @@ export const load: PageLoad = async ({ url, parent }) => {
|
||||
plan,
|
||||
coupon,
|
||||
hasFreeOrganizations,
|
||||
paymentMethods,
|
||||
name: url.searchParams.get('name') ?? ''
|
||||
};
|
||||
};
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import Alert from '$lib/components/alert.svelte';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { base } from '$app/paths';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let show = false;
|
||||
|
||||
@@ -39,18 +39,17 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Create new organization" {error} onSubmit={create} size="big" bind:show>
|
||||
<Modal title="Create new organization" {error} onSubmit={create} bind:show>
|
||||
{#if isCloud}
|
||||
<Alert type="info">
|
||||
<svelte:fragment slot="title">Get ready for Appwrite Pro</svelte:fragment>
|
||||
<Alert.Inline status="info" title="Get ready for Appwrite Pro">
|
||||
We will soon introduce the much-anticipated Pro plan. Your account will continue to have
|
||||
access to <b>one free organization</b>. If you manage more than one organization, you
|
||||
will need to either upgrade to the Pro plan, transfer your projects to a Pro
|
||||
organization, or migrate to self-hosting.
|
||||
<svelte:fragment slot="buttons">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button href="https://appwrite.io/pricing" external text>Learn more</Button>
|
||||
</svelte:fragment>
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<FormList>
|
||||
<InputText
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import AvailableCredit from './availableCredit.svelte';
|
||||
import PaymentHistory from './paymentHistory.svelte';
|
||||
import TaxId from './taxId.svelte';
|
||||
import { Alert } from '$lib/components';
|
||||
import { failedInvoice, paymentMethods, tierToPlan, upgradeURL } from '$lib/stores/billing';
|
||||
import type { PaymentMethodData } from '$lib/sdk/billing';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -21,7 +20,7 @@
|
||||
import { selectedInvoice, showRetryModal } from './store';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Alert, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -79,9 +78,9 @@
|
||||
<Container>
|
||||
{#if $failedInvoice}
|
||||
{#if $failedInvoice?.lastError}
|
||||
<Alert type="error" class="common-section">
|
||||
<Alert.Inline status="error">
|
||||
The scheduled payment for {$organization.name} failed due to following error: {$failedInvoice.lastError}
|
||||
<svelte:fragment slot="buttons">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
text
|
||||
on:click={() => {
|
||||
@@ -89,37 +88,33 @@
|
||||
$showRetryModal = true;
|
||||
}}>Try again</Button>
|
||||
</svelte:fragment>
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
{:else}
|
||||
<Alert type="error" class="common-section">
|
||||
<svelte:fragment slot="title">
|
||||
The scheduled payment for {$organization.name} failed
|
||||
</svelte:fragment>
|
||||
To avoid service disruptions in organization's your projects, please verify your payment
|
||||
details and update them if necessary.
|
||||
</Alert>
|
||||
<Alert.Inline
|
||||
status="error"
|
||||
title={`The scheduled payment for ${$organization.name} failed`}>
|
||||
To avoid service disruptions in organization's your projects, please verify your
|
||||
payment details and update them if necessary.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if defaultPaymentMethod?.failed && !backupPaymentMethod}
|
||||
<Alert type="error" class="common-section">
|
||||
<svelte:fragment slot="title">
|
||||
The default payment method for {$organization.name} has expired
|
||||
</svelte:fragment>
|
||||
<Alert.Inline
|
||||
status="error"
|
||||
title={`The default payment method for ${$organization.name} has expired`}>
|
||||
To avoid service disruptions in your organization's projects, please update your payment
|
||||
details.
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
{#if $organization?.billingPlanDowngrade}
|
||||
<Alert type="info" class="common-section">
|
||||
<Alert.Inline status="info">
|
||||
Your organization will change to a {tierToPlan($organization?.billingPlanDowngrade)
|
||||
.name} plan once your current billing cycle ends and your invoice is paid on {toLocaleDate(
|
||||
$organization.billingNextInvoiceDate
|
||||
)}.
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<div class="common-section">
|
||||
<Typography.Title>Billing</Typography.Title>
|
||||
</div>
|
||||
<Typography.Title>Billing</Typography.Title>
|
||||
<PlanSummary
|
||||
creditList={data?.creditList}
|
||||
members={data?.members}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, FormList, InputText } from '$lib/elements/forms';
|
||||
import { Button, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
@@ -51,6 +51,6 @@
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button disabled={!coupon} submit>Add credits</Button>
|
||||
<Button disabled={coupon === ''} submit>Add credits</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -34,8 +34,9 @@
|
||||
let showExitModal = false;
|
||||
let formComponent: Form;
|
||||
let isSubmitting = writable(false);
|
||||
let methods: PaymentList;
|
||||
let paymentMethodId: string;
|
||||
let paymentMethodId: string =
|
||||
data.organization.paymentMethodId ??
|
||||
data.paymentMethods.paymentMethods.find((method) => !!method?.last4)?.$id;
|
||||
let collaborators: string[] =
|
||||
data?.members?.memberships
|
||||
?.map((m) => {
|
||||
@@ -52,15 +53,6 @@
|
||||
previousPage = from?.url?.pathname || previousPage;
|
||||
});
|
||||
|
||||
async function loadPaymentMethods() {
|
||||
methods = await sdk.forConsole.billing.listPaymentMethods();
|
||||
|
||||
paymentMethodId =
|
||||
data.organization.paymentMethodId ??
|
||||
methods.paymentMethods.find((method) => !!method?.last4)?.$id ??
|
||||
null;
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (isDowngrade) {
|
||||
await downgrade();
|
||||
@@ -184,10 +176,9 @@
|
||||
|
||||
$: isUpgrade = data.plan > data.organization.billingPlan;
|
||||
$: isDowngrade = data.plan < data.organization.billingPlan;
|
||||
$: if (selectedPlan !== BillingPlan.FREE) {
|
||||
loadPaymentMethods();
|
||||
}
|
||||
$: isButtonDisabled = $organization.billingPlan === selectedPlan;
|
||||
|
||||
$: console.log(data.paymentMethods);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -248,6 +239,10 @@
|
||||
|
||||
<!-- Show email input if upgrading from free plan -->
|
||||
{#if selectedPlan !== BillingPlan.FREE && data.organization.billingPlan === BillingPlan.FREE}
|
||||
<SelectPaymentMethod
|
||||
methods={data.paymentMethods}
|
||||
bind:value={paymentMethodId}
|
||||
bind:taxId />
|
||||
<Fieldset legend="Invite members">
|
||||
<InputTags
|
||||
bind:tags={collaborators}
|
||||
@@ -255,7 +250,6 @@
|
||||
placeholder="Enter email address(es)"
|
||||
id="members" />
|
||||
</Fieldset>
|
||||
<SelectPaymentMethod bind:methods bind:value={paymentMethodId} bind:taxId />
|
||||
{#if !selectedCoupon?.code}
|
||||
<Button text on:click={() => (showCreditModal = true)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
|
||||
@@ -6,9 +6,13 @@ import type { Organization } from '$lib/stores/organization';
|
||||
|
||||
export const load: PageLoad = async ({ depends, parent, url }) => {
|
||||
const { members, organization, currentPlan, organizations } = await parent();
|
||||
depends(Dependencies.ORGANIZATION);
|
||||
depends(Dependencies.UPGRADE_PLAN);
|
||||
|
||||
const [coupon, paymentMethods] = await Promise.all([
|
||||
getCoupon(url),
|
||||
sdk.forConsole.billing.listPaymentMethods()
|
||||
]);
|
||||
|
||||
const coupon = await getCoupon(url);
|
||||
let plan = getPlanFromUrl(url);
|
||||
|
||||
if (organization?.billingPlan === BillingPlan.SCALE) {
|
||||
@@ -18,7 +22,6 @@ export const load: PageLoad = async ({ depends, parent, url }) => {
|
||||
}
|
||||
|
||||
const selfService = currentPlan?.selfService ?? true;
|
||||
|
||||
const hasFreeOrgs = organizations.teams?.some(
|
||||
(org) => (org as Organization)?.billingPlan === BillingPlan.FREE
|
||||
);
|
||||
@@ -28,7 +31,8 @@ export const load: PageLoad = async ({ depends, parent, url }) => {
|
||||
plan,
|
||||
coupon,
|
||||
selfService,
|
||||
hasFreeOrgs
|
||||
hasFreeOrgs,
|
||||
paymentMethods
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+14
-24
@@ -12,14 +12,6 @@
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { isCloud } from '$lib/system';
|
||||
import type { InvoiceList } from '$lib/sdk/billing';
|
||||
import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import { tierToPlan } from '$lib/stores/billing';
|
||||
import { Table, Tabs, Alert } from '@appwrite.io/pink-svelte';
|
||||
@@ -163,24 +155,22 @@
|
||||
{/each}
|
||||
</SecondaryTabs>
|
||||
|
||||
<TableScroll dense noMargin>
|
||||
<TableHeader>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
{#each tabData.headers as header, index}
|
||||
<TableCellHead width={index === 1 ? columnWidthSmall : columnWidth}
|
||||
>{header}</TableCellHead>
|
||||
<Table.Header.Cell
|
||||
width={(index === 1 ? columnWidthSmall : columnWidth) + 'px'}
|
||||
>{header}</Table.Header.Cell>
|
||||
{/each}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each tabData.rows as row}
|
||||
<TableRow>
|
||||
{#each row.cells as cell, index}
|
||||
<TableCell width={index === 1 ? columnWidthSmall : columnWidth}
|
||||
>{cell}</TableCell>
|
||||
{/each}
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
</svelte:fragment>
|
||||
{#each tabData.rows as row}
|
||||
<Table.Row>
|
||||
{#each row.cells as cell}
|
||||
<Table.Cell>{cell}</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
</div>
|
||||
{/if}
|
||||
<InputText
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Alert, CopyInput, Modal } from '$lib/components';
|
||||
import { CopyInput, Modal } from '$lib/components';
|
||||
import { Button, FormList, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
|
||||
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { updateOAuth } from '../updateOAuth';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
@@ -59,12 +60,11 @@
|
||||
label="App Secret"
|
||||
placeholder="Enter App Secret"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
bind:value={secret} />
|
||||
<Alert type="info">
|
||||
<Alert.Inline status="info">
|
||||
To complete the setup, create an OAuth2 client ID with "Web application" as the
|
||||
application type, then add this redirect URI to your {provider.name} configuration.
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Alert, CopyInput, Modal } from '$lib/components';
|
||||
import { CopyInput, Modal } from '$lib/components';
|
||||
import { Button, FormList, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
|
||||
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { updateOAuth } from '../updateOAuth';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
@@ -41,44 +42,41 @@
|
||||
clientSecret && tenantID ? JSON.stringify({ clientSecret, tenantID }) : provider.secret;
|
||||
</script>
|
||||
|
||||
<Modal {error} onSubmit={update} size="big" bind:show on:close>
|
||||
<Modal {error} onSubmit={update} bind:show on:close>
|
||||
<svelte:fragment slot="title">{provider.name} OAuth2 settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={oAuthProvider?.docs} target="_blank" rel="noopener noreferrer">
|
||||
visit the docs.
|
||||
</a>
|
||||
</p>
|
||||
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
|
||||
<InputText
|
||||
id="appID"
|
||||
label="Application (client) ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
showPasswordButton
|
||||
minlength={0}
|
||||
bind:value={clientSecret} />
|
||||
<InputText
|
||||
id="tenant"
|
||||
label="Target Tenant"
|
||||
placeholder="'common','organizations','consumers' or your TenantID"
|
||||
bind:value={tenantID} />
|
||||
<Alert type="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={oAuthProvider?.docs} target="_blank" rel="noopener noreferrer">
|
||||
visit the docs.
|
||||
</a>
|
||||
</p>
|
||||
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
|
||||
<InputText
|
||||
id="appID"
|
||||
label="Application (client) ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
bind:value={clientSecret} />
|
||||
<InputText
|
||||
id="tenant"
|
||||
label="Target Tenant"
|
||||
placeholder="'common','organizations','consumers' or your TenantID"
|
||||
bind:value={tenantID} />
|
||||
<Alert.Inline status="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert.Inline>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Alert, CopyInput, Modal } from '$lib/components';
|
||||
import { CopyInput, Modal } from '$lib/components';
|
||||
import { Button, FormList, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
|
||||
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { updateOAuth } from '../updateOAuth';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
@@ -66,57 +67,54 @@
|
||||
|
||||
<Modal {error} onSubmit={update} size="big" bind:show on:close>
|
||||
<svelte:fragment slot="title">{provider.name} OAuth2 settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={oAuthProvider?.docs} target="_blank" rel="noopener noreferrer"
|
||||
>visit the docs.</a>
|
||||
</p>
|
||||
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
|
||||
<InputText
|
||||
id="appID"
|
||||
label="Client ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
bind:value={clientSecret} />
|
||||
<InputText
|
||||
id="well-known-endpoint"
|
||||
label="Well-Known Endpoint"
|
||||
placeholder="https://example.com/.well-known/openid-configuration"
|
||||
bind:value={wellKnownEndpoint} />
|
||||
<InputText
|
||||
id="authorization-endpoint"
|
||||
label="Authorization Endpoint"
|
||||
placeholder="https://example.com/authorize"
|
||||
bind:value={authorizationEndpoint} />
|
||||
<InputText
|
||||
id="token-endpoint"
|
||||
label="Token Endpoint"
|
||||
placeholder="https://example.com/token"
|
||||
bind:value={tokenEndpoint} />
|
||||
<InputText
|
||||
id="userinfo-endpoint"
|
||||
label="User Info Endpoint"
|
||||
placeholder="https://example.com/userinfo"
|
||||
bind:value={userinfoEndpoint} />
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={oAuthProvider?.docs} target="_blank" rel="noopener noreferrer"
|
||||
>visit the docs.</a>
|
||||
</p>
|
||||
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
|
||||
<InputText
|
||||
id="appID"
|
||||
label="Client ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
bind:value={clientSecret} />
|
||||
<InputText
|
||||
id="well-known-endpoint"
|
||||
label="Well-Known Endpoint"
|
||||
placeholder="https://example.com/.well-known/openid-configuration"
|
||||
bind:value={wellKnownEndpoint} />
|
||||
<InputText
|
||||
id="authorization-endpoint"
|
||||
label="Authorization Endpoint"
|
||||
placeholder="https://example.com/authorize"
|
||||
bind:value={authorizationEndpoint} />
|
||||
<InputText
|
||||
id="token-endpoint"
|
||||
label="Token Endpoint"
|
||||
placeholder="https://example.com/token"
|
||||
bind:value={tokenEndpoint} />
|
||||
<InputText
|
||||
id="userinfo-endpoint"
|
||||
label="User Info Endpoint"
|
||||
placeholder="https://example.com/userinfo"
|
||||
bind:value={userinfoEndpoint} />
|
||||
|
||||
<Alert type="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<Alert.Inline status="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert.Inline>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Alert, CopyInput, Modal } from '$lib/components';
|
||||
import { Button, FormList, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
|
||||
import { CopyInput, Modal } from '$lib/components';
|
||||
import { Button, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
|
||||
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { updateOAuth } from '../updateOAuth';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
const projectId = $page.params.project;
|
||||
|
||||
@@ -48,47 +49,44 @@
|
||||
|
||||
<Modal {error} onSubmit={update} size="big" bind:show on:close>
|
||||
<svelte:fragment slot="title">{provider.name} OAuth2 settings</svelte:fragment>
|
||||
<FormList>
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={oAuthProvider?.docs} target="_blank" rel="noopener noreferrer"
|
||||
>visit the docs.</a>
|
||||
</p>
|
||||
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
|
||||
<InputText
|
||||
id="appID"
|
||||
label="Client ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
showPasswordButton
|
||||
bind:value={clientSecret} />
|
||||
<InputText
|
||||
id="domain"
|
||||
label="Okta Domain"
|
||||
placeholder="dev-1337.okta.com"
|
||||
bind:value={oktaDomain} />
|
||||
<InputText
|
||||
id="serverID"
|
||||
label="Authorization Server ID"
|
||||
placeholder="default"
|
||||
bind:value={authorizationServerId} />
|
||||
<p>
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={oAuthProvider?.docs} target="_blank" rel="noopener noreferrer"
|
||||
>visit the docs.</a>
|
||||
</p>
|
||||
<InputSwitch id="state" bind:value={enabled} label={enabled ? 'Enabled' : 'Disabled'} />
|
||||
<InputText
|
||||
id="appID"
|
||||
label="Client ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
bind:value={clientSecret} />
|
||||
<InputText
|
||||
id="domain"
|
||||
label="Okta Domain"
|
||||
placeholder="dev-1337.okta.com"
|
||||
bind:value={oktaDomain} />
|
||||
<InputText
|
||||
id="serverID"
|
||||
label="Authorization Server ID"
|
||||
placeholder="default"
|
||||
bind:value={authorizationServerId} />
|
||||
|
||||
<Alert type="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
</FormList>
|
||||
<Alert.Inline status="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert.Inline>
|
||||
<div>
|
||||
<p>URI</p>
|
||||
<CopyInput
|
||||
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
|
||||
</div>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
|
||||
+27
-29
@@ -277,35 +277,33 @@
|
||||
</p>
|
||||
|
||||
{#if relAttributes?.length}
|
||||
<TableScroll noMargin>
|
||||
<TableHeader>
|
||||
<TableCellHead width={50}>Relation</TableCellHead>
|
||||
<TableCellHead width={50}>Setting</TableCellHead>
|
||||
<TableCellHead width={200} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each relAttributes as attr}
|
||||
<TableRow>
|
||||
<TableCell title="relation">
|
||||
<span class="u-flex u-cross-center u-gap-8">
|
||||
{#if attr.twoWay}
|
||||
<span class="icon-switch-horizontal" />
|
||||
{:else}
|
||||
<span class="icon-arrow-sm-right" />
|
||||
{/if}
|
||||
<span data-private>{attr.key}</span>
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCellText title="Settings">
|
||||
{attr.onDelete}
|
||||
</TableCellText>
|
||||
<TableCellText title="description">
|
||||
{Deletion[attr.onDelete]}
|
||||
</TableCellText>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Cell width="50px">Relation</Table.Header.Cell>
|
||||
<Table.Header.Cell width="50px">Setting</Table.Header.Cell>
|
||||
<Table.Header.Cell />
|
||||
</svelte:fragment>
|
||||
{#each relAttributes as attr}
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<span class="u-flex u-cross-center u-gap-8">
|
||||
{#if attr.twoWay}
|
||||
<span class="icon-switch-horizontal" />
|
||||
{:else}
|
||||
<span class="icon-arrow-sm-right" />
|
||||
{/if}
|
||||
<span data-private>{attr.key}</span>
|
||||
</span>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{attr.onDelete}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{Deletion[attr.onDelete]}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<Alert>To change the selection edit the relationship settings.</Alert>
|
||||
|
||||
|
||||
+4
-3
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Modal, Code, Alert } from '$lib/components';
|
||||
import { Modal, Code } from '$lib/components';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { func } from '../store';
|
||||
import SecondaryTabs from '$lib/components/secondaryTabs.svelte';
|
||||
import SecondaryTabsItem from '$lib/components/secondaryTabsItem.svelte';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let show = false;
|
||||
|
||||
@@ -89,14 +90,14 @@
|
||||
function's folder.
|
||||
</p>
|
||||
|
||||
<Alert dismissible={false} type="warning">
|
||||
<Alert.Inline dismissible={false} status="warning">
|
||||
If you did not create your function using the CLI, initialize your function by following our <a
|
||||
href="https://appwrite.io/docs/tooling/command-line/installation"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">documentation</a
|
||||
>.
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
|
||||
<div class="editor-border box">
|
||||
<SecondaryTabs large class="u-sep-block-end u-padding-8">
|
||||
|
||||
+5
-7
@@ -7,7 +7,7 @@
|
||||
InputText,
|
||||
InputTextarea
|
||||
} from '$lib/elements/forms';
|
||||
import { Alert, Collapsible, CollapsibleItem, Modal } from '$lib/components';
|
||||
import { Collapsible, CollapsibleItem, Modal } from '$lib/components';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
@@ -15,6 +15,7 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { func } from '../store';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let show = false;
|
||||
|
||||
@@ -79,18 +80,15 @@
|
||||
required={true} />
|
||||
|
||||
{#if $func.version === 'v2'}
|
||||
<Alert type="info">
|
||||
<svelte:fragment slot="title">
|
||||
Build commands now available for functions v3.0
|
||||
</svelte:fragment>
|
||||
<Alert.Inline status="info" title="Build commands now available for functions v3.0">
|
||||
Update your function version to make use of new features including build commands.
|
||||
<svelte:fragment slot="buttons">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
href="https://appwrite.io/docs/products/functions/development"
|
||||
external
|
||||
text>Learn more</Button>
|
||||
</svelte:fragment>
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
{:else}
|
||||
<Collapsible>
|
||||
<CollapsibleItem>
|
||||
|
||||
+7
-11
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Alert, Empty, EmptyFilter, PaginationWithLimit, ViewSelector } from '$lib/components';
|
||||
import { Empty, EmptyFilter, PaginationWithLimit, ViewSelector } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
@@ -9,7 +9,7 @@
|
||||
import { project } from '$routes/(console)/project-[project]/store';
|
||||
import { base } from '$app/paths';
|
||||
import { View } from '$lib/helpers/load';
|
||||
import { Icon, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Alert, Icon, Layout, Link } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import Table from './table.svelte';
|
||||
import { columns } from './store';
|
||||
@@ -17,9 +17,7 @@
|
||||
|
||||
export let data;
|
||||
|
||||
let showMobileFilters = false;
|
||||
onMount(() => {
|
||||
data?.query ? (showMobileFilters = true) : (showMobileFilters = false);
|
||||
return sdk.forConsole.client.subscribe('console', (response) => {
|
||||
if (response.events.includes('functions.*.executions.*')) {
|
||||
invalidate(Dependencies.EXECUTIONS);
|
||||
@@ -51,15 +49,13 @@
|
||||
|
||||
{#if !data.func.logging}
|
||||
<div class="common-section">
|
||||
<Alert type="info" isStandalone>
|
||||
<svelte:fragment slot="title">Your execution logs are disabled</svelte:fragment>
|
||||
<Alert.Inline status="info" title="Your execution logs are disabled">
|
||||
To view execution logs and errors, enable them in your
|
||||
<a
|
||||
href={`${base}/project-${$project.$id}/functions/function-${data.func.$id}/settings`}
|
||||
class="link">
|
||||
Function settings</a
|
||||
<Link.Anchor
|
||||
href={`${base}/project-${$project.$id}/functions/function-${data.func.$id}/settings`}>
|
||||
Function settings</Link.Anchor
|
||||
>.
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
</div>
|
||||
{/if}
|
||||
{#if data?.executions?.total}
|
||||
|
||||
+9
-11
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { MessagingProviderType, type Models } from '@appwrite.io/console';
|
||||
import { CardGrid, Empty, PaginationInline, EmptySearch, Alert } from '$lib/components';
|
||||
import { CardGrid, Empty, PaginationInline, EmptySearch } from '$lib/components';
|
||||
import { onMount } from 'svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { invalidate } from '$app/navigation';
|
||||
@@ -12,7 +12,7 @@
|
||||
import UserTargetsModal from '../userTargetsModal.svelte';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon, Layout, Table, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Alert, Icon, Layout, Table, Typography } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let message: Models.Message & { data: Record<string, unknown> };
|
||||
export let selectedTargetsById: Record<string, Models.Target>;
|
||||
@@ -130,20 +130,18 @@
|
||||
{#if hasDeletedUsers}
|
||||
<div class:u-padding-block-end-16={dataSource.length}>
|
||||
{#if hasDeletedUsers && !dataSource.length}
|
||||
<Alert type="info">
|
||||
<svelte:fragment slot="title"
|
||||
>There are no targets to show</svelte:fragment>
|
||||
This message was sent to users who are no longer available, so their information
|
||||
cannot be displayed.
|
||||
</Alert>
|
||||
<Alert.Inline status="info" title="There are no targets to show">
|
||||
This message was sent to users who are no longer available, so their
|
||||
information cannot be displayed.
|
||||
</Alert.Inline>
|
||||
{:else}
|
||||
<Alert
|
||||
type="info"
|
||||
<Alert.Inline
|
||||
status="info"
|
||||
dismissible={dataSource.length > 0}
|
||||
on:dismiss={() => (hasDeletedUsers = false)}>
|
||||
This message was sent to users who are no longer available, so their
|
||||
information cannot be displayed.
|
||||
</Alert>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
+38
-50
@@ -26,6 +26,7 @@
|
||||
import { targetsById } from '../../store';
|
||||
import { MessagingProviderType, type Models } from '@appwrite.io/console';
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import { Selector, Table } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let columns: Column[];
|
||||
export let data: PageData;
|
||||
@@ -80,67 +81,54 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<TableScroll>
|
||||
<TableHeader>
|
||||
<TableCellHeadCheck
|
||||
bind:selected={selectedIds}
|
||||
pageItemsIds={data.subscribers.subscribers.map((d) => d.$id)} />
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Selector width="40px" />
|
||||
{#each columns as column}
|
||||
{#if column.show}
|
||||
<TableCellHead width={column.width}>{column.title}</TableCellHead>
|
||||
<Table.Header.Cell width={column.width + 'px'}>{column.title}</Table.Header.Cell>
|
||||
{/if}
|
||||
{/each}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.subscribers.subscribers as subscriber (subscriber.$id)}
|
||||
{@const target = subscriber.target}
|
||||
<TableRowLink
|
||||
href={`${base}/project-${$project.$id}/auth/user-${subscriber.target.userId}`}>
|
||||
<TableCellCheck bind:selectedIds id={subscriber.$id} />
|
||||
</svelte:fragment>
|
||||
{#each data.subscribers.subscribers as subscriber (subscriber.$id)}
|
||||
{@const target = subscriber.target}
|
||||
<Table.Link href={`${base}/project-${$project.$id}/auth/user-${subscriber.target.userId}`}>
|
||||
<Table.Cell>
|
||||
<Selector.Checkbox size="s" />
|
||||
</Table.Cell>
|
||||
|
||||
{#each columns as column}
|
||||
{#if column.show}
|
||||
{#each columns as column}
|
||||
{#if column.show}
|
||||
<Table.Cell>
|
||||
{#if column.id === '$id'}
|
||||
{#key column.id}
|
||||
<TableCell title="Subscriber ID">
|
||||
<Id value={subscriber.$id}>
|
||||
{subscriber.$id}
|
||||
</Id>
|
||||
</TableCell>
|
||||
<Id value={subscriber.$id}>
|
||||
{subscriber.$id}
|
||||
</Id>
|
||||
{/key}
|
||||
{:else if column.id === 'targetId'}
|
||||
<TableCell title={column.title}>
|
||||
<Id value={subscriber[column.id]}>
|
||||
{subscriber[column.id]}
|
||||
</Id>
|
||||
</TableCell>
|
||||
{:else if column.id === 'target'}
|
||||
<TableCell title={column.title}>
|
||||
{#if target.providerType === MessagingProviderType.Push}
|
||||
{target.name}
|
||||
{:else}
|
||||
{target.identifier}
|
||||
{/if}
|
||||
</TableCell>
|
||||
{:else if column.id === 'type'}
|
||||
<TableCellText title={column.title} width={column.width}>
|
||||
<ProviderType type={subscriber.target.providerType} size="s" />
|
||||
</TableCellText>
|
||||
{:else if column.id === '$createdAt'}
|
||||
<TableCellText title={column.title} width={column.width}>
|
||||
{toLocaleDateTime(subscriber[column.id])}
|
||||
</TableCellText>
|
||||
{:else}
|
||||
<TableCellText title={column.title} width={column.width}>
|
||||
<Id value={subscriber[column.id]}>
|
||||
{subscriber[column.id]}
|
||||
</TableCellText>
|
||||
</Id>
|
||||
{:else if column.id === 'target'}
|
||||
{#if target.providerType === MessagingProviderType.Push}
|
||||
{target.name}
|
||||
{:else}
|
||||
{target.identifier}
|
||||
{/if}
|
||||
{:else if column.id === 'type'}
|
||||
<ProviderType type={subscriber.target.providerType} size="s" />
|
||||
{:else if column.id === '$createdAt'}
|
||||
{toLocaleDateTime(subscriber[column.id])}
|
||||
{:else}
|
||||
{subscriber[column.id]}
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</TableRowLink>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
{/each}
|
||||
</Table.Link>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
|
||||
<FloatingActionBar show={selectedIds.length > 0}>
|
||||
<div class="u-flex u-cross-center u-main-space-between actions">
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Card } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/elements/table';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { messageParams, providerType, targetsById, getTotal } from './store';
|
||||
import Actions from '../actions.svelte';
|
||||
import { topicsById } from '../store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { Icon, Table } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let showTopics = false;
|
||||
let showUserTargets = false;
|
||||
@@ -24,7 +16,6 @@
|
||||
|
||||
function addTopics(event: CustomEvent<Record<string, Models.Topic>>) {
|
||||
$topicsById = event.detail;
|
||||
showDropdown = false;
|
||||
}
|
||||
|
||||
function removeTopic(topicId: string) {
|
||||
@@ -34,7 +25,6 @@
|
||||
|
||||
function addTargets(event: CustomEvent<Record<string, Models.Target>>) {
|
||||
$targetsById = event.detail;
|
||||
showDropdown = false;
|
||||
}
|
||||
|
||||
function removeTarget(targetId: string) {
|
||||
@@ -80,65 +70,63 @@
|
||||
</Card>
|
||||
{:else}
|
||||
<div class="table-wrapper">
|
||||
<Table noMargin noStyles>
|
||||
<TableHeader>
|
||||
<TableCellHead width={140}>Target</TableCellHead>
|
||||
<TableCellHead width={32} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each Object.entries($topicsById) as [topicId, topic] (topicId)}
|
||||
<TableRow>
|
||||
<TableCell title="Target">
|
||||
<div class="u-flex u-cross-center">
|
||||
<span class="title">
|
||||
<span class="u-line-height-1-5">
|
||||
<span class="body-text-2 u-bold" data-private>
|
||||
{topic.name}
|
||||
</span>
|
||||
<span class="collapsible-button-optional">
|
||||
({getTotal(topic)} targets)
|
||||
</span>
|
||||
</span></span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell title="Remove" width={40}>
|
||||
<div class="u-flex u-main-end">
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
type="button"
|
||||
aria-label="delete"
|
||||
on:click={() => removeTopic(topicId)}>
|
||||
<span class="icon-x u-font-size-20" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
{#each Object.entries($targetsById) as [targetId, target] (targetId)}
|
||||
<TableRow>
|
||||
<TableCell title="Target">
|
||||
<div class="u-flex u-cross-center">
|
||||
<span class="text">
|
||||
{target.name ? target.name : target.identifier}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Cell>Target</Table.Header.Cell>
|
||||
<Table.Header.Cell width="32px" />
|
||||
</svelte:fragment>
|
||||
{#each Object.entries($topicsById) as [topicId, topic] (topicId)}
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<div class="u-flex u-cross-center">
|
||||
<span class="title">
|
||||
<span class="u-line-height-1-5">
|
||||
<span class="body-text-2 u-bold" data-private>
|
||||
{topic.name}
|
||||
</span>
|
||||
<span class="collapsible-button-optional">
|
||||
({getTotal(topic)} targets)
|
||||
</span>
|
||||
</span></span>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div class="u-flex u-main-end">
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
type="button"
|
||||
aria-label="delete"
|
||||
on:click={() => removeTopic(topicId)}>
|
||||
<span class="icon-x u-font-size-20" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{#each Object.entries($targetsById) as [targetId, target] (targetId)}
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<div class="u-flex u-cross-center">
|
||||
<span class="text">
|
||||
{target.name ? target.name : target.identifier}
|
||||
</span>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
|
||||
<TableCell title="Remove" width={40}>
|
||||
<div class="u-flex u-main-end">
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
type="button"
|
||||
aria-label="delete"
|
||||
on:click={() => removeTarget(targetId)}>
|
||||
<span class="icon-x u-font-size-20" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Table.Cell>
|
||||
<div class="u-flex u-main-end">
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
type="button"
|
||||
aria-label="delete"
|
||||
on:click={() => removeTarget(targetId)}>
|
||||
<span class="icon-x u-font-size-20" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Root>
|
||||
</div>
|
||||
<Actions
|
||||
providerType={$providerType}
|
||||
|
||||
+16
-13
@@ -1,12 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Alert, CardGrid } from '$lib/components';
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form, FormList } from '$lib/elements/forms';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { diffDays } from '$lib/helpers/date';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
import { project } from '../../../store';
|
||||
import ExpirationInput from '../expirationInput.svelte';
|
||||
import { key } from './store';
|
||||
@@ -49,18 +50,20 @@
|
||||
Set a date after which your API key will expire.
|
||||
<svelte:fragment slot="aside">
|
||||
{#if isExpired}
|
||||
<Alert type="error" dismissible on:dismiss={() => (alertsDismissed = true)}>
|
||||
<span slot="title">Your API key has expired</span>
|
||||
<p>
|
||||
For security reasons, we recommend you delete your expired key and create a
|
||||
new one.
|
||||
</p>
|
||||
</Alert>
|
||||
<Alert.Inline
|
||||
status="error"
|
||||
on:dismiss={() => (alertsDismissed = true)}
|
||||
title="Your API key has expired">
|
||||
For security reasons, we recommend you delete your expired key and create a new
|
||||
one.
|
||||
</Alert.Inline>
|
||||
{:else if isExpiring}
|
||||
<Alert type="warning" dismissible on:dismiss={() => (alertsDismissed = true)}>
|
||||
<span slot="title">Your API key is about to expire</span>
|
||||
<p>Update the expiration date to keep the key active</p>
|
||||
</Alert>
|
||||
<Alert.Inline
|
||||
status="warning"
|
||||
on:dismiss={() => (alertsDismissed = true)}
|
||||
title="Your API key is about to expire">
|
||||
Update the expiration date to keep the key active
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<ExpirationInput bind:value={expiration} />
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -106,47 +106,43 @@
|
||||
</script>
|
||||
|
||||
<Modal title="Export to self-hosted instance" bind:show {onSubmit}>
|
||||
<FormList>
|
||||
<Alert isStandalone>
|
||||
<svelte:fragment slot="title">API key creation</svelte:fragment>
|
||||
By initiating the transfer, an API key will be automatically generated in the background,
|
||||
which you can delete after completion
|
||||
</Alert>
|
||||
<Alert isStandalone>
|
||||
<svelte:fragment slot="title">API key creation</svelte:fragment>
|
||||
By initiating the transfer, an API key will be automatically generated in the background, which
|
||||
you can delete after completion
|
||||
</Alert>
|
||||
|
||||
<InputText
|
||||
label="Endpoint self-hosted instance"
|
||||
required
|
||||
id="endpoint"
|
||||
placeholder="https://[YOUR_APPWRITE_HOSTNAME]"
|
||||
autofocus
|
||||
on:input={(e) => {
|
||||
if (!submitted) return;
|
||||
const input = e.target;
|
||||
const value = input.value;
|
||||
<InputText
|
||||
label="Endpoint self-hosted instance"
|
||||
required
|
||||
id="endpoint"
|
||||
placeholder="https://[YOUR_APPWRITE_HOSTNAME]"
|
||||
autofocus
|
||||
on:input={(e) => {
|
||||
if (!submitted) return;
|
||||
const input = e.target;
|
||||
const value = input.value;
|
||||
|
||||
if (!isValidEndpoint(value)) {
|
||||
input.setCustomValidity('Please enter a valid endpoint');
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
}
|
||||
input.reportValidity();
|
||||
}} />
|
||||
if (!isValidEndpoint(value)) {
|
||||
input.setCustomValidity('Please enter a valid endpoint');
|
||||
} else {
|
||||
input.setCustomValidity('');
|
||||
}
|
||||
input.reportValidity();
|
||||
}} />
|
||||
|
||||
<Box>
|
||||
<p class="u-bold">
|
||||
Share your feedback: why our self-hosted solution works better for you
|
||||
</p>
|
||||
<p class="u-margin-block-start-8">
|
||||
We appreciate your continued support and we understand that our self-hosted solution
|
||||
might better fit your needs. To help us improve our Cloud solution, please share why
|
||||
it works better for you. Your feedback is important to us and we'll use it to make
|
||||
our services better.
|
||||
</p>
|
||||
<div class="u-margin-block-start-24">
|
||||
<InputTextarea id="feedback" label="Your feedback" placeholder="Type here..." />
|
||||
</div>
|
||||
</Box>
|
||||
</FormList>
|
||||
<Box>
|
||||
<p class="u-bold">Share your feedback: why our self-hosted solution works better for you</p>
|
||||
<p class="u-margin-block-start-8">
|
||||
We appreciate your continued support and we understand that our self-hosted solution
|
||||
might better fit your needs. To help us improve our Cloud solution, please share why it
|
||||
works better for you. Your feedback is important to us and we'll use it to make our
|
||||
services better.
|
||||
</p>
|
||||
<div class="u-margin-block-start-24">
|
||||
<InputTextarea id="feedback" label="Your feedback" placeholder="Type here..." />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div class="u-flex u-gap-16 u-cross-center" slot="footer">
|
||||
<span> You will be redirected to your self-hosted instance </span>
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Alert } from '$lib/components';
|
||||
import { Layout, Link, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { Layout, Link, Typography, Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let teamId: string, membershipId: string, userId: string, secret: string;
|
||||
let terms = false;
|
||||
@@ -54,13 +53,10 @@
|
||||
</svelte:fragment>
|
||||
<svelte:fragment>
|
||||
{#if !userId || !secret || !membershipId || !teamId}
|
||||
<Alert type="warning">
|
||||
<svelte:fragment slot="title">The invite link is not valid</svelte:fragment>
|
||||
<Alert.Inline status="warning" title="The invite link is not valid">
|
||||
Please ask the project owner to send you a new invite.
|
||||
</Alert>
|
||||
<div class="u-flex u-main-end u-margin-block-start-40">
|
||||
<Button href={`${base}/register`}>Sign up to Appwrite</Button>
|
||||
</div>
|
||||
</Alert.Inline>
|
||||
<Button href={`${base}/register`}>Sign up to Appwrite</Button>
|
||||
{:else}
|
||||
<Layout.Stack>
|
||||
<Typography.Text>
|
||||
|
||||
Reference in New Issue
Block a user