mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'feat-pink-v2' into file-tokens
This commit is contained in:
+1
-1
@@ -21,7 +21,7 @@
|
||||
"dependencies": {
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@d13e864",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ffaca61",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@2ff4e332",
|
||||
"@appwrite.io/pink-legacy": "^1.0.1",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ffaca61",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@134aa5d",
|
||||
|
||||
Generated
+5
-5
@@ -15,8 +15,8 @@ importers:
|
||||
specifier: 0.25.0
|
||||
version: 0.25.0
|
||||
'@appwrite.io/pink-icons-svelte':
|
||||
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ffaca61
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ffaca61(svelte@4.2.19)
|
||||
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@2ff4e332
|
||||
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@2ff4e332(svelte@4.2.19)
|
||||
'@appwrite.io/pink-legacy':
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1
|
||||
@@ -223,8 +223,8 @@ packages:
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ffaca61c9e94c624744b168517346fb0f5b04d84':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ffaca61c9e94c624744b168517346fb0f5b04d84}
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@2ff4e332':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@2ff4e332}
|
||||
version: 1.0.0-next.7
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
@@ -4105,7 +4105,7 @@ snapshots:
|
||||
dependencies:
|
||||
svelte: 4.2.19
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ffaca61c9e94c624744b168517346fb0f5b04d84(svelte@4.2.19)':
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@2ff4e332(svelte@4.2.19)':
|
||||
dependencies:
|
||||
svelte: 4.2.19
|
||||
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
|
||||
{#if total > 2}
|
||||
<Avatar {size}>
|
||||
+{total - 2}
|
||||
<span style:font-size="10px">
|
||||
+{total - 2}
|
||||
</span>
|
||||
</Avatar>
|
||||
{/if}
|
||||
</AvatarGroup>
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import { isCloud } from '$lib/system.js';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
let showSupport = false;
|
||||
|
||||
@@ -68,6 +69,10 @@
|
||||
: 'light'
|
||||
: theme;
|
||||
|
||||
trackEvent('select_theme', {
|
||||
value: theme === 'auto' ? 'system' : theme
|
||||
});
|
||||
|
||||
app.update(() => ({
|
||||
themeInUse: themeInUse,
|
||||
theme: theme
|
||||
@@ -130,12 +135,24 @@
|
||||
<Button.Anchor
|
||||
size="s"
|
||||
variant="primary"
|
||||
on:click={() => {
|
||||
trackEvent('click_organization_upgrade', {
|
||||
from: 'button',
|
||||
source: 'top_nav'
|
||||
});
|
||||
}}
|
||||
href={`${base}/organization-${currentOrg.$id}/change-plan`}
|
||||
>Upgrade</Button.Anchor>
|
||||
{/if}
|
||||
|
||||
<DropList show={$feedback.show} class="extended-width">
|
||||
<Button.Button type="button" variant="compact" on:click={() => toggleFeedback()}
|
||||
<Button.Button
|
||||
type="button"
|
||||
variant="compact"
|
||||
on:click={() => {
|
||||
toggleFeedback();
|
||||
trackEvent('click_menu_feedback', { source: 'top_nav' });
|
||||
}}
|
||||
>Feedback
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="other">
|
||||
@@ -152,7 +169,10 @@
|
||||
<Button.Button
|
||||
variant="compact"
|
||||
type="button"
|
||||
on:click={() => (showSupport = !showSupport)}>
|
||||
on:click={() => {
|
||||
showSupport = !showSupport;
|
||||
trackEvent('click_menu_support', { source: 'top_nav' });
|
||||
}}>
|
||||
Support
|
||||
</Button.Button>
|
||||
|
||||
@@ -174,6 +194,9 @@
|
||||
<Link.Button
|
||||
on:click={() => {
|
||||
showAccountMenu = !showAccountMenu;
|
||||
if (showAccountMenu) {
|
||||
trackEvent('click_menu_dropdown');
|
||||
}
|
||||
}}>
|
||||
<div style:user-select="none">
|
||||
<Avatar size="s" src={avatar} />
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
<div class="u-flex-vertical u-gap-16">
|
||||
<div>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
color: hsl(var(--color-neutral-50));
|
||||
line-height: 1.25rem;
|
||||
&:first-child {
|
||||
color: hsl(var(--color-neutral-70));
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-dark) div {
|
||||
color: hsl(var(--color-neutral-20));
|
||||
|
||||
&:first-child {
|
||||
color: hsl(var(--color-neutral-10));
|
||||
}
|
||||
max-width: 360px;
|
||||
text-wrap: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,58 +5,62 @@
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import { Badge, Layout, Link, Tag, Typography } from '@appwrite.io/pink-svelte';
|
||||
</script>
|
||||
|
||||
<Base>
|
||||
{#if isCloud}
|
||||
{#if $organization?.billingPlan !== BillingPlan.FREE}
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<p>
|
||||
<span class="u-bold">Roles</span>
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
<span class="inline-tag u-normal u-x-small">Pro plan</span>
|
||||
{/if}
|
||||
</p>
|
||||
<p>Owner, Developer, Editor, Analyst and Billing.</p>
|
||||
</div>
|
||||
<p>
|
||||
<Button link external href="https://appwrite.io/docs/advanced/platform/roles"
|
||||
>Learn more</Button> about roles.
|
||||
</p>
|
||||
{:else}
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<p>
|
||||
<span class="u-bold">Roles</span>
|
||||
{#if $organization?.billingPlan === BillingPlan.FREE}
|
||||
<span class="inline-tag u-normal u-x-small">Pro plan</span>
|
||||
{/if}
|
||||
</p>
|
||||
<p>
|
||||
<Layout.Stack>
|
||||
{#if isCloud}
|
||||
{#if $organization?.billingPlan !== BillingPlan.FREE}
|
||||
<Typography.Text variant="m-600">Roles</Typography.Text>
|
||||
<Typography.Text>Owner, Developer, Editor, Analyst and Billing.</Typography.Text>
|
||||
<Typography.Text>
|
||||
<Link.Anchor
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://appwrite.io/docs/advanced/platform/roles"
|
||||
>Learn more</Link.Anchor>
|
||||
</Typography.Text>
|
||||
{:else}
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Typography.Text variant="m-600">
|
||||
<span class="u-bold">Roles</span>
|
||||
</Typography.Text>
|
||||
<Badge variant="secondary" size="xs" content="Pro plan" />
|
||||
</Layout.Stack>
|
||||
<Typography.Text>
|
||||
Upgrade to Pro to assign new roles to members such as Owner, Developer, Editor
|
||||
or Analyst.
|
||||
</Typography.Text>
|
||||
|
||||
<p class="u-flex u-main-end u-cross-center u-gap-4">
|
||||
<Button
|
||||
size="s"
|
||||
text
|
||||
external
|
||||
href="https://appwrite.io/docs/advanced/platform/roles">Learn more</Button>
|
||||
<Button size="s" secondary external href={$upgradeURL}>Upgrade plan</Button>
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Layout.Stack direction="row" gap="s">
|
||||
<Typography.Text variant="m-600">
|
||||
<span class="u-bold">Roles</span>
|
||||
</Typography.Text>
|
||||
<Badge variant="secondary" size="xs" content="Cloud" />
|
||||
</Layout.Stack>
|
||||
<Typography.Text>
|
||||
Upgrade to Cloud to assign new roles to members or ask us about our enterprise self
|
||||
hosted offering.
|
||||
</Typography.Text>
|
||||
<p class="u-flex u-main-end u-cross-center u-gap-4">
|
||||
<Button text external href="https://appwrite.io/docs/advanced/platform/roles"
|
||||
>Learn more</Button>
|
||||
<Button secondary external href={$upgradeURL}>Upgrade plan</Button>
|
||||
<Button
|
||||
size="s"
|
||||
text
|
||||
external
|
||||
href="https://appwrite.io/docs/advanced/platform/roles">Learn more</Button>
|
||||
<Button size="s" secondary external href={$upgradeURL}>Upgrade to Cloud</Button>
|
||||
</p>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<p>
|
||||
<span class="u-bold">Roles</span>
|
||||
<span class="inline-tag u-normal u-x-small">Appwrite Cloud</span>
|
||||
</p>
|
||||
<p>
|
||||
Upgrade to Cloud to assign new roles to members or ask us about our entriprise self
|
||||
hosted offering.
|
||||
</p>
|
||||
</div>
|
||||
<p class="u-flex u-main-end u-cross-center u-gap-4">
|
||||
<Button text external href="https://appwrite.io/docs/advanced/platform/roles"
|
||||
>Learn more</Button>
|
||||
<Button secondary external href={$upgradeURL}>Upgrade to Cloud</Button>
|
||||
</p>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Base>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
import MobileFeedbackModal from '$routes/(console)/wizard/feedback/mobileFeedbackModal.svelte';
|
||||
import { getSidebarState, updateSidebarState } from '$lib/helpers/sidebar';
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
type $$Props = HTMLElement & {
|
||||
state?: 'closed' | 'open' | 'icons';
|
||||
@@ -66,6 +67,7 @@
|
||||
|
||||
$: state = $isTabletViewport ? 'closed' : getSidebarState();
|
||||
$: pathname = $page.url.pathname;
|
||||
$: isOnProjectSettings = /^\/console\/project-[a-zA-Z0-9-]+\/settings$/.test(pathname);
|
||||
|
||||
const projectOptions = [
|
||||
{ name: 'Auth', icon: IconUserGroup, slug: 'auth' },
|
||||
@@ -111,6 +113,7 @@
|
||||
class="progress-card"
|
||||
href={`/console/project-${project.$id}/get-started`}
|
||||
on:click={() => {
|
||||
trackEvent('click_menu_get_started');
|
||||
sideBarIsOpen = false;
|
||||
}}>
|
||||
<div class="progressCircle">
|
||||
@@ -138,6 +141,7 @@
|
||||
class="link"
|
||||
class:active={pathname.includes('overview')}
|
||||
on:click={() => {
|
||||
trackEvent('click_menu_overview');
|
||||
sideBarIsOpen = false;
|
||||
}}
|
||||
><span class="link-icon"
|
||||
@@ -165,6 +169,7 @@
|
||||
class="link"
|
||||
class:active={pathname.includes(projectOption.slug)}
|
||||
on:click={() => {
|
||||
trackEvent(`click_menu_${projectOption.slug}`);
|
||||
sideBarIsOpen = false;
|
||||
}}
|
||||
><span class="link-icon"
|
||||
@@ -182,7 +187,12 @@
|
||||
</div>
|
||||
<div class="only-mobile">
|
||||
<Tooltip inline={false} placement="right" disabled={state !== 'icons'}>
|
||||
<a href={`/console/project-${project.$id}/settings`} class="link"
|
||||
<a
|
||||
href={`/console/project-${project.$id}/settings`}
|
||||
on:click={() => {
|
||||
trackEvent('click_menu_settings');
|
||||
}}
|
||||
class="link"
|
||||
><span class="link-icon"><Icon icon={IconCog} size="s" /></span
|
||||
><span
|
||||
class:no-text={state === 'icons'}
|
||||
@@ -200,7 +210,10 @@
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => toggleFeedback()}
|
||||
on:click={() => {
|
||||
toggleFeedback();
|
||||
trackEvent('click_menu_feedback', { source: 'side_nav' });
|
||||
}}
|
||||
>Feedback
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="other">
|
||||
@@ -212,7 +225,10 @@
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => ($showSupportModal = true)}>
|
||||
on:click={() => {
|
||||
$showSupportModal = true;
|
||||
trackEvent('click_menu_support', { source: 'side_nav' });
|
||||
}}>
|
||||
<span>Support</span>
|
||||
|
||||
<svelte:fragment slot="other">
|
||||
@@ -232,7 +248,10 @@
|
||||
<a
|
||||
href={`/console/project-${project.$id}/settings`}
|
||||
class="link"
|
||||
class:active={pathname.includes('settings')}
|
||||
on:click={() => {
|
||||
trackEvent('click_menu_settings');
|
||||
}}
|
||||
class:active={isOnProjectSettings}
|
||||
><span class="link-icon"><Icon icon={IconCog} size="s" /></span><span
|
||||
class:no-text={state === 'icons'}
|
||||
class:has-text={state === 'open'}
|
||||
@@ -251,7 +270,10 @@
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => toggleFeedback()}
|
||||
on:click={() => {
|
||||
toggleFeedback();
|
||||
trackEvent('click_menu_feedback', { source: 'side_nav' });
|
||||
}}
|
||||
><span>Feedback</span>
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="other">
|
||||
@@ -263,7 +285,10 @@
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => ($showSupportModal = true)}>
|
||||
on:click={() => {
|
||||
$showSupportModal = true;
|
||||
trackEvent('click_menu_support', { source: 'side_nav' });
|
||||
}}>
|
||||
<span>Support</span>
|
||||
|
||||
<svelte:fragment slot="other">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let selected = false;
|
||||
export let href: string = null;
|
||||
export let event: string = null;
|
||||
export let noscroll = false;
|
||||
export let root: { variant: Variant; stretch: boolean } = {
|
||||
variant: 'primary',
|
||||
stretch: false
|
||||
@@ -87,6 +88,7 @@
|
||||
|
||||
{#if href}
|
||||
<Tabs.Item.Link
|
||||
{noscroll}
|
||||
{root}
|
||||
{href}
|
||||
bind:active={selected}
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
{disabled}
|
||||
{isSearchable}
|
||||
helper={error ?? helper}
|
||||
{required}
|
||||
state={error ? 'error' : 'default'}
|
||||
on:invalid={handleInvalid}
|
||||
on:input
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { Submit, trackEvent } from '$lib/actions/analytics';
|
||||
import { Heading, Trim } from '$lib/components';
|
||||
import { Trim } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Card, Heading } from '$lib/components';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { Button, Form, InputSelect, InputText } from '$lib/elements/forms';
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import { Container } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import { onMount } from 'svelte';
|
||||
import { tierToPlan, type Tier, plansInfo } from '$lib/stores/billing';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import { base } from '$app/paths';
|
||||
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
|
||||
|
||||
let name: string;
|
||||
let plan: Tier;
|
||||
|
||||
const options = isCloud
|
||||
? [
|
||||
{
|
||||
value: BillingPlan.FREE,
|
||||
label: `${tierToPlan(BillingPlan.FREE).name} - ${formatCurrency($plansInfo.get(BillingPlan.FREE).price)} / month`
|
||||
},
|
||||
{
|
||||
value: BillingPlan.PRO,
|
||||
label: `${tierToPlan(BillingPlan.PRO).name} - ${formatCurrency($plansInfo.get(BillingPlan.PRO).price)} / month + add-ons`
|
||||
},
|
||||
{
|
||||
value: BillingPlan.SCALE,
|
||||
label: `${tierToPlan(BillingPlan.SCALE).name} - ${formatCurrency($plansInfo.get(BillingPlan.SCALE).price)}/month + usage`
|
||||
}
|
||||
]
|
||||
: [];
|
||||
|
||||
onMount(() => {
|
||||
if (isCloud) {
|
||||
checkPricingRefAndRedirect($page.url.searchParams);
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
const orgName = name?.length ? name : 'Personal Projects';
|
||||
if (isCloud) {
|
||||
if (plan === BillingPlan.FREE) {
|
||||
try {
|
||||
const org = await sdk.forConsole.billing.createOrganization(
|
||||
ID.unique(),
|
||||
orgName,
|
||||
plan,
|
||||
null,
|
||||
null
|
||||
);
|
||||
trackEvent(Submit.OrganizationCreate, {
|
||||
plan: tierToPlan(plan)?.name
|
||||
});
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
await goto(`${base}/organization-${org.$id}`);
|
||||
addNotification({
|
||||
message: `${orgName} organization successfully created`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.OrganizationCreate);
|
||||
}
|
||||
} else {
|
||||
goto(`${base}/create-organization?name=${orgName}&plan=${plan}`);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const org = await sdk.forConsole.teams.create(ID.unique(), orgName);
|
||||
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
await goto(`${base}/organization-${org.$id}`);
|
||||
|
||||
addNotification({
|
||||
message: `${orgName} organization successfully created`,
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.OrganizationCreate);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container overlapCover size="large">
|
||||
<Card>
|
||||
<Heading size="4" tag="h2">Create a new organization</Heading>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<FormList gap={16}>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Organization name"
|
||||
hideRequired
|
||||
bind:value={name} />
|
||||
{#if isCloud}
|
||||
<div class="u-margin-block-start-8">
|
||||
<h3><b>Plan</b></h3>
|
||||
<p>
|
||||
For more details on our plans, visit our <Button
|
||||
link
|
||||
external
|
||||
href="https://appwrite.io/pricing">pricing page</Button
|
||||
>.
|
||||
</p>
|
||||
<FormList class="u-margin-block-start-8">
|
||||
<InputSelect
|
||||
placeholder="Select a plan"
|
||||
label="Select plan"
|
||||
showLabel={false}
|
||||
id="plan"
|
||||
required
|
||||
hideRequired
|
||||
{options}
|
||||
bind:value={plan} />
|
||||
</FormList>
|
||||
</div>
|
||||
{/if}
|
||||
<Button
|
||||
fullWidth
|
||||
submit
|
||||
disabled={isCloud && !plan}
|
||||
event="create_organization"
|
||||
submissionLoader
|
||||
let:isSubmitting>
|
||||
{#if isSubmitting}
|
||||
Creating your first organization
|
||||
{:else}
|
||||
Get started
|
||||
{/if}
|
||||
</Button>
|
||||
</FormList>
|
||||
</Form>
|
||||
</Card>
|
||||
</Container>
|
||||
@@ -25,16 +25,25 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { loading } from '$routes/store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { ID, Region } from '@appwrite.io/console';
|
||||
import { ID, Region, PlatformType } from '@appwrite.io/console';
|
||||
import { openImportWizard } from '../project-[project]/settings/migrations/(import)';
|
||||
import { readOnly } from '$lib/stores/billing';
|
||||
import type { RegionList } from '$lib/sdk/billing';
|
||||
import { onMount } from 'svelte';
|
||||
import { onMount, type ComponentType } from 'svelte';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
IconAndroid,
|
||||
IconApple,
|
||||
IconCode,
|
||||
IconFlutter,
|
||||
IconGlobe,
|
||||
IconPlus,
|
||||
IconReact,
|
||||
IconUnity
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import { getPlatformInfo } from '$lib/helpers/platform';
|
||||
|
||||
export let data;
|
||||
@@ -64,6 +73,26 @@
|
||||
if (isCloud) wizard.start(Create);
|
||||
else showCreate = true;
|
||||
}
|
||||
|
||||
function getIconForPlatform(platform: string): ComponentType {
|
||||
switch (platform) {
|
||||
case 'web':
|
||||
return IconCode;
|
||||
case 'flutter':
|
||||
return IconFlutter;
|
||||
case 'apple':
|
||||
return IconApple;
|
||||
case 'android':
|
||||
return IconAndroid;
|
||||
case 'react-native':
|
||||
return IconReact;
|
||||
case 'unity':
|
||||
return IconUnity;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Create project',
|
||||
@@ -170,10 +199,10 @@
|
||||
|
||||
{#each platforms as platform, i}
|
||||
{#if i < 3}
|
||||
<Pill>
|
||||
<span class={`icon-${platform.icon}`} aria-hidden="true" />
|
||||
{platform.name}
|
||||
</Pill>
|
||||
{@const icon = getIconForPlatform(platform.icon)}
|
||||
<Badge variant="secondary" content={platform.name}>
|
||||
<Icon {icon} size="s" slot="start" />
|
||||
</Badge>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if platforms?.length > 3}
|
||||
|
||||
@@ -59,24 +59,24 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Invite member" {error} size="big" bind:show={showCreate} onSubmit={create}>
|
||||
<FormList>
|
||||
<InputEmail
|
||||
<Modal title="Invite member" {error} bind:show={showCreate} onSubmit={create}>
|
||||
<InputEmail
|
||||
required
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autofocus={true}
|
||||
bind:value={email} />
|
||||
<InputText id="member-name" label="Name" placeholder="Enter name" bind:value={name} />
|
||||
{#if isCloud}
|
||||
<InputSelect
|
||||
required
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autofocus={true}
|
||||
bind:value={email} />
|
||||
<InputText
|
||||
id="member-name"
|
||||
label="Name (optional)"
|
||||
placeholder="Enter name"
|
||||
bind:value={name} />
|
||||
{#if isCloud}
|
||||
<InputSelect popover={Roles} id="role" label="Role" options={roles} bind:value={role} />
|
||||
{/if}
|
||||
</FormList>
|
||||
popover={Roles}
|
||||
id="role"
|
||||
label="Role"
|
||||
options={roles}
|
||||
bind:value={role} />
|
||||
{/if}
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
|
||||
<Button submit submissionLoader>Send invite</Button>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { ID, Region } from '@appwrite.io/console';
|
||||
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Icon, Layout, Tag } from '@appwrite.io/pink-svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let show = false;
|
||||
@@ -49,21 +51,27 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Create project" {error} onSubmit={create} size="big" bind:show>
|
||||
<FormList>
|
||||
<Modal title="Create project" {error} onSubmit={create} bind:show>
|
||||
<Layout.Stack gap="l">
|
||||
<InputText id="name" label="Name" bind:value={name} required autofocus={true} />
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}>
|
||||
<span class="icon-pencil" aria-hidden="true" /><span class="text">
|
||||
Project ID
|
||||
</span>
|
||||
</Pill>
|
||||
</div>
|
||||
<span>
|
||||
<Tag size="s" on:click={() => (showCustomId = !showCustomId)}>
|
||||
<Icon icon={IconPencil} slot="start" size="s" />
|
||||
Project ID
|
||||
</Tag>
|
||||
</span>
|
||||
{:else}
|
||||
<CustomId autofocus bind:show={showCustomId} name="Project" isProject bind:id />
|
||||
<CustomId
|
||||
autofocus
|
||||
bind:show={showCustomId}
|
||||
name="Project"
|
||||
isProject
|
||||
bind:id
|
||||
fullWidth />
|
||||
{/if}
|
||||
</FormList>
|
||||
</Layout.Stack>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button submit {disabled}>Create</Button>
|
||||
|
||||
@@ -14,9 +14,24 @@
|
||||
import { isOwner } from '$lib/stores/roles';
|
||||
import Edit from './edit.svelte';
|
||||
import { getRoleLabel } from '$lib/stores/billing';
|
||||
import { Drop } from '$lib/components';
|
||||
import { IconDotsHorizontal, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Layout, Table, Badge, Button, Icon } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
IconDotsHorizontal,
|
||||
IconInfo,
|
||||
IconPencil,
|
||||
IconPlus,
|
||||
IconRefresh,
|
||||
IconTrash
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
import {
|
||||
Layout,
|
||||
Table,
|
||||
Badge,
|
||||
Button,
|
||||
Icon,
|
||||
Typography,
|
||||
Popover,
|
||||
ActionMenu
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import Upgrade from '$lib/components/roles/upgrade.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
@@ -25,7 +40,6 @@
|
||||
let showDelete = false;
|
||||
let showEdit = false;
|
||||
let showDropdown = [];
|
||||
let showPopover = false;
|
||||
|
||||
const resend = async (member: Models.Membership) => {
|
||||
try {
|
||||
@@ -54,7 +68,8 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Typography.Title>Members</Typography.Title>
|
||||
<ConsoleButton on:mousedown={() => newMemberModal.set(true)} event="create_user" size="s">
|
||||
<Icon size="s" icon={IconPlus} slot="start" />
|
||||
<span class="text">Invite</span>
|
||||
@@ -65,28 +80,16 @@
|
||||
<svelte:fragment slot="header">
|
||||
<Table.Header.Cell>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell>Email</Table.Header.Cell>
|
||||
<Table.Header.Cell
|
||||
>Roles
|
||||
<Drop isPopover bind:show={showPopover} display="inline-block">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => (showPopover = !showPopover)}
|
||||
aria-label="input tooltip">
|
||||
<span
|
||||
class="icon-info"
|
||||
aria-hidden="true"
|
||||
style="font-size: var(--icon-size-small)" />
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<div
|
||||
class="dropped card u-max-width-300 u-break-word"
|
||||
style:--card-border-radius="var(--border-radius-small)"
|
||||
style:--p-card-padding=".75rem"
|
||||
style:box-shadow="var(--shadow-large)">
|
||||
<svelte:component this={Upgrade} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Drop>
|
||||
<Table.Header.Cell>
|
||||
<Layout.Stack direction="row" gap="xxs" alignItems="center">
|
||||
Roles
|
||||
<Popover let:toggle>
|
||||
<Button.Button icon variant="compact" size="s" on:click={toggle}>
|
||||
<Icon size="s" icon={IconInfo} />
|
||||
</Button.Button>
|
||||
<svelte:component this={Upgrade} slot="tooltip" />
|
||||
</Popover>
|
||||
</Layout.Stack>
|
||||
</Table.Header.Cell>
|
||||
<Table.Header.Cell>2FA</Table.Header.Cell>
|
||||
{#if $isOwner}
|
||||
@@ -102,7 +105,7 @@
|
||||
{member.userName ? member.userName : 'n/a'}
|
||||
</span>
|
||||
{#if member.invited && !member.joined}
|
||||
<Badge type="warning" variant="secondary" content="Pending" />
|
||||
<Badge type="warning" variant="secondary" content="Pending" size="xs" />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
@@ -121,49 +124,41 @@
|
||||
</Table.Cell>
|
||||
{#if $isOwner}
|
||||
<Table.Cell>
|
||||
<DropList bind:show={showDropdown[index]} placement="bottom-end" noArrow>
|
||||
<Button.Button
|
||||
icon
|
||||
variant="text"
|
||||
size="s"
|
||||
aria-label="More options"
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
showDropdown[index] = !showDropdown[index];
|
||||
}}>
|
||||
<Icon icon={IconDotsHorizontal} size="m" />
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
<Button.Button icon variant="ghost" size="s" on:click={toggle}>
|
||||
<Icon size="s" icon={IconDotsHorizontal} />
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem
|
||||
icon="pencil"
|
||||
on:click={() => {
|
||||
selectedMember = member;
|
||||
showEdit = true;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Edit role
|
||||
</DropListItem>
|
||||
{#if member.invited && !member.joined}
|
||||
<DropListItem
|
||||
icon="refresh"
|
||||
<div style:min-width="232px" slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
trailingIcon={IconPencil}
|
||||
on:click={() => {
|
||||
resend(member);
|
||||
selectedMember = member;
|
||||
showEdit = true;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Resend
|
||||
</DropListItem>
|
||||
{/if}
|
||||
<DropListItem
|
||||
icon="trash"
|
||||
on:click={() => {
|
||||
selectedMember = member;
|
||||
showDelete = true;
|
||||
showDropdown[index] = false;
|
||||
}}>
|
||||
Remove
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
Edit role
|
||||
</ActionMenu.Item.Button>
|
||||
{#if member.invited && !member.joined}
|
||||
<ActionMenu.Item.Button
|
||||
trailingIcon={IconRefresh}
|
||||
on:click={() => {
|
||||
resend(member);
|
||||
}}>
|
||||
Resend
|
||||
</ActionMenu.Item.Button>
|
||||
{/if}
|
||||
<ActionMenu.Item.Button
|
||||
trailingIcon={IconTrash}
|
||||
on:click={() => {
|
||||
selectedMember = member;
|
||||
showDelete = true;
|
||||
}}>
|
||||
Remove
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
</Popover>
|
||||
</Table.Cell>
|
||||
{/if}
|
||||
</Table.Row>
|
||||
|
||||
@@ -53,14 +53,13 @@
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Name</svelte:fragment>
|
||||
<svelte:fragment slot="aside">
|
||||
<ul>
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={name} />
|
||||
</ul>
|
||||
<InputText
|
||||
required
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="Enter name"
|
||||
autocomplete={false}
|
||||
bind:value={name} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
+94
-107
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Alert, Modal, SecondaryTabs, SecondaryTabsItem } from '$lib/components';
|
||||
import { Button, FormList, InputText } from '$lib/elements/forms';
|
||||
import { Modal, SecondaryTabs, SecondaryTabsItem } from '$lib/components';
|
||||
import { Button, InputText } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { members, organization, organizationList } from '$lib/stores/organization';
|
||||
@@ -22,6 +22,7 @@
|
||||
} 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';
|
||||
|
||||
export let showDelete = false;
|
||||
export let invoices: InvoiceList;
|
||||
@@ -102,116 +103,102 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="max-height-dialog">
|
||||
<Modal title="Delete organization" onSubmit={deleteOrg} bind:show={showDelete} bind:error>
|
||||
{#if upcomingInvoice}
|
||||
<Alert type="warning">
|
||||
<span slot="title">
|
||||
You have a pending {formatCurrency(upcomingInvoice.grossAmount)} invoice for your
|
||||
{tierToPlan(upcomingInvoice.plan).name} plan
|
||||
</span>
|
||||
<p>
|
||||
By proceeding, your invoice will be processed within the hour. Upon successful
|
||||
payment, your organization will be deleted.
|
||||
</p>
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
<p data-private>
|
||||
{#if $projects.total > 0}
|
||||
The following projects and all data associated with <b>{$organization.name}</b> will
|
||||
be permanently deleted. <b>This action is irreversible</b>.
|
||||
{:else}
|
||||
All data associated with <b>{$organization.name}</b> will be permanently deleted.
|
||||
<b>This action is irreversible</b>.
|
||||
{/if}
|
||||
</p>
|
||||
<Modal title="Delete organization" onSubmit={deleteOrg} bind:show={showDelete} bind:error>
|
||||
{#if upcomingInvoice}
|
||||
<Alert.Inline
|
||||
status="warning"
|
||||
title={`You have a pending ${formatCurrency(upcomingInvoice.grossAmount)} invoice for your
|
||||
${tierToPlan(upcomingInvoice.plan).name} plan`}>
|
||||
By proceeding, your invoice will be processed within the hour. Upon successful payment,
|
||||
your organization will be deleted.
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
|
||||
<p slot="description">
|
||||
{#if $projects.total > 0}
|
||||
<div class="box is-only-desktop">
|
||||
<SecondaryTabs large stretch class="u-sep-block-end u-padding-8">
|
||||
{#each tabs as { name, label, total }}
|
||||
<SecondaryTabsItem
|
||||
center
|
||||
fullWidth
|
||||
disabled={selectedTab === name}
|
||||
on:click={() => (selectedTab = name)}>
|
||||
{label.desktop} ({total})
|
||||
</SecondaryTabsItem>
|
||||
{/each}
|
||||
</SecondaryTabs>
|
||||
|
||||
<TableScroll dense noMargin>
|
||||
<TableHeader>
|
||||
{#each tabData.headers as header}
|
||||
<TableCellHead width={columnWidth}>{header}</TableCellHead>
|
||||
{/each}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each tabData.rows as row}
|
||||
<TableRow>
|
||||
{#each row.cells as cell}
|
||||
<TableCell width={columnWidth}>{cell}</TableCell>
|
||||
{/each}
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
</div>
|
||||
<div class="box is-not-desktop">
|
||||
<SecondaryTabs large stretch class="u-sep-block-end u-padding-8">
|
||||
{#each tabs as { name, label, total }}
|
||||
<SecondaryTabsItem
|
||||
center
|
||||
fullWidth
|
||||
disabled={selectedTab === name}
|
||||
on:click={() => (selectedTab = name)}>
|
||||
{label.mobile} ({total})
|
||||
</SecondaryTabsItem>
|
||||
{/each}
|
||||
</SecondaryTabs>
|
||||
|
||||
<TableScroll dense noMargin>
|
||||
<TableHeader>
|
||||
{#each tabData.headers as header, index}
|
||||
<TableCellHead width={index === 1 ? columnWidthSmall : columnWidth}
|
||||
>{header}</TableCellHead>
|
||||
{/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>
|
||||
</div>
|
||||
The following projects and all data associated with <b>{$organization.name}</b> will be
|
||||
permanently deleted. <b>This action is irreversible</b>.
|
||||
{:else}
|
||||
All data associated with <b>{$organization.name}</b> will be permanently deleted.
|
||||
<b>This action is irreversible</b>.
|
||||
{/if}
|
||||
</p>
|
||||
|
||||
<FormList>
|
||||
<InputText
|
||||
label={`Confirm the organization name to continue`}
|
||||
placeholder="Enter {$organization.name} to continue"
|
||||
id="organization-name"
|
||||
required
|
||||
bind:value={organizationName} />
|
||||
</FormList>
|
||||
{#if $projects.total > 0}
|
||||
<Tabs.Root let:root stretch>
|
||||
{#each tabs as { name, label, total }}
|
||||
<Tabs.Item.Button
|
||||
{root}
|
||||
on:click={() => (selectedTab = name)}
|
||||
active={selectedTab === name}>
|
||||
{label.desktop} ({total})
|
||||
</Tabs.Item.Button>
|
||||
{/each}
|
||||
</Tabs.Root>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button
|
||||
secondary
|
||||
submit
|
||||
disabled={!error && (!organizationName || organizationName !== $organization.name)}>
|
||||
Delete
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</div>
|
||||
<Table.Root>
|
||||
<svelte:fragment slot="header">
|
||||
{#each tabData.headers as header}
|
||||
<Table.Header.Cell width={columnWidth + 'px'}>{header}</Table.Header.Cell>
|
||||
{/each}
|
||||
</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 class="box is-not-desktop">
|
||||
<SecondaryTabs large stretch class="u-sep-block-end u-padding-8">
|
||||
{#each tabs as { name, label, total }}
|
||||
<SecondaryTabsItem
|
||||
center
|
||||
fullWidth
|
||||
disabled={selectedTab === name}
|
||||
on:click={() => (selectedTab = name)}>
|
||||
{label.mobile} ({total})
|
||||
</SecondaryTabsItem>
|
||||
{/each}
|
||||
</SecondaryTabs>
|
||||
|
||||
<TableScroll dense noMargin>
|
||||
<TableHeader>
|
||||
{#each tabData.headers as header, index}
|
||||
<TableCellHead width={index === 1 ? columnWidthSmall : columnWidth}
|
||||
>{header}</TableCellHead>
|
||||
{/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>
|
||||
</div>
|
||||
{/if}
|
||||
<InputText
|
||||
label={`Confirm the organization name to continue`}
|
||||
placeholder="Enter {$organization.name} to continue"
|
||||
id="organization-name"
|
||||
required
|
||||
bind:value={organizationName} />
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button
|
||||
secondary
|
||||
submit
|
||||
disabled={!error && (!organizationName || organizationName !== $organization.name)}>
|
||||
Delete
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.box {
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
import { periodToDates } from '$lib/layout/usage.svelte';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
import { hasOnboardingDismissed } from '$lib/helpers/onboarding';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Card, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
@@ -89,143 +89,166 @@
|
||||
|
||||
{#if $project}
|
||||
<Container overlapCover>
|
||||
{#if $usage}
|
||||
{@const storage = humanFileSize($usage.filesStorageTotal ?? 0)}
|
||||
<section class="common-section">
|
||||
<div class="grid-dashboard-1s-2m-6l">
|
||||
<div class="card is-2-columns-medium-screen is-3-columns-large-screen">
|
||||
<Bandwidth {period} on:change={(e) => changePeriod(e.detail)} />
|
||||
<!-- TODO: fix with nicer solution -->
|
||||
<div class="flex-container">
|
||||
{#if $usage}
|
||||
{@const storage = humanFileSize($usage.filesStorageTotal ?? 0)}
|
||||
<section class="common-section">
|
||||
<div class="grid-dashboard-1s-2m-6l" style:gap="1rem">
|
||||
<Card.Base
|
||||
class="is-2-columns-medium-screen is-3-columns-large-screen"
|
||||
padding="s">
|
||||
<Bandwidth {period} on:change={(e) => changePeriod(e.detail)} />
|
||||
</Card.Base>
|
||||
<Card.Base
|
||||
class="is-2-columns-medium-screen is-3-columns-large-screen"
|
||||
padding="s">
|
||||
<Requests {period} on:change={(e) => changePeriod(e.detail)} />
|
||||
</Card.Base>
|
||||
<Card.Link
|
||||
padding="s"
|
||||
href={`${base}/project-${projectId}/databases`}
|
||||
class="is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
<span class="icon-database" aria-hidden="true" />
|
||||
<span class="text">Database</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<Typography.Title>
|
||||
{formatNum($usage.documentsTotal ?? 0)}
|
||||
</Typography.Title>
|
||||
<Typography.Text>Documents</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<Typography.Text
|
||||
>Databases: {formatNum(
|
||||
$usage.databasesTotal ?? 0
|
||||
)}</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Link>
|
||||
<Card.Link
|
||||
padding="s"
|
||||
href={`${base}/project-${projectId}/storage`}
|
||||
class="is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
<span class="icon-folder" aria-hidden="true" />
|
||||
<span class="text">Storage</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<Typography.Title>
|
||||
{storage.value}
|
||||
<span class="body-text-2">{storage.unit}</span>
|
||||
</Typography.Title>
|
||||
<Typography.Text>Storage</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<Typography.Text>
|
||||
Buckets: {formatNum($usage.bucketsTotal ?? 0)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Link>
|
||||
<Card.Link
|
||||
padding="s"
|
||||
href={`${base}/project-${projectId}/auth`}
|
||||
class="is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
<span class="icon-user-group" aria-hidden="true" />
|
||||
<span class="text">Auth</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<Typography.Title>
|
||||
{formatNum($usage.usersTotal ?? 0)}
|
||||
</Typography.Title>
|
||||
<Typography.Text>Users</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Link>
|
||||
<Card.Link
|
||||
padding="s"
|
||||
href={`${base}/project-${projectId}/functions`}
|
||||
class="is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
<span class="icon-lightning-bolt" aria-hidden="true" />
|
||||
<span class="text">Functions</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<Typography.Title>
|
||||
{formatNum($usage.executionsTotal ?? 0)}
|
||||
</Typography.Title>
|
||||
<Typography.Text>Executions</Typography.Text>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text" />
|
||||
</div>
|
||||
</div>
|
||||
</Card.Link>
|
||||
<Card.Base
|
||||
padding="s"
|
||||
class="is-2-columns-medium-screen is-2-columns-large-screen is-2-rows-large-screen is-location-row-2-end-large-screen">
|
||||
<Realtime />
|
||||
</Card.Base>
|
||||
</div>
|
||||
<div class="card is-2-columns-medium-screen is-3-columns-large-screen">
|
||||
<Requests {period} on:change={(e) => changePeriod(e.detail)} />
|
||||
</div>
|
||||
<a
|
||||
href={`${base}/project-${projectId}/databases`}
|
||||
class="card is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
<span class="icon-database" aria-hidden="true" />
|
||||
<span class="text">Database</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
{formatNum($usage.documentsTotal ?? 0)}
|
||||
</div>
|
||||
<div>Documents</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text">
|
||||
Databases: {formatNum($usage.databasesTotal ?? 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href={`${base}/project-${projectId}/storage`}
|
||||
class="card is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
<span class="icon-folder" aria-hidden="true" />
|
||||
<span class="text">Storage</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
{storage.value}
|
||||
<span class="body-text-2">{storage.unit}</span>
|
||||
</div>
|
||||
<div>Storage</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text">
|
||||
Buckets: {formatNum($usage.bucketsTotal ?? 0)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href={`${base}/project-${projectId}/auth`}
|
||||
class="card is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
<span class="icon-user-group" aria-hidden="true" />
|
||||
<span class="text">Auth</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
{formatNum($usage.usersTotal ?? 0)}
|
||||
</div>
|
||||
<div>Users</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
href={`${base}/project-${projectId}/functions`}
|
||||
class="card is-2-columns-large-screen">
|
||||
<div class="grid-item-1">
|
||||
<div class="grid-item-1-start-start">
|
||||
<div class="eyebrow-heading-3">
|
||||
<span class="icon-lightning-bolt" aria-hidden="true" />
|
||||
<span class="text">Functions</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-start-end" />
|
||||
|
||||
<div class="grid-item-1-end-start">
|
||||
<div class="heading-level-4">
|
||||
{formatNum($usage.executionsTotal ?? 0)}
|
||||
</div>
|
||||
<div>Executions</div>
|
||||
</div>
|
||||
|
||||
<div class="grid-item-1-end-end">
|
||||
<div class="text" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
class="card is-2-columns-medium-screen is-2-columns-large-screen is-2-rows-large-screen is-location-row-2-end-large-screen">
|
||||
<Realtime />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<Layout.Stack gap="xl">
|
||||
<Heading tag="h2" size="5" id="integrations">Integrations</Heading>
|
||||
<Layout.Stack gap="xl" direction="row" justifyContent="space-between">
|
||||
<Tabs>
|
||||
<Tab
|
||||
href={`${path}/platforms`}
|
||||
selected={$page.url.pathname === `${path}/platforms`}
|
||||
event="platforms">Platforms</Tab>
|
||||
<Tab
|
||||
href={`${path}/keys`}
|
||||
selected={$page.url.pathname === `${path}/keys`}
|
||||
event="keys">API keys</Tab>
|
||||
</Tabs>
|
||||
{#if $action}
|
||||
<svelte:component this={$action} />
|
||||
{/if}
|
||||
<Layout.Stack gap="xl">
|
||||
<Heading tag="h2" size="5" id="integrations">Integrations</Heading>
|
||||
<Layout.Stack gap="xl" direction="row" justifyContent="space-between">
|
||||
<Tabs>
|
||||
<Tab
|
||||
noscroll
|
||||
href={`${path}/platforms`}
|
||||
selected={$page.url.pathname === `${path}/platforms`}
|
||||
event="platforms">Platforms</Tab>
|
||||
<Tab
|
||||
noscroll
|
||||
href={`${path}/keys`}
|
||||
selected={$page.url.pathname === `${path}/keys`}
|
||||
event="keys">API keys</Tab>
|
||||
</Tabs>
|
||||
{#if $action}
|
||||
<svelte:component this={$action} />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
</Container>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.flex-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-12);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { Card, DropList, DropListItem } from '$lib/components';
|
||||
import { Card } from '$lib/components';
|
||||
import { humanFileSize } from '$lib/helpers/sizeConvertion';
|
||||
import { totalMetrics } from './+layout.svelte';
|
||||
import { usage } from './store';
|
||||
import type { UsagePeriods } from '$lib/layout';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { BarChart } from '$lib/charts';
|
||||
import { Popover, Typography, Icon, ActionMenu, Link, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { IconChevronDown } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let period: UsagePeriods;
|
||||
|
||||
let showPeriod = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
$: network = $usage?.network as unknown as Array<{
|
||||
@@ -19,32 +19,35 @@
|
||||
}>;
|
||||
|
||||
$: bandwidth = humanFileSize(totalMetrics($usage?.network));
|
||||
|
||||
$: if (period) {
|
||||
showPeriod = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="u-flex u-gap-16 u-main-space-between">
|
||||
<Layout.Stack justifyContent="space-between" direction="row" alignItems="flex-start">
|
||||
<div>
|
||||
<div class="heading-level-4">
|
||||
<Typography.Title>
|
||||
{bandwidth.value}
|
||||
<span class="body-text-2">{bandwidth.unit}</span>
|
||||
</div>
|
||||
<div>Bandwidth</div>
|
||||
</Typography.Title>
|
||||
<Typography.Text>Bandwidth</Typography.Text>
|
||||
</div>
|
||||
<DropList bind:show={showPeriod} placement="bottom-start" childStart noArrow>
|
||||
<button class="transparent-button" on:click={() => (showPeriod = !showPeriod)}>
|
||||
<span class="text">{period}</span>
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => dispatch('change', '24h')}>24h</DropListItem>
|
||||
<DropListItem on:click={() => dispatch('change', '30d')}>30d</DropListItem>
|
||||
<DropListItem on:click={() => dispatch('change', '90d')}>90d</DropListItem>
|
||||
<Popover let:toggle padding="none">
|
||||
<Link.Button on:click={toggle} variant="quiet">
|
||||
<Layout.Stack direction="row" gap="none">
|
||||
<span>{period}</span>
|
||||
<Icon icon={IconChevronDown} />
|
||||
</Layout.Stack>
|
||||
</Link.Button>
|
||||
<svelte:fragment slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button on:click={() => dispatch('change', '24h')}
|
||||
>24h</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => dispatch('change', '30d')}
|
||||
>30d</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => dispatch('change', '90d')}
|
||||
>90d</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</div>
|
||||
</Popover>
|
||||
</Layout.Stack>
|
||||
{#if bandwidth.value !== '0'}
|
||||
<div style="height: 12rem;">
|
||||
<BarChart
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { user } from '$lib/stores/user';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
</script>
|
||||
|
||||
{#if !$page.url.pathname.includes('get-started')}
|
||||
@@ -45,6 +46,7 @@
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={async () => {
|
||||
trackEvent('onboarding_hub_platform_dismiss');
|
||||
await setHasOnboardingDismissed($project.$id);
|
||||
goto(`${base}/project-${$project.$id}/overview`);
|
||||
requestAnimationFrame(() => {
|
||||
|
||||
@@ -37,8 +37,15 @@
|
||||
import { AvatarGroup } from '$lib/components';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { getPlatformInfo } from '$lib/helpers/platform';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
function createKey() {
|
||||
trackEvent('onboarding_hub_platform', {
|
||||
platform: 'server',
|
||||
state: 'add'
|
||||
});
|
||||
wizard.start(Wizard);
|
||||
}
|
||||
|
||||
@@ -386,7 +393,12 @@
|
||||
<Layout.Stack
|
||||
gap="l"
|
||||
direction={$isSmallViewport ? 'column' : 'row'}>
|
||||
<Card.Button on:click={() => addPlatform(0)} padding="s"
|
||||
<Card.Button
|
||||
on:click={() => {
|
||||
trackEvent('onboarding_hub_setup_databases');
|
||||
goto(`${base}/project-${projectId}/databases`);
|
||||
}}
|
||||
padding="s"
|
||||
><Layout.Stack gap="xl"
|
||||
><div
|
||||
class="card-top-image database-card-image"
|
||||
@@ -419,20 +431,38 @@
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href="https://appwrite.io/docs/references"
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_api_references'
|
||||
);
|
||||
}}
|
||||
target="_blank">API references</Link.Anchor>
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href="https://appwrite.io/docs/tutorials"
|
||||
on:click={() => {
|
||||
trackEvent('onboarding_hub_tutorials');
|
||||
}}
|
||||
target="_blank">Tutorials</Link.Anchor>
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href="https://appwrite.io/docs/products/storage/quick-start"
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_storage_quick_start'
|
||||
);
|
||||
}}
|
||||
target="_blank"
|
||||
>Storage quick start
|
||||
</Link.Anchor>
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href="https://appwrite.io/docs/products/functions/quick-start"
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_functions_quick_start'
|
||||
);
|
||||
}}
|
||||
target="_blank"
|
||||
>Functions quick start</Link.Anchor>
|
||||
</Layout.Stack>
|
||||
@@ -458,16 +488,29 @@
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href={`${base}/project-${projectId}/auth/settings`}
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_auth_email_password'
|
||||
);
|
||||
}}
|
||||
>E-mail and password
|
||||
</Link.Anchor>
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href={`${base}/project-${projectId}/auth/settings`}
|
||||
>OAuth 2</Link.Anchor>
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_auth_oauth2'
|
||||
);
|
||||
}}>OAuth 2</Link.Anchor>
|
||||
<Link.Anchor
|
||||
variant="quiet-muted"
|
||||
href={`${base}/project-${projectId}/auth/settings`}
|
||||
>View all methods</Link.Anchor>
|
||||
on:click={() => {
|
||||
trackEvent(
|
||||
'onboarding_hub_auth_all_methods'
|
||||
);
|
||||
}}>View all methods</Link.Anchor>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<div
|
||||
@@ -481,7 +524,12 @@
|
||||
</div></Card.Base>
|
||||
</div>
|
||||
|
||||
<Card.Link href="https://appwrite.io/discord" padding="s">
|
||||
<Card.Link
|
||||
href="https://appwrite.io/discord"
|
||||
padding="s"
|
||||
on:click={() => {
|
||||
trackEvent('onboarding_hub_discord');
|
||||
}}>
|
||||
<div class="full-height-card">
|
||||
<Layout.Stack gap="xs" justifyContent="space-between">
|
||||
<Layout.Stack direction="column" gap="xxxs">
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import CreateReactNative from './createReactNative.svelte';
|
||||
import CreateWeb from './createWeb.svelte';
|
||||
import { createPlatform, versions } from './wizard/store';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
export enum Platform {
|
||||
Web,
|
||||
@@ -20,6 +21,10 @@
|
||||
export async function addPlatform(type: Platform) {
|
||||
await versions.load();
|
||||
createPlatform.reset();
|
||||
trackEvent('onboarding_hub_platform', {
|
||||
platform: platforms[type],
|
||||
state: 'add'
|
||||
});
|
||||
wizard.start(platforms[type]);
|
||||
}
|
||||
|
||||
@@ -35,6 +40,10 @@
|
||||
key: key,
|
||||
type: type
|
||||
});
|
||||
trackEvent('onboarding_hub_platform', {
|
||||
platform: platforms[platform],
|
||||
state: 'continue'
|
||||
});
|
||||
wizard.start(platforms[platform], null, 1, {
|
||||
isPlatformCreated: true,
|
||||
platform: type,
|
||||
@@ -61,8 +70,15 @@
|
||||
import type { PageData } from './$types';
|
||||
import { canWritePlatforms } from '$lib/stores/roles';
|
||||
import { setOverviewAction } from '../context';
|
||||
import { Table } from '@appwrite.io/pink-svelte';
|
||||
import { ActionMenu, Card, Empty, Popover, Table } from '@appwrite.io/pink-svelte';
|
||||
import Action from './action.svelte';
|
||||
import {
|
||||
IconAndroid,
|
||||
IconApple,
|
||||
IconCode,
|
||||
IconFlutter,
|
||||
IconReact
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -124,52 +140,49 @@
|
||||
{/each}
|
||||
</Table.Root>
|
||||
{:else}
|
||||
<article class="card u-grid u-cross-center u-width-full-line common-section">
|
||||
<div class="u-flex u-flex-vertical u-cross-center u-gap-24">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
<img src={SearchDark} alt="empty" aria-hidden="true" />
|
||||
{:else}
|
||||
<img src={SearchLight} alt="empty" aria-hidden="true" />
|
||||
{/if}
|
||||
<slot>
|
||||
<div class="u-text-center">
|
||||
<Heading size="7" tag="h4">Create a platform to get started.</Heading>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">
|
||||
Need a hand? Learn more in our documentation.
|
||||
</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 u-main-center">
|
||||
<Button external href="https://appwrite.io/docs/sdks" text>
|
||||
Documentation
|
||||
</Button>
|
||||
<DropList bind:show={showDropdownEmpty} placement="bottom-start">
|
||||
{#if $canWritePlatforms}
|
||||
<Button
|
||||
secondary
|
||||
on:click={() => (showDropdownEmpty = !showDropdownEmpty)}>
|
||||
<span class="text">Add platform</span>
|
||||
</Button>
|
||||
{/if}
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => addPlatform(Platform.Web)}>
|
||||
<Card.Base padding="none">
|
||||
<Empty
|
||||
title="Add a platform to get started."
|
||||
description="Need a hand? Learn more in our documentation.">
|
||||
<svelte:fragment slot="actions">
|
||||
<Button external href="https://appwrite.io/docs/sdks" text>Documentation</Button>
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
{#if $canWritePlatforms}
|
||||
<Button secondary on:click={toggle}>
|
||||
<span class="text">Add platform</span>
|
||||
</Button>
|
||||
{/if}
|
||||
<div style:min-width="232px" slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Web)}
|
||||
leadingIcon={IconCode}>
|
||||
Web
|
||||
</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.Flutter)}>
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Flutter)}
|
||||
leadingIcon={IconFlutter}>
|
||||
Flutter
|
||||
</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.Android)}>
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Android)}
|
||||
leadingIcon={IconAndroid}>
|
||||
Android
|
||||
</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.Apple)}>
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Apple)}
|
||||
leadingIcon={IconApple}>
|
||||
Apple
|
||||
</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.ReactNative)}>
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.ReactNative)}
|
||||
leadingIcon={IconReact}>
|
||||
React Native
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
</article>
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
</Popover>
|
||||
</svelte:fragment>
|
||||
</Empty>
|
||||
</Card.Base>
|
||||
{/if}
|
||||
|
||||
@@ -3,26 +3,56 @@
|
||||
import DropListItem from '$lib/components/dropListItem.svelte';
|
||||
import Button from '$lib/elements/forms/button.svelte';
|
||||
import { canWritePlatforms } from '$lib/stores/roles';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { ActionMenu, Icon, Popover } from '@appwrite.io/pink-svelte';
|
||||
import { addPlatform, Platform } from './+page.svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import {
|
||||
IconAndroid,
|
||||
IconApple,
|
||||
IconCode,
|
||||
IconFlutter,
|
||||
IconPlus,
|
||||
IconReact
|
||||
} from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
let showDropdown = false;
|
||||
</script>
|
||||
|
||||
{#if $canWritePlatforms}
|
||||
<DropList bind:show={showDropdown} placement="bottom-start">
|
||||
<Button on:click={() => (showDropdown = !showDropdown)}>
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Add platform
|
||||
</Button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => addPlatform(Platform.Web)}>Web app</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.Flutter)}>Flutter app</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.Android)}>Android app</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.Apple)}>Apple app</DropListItem>
|
||||
<DropListItem on:click={() => addPlatform(Platform.ReactNative)}
|
||||
>React Native app</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
<Popover let:toggle padding="none" placement="bottom-end">
|
||||
{#if $canWritePlatforms}
|
||||
<Button on:click={toggle}>
|
||||
<Icon icon={IconPlus} slot="start" />
|
||||
<span class="text">Add platform</span>
|
||||
</Button>
|
||||
{/if}
|
||||
<div style:min-width="232px" slot="tooltip">
|
||||
<ActionMenu.Root>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Web)}
|
||||
leadingIcon={IconCode}>
|
||||
Web
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Flutter)}
|
||||
leadingIcon={IconFlutter}>
|
||||
Flutter
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Android)}
|
||||
leadingIcon={IconAndroid}>
|
||||
Android
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.Apple)}
|
||||
leadingIcon={IconApple}>
|
||||
Apple
|
||||
</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => addPlatform(Platform.ReactNative)}
|
||||
leadingIcon={IconReact}>
|
||||
React Native
|
||||
</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</div>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { BarChart } from '$lib/charts';
|
||||
import { Card } from '$lib/components';
|
||||
import { formatNum } from '$lib/helpers/string';
|
||||
import { Link, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { stats } from '../store';
|
||||
|
||||
$: projectId = $page.params.project;
|
||||
@@ -34,12 +35,11 @@
|
||||
class="icon-chart-square-bar text-large"
|
||||
aria-hidden="true"
|
||||
style="font-size: 32px;" />
|
||||
<p class="u-bold">No data to show</p>
|
||||
<a
|
||||
class="link"
|
||||
<Typography.Text variant="m-600">No data to show</Typography.Text>
|
||||
<Link.Anchor
|
||||
href="https://appwrite.io/docs/apis/realtime"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">Get started with Realtime</a>
|
||||
rel="noopener noreferrer">Get started with Realtime</Link.Anchor>
|
||||
</div>
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { LineChart } from '$lib/charts';
|
||||
import { formatNum } from '$lib/helpers/string';
|
||||
import { ActionMenu, Icon, Layout, Link, Popover, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconChevronDown } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let period: UsagePeriods;
|
||||
|
||||
@@ -23,25 +25,31 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="u-flex u-gap-16 u-main-space-between">
|
||||
<Layout.Stack justifyContent="space-between" direction="row" alignItems="flex-start">
|
||||
<div>
|
||||
<div class="heading-level-4">
|
||||
<Typography.Title>
|
||||
{formatNum(totalMetrics($usage?.requests))}
|
||||
</div>
|
||||
<div>Requests</div>
|
||||
</Typography.Title>
|
||||
<Typography.Text>Requests</Typography.Text>
|
||||
</div>
|
||||
<DropList bind:show={showPeriod} placement="bottom-start" childStart noArrow>
|
||||
<button class="transparent-button" on:click={() => (showPeriod = !showPeriod)}>
|
||||
<span class="text">{period}</span>
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem on:click={() => dispatch('change', '24h')}>24h</DropListItem>
|
||||
<DropListItem on:click={() => dispatch('change', '30d')}>30d</DropListItem>
|
||||
<DropListItem on:click={() => dispatch('change', '90d')}>90d</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</div>
|
||||
<Popover let:toggle padding="none">
|
||||
<Link.Button on:click={toggle} variant="quiet">
|
||||
<Layout.Stack direction="row" gap="none">
|
||||
<span>{period}</span>
|
||||
<Icon icon={IconChevronDown} />
|
||||
</Layout.Stack>
|
||||
</Link.Button>
|
||||
<ActionMenu.Root slot="tooltip">
|
||||
<ActionMenu.Item.Button on:click={() => dispatch('change', '24h')}
|
||||
>24h</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => dispatch('change', '30d')}
|
||||
>30d</ActionMenu.Item.Button>
|
||||
<ActionMenu.Item.Button on:click={() => dispatch('change', '90d')}
|
||||
>90d</ActionMenu.Item.Button>
|
||||
</ActionMenu.Root>
|
||||
</Popover>
|
||||
</Layout.Stack>
|
||||
|
||||
{#if totalMetrics($usage?.requests) !== 0}
|
||||
<div style="height: 12rem;">
|
||||
<LineChart
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
"color-border-neutral-stronger": "var(--neutral-500)",
|
||||
"color-fgcolor-on-warning-weak": "var(--web-orange-700)",
|
||||
"color-border-neutral-strongest": "var(--neutral-900)",
|
||||
"color-border-focus": "var(--neutral-500)",
|
||||
"color-border-focus": "var(--neutral-300)",
|
||||
"color-border-accent": "var(--brand-pink-500)",
|
||||
"color-border-accent-secondary": "var(--brand-pink-600)",
|
||||
"color-border-success": "var(--web-green-500)",
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
"color-border-neutral-stronger": "var(--neutral-500)",
|
||||
"color-fgcolor-on-warning-weak": "var(--web-orange-700)",
|
||||
"color-border-neutral-strongest": "var(--neutral-900)",
|
||||
"color-border-focus": "var(--neutral-500)",
|
||||
"color-border-focus": "var(--neutral-300)",
|
||||
"color-border-accent": "var(--brand-pink-500)",
|
||||
"color-border-accent-secondary": "var(--brand-pink-600)",
|
||||
"color-border-success": "var(--web-green-500)",
|
||||
|
||||
Reference in New Issue
Block a user