Merge branch 'main' into spreadsheet

This commit is contained in:
Darshan
2025-08-25 11:55:22 +05:30
4 changed files with 103 additions and 99 deletions
@@ -7,8 +7,8 @@
import { LabelCard } from '..';
export let billingPlan: Tier;
export let anyOrgFree = false;
export let isNewOrg = false;
export let anyOrgFree = false;
export let selfService = true;
$: freePlan = $plansInfo.get(BillingPlan.FREE);
@@ -22,12 +22,9 @@
<LabelCard
name="plan"
bind:group={billingPlan}
disabled={anyOrgFree || !selfService}
disabled={!selfService}
value={BillingPlan.FREE}
title={tierFree.name}
tooltipShow={anyOrgFree}
tooltipText="You are limited to 1 Free organization per account."
tooltipWidth="100%">
title={tierFree.name}>
<svelte:fragment slot="action">
{#if $organization?.billingPlan === BillingPlan.FREE && !isNewOrg}
<Badge variant="secondary" size="xs" content="Current plan" />
@@ -13,7 +13,8 @@
import type { PageData } from './$types';
import { addNotification } from '$lib/stores/notifications';
import { onMount } from 'svelte';
import { Badge, Layout, Table, Typography } from '@appwrite.io/pink-svelte';
import { Badge, Layout, Table, Typography, Icon } from '@appwrite.io/pink-svelte';
import { IconGlobeAlt } from '@appwrite.io/pink-icons-svelte';
export let data: PageData;
@@ -92,9 +93,9 @@
let:root
columns={[
{ id: 'client' },
{ id: 'location', width: 120 },
{ id: 'ip', width: 120 },
{ id: 'actions', width: 70 }
{ id: 'location', width: 200 },
{ id: 'ip', width: 200 },
{ id: 'actions', width: 100 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="client" {root}>Client</Table.Header.Cell>
@@ -114,13 +115,9 @@
height="20"
width="20"
src={getBrowser(session.clientCode).toString()}
style="--p-text-size: 1.25rem"
alt={session.clientName} />
{:else}
<span
class="icon-globe-alt"
style="--p-text-size: 1.25rem"
aria-hidden="true"></span>
<Icon icon={IconGlobeAlt} size="s" />
{/if}
</div>
<Trim>
@@ -150,8 +150,7 @@
await goto(previousPage);
addNotification({
type: 'success',
isHtml: true,
message: `<b>${$organization.name}</b> plan has been successfully updated.`
message: `${$organization.name} plan has been successfully updated.`
});
trackEvent(Submit.OrganizationDowngrade, {
@@ -264,7 +263,9 @@
$: isUpgrade = $plansInfo.get(selectedPlan).order > $currentPlan?.order;
$: isDowngrade = $plansInfo.get(selectedPlan).order < $currentPlan?.order;
$: isButtonDisabled = $organization?.billingPlan === selectedPlan;
$: isButtonDisabled =
$organization?.billingPlan === selectedPlan ||
(isDowngrade && selectedPlan === BillingPlan.FREE && data.hasFreeOrgs);
</script>
<svelte:head>
@@ -297,8 +298,21 @@
selfService={data.selfService}
anyOrgFree={data.hasFreeOrgs} />
{#if isDowngrade && selectedPlan === BillingPlan.FREE && data.hasFreeOrgs}
<Alert.Inline
status="warning"
title="You can only have one free organization per account">
To downgrade this organization, first migrate or delete one of your
existing paid organizations.
<Button
compact
href="https://appwrite.io/docs/advanced/migrations/cloud"
>Migration guide</Button>
</Alert.Inline>
{/if}
{#if isDowngrade}
{#if selectedPlan === BillingPlan.FREE}
{#if selectedPlan === BillingPlan.FREE && !data.hasFreeOrgs}
<PlanExcess tier={BillingPlan.FREE} />
{:else if selectedPlan === BillingPlan.PRO && data.organization.billingPlan === BillingPlan.SCALE && collaborators?.length > 0}
{@const extraMembers = collaborators?.length ?? 0}
@@ -364,7 +378,7 @@
id="members" />
</Fieldset>
{/if}
{#if isDowngrade && selectedPlan === BillingPlan.FREE}
{#if isDowngrade && selectedPlan === BillingPlan.FREE && !data.hasFreeOrgs}
<Fieldset legend="Feedback">
<Layout.Stack gap="xl">
<InputSelect
@@ -395,7 +409,7 @@
bind:billingBudget
organizationId={data.organization.$id} />
{:else if data.organization.billingPlan !== BillingPlan.CUSTOM}
<PlanComparisonBox downgrade={isDowngrade} />
<PlanComparisonBox downgrade={data.hasFreeOrgs ? false : isDowngrade} />
{/if}
</svelte:fragment>
<svelte:fragment slot="footer">
@@ -1,18 +1,10 @@
<script lang="ts">
import { EmptySearch } from '$lib/components';
import { Badge } from '@appwrite.io/pink-svelte';
import { EmptySearch, Trim } from '$lib/components';
import { Badge, Layout, Table, Typography, Icon } from '@appwrite.io/pink-svelte';
import { IconGlobeAlt } from '@appwrite.io/pink-icons-svelte';
import { Button } from '$lib/elements/forms';
import {
TableBody,
TableCell,
TableCellHead,
TableCellText,
TableHeader,
TableRow,
TableScroll
} from '$lib/elements/table';
import { isValueOfStringEnum } from '$lib/helpers/types';
import { Container, ContainerHeader } from '$lib/layout';
import { Container } from '$lib/layout';
import { sdk } from '$lib/stores/sdk';
import { Browser } from '@appwrite.io/console';
import DeleteAllSessions from '../deleteAllSessions.svelte';
@@ -33,77 +25,81 @@
</script>
<Container>
<ContainerHeader title="Sessions">
<Layout.Stack direction="row" justifyContent="space-between">
<Typography.Title>Sessions</Typography.Title>
{#if data.sessions.total}
<Button secondary on:click={() => (showDeleteAll = true)}>
<span class="text">Delete All</span>
</Button>
<Button secondary on:click={() => (showDeleteAll = true)}>Sign out all sessions</Button>
{/if}
</ContainerHeader>
</Layout.Stack>
{#if data.sessions.total}
<TableScroll>
<TableHeader>
<TableCellHead width={140}>Browser and device</TableCellHead>
<TableCellHead width={140}>Session</TableCellHead>
<TableCellHead width={140}>Location</TableCellHead>
<TableCellHead width={140}>IP</TableCellHead>
<TableCellHead width={30} />
</TableHeader>
<TableBody>
{#each data.sessions.sessions as session}
{@const browser = getBrowser(session.clientCode)}
<TableRow>
<TableCell title="Client">
<div class="u-flex u-gap-12 u-cross-center">
<div class="avatar">
{#if browser}
<img
height="20"
width="20"
src={getBrowser(session.clientCode).toString()}
style="--p-text-size: 1.25rem"
alt={session.clientName} />
{:else}
<span
class="icon-globe-alt"
style="--p-text-size: 1.25rem"
aria-hidden="true">
</span>
{/if}
</div>
<p class="text">
{session.clientName}
{session.clientVersion} on {session.osName}
{session.osVersion}
</p>
{#if session.current}
<Badge
variant="secondary"
type="success"
content="current session" />
<Table.Root
let:root
columns={[
{ id: 'client' },
{ id: 'location', width: 200 },
{ id: 'ip', width: 200 },
{ id: 'actions', width: 100 }
]}>
<svelte:fragment slot="header" let:root>
<Table.Header.Cell column="client" {root}>Client</Table.Header.Cell>
<Table.Header.Cell column="location" {root}>Location</Table.Header.Cell>
<Table.Header.Cell column="ip" {root}>IP</Table.Header.Cell>
<Table.Header.Cell column="actions" {root} />
</svelte:fragment>
{#each data.sessions.sessions as session}
{@const browser = getBrowser(session.clientCode)}
<Table.Row.Base {root}>
<Table.Cell column="client" {root}>
<Layout.Stack direction="row" alignItems="center">
<div class="avatar is-size-small">
{#if browser}
<img
height="20"
width="20"
src={getBrowser(session.clientCode).toString()}
alt={session.clientName} />
{:else}
<Icon icon={IconGlobeAlt} size="s" />
{/if}
</div>
</TableCell>
<TableCellText title="Session">{session.clientType}</TableCellText>
<TableCellText title="Location">{session.countryName}</TableCellText>
<TableCellText title="IP">{session.ip}</TableCellText>
<TableCell>
<Button
text
icon
ariaLabel="Delete item"
on:click={() => {
selectedSessionId = session.$id;
showDelete = true;
}}>
<span class="icon-trash" aria-hidden="true"></span>
</Button>
</TableCell>
</TableRow>
{/each}
</TableBody>
</TableScroll>
<Trim>
{session.clientName}
{session.clientVersion} on {session.osName}
{session.osVersion}
</Trim>
{#if session.current}
<Badge
type="success"
variant="secondary"
content="current session" />
{/if}
</Layout.Stack>
</Table.Cell>
<Table.Cell column="location" {root}>
{#if session.countryCode !== '--'}
{session.countryName}
{:else}
Unknown
{/if}
</Table.Cell>
<Table.Cell column="ip" {root}>
{session.ip}
</Table.Cell>
<Table.Cell column="actions" {root}>
<Button
size="xs"
secondary
on:click={() => {
selectedSessionId = session.$id;
showDelete = true;
}}>
Sign out
</Button>
</Table.Cell>
</Table.Row.Base>
{/each}
</Table.Root>
{:else}
<EmptySearch>
<div class="u-flex u-flex-vertical u-cross-center u-gap-24">