Merge branch 'feat-pink-v2' into feat-new-onboarding-flow

# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	src/lib/layout/navigation.svelte
This commit is contained in:
ernstmul
2025-01-27 14:32:46 +01:00
239 changed files with 3097 additions and 2942 deletions
+2 -2
View File
@@ -22,7 +22,7 @@
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@9193",
"@appwrite.io/pink": "0.25.0",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@5824678",
"@appwrite.io/pink-icons-svelte": "1.0.0-next.7",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@250",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
@@ -46,7 +46,7 @@
},
"devDependencies": {
"@melt-ui/pp": "^0.3.2",
"@melt-ui/svelte": "^0.86.2",
"@melt-ui/svelte": "^0.83.0",
"@playwright/test": "^1.49.0",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.8.1",
+2 -1
View File
@@ -338,5 +338,6 @@ export enum Submit {
SiteUpdateBuildSettings = 'submit_site_update_build_settings',
SiteUpdateSinglePageApplication = 'submit_site_update_single_page_application',
SiteConnectRepo = 'submit_site_connect_repo',
SiteRedeploy = 'submit_site_redeploy'
SiteRedeploy = 'submit_site_redeploy',
SiteDisconnectRepo = 'submit_site_disconnect_repo'
}
@@ -68,12 +68,7 @@
}
</script>
<FakeModal
bind:show
title="Add payment method"
bind:error
onSubmit={handleSubmit}
headerDivider={false}>
<FakeModal bind:show title="Add payment method" bind:error onSubmit={handleSubmit}>
<FormList gap={16}>
<slot />
<InputText
+1 -1
View File
@@ -52,7 +52,7 @@
$: isFree = org.billingPlan === BillingPlan.FREE;
</script>
<Modal bind:show size="big" headerDivider={false} title="Usage rates">
<Modal bind:show size="big" 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)}.
@@ -37,13 +37,7 @@
}
</script>
<Modal
bind:show
title="Add credits"
headerDivider={false}
onSubmit={addCoupon}
size="big"
bind:error>
<Modal bind:show title="Add credits" onSubmit={addCoupon} size="big" bind:error>
Credits will be applied automatically to your next invoice.
<FormList>
-1
View File
@@ -26,7 +26,6 @@
export let isTile = false;
export let isDashed = false;
export let isButton = false;
export let danger = false;
export let href: string = null;
let classes = '';
export { classes as class };
-1
View File
@@ -1,7 +1,6 @@
<script lang="ts">
import { Divider, Layout, Card } from '@appwrite.io/pink-svelte';
export let danger = false;
export let hideOverflow = false;
export let hideFooter = false;
</script>
-1
View File
@@ -1,6 +1,5 @@
<script lang="ts">
import { trackEvent } from '$lib/actions/analytics';
import { tooltip } from '$lib/actions/tooltip';
import { clickOnEnter } from '$lib/helpers/a11y';
import { copy } from '$lib/helpers/copy';
import { addNotification } from '$lib/stores/notifications';
-1
View File
@@ -5,7 +5,6 @@
export let single = false;
export let isCard = true;
export let noMedia = false;
export let target: string = null;
export let href: string = null;
export let marginTop = false;
+6 -12
View File
@@ -5,20 +5,10 @@
type Props = ComponentProps<Selector>;
export let name: string;
export let group: string;
export let value: string | number | boolean;
export let disabled = false;
export let icon: string = null;
export let imageIcon: string = null;
export let fullHeight = true;
export let borderRadius: 'xsmall' | 'small' | 'medium' | 'large' = 'small';
export let backgroundColor: string = null;
export let backgroundColorHover: string = null;
export let tooltipText: string = null;
export let tooltipShow = false;
export let src: string = null;
export let alt: string = null;
// Pink v2
export let radius: Props['radius'] = 's';
@@ -27,6 +17,9 @@
export let variant: Props['variant'] = 'primary';
//temporarily unefined
export let title: Props['title'] = undefined;
export let disabled = false;
export let src: string = null;
export let alt: string = null;
// TODO: remove after label card migration
let slotTitle: HTMLSpanElement;
@@ -35,12 +28,13 @@
<Tooltip disabled={!tooltipText || !tooltipShow} inline={false}>
<div>
<Card.Selector
{padding}
{src}
{imageRadius}
{alt}
{padding}
{imageRadius}
{variant}
{value}
{radius}
{disabled}
title={title ?? slotTitle?.innerText}
bind:group>
+33 -43
View File
@@ -22,7 +22,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { invalidate } from '$app/navigation';
import { Button, FormItem, FormList, InputDigits, InputText } from '$lib/elements/forms';
import { Button, FormList, InputDigits, InputText } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { Dependencies } from '$lib/constants';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
@@ -92,59 +92,49 @@
bind:this={inputDigitFields} />
{/if}
{#if showVerifyButton}
<FormItem>
<Button fullWidth submit {disabled}>Verify</Button>
</FormItem>
<Button fullWidth submit {disabled}>Verify</Button>
{/if}
{#if enabledFactors.length > 1}
<span class="with-separators eyebrow-heading-3">or</span>
<div class="u-flex-vertical u-gap-8">
{#if factors.totp && challengeType != null && challengeType != AuthenticationFactor.Totp}
<FormItem>
<Button
secondary
fullWidth
{disabled}
on:click={() => createChallenge(AuthenticationFactor.Totp)}>
<span class="icon-device-mobile u-font-size-20" aria-hidden="true" />
<span class="text">Authenticator app</span>
</Button>
</FormItem>
<Button
secondary
fullWidth
{disabled}
on:click={() => createChallenge(AuthenticationFactor.Totp)}>
<span class="icon-device-mobile u-font-size-20" aria-hidden="true" />
<span class="text">Authenticator app</span>
</Button>
{/if}
{#if factors.email && challengeType != AuthenticationFactor.Email}
<FormItem>
<Button
secondary
fullWidth
{disabled}
on:click={() => createChallenge(AuthenticationFactor.Email)}>
<span class="icon-mail u-font-size-20" aria-hidden="true" />
<span class="text">Email verification</span>
</Button>
</FormItem>
<Button
secondary
fullWidth
{disabled}
on:click={() => createChallenge(AuthenticationFactor.Email)}>
<span class="icon-mail u-font-size-20" aria-hidden="true" />
<span class="text">Email verification</span>
</Button>
{/if}
{#if factors.phone && challengeType != AuthenticationFactor.Phone}
<FormItem>
<Button
secondary
fullWidth
{disabled}
on:click={() => createChallenge(AuthenticationFactor.Phone)}>
<span class="icon-chat-alt u-font-size-20" aria-hidden="true" />
<span class="text">Phone verification</span>
</Button>
</FormItem>
<Button
secondary
fullWidth
{disabled}
on:click={() => createChallenge(AuthenticationFactor.Phone)}>
<span class="icon-chat-alt u-font-size-20" aria-hidden="true" />
<span class="text">Phone verification</span>
</Button>
{/if}
{#if factors.recoveryCode && challengeType != AuthenticationFactor.Recoverycode}
<FormItem>
<Button
text
fullWidth
{disabled}
on:click={() => createChallenge(AuthenticationFactor.Recoverycode)}>
<span class="text">Use recovery code</span>
</Button>
</FormItem>
<Button
text
fullWidth
{disabled}
on:click={() => createChallenge(AuthenticationFactor.Recoverycode)}>
<span class="text">Use recovery code</span>
</Button>
{/if}
</div>
{/if}
+17 -6
View File
@@ -4,14 +4,12 @@
import { disableCommands } from '$lib/commandCenter';
import { beforeNavigate } from '$app/navigation';
import { Layout, Modal } from '@appwrite.io/pink-svelte';
import { trackEvent } from '$lib/actions/analytics';
export let show = false;
export let icon: string = null;
export let state: 'success' | 'warning' | 'error' | 'info' = null;
export let error: string = null;
export let closable = true;
export let headerDivider = true;
export let open = false;
export let dismissible = true;
export let onSubmit: (e: SubmitEvent) => Promise<void> | void = function () {
return;
};
@@ -19,11 +17,22 @@
export let hideFooter = false;
let alert: HTMLElement;
let formComponent: Form;
beforeNavigate(() => {
show = false;
});
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
event.preventDefault();
if (show) {
formComponent.triggerSubmit();
trackEvent('click_submit_form', { from: 'enter' });
}
}
}
$: $disableCommands(show);
$: if (error) {
@@ -31,8 +40,10 @@
}
</script>
<Form isModal {onSubmit}>
<Modal {title} bind:open={show} {hideFooter}>
<svelte:window on:keydown={handleKeydown} />
<Form isModal {onSubmit} bind:this={formComponent}>
<Modal {title} bind:open={show} {hideFooter} {dismissible}>
<svelte:fragment slot="description">
<slot name="description" />
</svelte:fragment>
+9 -11
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { Button, FormItem, FormList, Helper, InputText } from '$lib/elements/forms';
import { Button, FormList, Helper, InputText } from '$lib/elements/forms';
import { createEventDispatcher } from 'svelte';
import { Modal } from '..';
import type { Writable } from 'svelte/store';
@@ -31,16 +31,14 @@
role.
</span>
<FormList>
<FormItem>
<InputText
id="custom-permission"
label="Role"
placeholder="user:[USER_ID] or team:[TEAM_ID]/[ROLE]"
bind:value />
<Helper type="neutral">
A permission should be formatted as: user:[USER_ID] or team:[TEAM_ID]/[ROLE]¸
</Helper>
</FormItem>
<InputText
id="custom-permission"
label="Role"
placeholder="user:[USER_ID] or team:[TEAM_ID]/[ROLE]"
bind:value />
<Helper type="neutral">
A permission should be formatted as: user:[USER_ID] or team:[TEAM_ID]/[ROLE]¸
</Helper>
</FormList>
<svelte:fragment slot="footer">
+4 -6
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { Button, FormItem, FormList, Helper, InputText } from '$lib/elements/forms';
import { Button, FormList, Helper, InputText } from '$lib/elements/forms';
import { createEventDispatcher } from 'svelte';
import { Modal } from '..';
import type { Writable } from 'svelte/store';
@@ -37,11 +37,9 @@
Labels allow you to grant access to users with the specified label.
</span>
<FormList>
<FormItem>
<InputText id="label" label="Label" placeholder="Enter label" bind:value />
<Helper type={isError ? 'warning' : 'neutral'}
>Only alphanumeric characters are allowed.</Helper>
</FormItem>
<InputText id="label" label="Label" placeholder="Enter label" bind:value />
<Helper type={isError ? 'warning' : 'neutral'}
>Only alphanumeric characters are allowed.</Helper>
</FormList>
<svelte:fragment slot="footer">
+10 -15
View File
@@ -4,7 +4,7 @@
import { trackEvent } from '$lib/actions/analytics';
import { onMount } from 'svelte';
import { onDestroy } from 'svelte';
import { Input, Layout } from '@appwrite.io/pink-svelte';
import { Input } from '@appwrite.io/pink-svelte';
import { IconX } from '@appwrite.io/pink-icons-svelte';
export let search = '';
@@ -13,8 +13,6 @@
export let required = false;
export let disabled = false;
export let autofocus = false;
export let isWithEndButton = true;
export let fullWidth = false;
let element: HTMLInputElement;
let timer: ReturnType<typeof setTimeout>;
@@ -60,15 +58,12 @@
}
</script>
<Layout.Stack direction="row" justifyContent="space-between">
<div class={fullWidth ? 'u-width-full-line' : 'u-flex-basis-50-percent'}>
<Input.Text {placeholder} {disabled} {required} type="search" bind:value={search}>
<svelte:fragment slot="end">
{#if search}
<Input.Action icon={IconX} on:click={() => (search = '')} />
{/if}
</svelte:fragment>
</Input.Text>
</div>
<slot />
</Layout.Stack>
<div style:max-width="260px" style:width="100%">
<Input.Text {placeholder} {disabled} {required} type="search" bind:value={search}>
<svelte:fragment slot="end">
{#if search}
<Input.Action icon={IconX} on:click={() => (search = '')} />
{/if}
</svelte:fragment>
</Input.Text>
</div>
+6 -18
View File
@@ -1,25 +1,13 @@
<script lang="ts">
import { tooltip } from '$lib/actions/tooltip';
import { throttle } from '$lib/helpers/functions';
import { onMount } from 'svelte';
import { Tooltip } from '@appwrite.io/pink-svelte';
export let alternativeTrim = false;
let showTooltip = false;
let container: HTMLSpanElement | null;
onMount(onResize);
function onResize() {
showTooltip = container.offsetWidth < container.scrollWidth;
}
</script>
<svelte:window on:resize={throttle(onResize, 250)} />
<span class={`text ${alternativeTrim ? 'u-trim-1' : 'u-trim'}`} bind:this={container}>
<Tooltip inline={false}
><span class="text u-trim"><slot /></span><span slot="tooltip"
>{container?.innerText ?? undefined}</span
></Tooltip>
</span>
<Tooltip maxWidth="auto" inline={false}>
<span class={`text ${alternativeTrim ? 'u-trim-1' : 'u-trim'}`} bind:this={container}>
<slot />
</span>
<span slot="tooltip">{container?.innerText ?? undefined} </span>
</Tooltip>
+30 -33
View File
@@ -73,40 +73,37 @@
}, 0);
</script>
<div class="grid-header-col-4">
{#if !hideColumns && view === View.Table}
{#if $columns?.length}
<DropList bind:show={showSelectColumns} scrollable wrapperFullWidth={fullWidthMobile}>
<Button
size="s"
secondary
on:click={() => (showSelectColumns = !showSelectColumns)}
{fullWidthMobile}>
<span
class="icon-view-boards u-opacity-50"
aria-hidden="true"
aria-label="columns" />
{#if !hideText}
<span class="text {showColsTextMobile ? '' : 'is-only-desktop'}"
>Columns</span>
{/if}
<span class="inline-tag">{selectedColumnsNumber}</span>
</Button>
<svelte:fragment slot="list">
{#each $columns as column}
<InputChoice
id={column.id}
label={column.title}
bind:value={column.show}
disabled={allowNoColumns
? false
: selectedColumnsNumber <= 1 && column.show} />
{/each}
</svelte:fragment>
</DropList>
{/if}
{#if !hideColumns && view === View.Table}
{#if $columns?.length}
<DropList bind:show={showSelectColumns} scrollable wrapperFullWidth={fullWidthMobile}>
<Button
size="s"
secondary
on:click={() => (showSelectColumns = !showSelectColumns)}
{fullWidthMobile}>
<span
class="icon-view-boards u-opacity-50"
aria-hidden="true"
aria-label="columns" />
{#if !hideText}
<span class="text {showColsTextMobile ? '' : 'is-only-desktop'}">Columns</span>
{/if}
<span class="inline-tag">{selectedColumnsNumber}</span>
</Button>
<svelte:fragment slot="list">
{#each $columns as column}
<InputChoice
id={column.id}
label={column.title}
bind:value={column.show}
disabled={allowNoColumns
? false
: selectedColumnsNumber <= 1 && column.show} />
{/each}
</svelte:fragment>
</DropList>
{/if}
</div>
{/if}
{#if !hideView}
<div class="grid-header-col-3 toggle-button">
+1 -3
View File
@@ -6,14 +6,12 @@
import { Button } from '@appwrite.io/pink-svelte';
import type { ComponentProps } from 'svelte';
type Props = ComponentProps<Button.Button>;
type Props = ComponentProps<Button.Button | Button.Anchor>;
export let submit = false;
export let secondary = false;
export let text = false;
export let danger = false;
export let icon = false;
export let link = false;
export let size: Props['size'] = 's';
export let disabled = false;
export let external = false;
-15
View File
@@ -1,15 +0,0 @@
<script context="module" lang="ts">
export type FormItemTag = 'li' | 'div';
</script>
<script lang="ts">
export let tag: FormItemTag = 'li';
export let fullWidth = false;
export let isMultiple = false;
export let stackOnMobile = false;
export let style: string = '';
let classes: string = '';
export { classes as class };
</script>
<slot />
@@ -1,8 +0,0 @@
<script lang="ts">
export let fullWidth = false;
export let alignEnd = false;
</script>
<div class="form-item-part" class:u-cross-child-end={alignEnd} class:u-stretch={fullWidth}>
<slot />
</div>
-5
View File
@@ -1,10 +1,5 @@
<script lang="ts">
import { Layout } from '@appwrite.io/pink-svelte';
export let isCommonSection = false;
export let gap = null;
let classes = '';
export { classes as class };
</script>
<Layout.Stack gap="l">
-2
View File
@@ -1,6 +1,4 @@
export { default as Form } from './form.svelte';
export { default as FormItem } from './formItem.svelte';
export { default as FormItemPart } from './formItemPart.svelte';
export { default as FormList } from './formList.svelte';
export { default as Button } from './button.svelte';
export { default as InputDomain } from './inputDomain.svelte';
@@ -1,7 +1,6 @@
<script lang="ts">
import { Selector } from '@appwrite.io/pink-svelte';
import { Helper } from '.';
import type { FormItemTag } from './formItem.svelte';
interface $$Props extends Partial<HTMLLabelElement> {
id: string;
@@ -11,7 +10,6 @@
disabled?: boolean;
element?: HTMLInputElement | undefined;
indeterminate?: boolean;
wrapperTag?: FormItemTag;
size?: 's' | 'm';
}
@@ -21,7 +19,6 @@
export let required = false;
export let disabled = false;
export let element: HTMLInputElement | undefined = undefined;
export let wrapperTag: FormItemTag = 'li';
export let size: $$Props['size'] = 's';
let error: string;
@@ -1,5 +1,4 @@
<script lang="ts">
import { tooltip as tooltipAction } from '$lib/actions/tooltip';
import { Layout, Selector, Tooltip } from '@appwrite.io/pink-svelte';
import { Helper } from '.';
@@ -37,7 +36,6 @@
{id}
{disabled}
{required}
bind:value
bind:checked={value}
on:change
on:invalid={handleInvalid} />
@@ -47,7 +45,6 @@
{disabled}
size="s"
{required}
bind:value
bind:checked={value}
on:invalid={handleInvalid}
on:change />
+1 -2
View File
@@ -1,8 +1,7 @@
<script lang="ts">
import { Input } from '@appwrite.io/pink-svelte';
export let label: string;
export let showLabel = true;
export let label: string = null;
export let id: string;
export let name = id;
export let value: string = null;
+36 -40
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from 'svelte';
import { FormItem, FormItemPart, Helper, Label } from '.';
import { Helper, Label } from '.';
import NullCheckbox from './nullCheckbox.svelte';
export let label: string;
@@ -17,7 +17,6 @@
export let autofocus = false;
export let autocomplete = false;
export let fullWidth = false;
export let isMultiple = false;
let element: HTMLInputElement;
let error: string;
@@ -55,45 +54,42 @@
}
$: isNullable = nullable && !required;
$: wrapper = isMultiple ? FormItemPart : FormItem;
</script>
<svelte:component this={wrapper} {fullWidth}>
<Label {required} {optionalText} hide={!showLabel} for={id}>
{label}
</Label>
<Label {required} {optionalText} hide={!showLabel} for={id}>
{label}
</Label>
<div class="input-text-wrapper">
<input
{id}
{disabled}
{readonly}
{required}
step=".001"
{min}
{max}
autocomplete={autocomplete ? 'on' : 'off'}
type="date"
style={disabled ? '' : 'cursor: pointer;'}
class="input-text"
bind:value
bind:this={element}
on:invalid={handleInvalid}
on:click={function () {
this.showPicker();
}}
style:--amount-of-buttons={isNullable ? 2.75 : 1}
style:--button-size={isNullable ? '2rem' : '1rem'} />
{#if isNullable}
<ul
class="buttons-list u-cross-center u-gap-8 u-position-absolute u-inset-block-start-8 u-inset-block-end-8 u-inset-inline-end-12">
<li class="buttons-list-item">
<NullCheckbox checked={value === null} on:change={handleNullChange} />
</li>
</ul>
{/if}
</div>
{#if error}
<Helper type="warning">{error}</Helper>
<div class="input-text-wrapper">
<input
{id}
{disabled}
{readonly}
{required}
step=".001"
{min}
{max}
autocomplete={autocomplete ? 'on' : 'off'}
type="date"
style={disabled ? '' : 'cursor: pointer;'}
class="input-text"
bind:value
bind:this={element}
on:invalid={handleInvalid}
on:click={function () {
this.showPicker();
}}
style:--amount-of-buttons={isNullable ? 2.75 : 1}
style:--button-size={isNullable ? '2rem' : '1rem'} />
{#if isNullable}
<ul
class="buttons-list u-cross-center u-gap-8 u-position-absolute u-inset-block-start-8 u-inset-block-end-8 u-inset-inline-end-12">
<li class="buttons-list-item">
<NullCheckbox checked={value === null} on:change={handleNullChange} />
</li>
</ul>
{/if}
</svelte:component>
</div>
{#if error}
<Helper type="warning">{error}</Helper>
{/if}
+31 -33
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from 'svelte';
import { FormItem, Helper, Label } from '.';
import { Helper, Label } from '.';
import NullCheckbox from './nullCheckbox.svelte';
export let label: string;
@@ -59,37 +59,35 @@
$: isNullable = nullable && !required;
</script>
<FormItem>
<Label {required} {optionalText} hide={!showLabel} for={id}>
{label}
</Label>
<Label {required} {optionalText} hide={!showLabel} for={id}>
{label}
</Label>
<div class="input-text-wrapper">
<input
{id}
{disabled}
{readonly}
{required}
{value}
{step}
autocomplete={autocomplete ? 'on' : 'off'}
type="datetime-local"
class="input-text"
bind:this={element}
on:input={handleInput}
on:invalid={handleInvalid}
style:--amount-of-buttons={isNullable ? 2.75 : 1}
style:--button-size={isNullable ? '2rem' : '1rem'} />
{#if isNullable}
<ul
class="buttons-list u-cross-center u-gap-8 u-position-absolute u-inset-block-start-8 u-inset-block-end-8 u-inset-inline-end-12">
<li class="buttons-list-item">
<NullCheckbox checked={value === null} on:change={handleNullChange} />
</li>
</ul>
{/if}
</div>
{#if error}
<Helper type="warning">{error}</Helper>
<div class="input-text-wrapper">
<input
{id}
{disabled}
{readonly}
{required}
{value}
{step}
autocomplete={autocomplete ? 'on' : 'off'}
type="datetime-local"
class="input-text"
bind:this={element}
on:input={handleInput}
on:invalid={handleInvalid}
style:--amount-of-buttons={isNullable ? 2.75 : 1}
style:--button-size={isNullable ? '2rem' : '1rem'} />
{#if isNullable}
<ul
class="buttons-list u-cross-center u-gap-8 u-position-absolute u-inset-block-start-8 u-inset-block-end-8 u-inset-inline-end-12">
<li class="buttons-list-item">
<NullCheckbox checked={value === null} on:change={handleNullChange} />
</li>
</ul>
{/if}
</FormItem>
</div>
{#if error}
<Helper type="warning">{error}</Helper>
{/if}
+16 -19
View File
@@ -1,6 +1,5 @@
<script lang="ts">
import { onMount } from 'svelte';
import { FormItem } from '.';
import { createPinInput, melt } from '@melt-ui/svelte';
export let length: number = 6;
@@ -66,24 +65,22 @@
});
</script>
<FormItem {fullWidth}>
<ol class="u-flex u-main-center" use:melt={$root} bind:this={element}>
{#each Array.from({ length }) as _}
<li>
<input
type="number"
class="verification-code-input u-bold u-remove-input-number-buttons"
inputmode="numeric"
maxlength="1"
style:border-radius="var(--border-radius-small)"
use:melt={$input()}
{required}
{readonly}
{disabled} />
</li>
{/each}
</ol>
</FormItem>
<ol class="u-flex u-main-center" use:melt={$root} bind:this={element}>
{#each Array.from({ length }) as _}
<li>
<input
type="number"
class="verification-code-input u-bold u-remove-input-number-buttons"
inputmode="numeric"
maxlength="1"
style:border-radius="var(--border-radius-small)"
use:melt={$input()}
{required}
{readonly}
{disabled} />
</li>
{/each}
</ol>
<style lang="scss">
@use '@appwrite.io/pink/src/abstract/variables/devices';
+1 -7
View File
@@ -1,9 +1,7 @@
<script lang="ts">
import { SvelteComponent, onMount } from 'svelte';
import { Input } from '@appwrite.io/pink-svelte';
export let label: string;
export let showLabel = true;
export let label: string = null;
export let id: string;
export let name = id;
export let value = '';
@@ -14,10 +12,6 @@
export let readonly = false;
export let autofocus = false;
export let autocomplete = false;
export let maxlength: number = null;
export let popover: typeof SvelteComponent<unknown> = null;
export let popoverProps: Record<string, unknown> = {};
export let fullWidth = false;
// https://www.geeksforgeeks.org/how-to-validate-a-domain-name-using-regular-expression/
const pattern = String.raw`(?!-)[A-Za-z0-9\-]+([\-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,18}`;
+2 -8
View File
@@ -1,10 +1,7 @@
<script lang="ts">
import { SvelteComponent } from 'svelte';
import { Input } from '@appwrite.io/pink-svelte';
export let label: string;
export let optionalText: string | undefined = undefined;
export let showLabel = true;
export let label: string = null;
export let id: string;
export let value = '';
export let placeholder = '';
@@ -15,10 +12,6 @@
export let readonly = false;
export let autofocus = false;
export let autocomplete = false;
export let tooltip: string = null;
export let popover: typeof SvelteComponent<unknown> = null;
export let popoverProps: Record<string, unknown> = {};
export let fullWidth = false;
let error: string;
@@ -47,6 +40,7 @@
{placeholder}
{disabled}
{required}
{readonly}
{label}
{nullable}
on:input
+1 -5
View File
@@ -1,9 +1,7 @@
<script lang="ts">
import { Input } from '@appwrite.io/pink-svelte';
export let label: string | undefined = undefined;
export let optionalText: string | undefined = undefined;
export let showLabel = true;
export let label: string = null;
export let id: string;
export let name = id;
export let value: number = null;
@@ -16,8 +14,6 @@
export let min: number = null;
export let max: number = null;
export let step: number | 'any' = 1;
export let isMultiple = false;
export let fullWidth = false;
let error: string;
+53 -58
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { SvelteComponent, onMount } from 'svelte';
import { FormItem, FormItemPart, Helper, Label } from '.';
import { Helper, Label } from '.';
import { Drop } from '$lib/components';
export let label: string = undefined;
@@ -21,7 +21,6 @@
export let autocomplete = false;
export let fullWidth = false;
export let tooltip: string = null;
export let isMultiple = false;
export let popover: typeof SvelteComponent<unknown> = null;
export let popoverProps: Record<string, unknown> = {};
@@ -58,63 +57,59 @@
type $$Events = {
input: Event & { target: HTMLInputElement };
};
$: wrapper = isMultiple ? FormItemPart : FormItem;
</script>
<svelte:component this={wrapper} {fullWidth}>
{#if label}
<Label {required} {hideRequired} {tooltip} {optionalText} hide={!showLabel} for={id}>
{label}{#if popover}
<Drop isPopover bind:show display="inline-block">
<!-- TODO: make unclicked icon greyed out and hover and clicked filled -->
&nbsp;<button
type="button"
on:click={() => (show = !show)}
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-250"
style:--p-card-padding=".75rem"
style:--card-border-radius="var(--border-radius-small)"
style:box-shadow="var(--shadow-large)">
<svelte:component this={popover} {...popoverProps} />
</div>
</svelte:fragment>
</Drop>
{/if}
</Label>
{/if}
<div class="input-text-wrapper">
<input
{id}
{name}
{placeholder}
{disabled}
{readonly}
{required}
{maxlength}
{pattern}
type="text"
autocomplete={autocomplete ? 'on' : 'off'}
class="input-text"
bind:value
bind:this={element}
on:invalid={handleInvalid} />
{#if $$slots.options}
<div class="options-list">
<slot name="options" />
</div>
{#if label}
<Label {required} {hideRequired} {tooltip} {optionalText} hide={!showLabel} for={id}>
{label}{#if popover}
<Drop isPopover bind:show display="inline-block">
<!-- TODO: make unclicked icon greyed out and hover and clicked filled -->
&nbsp;<button
type="button"
on:click={() => (show = !show)}
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-250"
style:--p-card-padding=".75rem"
style:--card-border-radius="var(--border-radius-small)"
style:box-shadow="var(--shadow-large)">
<svelte:component this={popover} {...popoverProps} />
</div>
</svelte:fragment>
</Drop>
{/if}
<slot />
</div>
{#if error}
<Helper type="warning" class="u-line-height-1">{error}</Helper>
</Label>
{/if}
<div class="input-text-wrapper">
<input
{id}
{name}
{placeholder}
{disabled}
{readonly}
{required}
{maxlength}
{pattern}
type="text"
autocomplete={autocomplete ? 'on' : 'off'}
class="input-text"
bind:value
bind:this={element}
on:invalid={handleInvalid} />
{#if $$slots.options}
<div class="options-list">
<slot name="options" />
</div>
{/if}
</svelte:component>
<slot />
</div>
{#if error}
<Helper type="warning" class="u-line-height-1">{error}</Helper>
{/if}
@@ -1,23 +1,16 @@
<script lang="ts">
import { SvelteComponent } from 'svelte';
import { Input } from '@appwrite.io/pink-svelte';
export let id: string;
export let label: string;
export let showLabel = true;
export let value = '';
export let placeholder = '';
export let required = false;
export let disabled = false;
export let autofocus = false;
export let meter = false;
export let autocomplete = false;
export let showPasswordButton = false;
export let minlength = 8;
export let maxlength: number = null;
export let popover: typeof SvelteComponent<unknown> = null;
export let popoverProps: Record<string, unknown> = {};
export let fullWidth = false;
let error: string;
+2 -7
View File
@@ -1,10 +1,7 @@
<script lang="ts">
import { SvelteComponent, onMount } from 'svelte';
import { Input } from '@appwrite.io/pink-svelte';
import type { States } from '@appwrite.io/pink-svelte/dist/input/types';
export let label: string;
export let showLabel = true;
export let label: string = null;
export let id: string;
export let name: string = id;
export let value = '';
@@ -17,9 +14,6 @@
export let minlength: number = null;
export let maxlength: number = 15;
export let nullable = false;
export let popover: typeof SvelteComponent<unknown> = null;
export let popoverProps: Record<string, unknown> = {};
export let fullWidth = false;
const pattern = String.raw`^\+[1-9]\d{1,14}$`;
@@ -57,6 +51,7 @@
{placeholder}
{disabled}
{required}
{readonly}
{label}
{nullable}
{pattern}
+3 -20
View File
@@ -7,31 +7,13 @@
type Props = ComponentProps<Radio>;
export let label: string = null;
export let showLabel = true;
export let id: string;
export let group: string;
export let value: string;
export let name: string;
export let required = false;
export let disabled = false;
export let size: Props['size'] = 'medium';
let element: HTMLInputElement;
let error: string;
const handleInvalid = (event: Event) => {
event.preventDefault();
if (element.validity.valueMissing) {
error = 'This field is required';
return;
}
error = element.validationMessage;
};
$: if (value) {
error = null;
}
export let size: Props['size'] = 'm';
</script>
<Selector.Radio
@@ -43,4 +25,5 @@
{label}
{size}
bind:group
on:invalid={handleInvalid} />
on:invalid
on:change />
@@ -10,8 +10,6 @@
export let required = false;
export let disabled = false;
export let autofocus = false;
export let isWithEndButton = true;
export let style: string = '';
export let label: string = '';
const dispatch = createEventDispatcher();
+1 -2
View File
@@ -2,8 +2,7 @@
import { Input } from '@appwrite.io/pink-svelte';
export let id: string;
export let label: string;
export let showLabel = true;
export let label: string = '';
export let value = '';
export let placeholder = '';
</script>
-13
View File
@@ -1,34 +1,21 @@
<script lang="ts">
import { Input } from '@appwrite.io/pink-svelte';
import type { FormItemTag } from './formItem.svelte';
import type { SvelteComponent } from 'svelte';
export let id: string;
export let label: string | undefined = undefined;
export let ariaLabel = label;
export let optionalText: string | undefined = undefined;
export let showLabel = true;
export let value: string | number | boolean | null;
export let placeholder = '';
export let required = false;
export let hideRequired = false;
export let disabled = false;
export let wrapperTag: FormItemTag = 'li';
export let options: {
value: string | boolean | number | null;
label: string;
disabled?: boolean;
}[];
export let isMultiple = false;
export let fullWidth = false;
export let popover: typeof SvelteComponent<unknown> = null;
export let popoverProps: Record<string, unknown> = {};
let element: HTMLSelectElement;
let error: string;
let show: boolean = false;
const handleInvalid = (event: Event) => {
event.preventDefault();
+2 -21
View File
@@ -1,29 +1,10 @@
<script lang="ts">
import { Selector } from '@appwrite.io/pink-svelte';
export let label: string;
export let id: string;
export let label: string;
export let value = false;
export let disabled = false;
let element: HTMLInputElement;
let error: string;
const handleInvalid = (event: Event) => {
event.preventDefault();
error = element.validationMessage;
};
$: if (value) {
error = null;
}
</script>
<Selector.Switch
{label}
{disabled}
bind:checked={value}
helper={error}
state={error ? 'error' : 'default'}
on:invalid={handleInvalid} />
<Selector.Switch {id} {label} {disabled} bind:checked={value} on:invalid on:change />
+64 -69
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { last } from '$lib/helpers/array';
import { onMount, SvelteComponent } from 'svelte';
import { FormItem, Helper, Label } from '.';
import { Helper, Label } from '.';
import { Drop } from '$lib/components';
export let id: string;
@@ -86,72 +86,67 @@
}
</script>
<FormItem>
<input
class="u-hide"
bind:this={hiddenEl}
value={tags.join(',')}
{required}
on:invalid={handleInvalid} />
<Label {required} {tooltip} hide={!showLabel} for={id}>
{label}{#if popover}
<Drop isPopover bind:show display="inline-block">
&nbsp;<button
type="button"
on:click={() => (show = !show)}
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-250"
style:--card-border-radius="var(--border-radius-small)"
style:--p-card-padding=".75rem"
style:box-shadow="var(--shadow-large)">
<svelte:component this={popover} {...popoverProps} />
</div>
</svelte:fragment>
</Drop>
{/if}
</Label>
<div class="input-text-wrapper">
<div class="tags-input">
<div class="tags">
<ul class="tags-list">
{#each tags as tag}
<li class="tags-item">
<div class="input-tag">
<span class="tag-text">{tag}</span>
<button
type="button"
class="input-tag-delete-button"
aria-label={`delete ${tag} tag`}
on:click={() => removeValue(tag)}>
<span class="icon-x" aria-hidden="true" />
</button>
</div>
</li>
{/each}
</ul>
</div>
<input
{id}
{placeholder}
{disabled}
{readonly}
type="text"
class="tags-input-text"
bind:value
bind:this={element}
on:keydown={handleInput}
on:blur={addValue} />
</div>
</div>
{#if error}
<Helper type="warning">{error}</Helper>
<input
class="u-hide"
bind:this={hiddenEl}
value={tags.join(',')}
{required}
on:invalid={handleInvalid} />
<Label {required} {tooltip} hide={!showLabel} for={id}>
{label}{#if popover}
<Drop isPopover bind:show display="inline-block">
&nbsp;<button type="button" on:click={() => (show = !show)} 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-250"
style:--card-border-radius="var(--border-radius-small)"
style:--p-card-padding=".75rem"
style:box-shadow="var(--shadow-large)">
<svelte:component this={popover} {...popoverProps} />
</div>
</svelte:fragment>
</Drop>
{/if}
</FormItem>
</Label>
<div class="input-text-wrapper">
<div class="tags-input">
<div class="tags">
<ul class="tags-list">
{#each tags as tag}
<li class="tags-item">
<div class="input-tag">
<span class="tag-text">{tag}</span>
<button
type="button"
class="input-tag-delete-button"
aria-label={`delete ${tag} tag`}
on:click={() => removeValue(tag)}>
<span class="icon-x" aria-hidden="true" />
</button>
</div>
</li>
{/each}
</ul>
</div>
<input
{id}
{placeholder}
{disabled}
{readonly}
type="text"
class="tags-input-text"
bind:value
bind:this={element}
on:keydown={handleInput}
on:blur={addValue} />
</div>
</div>
{#if error}
<Helper type="warning">{error}</Helper>
{/if}
-17
View File
@@ -1,37 +1,20 @@
<script lang="ts">
import { Input } from '@appwrite.io/pink-svelte';
import { SvelteComponent, onMount, tick } from 'svelte';
export let label: string = undefined;
export let optionalText: string | undefined = undefined;
export let showLabel = true;
export let id: string;
export let name: string = id;
export let value = '';
export let placeholder = '';
export let required = false;
export let hideRequired = false;
export let nullable = false;
export let disabled = false;
export let readonly = false;
export let autofocus = false;
export let autocomplete = false;
export let fullWidth = false;
export let maxlength: number = null;
export let tooltip: string = null;
export let isMultiple = false;
export let popover: typeof SvelteComponent<unknown> = null;
export let popoverProps: Record<string, unknown> = {};
let error: string;
// TODO: re-enable
// onMount(async () => {
// if (element && autofocus) {
// await tick();
// element.focus();
// }
// });
const handleInvalid = (event: Event) => {
event.preventDefault();
@@ -1,13 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { FormItem, Helper, Label } from '.';
import NullCheckbox from './nullCheckbox.svelte';
import TextCounter from './textCounter.svelte';
import { clickOnEnter } from '$lib/helpers/a11y';
import { Input } from '@appwrite.io/pink-svelte';
export let label: string;
export let showLabel = true;
export let id: string;
export let name: string = id;
export let value = '';
@@ -18,8 +12,6 @@
export let readonly = false;
export let autofocus = false;
export let maxlength: number = null;
export let optionalText: string | undefined = undefined;
export let tooltip: string = null;
let error: string;
+27 -32
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from 'svelte';
import { FormItem, FormItemPart, Helper, Label } from '.';
import { Helper, Label } from '.';
export let label: string;
export let showLabel = true;
@@ -14,8 +14,6 @@
export let readonly = false;
export let autofocus = false;
export let autocomplete = false;
export let fullWidth = false;
export let isMultiple = false;
export let step: number | 'any' = 60;
let element: HTMLInputElement;
@@ -41,35 +39,32 @@
$: if (value) {
error = null;
}
$: wrapper = isMultiple ? FormItemPart : FormItem;
</script>
<svelte:component this={wrapper} {fullWidth}>
<Label {required} {optionalText} hide={!showLabel} for={id}>
{label}
</Label>
<Label {required} {optionalText} hide={!showLabel} for={id}>
{label}
</Label>
<div class="input-text-wrapper" style="--amount-of-buttons:1; --button-size: 1rem">
<input
{id}
{disabled}
{readonly}
{required}
{min}
{max}
{step}
autocomplete={autocomplete ? 'on' : 'off'}
type="time"
class="input-text"
style={disabled ? '' : 'cursor: pointer;'}
bind:value
bind:this={element}
on:invalid={handleInvalid}
on:click={function () {
this.showPicker();
}} />
</div>
{#if error}
<Helper type="warning">{error}</Helper>
{/if}
</svelte:component>
<div class="input-text-wrapper" style="--amount-of-buttons:1; --button-size: 1rem">
<input
{id}
{disabled}
{readonly}
{required}
{min}
{max}
{step}
autocomplete={autocomplete ? 'on' : 'off'}
type="time"
class="input-text"
style={disabled ? '' : 'cursor: pointer;'}
bind:value
bind:this={element}
on:invalid={handleInvalid}
on:click={function () {
this.showPicker();
}} />
</div>
{#if error}
<Helper type="warning">{error}</Helper>
{/if}
-3
View File
@@ -1,10 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { Input } from '@appwrite.io/pink-svelte';
export let label: string;
export let optionalText: string | undefined = undefined;
export let showLabel = true;
export let id: string;
export let name = id;
export let value = '';
+46 -66
View File
@@ -6,23 +6,11 @@
PaginationWithLimit,
Trim
} from '$lib/components';
import {
TableBody,
TableHeader,
TableRow,
TableCellHead,
TableCell,
TableCellText,
TableScroll
} from '$lib/elements/table';
import { Container, ContainerHeader } from '$lib/layout';
import { hoursToDays, toLocaleDateTime } from '$lib/helpers/date';
import { toLocaleDateTime } from '$lib/helpers/date';
import type { Models } from '@appwrite.io/console';
import { tierToPlan, type PlanServices } from '$lib/stores/billing';
import { isCloud } from '$lib/system';
import { organization } from '$lib/stores/organization';
import { Button } from '$lib/elements/forms';
import { BillingPlan } from '$lib/constants';
import { type PlanServices } from '$lib/stores/billing';
import { Layout, Table } from '@appwrite.io/pink-svelte';
export let logs: Models.LogList;
export let offset = 0;
@@ -38,61 +26,53 @@
<Heading tag="h2" size="5">Activity</Heading>
{/if}
{#if logs.total}
<TableScroll>
<TableHeader>
<TableCellHead width={100}>User</TableCellHead>
<TableCellHead width={100}>Event</TableCellHead>
<TableCellHead width={80}>Location</TableCellHead>
<TableCellHead width={90}>IP</TableCellHead>
<TableCellHead width={140}>Date</TableCellHead>
</TableHeader>
<TableBody service="logs" total={isCloud ? Infinity : 0}>
<svelte:fragment slot="limit" let:limit let:upgradeMethod>
<p class="text">
Logs are retained in rolling {hoursToDays(limit)} intervals with the
{tierToPlan($organization.billingPlan).name}
plan.
{#if $organization?.billingPlan === BillingPlan.FREE}
<Button link on:click={upgradeMethod}>Upgrade</Button> to increase your log
retention for a longer period.
{/if}
</p>
</svelte:fragment>
{#each logs.logs as log}
<TableRow>
<TableCell title="Client">
<div class="u-flex u-cross-center u-gap-12">
{#if log.userEmail}
{#if log.userName}
<AvatarInitials size={32} name={log.userName} />
<Trim>{log.userName}</Trim>
{:else}
<AvatarInitials size={32} name={log.userEmail} />
<Trim>{log.userEmail}</Trim>
{/if}
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>User</Table.Header.Cell>
<Table.Header.Cell>Event</Table.Header.Cell>
<Table.Header.Cell>Location</Table.Header.Cell>
<Table.Header.Cell>IP</Table.Header.Cell>
<Table.Header.Cell>Date</Table.Header.Cell>
</svelte:fragment>
{#each logs.logs as log}
<Table.Row>
<Table.Cell>
<Layout.Stack direction="row" alignItems="center">
{#if log.userEmail}
{#if log.userName}
<AvatarInitials size={32} name={log.userName} />
<Trim>{log.userName}</Trim>
{:else}
<div class="avatar is-size-small">
<span class="icon-anonymous" aria-hidden="true" />
</div>
<span class="text u-trim">{log.userName ?? 'Anonymous'}</span>
<AvatarInitials size={32} name={log.userEmail} />
<Trim>{log.userEmail}</Trim>
{/if}
</div>
</TableCell>
<TableCellText title="Event">{log.event}</TableCellText>
<TableCellText title="Location">
{#if log.countryCode !== '--'}
{log.countryName}
{:else}
Unknown
<div class="avatar is-size-small">
<span class="icon-anonymous" aria-hidden="true" />
</div>
<span class="text u-trim">{log.userName ?? 'Anonymous'}</span>
{/if}
</TableCellText>
<TableCellText title="IP">{log.ip}</TableCellText>
<TableCellText title="Date">{toLocaleDateTime(log.time)}</TableCellText>
</TableRow>
{/each}
</TableBody>
</TableScroll>
</Layout.Stack>
</Table.Cell>
<Table.Cell>
{log.event}
</Table.Cell>
<Table.Cell>
{#if log.countryCode !== '--'}
{log.countryName}
{:else}
Unknown
{/if}
</Table.Cell>
<Table.Cell>
{log.ip}
</Table.Cell>
<Table.Cell>
{toLocaleDateTime(log.time)}
</Table.Cell>
</Table.Row>
{/each}
</Table.Root>
<PaginationWithLimit name="Logs" {limit} {offset} total={logs.total} />
{:else}
-1
View File
@@ -19,7 +19,6 @@
import { goto } from '$app/navigation';
import { Layout } from '@appwrite.io/pink-svelte';
export let isFlex = true;
export let title: string;
export let titleTag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' = 'h2';
export let titleSize: '1' | '2' | '3' | '4' | '5' | '6' | '7' = '5';
+1 -2
View File
@@ -18,7 +18,6 @@
import { organization } from '$lib/stores/organization';
import { Button } from '$lib/elements/forms';
import { BillingPlan } from '$lib/constants';
import { tooltip } from '$lib/actions/tooltip';
import { Tooltip } from '@appwrite.io/pink-svelte';
let selectedRequest = 'parameters';
@@ -347,7 +346,7 @@
Logs are retained in rolling {hoursToDays(limit)} intervals
with the {tier} plan.
{#if $organization.billingPlan === BillingPlan.FREE}
<Button link href={$upgradeURL}>Upgrade</Button> to increase
<Button href={$upgradeURL}>Upgrade</Button> to increase
your log retention for a longer period.
{/if}
</Alert>
+1 -8
View File
@@ -13,14 +13,7 @@
}
</script>
<Modal
title="Exit Process"
bind:show
onSubmit={handleSubmit}
icon="exclamation"
state="warning"
size="small"
headerDivider={false}>
<Modal title="Exit Process" bind:show onSubmit={handleSubmit}>
<p class="text">
<slot />
</p>
+1 -7
View File
@@ -32,13 +32,7 @@
}
</script>
<Modal
title="Delete domain"
bind:show={showDelete}
onSubmit={deleteDomain}
icon="exclamation"
state="warning"
headerDivider={false}>
<Modal title="Delete domain" bind:show={showDelete} onSubmit={deleteDomain}>
{#if selectedDomain}
<p data-private>
Are you sure you want to delete <b>{selectedDomain.domain}</b>? You will no longer be
+108 -114
View File
@@ -21,6 +21,7 @@
import Retry from './wizard/retry.svelte';
import { Pill } from '$lib/elements';
import { canWriteRules } from '$lib/stores/roles';
import { Layout, Table, Link } from '@appwrite.io/pink-svelte';
export let rules: Models.ProxyRuleList;
export let type: ResourceType;
@@ -63,127 +64,120 @@
{/if}
</div>
{#if rules.total}
<TableScroll>
<TableHeader>
<TableCellHead width={150}>Name</TableCellHead>
<TableCellHead width={120}>Verification Status</TableCellHead>
<TableCellHead width={180}>Certificate Status</TableCellHead>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Verification Status</Table.Header.Cell>
<Table.Header.Cell>Certificate Status</Table.Header.Cell>
{#if $canWriteRules}
<TableCellHead width={40} />
<Table.Header.Cell width="40" />
{/if}
</TableHeader>
<TableBody>
{#each rules.rules as domain, i}
<TableRow>
<TableCellLink
title="Domain"
href={`https://${domain.domain}`}
external
noStyle>
<span class="u-flex u-gap-4 u-cross-center">
<Trim>
<span class="link">{domain.domain}</span>
</Trim>
<span class="icon-external-link" aria-hidden="true" />
</span>
</TableCellLink>
<TableCell title="Verification">
{#if domain.status === 'created'}
<div class="u-flex u-gap-16 u-cross-center">
<Pill danger>
<span
class="icon-exclamation-circle u-color-text-danger"
aria-hidden="true" />
<span class="u-text">failed</span>
</Pill>
<button type="button" on:click={() => openRetry(domain)}>
<span class="link">Retry</span>
</button>
</div>
{:else}
</svelte:fragment>
{#each rules.rules as domain, index}
<Table.Row>
<Table.Cell>
<Layout.Stack direction="row" alignItems="center">
<Link.Anchor href={`https://${domain.domain}`} target="_blank">
{domain.domain}
</Link.Anchor>
</Layout.Stack>
</Table.Cell>
<Table.Cell>
{#if domain.status === 'created'}
<Layout.Stack direction="row" alignItems="center">
<Pill danger>
<span
class="icon-exclamation-circle u-color-text-danger"
aria-hidden="true" />
<span class="u-text">failed</span>
</Pill>
<button type="button" on:click={() => openRetry(domain)}>
<span class="link">Retry</span>
</button>
</Layout.Stack>
{:else}
<Pill success>
<span
class="icon-check-circle u-color-text-success"
aria-hidden="true" />
<p class="text">verified</p>
</Pill>
{/if}
</Table.Cell>
<Table.Cell>
{#if domain.status === 'unverified'}
<Layout.Stack direction="row" alignItems="center">
<Pill danger>
<span
class="icon-exclamation-circle u-color-text-danger"
aria-hidden="true" />
<span class="u-text">failed</span>
</Pill>
<button type="button" on:click={() => openRetry(domain)}>
<span class="link">Retry</span>
</button>
</Layout.Stack>
{:else if domain.status === 'verified'}
<Layout.Stack direction="row" alignItems="center">
<Pill success>
<span
class="icon-check-circle u-color-text-success"
aria-hidden="true" />
<p class="text">verified</p>
<span>generated</span>
</Pill>
{/if}
</TableCell>
<TableCell title="Certificate">
{#if domain.status === 'unverified'}
<div class="u-flex u-gap-16 u-cross-center">
<Pill danger>
<span
class="icon-exclamation-circle u-color-text-danger"
aria-hidden="true" />
<span class="u-text">failed</span>
</Pill>
<button type="button" on:click={() => openRetry(domain)}>
<span class="link">Retry</span>
</button>
</div>
{:else if domain.status === 'verified'}
<div class="u-flex u-gap-16 u-cross-center">
<Pill success>
<span
class="icon-check-circle u-color-text-success"
aria-hidden="true" />
<span>generated</span>
</Pill>
{#if domain.renewAt}
<span class="u-text-color-gray">
Auto-renewal: {toLocaleDate(domain.renewAt)}
</span>
{/if}
</div>
{:else}
<Pill warning>
<span class="icon-clock u-text-color-gray" aria-hidden="true" />
<p class="text">blocked by verification</p>
</Pill>
{/if}
</TableCell>
{#if $canWriteRules}
<TableCell right>
<DropList
bind:show={showDomainsDropdown[i]}
placement="bottom-start"
noArrow>
<Button
text
icon
ariaLabel="more options"
on:click={() =>
(showDomainsDropdown[i] = !showDomainsDropdown[i])}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</Button>
<svelte:fragment slot="list">
{#if domain.status !== 'verified'}
<DropListItem
icon="refresh"
on:click={() => openRetry(domain, i)}>
{domain.status === 'unverified'
? 'Retry generation'
: 'Retry verification'}
</DropListItem>
{/if}
<DropListItem
icon="trash"
on:click={() => {
selectedDomain = domain;
showDelete = true;
showDomainsDropdown[i] = false;
}}>
Delete
</DropListItem>
</svelte:fragment>
</DropList>
</TableCell>
{#if domain.renewAt}
<span class="u-text-color-gray">
Auto-renewal: {toLocaleDate(domain.renewAt)}
</span>
{/if}
</Layout.Stack>
{:else}
<Pill warning>
<span class="icon-clock u-text-color-gray" aria-hidden="true" />
<p class="text">blocked by verification</p>
</Pill>
{/if}
</TableRow>
{/each}
</TableBody>
</TableScroll>
</Table.Cell>
{#if $canWriteRules}
<Table.Cell>
<DropList
bind:show={showDomainsDropdown[index]}
placement="bottom-start"
noArrow>
<Button
text
icon
ariaLabel="more options"
on:click={() =>
(showDomainsDropdown[index] = !showDomainsDropdown[index])}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</Button>
<svelte:fragment slot="list">
{#if domain.status !== 'verified'}
<DropListItem
icon="refresh"
on:click={() => openRetry(domain, index)}>
{domain.status === 'unverified'
? 'Retry generation'
: 'Retry verification'}
</DropListItem>
{/if}
<DropListItem
icon="trash"
on:click={() => {
selectedDomain = domain;
showDelete = true;
showDomainsDropdown[index] = false;
}}>
Delete
</DropListItem>
</svelte:fragment>
</DropList>
</Table.Cell>
{/if}
</Table.Row>
{/each}
</Table.Root>
{:else}
<Empty
single
@@ -193,7 +187,7 @@
{/if}
<Delete bind:showDelete bind:selectedDomain {dependency} />
<Modal bind:show={showRetry} headerDivider={false} bind:error={retryError} size="big">
<Modal bind:show={showRetry} bind:error={retryError}>
<svelte:fragment slot="title">
Retry {$domain.status === 'unverified' ? 'certificate generation' : 'verification'}
</svelte:fragment>
+1 -8
View File
@@ -27,14 +27,7 @@
}
</script>
<Modal
title="Delete account"
bind:show={showDelete}
bind:error
onSubmit={deleteAccount}
icon="exclamation"
state="warning"
headerDivider={false}>
<Modal title="Delete account" bind:show={showDelete} bind:error onSubmit={deleteAccount}>
<p>Are you sure you want to delete your account?</p>
<svelte:fragment slot="footer">
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
@@ -38,10 +38,7 @@
title="Delete authentication method"
bind:show={showDelete}
onSubmit={deleteAuthenticator}
icon="exclamation"
state="warning"
bind:error
headerDivider={false}>
bind:error>
<p>
Are you sure you want to delete this authentication method? You will no longer be able to
use this method to authenticate your account.
@@ -29,14 +29,7 @@
}
</script>
<Modal
title="Regenerate recovery codes"
icon="exclamation"
state="warning"
headerDivider={false}
{error}
onSubmit={submit}
bind:show>
<Modal title="Regenerate recovery codes" {error} onSubmit={submit} bind:show>
<p>
Are you sure you want to regenerate all recovery codes? All <b
>previously generated recovery codes will become invalid.</b>
@@ -3,7 +3,7 @@
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Modal } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, FormItem, FormList, InputSelect, InputText } from '$lib/elements/forms';
import { Button, FormList, InputSelect, InputText } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import type { Organization } from '$lib/stores/organization';
import { sdk } from '$lib/stores/sdk';
@@ -101,23 +101,13 @@
label="City or suburb"
placeholder="Enter your city"
required />
<FormItem isMultiple>
<InputText
isMultiple
fullWidth
bind:value={state}
id="state"
label="State"
placeholder="Enter your state"
required />
<InputText
isMultiple
fullWidth
bind:value={zip}
id="zip"
label="Postal code"
placeholder="Enter postal code" />
</FormItem>
<InputText
bind:value={state}
id="state"
label="State"
placeholder="Enter your state"
required />
<InputText bind:value={zip} id="zip" label="Postal code" placeholder="Enter postal code" />
</FormList>
<svelte:fragment slot="footer">
@@ -32,13 +32,7 @@
}
</script>
<Modal
bind:show={showDelete}
onSubmit={handleDelete}
icon="exclamation"
state="warning"
headerDivider={false}
bind:error>
<Modal bind:show={showDelete} onSubmit={handleDelete} bind:error>
<svelte:fragment slot="title">
{linkedOrgs.length ? 'Unable to delete billing address' : 'Delete billing address'}
</svelte:fragment>
@@ -33,12 +33,7 @@
}
</script>
<Modal
bind:show={showDelete}
onSubmit={handleDelete}
icon="exclamation"
state="warning"
headerDivider={false}>
<Modal bind:show={showDelete} onSubmit={handleDelete}>
<svelte:fragment slot="title">
{linkedOrgs.length ? 'Unable to delete payment method' : 'Delete payment method'}
</svelte:fragment>
@@ -3,7 +3,7 @@
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Modal } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, FormItem, FormList, InputSelect, InputText } from '$lib/elements/forms';
import { Button, FormList, InputSelect, InputText } from '$lib/elements/forms';
import type { Address } from '$lib/sdk/billing';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
@@ -85,23 +85,18 @@
label="City or suburb"
placeholder="Enter your city"
required />
<FormItem isMultiple>
<InputText
isMultiple
fullWidth
bind:value={selectedAddress.state}
id="state"
label="State"
placeholder="Enter your state"
required />
<InputText
isMultiple
fullWidth
bind:value={selectedAddress.postalCode}
id="zip"
label="Postal code"
placeholder="Enter postal code" />
</FormItem>
<InputText
fullWidth
bind:value={selectedAddress.state}
id="state"
label="State"
placeholder="Enter your state"
required />
<InputText
bind:value={selectedAddress.postalCode}
id="zip"
label="Postal code"
placeholder="Enter postal code" />
</FormList>
<svelte:fragment slot="footer">
<Button text on:click={() => (show = false)}>Cancel</Button>
@@ -1,6 +1,6 @@
<script lang="ts">
import { Alert, Modal } from '$lib/components';
import { Button, FormItem, FormList, InputNumber, InputSelect } from '$lib/elements/forms';
import { Button, FormList, InputNumber, InputSelect } from '$lib/elements/forms';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { addNotification } from '$lib/stores/notifications';
@@ -63,13 +63,7 @@
$: options = createMonthOptions(year);
</script>
<Modal
bind:error
onSubmit={handleSubmit}
size="big"
bind:show
title="Update payment method"
headerDivider={false}>
<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>
@@ -79,26 +73,20 @@
Updates to this payment method will be applied to any linked organizations.
{/if}
<FormList>
<FormItem isMultiple style="align-items: start;">
<InputSelect
isMultiple
fullWidth
id="month"
label="Month"
bind:value={month}
{options}
required
placeholder="Enter expiry month" />
<InputNumber
isMultiple
fullWidth
id="year"
label="Year"
bind:value={year}
min={currentYear}
required
placeholder="Enter expiry year" />
</FormItem>
<InputSelect
id="month"
label="Month"
bind:value={month}
{options}
required
placeholder="Enter expiry month" />
<InputNumber
id="year"
label="Year"
bind:value={year}
min={currentYear}
required
placeholder="Enter expiry year" />
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (show = false)}>Cancel</Button>
@@ -36,7 +36,7 @@
}
</script>
<Modal bind:show title="Add credits" headerDivider={false} onSubmit={redeem} size="big" bind:error>
<Modal bind:show title="Add credits" onSubmit={redeem} bind:error>
Apply Appwrite credits to your organization.
<FormList>
@@ -55,12 +55,7 @@
</script>
{#if disabled}
<Modal
bind:show={showDelete}
icon="exclamation"
state="warning"
headerDivider={false}
title="Unable to delete payment method">
<Modal bind:show={showDelete} title="Unable to delete payment method">
<p data-private>
The {isBackup ? 'backup' : 'default'} payment method cannot be removed as
<b>{$organization?.name}</b>
@@ -76,9 +71,6 @@
bind:show={showDelete}
bind:error
onSubmit={isBackup ? removeBackuptMethod : removeDefaultMethod}
icon="exclamation"
state="warning"
headerDivider={false}
title="Remove payment method">
<p data-private>
Are you sure you want to remove the payment method from <b>{$organization?.name}</b>?
@@ -29,14 +29,7 @@
}
</script>
<Modal
bind:show
bind:error
onSubmit={removeAddress}
icon="exclamation"
state="warning"
headerDivider={false}
title="Remove billing address">
<Modal bind:show bind:error onSubmit={removeAddress} title="Remove billing address">
<p data-private>
Are you sure you want to remove the billing address from <b>{$organization?.name}</b>?
</p>
@@ -1,7 +1,7 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Alert, Modal, RadioBoxes } from '$lib/components';
import { Button, FormItem, FormList, InputSelect, InputText } from '$lib/elements/forms';
import { Button, FormList, InputSelect, InputText } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { organization } from '$lib/stores/organization';
import { Dependencies } from '$lib/constants';
@@ -103,13 +103,7 @@
}
</script>
<Modal
bind:show
bind:error
onSubmit={handleSubmit}
size="big"
headerDivider={false}
title="Replace billing address">
<Modal bind:show bind:error onSubmit={handleSubmit} title="Replace billing address">
<p class="text">Replace the existing billing address for your organization.</p>
{#if addresses?.total}
<FormList>
@@ -141,7 +135,7 @@
<svelte:fragment slot="new">
<span style="padding-inline:0.25rem">Add a new billing address</span>
</svelte:fragment>
<FormList gap={16} class="u-margin-block-start-24">
<FormList>
<InputSelect
bind:value={country}
{options}
@@ -166,23 +160,17 @@
label="City or suburb"
placeholder="Enter your city"
required />
<FormItem isMultiple>
<InputText
isMultiple
fullWidth
bind:value={state}
id="state"
label="State"
placeholder="Enter your state"
required />
<InputText
isMultiple
fullWidth
bind:value={postalCode}
id="zip"
label="Postal code"
placeholder="Enter postal code" />
</FormItem>
<InputText
bind:value={state}
id="state"
label="State"
placeholder="Enter your state"
required />
<InputText
bind:value={postalCode}
id="zip"
label="Postal code"
placeholder="Enter postal code" />
</FormList>
</RadioBoxes>
</FormList>
@@ -103,13 +103,7 @@
}
</script>
<FakeModal
bind:show
bind:error
onSubmit={handleSubmit}
size="big"
headerDivider={false}
title="Replace payment method">
<FakeModal bind:show bind:error onSubmit={handleSubmit} size="big" title="Replace payment method">
<p class="text">Replace the existing payment method for your organization.</p>
<PaymentBoxes
@@ -118,7 +118,6 @@
state="warning"
onSubmit={handleSubmit}
size="big"
headerDivider={false}
title="Retry payment">
<!-- TODO: format currency -->
<p class="text">
@@ -129,7 +128,6 @@
<Button
external
link
href={`${endpoint}/organizations/${$page.params.organization}/invoices/${invoice.$id}/view`}>
View invoice
</Button>
@@ -13,7 +13,6 @@
import PlanSelection from '$lib/components/billing/planSelection.svelte';
import ValidateCreditModal from '$lib/components/billing/validateCreditModal.svelte';
import Default from '$lib/components/roles/default.svelte';
import { Layout } from '@appwrite.io/pink-svelte';
import { BillingPlan, Dependencies, feedbackDowngradeOptions } from '$lib/constants';
import {
Button,
@@ -259,7 +258,7 @@
<Label class="label u-margin-block-start-16">Select plan</Label>
<p class="text">
For more details on our plans, visit our
<Button href="https://appwrite.io/pricing" external link>pricing page</Button>.
<Button href="https://appwrite.io/pricing" external>pricing page</Button>.
</p>
{#if !selfService}
<Alert class="u-position-relative u-margin-block-start-16" type="info"
@@ -317,7 +316,7 @@
</FormList>
{/if}
{#if isDowngrade}
<FormList class="u-margin-block-start-24">
<FormList>
<InputSelect
id="reason"
label="Reason for plan change"
@@ -51,12 +51,7 @@
$: isUser = selectedMember?.userId === $user?.$id;
</script>
<Modal
bind:show={showDelete}
onSubmit={deleteMembership}
icon="exclamation"
state="warning"
headerDivider={false}>
<Modal bind:show={showDelete} onSubmit={deleteMembership}>
<svelte:fragment slot="title">
{isUser ? 'Leave organization' : 'Delete member'}
</svelte:fragment>
@@ -2,7 +2,6 @@
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { tooltip } from '$lib/actions/tooltip';
import {
AvatarGroup,
DropList,
@@ -3,15 +3,6 @@
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { AvatarInitials, DropList, DropListItem, PaginationWithLimit } from '$lib/components';
import { Pill } from '$lib/elements';
import {
TableBody,
TableCell,
TableCellHead,
TableCellText,
TableHeader,
TableRow,
TableScroll
} from '$lib/elements/table';
import { Container, ContainerHeader } from '$lib/layout';
import { addNotification } from '$lib/stores/notifications';
import { newMemberModal, organization } from '$lib/stores/organization';
@@ -24,6 +15,8 @@
import Edit from './edit.svelte';
import { getRoleLabel } from '$lib/stores/billing';
import { Drop } from '$lib/components';
import { Layout, Table } from '@appwrite.io/pink-svelte';
import Upgrade from '$lib/components/roles/upgrade.svelte';
export let data: PageData;
@@ -68,127 +61,119 @@
buttonText={$isOwner ? 'Invite' : ''}
buttonMethod={() => newMemberModal.set(true)}
showAlert={false} />
<TableScroll>
<TableHeader>
<TableCellHead width={160}>Name</TableCellHead>
<TableCellHead width={120}>Email</TableCellHead>
<div style:--p-col-width={120} class="table-thead-col" role="columnheader">
<span class="u-flex u-cross-baseline">
<span class="eyebrow-heading-3"> Role </span>
<Drop isPopover bind:show={showPopover} display="inline-block">
&nbsp;<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>
</span>
</div>
<TableCellHead width={90}>2FA</TableCellHead>
{#if $isOwner}
<TableCellHead width={30} />
{/if}
</TableHeader>
<TableBody
service="members"
total={data.organizationMembers.total}
event="members_list">
{#each data.organizationMembers.memberships as member, index}
<TableRow>
<TableCell title="Name">
<div class="u-flex u-gap-12 u-cross-center">
<AvatarInitials size={40} name={member.userName} />
<span class="text u-trim">
{member.userName ? member.userName : 'n/a'}
</span>
{#if member.invited && !member.joined}
<Pill warning>Pending</Pill>
{/if}
<Table.Root>
<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">
&nbsp;<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>
</TableCell>
<TableCellText title="Email">{member.userEmail}</TableCellText>
<TableCellText title="Role"
>{member.roles
.map((role) => getRoleLabel(role))
.join(', ')}</TableCellText>
<TableCellText title="2FA">
<Pill success={member.mfa}>
{#if member.mfa}
<span
class="icon-check-circle u-color-text-success"
aria-hidden="true" />
<p class="text">enabled</p>
{:else}
<p class="text">disabled</p>
{/if}
</Pill>
</TableCellText>
{#if $isOwner}
<TableCell showOverflow right>
<DropList
bind:show={showDropdown[index]}
placement="bottom-start"
noArrow>
<button
class="button is-only-icon is-text"
aria-label="More options"
on:click|preventDefault={() => {
showDropdown[index] = !showDropdown[index];
</svelte:fragment>
</Drop>
</Table.Header.Cell>
<Table.Header.Cell>2FA</Table.Header.Cell>
{#if $isOwner}
<Table.Header.Cell />
{/if}
</svelte:fragment>
{#each data.organizationMembers.memberships as member, index}
<Table.Row>
<Table.Cell>
<Layout.Stack direction="row" alignItems="center">
<AvatarInitials size={40} name={member.userName} />
<span class="u-trim">
{member.userName ? member.userName : 'n/a'}
</span>
{#if member.invited && !member.joined}
<Pill warning>Pending</Pill>
{/if}
</Layout.Stack>
</Table.Cell>
<Table.Cell>
{member.userEmail}
</Table.Cell>
<Table.Cell>
{member.roles.map((role) => getRoleLabel(role)).join(', ')}
</Table.Cell>
<Table.Cell>
<Pill success={member.mfa}>
{#if member.mfa}
<span
class="icon-check-circle u-color-text-success"
aria-hidden="true" />
<p class="text">enabled</p>
{:else}
<p class="text">disabled</p>
{/if}
</Pill>
</Table.Cell>
{#if $isOwner}
<Table.Cell>
<DropList
bind:show={showDropdown[index]}
placement="bottom-start"
noArrow>
<button
class="button is-only-icon is-text"
aria-label="More options"
on:click|preventDefault={() => {
showDropdown[index] = !showDropdown[index];
}}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</button>
<svelte:fragment slot="list">
<DropListItem
icon="pencil"
on:click={() => {
selectedMember = member;
showEdit = true;
showDropdown[index] = false;
}}>
<span class="icon-dots-horizontal" aria-hidden="true" />
</button>
<svelte:fragment slot="list">
Edit role
</DropListItem>
{#if member.invited && !member.joined}
<DropListItem
icon="pencil"
icon="refresh"
on:click={() => {
selectedMember = member;
showEdit = true;
resend(member);
showDropdown[index] = false;
}}>
Edit role
Resend
</DropListItem>
{#if member.invited && !member.joined}
<DropListItem
icon="refresh"
on:click={() => {
resend(member);
showDropdown[index] = false;
}}>
Resend
</DropListItem>
{/if}
<DropListItem
icon="trash"
on:click={() => {
selectedMember = member;
showDelete = true;
showDropdown[index] = false;
}}>
Remove
</DropListItem>
</svelte:fragment>
</DropList>
</TableCell>
{/if}
</TableRow>
{/each}
</TableBody>
</TableScroll>
{/if}
<DropListItem
icon="trash"
on:click={() => {
selectedMember = member;
showDelete = true;
showDropdown[index] = false;
}}>
Remove
</DropListItem>
</svelte:fragment>
</DropList>
</Table.Cell>
{/if}
</Table.Row>
{/each}
</Table.Root>
<PaginationWithLimit
name="Members"
@@ -93,13 +93,7 @@
}
</script>
<Modal
bind:error
bind:show
onSubmit={handleSubmit}
size="big"
title="Request BAA"
headerDivider={false}>
<Modal bind:error bind:show onSubmit={handleSubmit} title="Request BAA">
<FormList>
<InputEmail label="Email" placeholder="Enter email" id="email" bind:value={email} />
<InputSelect
@@ -95,13 +95,7 @@
}
</script>
<Modal
bind:error
bind:show
onSubmit={handleSubmit}
size="big"
headerDivider={false}
title="Request SOC-2">
<Modal bind:error bind:show onSubmit={handleSubmit} title="Request SOC-2">
<FormList>
<InputEmail label="Email" placeholder="Enter email" id="email" bind:value={email} />
<InputSelect
@@ -103,14 +103,7 @@
</script>
<div class="max-height-dialog">
<Modal
title="Delete organization"
onSubmit={deleteOrg}
bind:show={showDelete}
bind:error
icon="exclamation"
state="warning"
headerDivider={false}>
<Modal title="Delete organization" onSubmit={deleteOrg} bind:show={showDelete} bind:error>
{#if upcomingInvoice}
<Alert type="warning">
<span slot="title">
@@ -21,7 +21,8 @@
canSeeDatabases,
canSeeFunctions,
canSeeMessages,
canWriteProjects
canWriteProjects,
canWriteSites
} from '$lib/stores/roles';
onMount(() => {
@@ -88,6 +89,15 @@
group: 'navigation',
disabled: !$canWriteProjects
},
{
label: 'Go to Sites',
callback: () => {
goto(`${base}/project-${$project.$id}/sites`);
},
keys: ['g', 'i'],
group: 'navigation',
disabled: !$canWriteSites
},
{
label: 'Go to Overview',
callback: () => {
@@ -35,24 +35,18 @@
</script>
<Container>
<ContainerHeader title="Users" isFlex={false} total={data.users.total} let:isButtonDisabled>
<SearchQuery search={data.search} placeholder="Search by name, email, phone, or ID">
{#if $canWriteUsers}
<Tooltip disabled={!isButtonDisabled}>
<div>
<Button
on:mousedown={() => ($showCreateUser = true)}
event="create_user"
disabled={isButtonDisabled}>
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create user</span>
</Button>
</div>
<span slot="tooltip">Upgrade to add more users</span>
</Tooltip>
{/if}
</SearchQuery>
</ContainerHeader>
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center">
<SearchQuery search={data.search} placeholder="Search by name, email, phone, or ID" />
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<Button on:mousedown={() => ($showCreateUser = true)} event="create_user" size="s">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create user</span>
</Button>
</Layout.Stack>
</Layout.Stack>
{#if data.users.total}
<Table.Root>
<svelte:fragment slot="header">
@@ -2,7 +2,7 @@
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { InputPhone, InputOTP } from '$lib/elements/forms';
import { Button, Form, FormItem, FormItemPart } from '$lib/elements/forms';
import { Button, Form } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { project } from '../../store';
import { upgradeURL } from '$lib/stores/billing';
@@ -17,7 +17,6 @@
import { app } from '$lib/stores/app';
import Empty from '$lib/components/empty.svelte';
import type { Models } from '@appwrite.io/console';
import { tooltip } from '$lib/actions/tooltip';
import { Tooltip } from '@appwrite.io/pink-svelte';
let numbers: Models.MockNumber[] = $project?.authMockNumbers ?? [];
@@ -150,73 +149,67 @@
{:else if numbers?.length > 0}
<ul class="form-list u-gap-8">
{#each numbers as number, index}
<FormItem isMultiple>
<InputPhone
id={`key-${index}`}
bind:value={number.phone}
fullWidth
placeholder="Enter phone number"
label="Phone number"
showLabel={index === 0}
minlength={9}
maxlength={16}
required>
<button
slot="options"
on:click={() => (number.phone = generateNumber())}
class="options-list-button"
aria-label="regenerate text"
type="button">
<Tooltip>
<span class="icon-refresh" aria-hidden="true"></span>
<span slot="tooltip">Regenerate</span>
</Tooltip>
</button>
</InputPhone>
<InputOTP
id={`value-${index}`}
bind:value={number.otp}
fullWidth
placeholder="Enter value"
label="Verification code"
maxlength={6}
pattern={'^[0-9]{6}$'}
patternError="The value must contain 6 digits"
showLabel={index === 0}
required>
<button
slot="options"
on:click={() => (number.otp = generateOTP())}
class="options-list-button"
aria-label="regenerate text"
type="button">
<Tooltip
><span class="icon-refresh" aria-hidden="true"></span><span
slot="tooltip">Regenerate</span
></Tooltip>
</button>
</InputOTP>
<FormItemPart>
<Button
text
disabled={numbers.length === 0}
class={'u-padding-4 ' +
(index === 0 ? 'u-margin-block-start-24' : '')}
on:click={() => {
deletePhoneNumber(index);
}}>
<Tooltip
><span class="icon-refresh" aria-hidden="true"></span><span
slot="tooltip">Regenerate</span
></Tooltip>
</Button>
</FormItemPart>
</FormItem>
<InputPhone
id={`key-${index}`}
bind:value={number.phone}
fullWidth
placeholder="Enter phone number"
label="Phone number"
showLabel={index === 0}
minlength={9}
maxlength={16}
required>
<button
slot="options"
on:click={() => (number.phone = generateNumber())}
class="options-list-button"
aria-label="regenerate text"
type="button">
<Tooltip>
<span class="icon-refresh" aria-hidden="true"></span>
<span slot="tooltip">Regenerate</span>
</Tooltip>
</button>
</InputPhone>
<InputOTP
id={`value-${index}`}
bind:value={number.otp}
fullWidth
placeholder="Enter value"
label="Verification code"
maxlength={6}
pattern={'^[0-9]{6}$'}
patternError="The value must contain 6 digits"
showLabel={index === 0}
required>
<button
slot="options"
on:click={() => (number.otp = generateOTP())}
class="options-list-button"
aria-label="regenerate text"
type="button">
<Tooltip
><span class="icon-refresh" aria-hidden="true"></span><span
slot="tooltip">Regenerate</span
></Tooltip>
</button>
</InputOTP>
<Button
text
disabled={numbers.length === 0}
class={'u-padding-4 ' + (index === 0 ? 'u-margin-block-start-24' : '')}
on:click={() => {
deletePhoneNumber(index);
}}>
<Tooltip
><span class="icon-refresh" aria-hidden="true"></span><span
slot="tooltip">Regenerate</span
></Tooltip>
</Button>
{/each}
</ul>
{#if numbers?.length < 10}
<Button
noMargin
text
on:click={() =>
addPhoneNumber({
@@ -4,15 +4,6 @@
<script lang="ts">
import { page } from '$app/stores';
import {
Table,
TableHeader,
TableBody,
TableRowLink,
TableCellHead,
TableCellText,
TableCell
} from '$lib/elements/table';
import { Button } from '$lib/elements/forms';
import {
Empty,
@@ -28,12 +19,11 @@
import { base } from '$app/paths';
import type { Models } from '@appwrite.io/console';
import type { PageData } from './$types';
import { tooltip } from '$lib/actions/tooltip';
import { writable } from 'svelte/store';
import { readOnly } from '$lib/stores/billing';
import { isCloud } from '$lib/system';
import { canWriteTeams } from '$lib/stores/roles';
import { Tooltip } from '@appwrite.io/pink-svelte';
import { Tooltip, Layout, Table } from '@appwrite.io/pink-svelte';
export let data: PageData;
@@ -44,55 +34,42 @@
</script>
<Container>
<ContainerHeader
title="Teams"
isFlex={false}
total={data.teams.total}
buttonDisabled={isCloud && $readOnly}
let:isButtonDisabled>
<SearchQuery search={data.search} placeholder="Search by name">
{#if $canWriteTeams}
<Tooltip disabled={!isButtonDisabled}>
<div>
<Button
on:click={() => ($showCreateTeam = true)}
event="create_team"
disabled={isButtonDisabled}>
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create team</span>
</Button>
</div>
<span slot="tooltip">Upgrade to add more teams</span>
</Tooltip>
{/if}
</SearchQuery>
</ContainerHeader>
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center">
<SearchQuery search={data.search} placeholder="Search by name" />
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<Button on:mousedown={() => ($showCreateTeam = true)} event="create_user" size="s">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create team</span>
</Button>
</Layout.Stack>
</Layout.Stack>
{#if data.teams.total}
<Table>
<TableHeader>
<TableCellHead>Name</TableCellHead>
<TableCellHead onlyDesktop>Members</TableCellHead>
<TableCellHead onlyDesktop>Created</TableCellHead>
</TableHeader>
<TableBody service="teams" total={data.teams.total}>
{#each data.teams.teams as team}
<TableRowLink href={`${base}/project-${project}/auth/teams/team-${team.$id}`}>
<TableCell title="Name">
<div class="u-flex u-gap-12 u-cross-center">
<AvatarInitials size={32} name={team.name} />
<span class="text u-trim">{team.name}</span>
</div>
</TableCell>
<TableCellText onlyDesktop title="Members"
>{team.total} members</TableCellText>
<TableCellText onlyDesktop title="Created">
{toLocaleDateTime(team.$createdAt)}
</TableCellText>
</TableRowLink>
{/each}
</TableBody>
</Table>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Members</Table.Header.Cell>
<Table.Header.Cell>Created</Table.Header.Cell>
</svelte:fragment>
{#each data.teams.teams as team}
<Table.Link href={`${base}/project-${project}/auth/teams/team-${team.$id}`}>
<Table.Cell>
<Layout.Stack direction="row" alignItems="center">
<AvatarInitials size={32} name={team.name} />
<span class="u-trim">{team.name}</span>
</Layout.Stack>
</Table.Cell>
<Table.Cell>
{team.total} members
</Table.Cell>
<Table.Cell>
{toLocaleDateTime(team.$createdAt)}
</Table.Cell>
</Table.Link>
{/each}
</Table.Root>
<PaginationWithLimit
name="Teams"
@@ -37,13 +37,7 @@
};
</script>
<Modal
title="Delete Member"
bind:show={showDelete}
onSubmit={deleteMembership}
icon="exclamation"
state="warning"
headerDivider={false}>
<Modal title="Delete Member" bind:show={showDelete} onSubmit={deleteMembership}>
<p data-private>
Are you sure you want to delete <b>{selectedMembership.userName}</b> from '{selectedMembership.teamName}'?
</p>
@@ -28,13 +28,7 @@
};
</script>
<Modal
title="Delete team"
bind:show={showDelete}
onSubmit={deleteTeam}
icon="exclamation"
state="warning"
headerDivider={false}>
<Modal title="Delete team" bind:show={showDelete} onSubmit={deleteTeam}>
<p data-private>
Are you sure you want to delete <b>{team.name}</b>?
</p>
@@ -3,7 +3,7 @@
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormItem, FormItemPart, InputText } from '$lib/elements/forms';
import { Button, Form, InputText } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { onMount } from 'svelte';
@@ -66,40 +66,32 @@
<ul class="form-list">
{#if prefs}
{#each prefs as [key, value], index}
<FormItem isMultiple>
<InputText
id={`key-${index}`}
isMultiple
fullWidth
bind:value={key}
placeholder="Enter key"
label="Key"
required />
<InputText
id={`key-${index}`}
bind:value={key}
placeholder="Enter key"
label="Key"
required />
<InputText
id={`value-${index}`}
isMultiple
fullWidth
bind:value
placeholder="Enter value"
label="Value"
required />
<FormItemPart alignEnd>
<Button
text
disabled={(!key || !value) && index === 0}
on:click={() => {
if (index === 0) {
prefs = [['', '']];
} else {
prefs.splice(index, 1);
prefs = prefs;
}
}}>
<span class="icon-x" aria-hidden="true" />
</Button>
</FormItemPart>
</FormItem>
<InputText
id={`value-${index}`}
bind:value
placeholder="Enter value"
label="Value"
required />
<Button
text
disabled={(!key || !value) && index === 0}
on:click={() => {
if (index === 0) {
prefs = [['', '']];
} else {
prefs.splice(index, 1);
prefs = prefs;
}
}}>
<span class="icon-x" aria-hidden="true" />
</Button>
{/each}
{/if}
</ul>
@@ -44,14 +44,7 @@
}
</script>
<Modal
icon="exclamation"
state="warning"
onSubmit={reset}
size="big"
{error}
bind:show
headerDivider={false}>
<Modal onSubmit={reset} {error} bind:show>
<svelte:fragment slot="title">Reset Email Template?</svelte:fragment>
<p class="text">
Are you sure you want to reset the email template?
@@ -39,14 +39,7 @@
}
</script>
<Modal
icon="exclamation"
state="warning"
onSubmit={reset}
size="big"
{error}
bind:show
headerDivider={false}>
<Modal onSubmit={reset} {error} bind:show>
<svelte:fragment slot="title">Reset SMS Template?</svelte:fragment>
<p class="text">
Are you sure you want to reset the SMS template?
@@ -31,13 +31,7 @@
}
</script>
<Modal
title="Delete all sessions"
bind:show={showDeleteAll}
onSubmit={deleteAllSessions}
icon="exclamation"
state="warning"
headerDivider={false}>
<Modal title="Delete all sessions" bind:show={showDeleteAll} onSubmit={deleteAllSessions}>
<p data-private>
Are you sure you want to delete <b>all of {$user.name}'s sessions?</b>
</p>
@@ -39,13 +39,7 @@
}
</script>
<Modal
title="Delete member"
bind:show={showDelete}
onSubmit={deleteMembership}
icon="exclamation"
state="warning"
headerDivider={false}>
<Modal title="Delete member" bind:show={showDelete} onSubmit={deleteMembership}>
{#if selectedMembership}
<p data-private>
Are you sure you want to delete <b>{selectedMembership.userName}</b> from '{selectedMembership.teamName}'?
@@ -38,10 +38,7 @@
title="Delete authentication method"
bind:show={showDelete}
onSubmit={deleteProvider}
icon="exclamation"
state="warning"
bind:error
headerDivider={false}>
bind:error>
<p>Are you sure you want to delete this authentication?</p>
<p class="text u-bold">This action is irreversible.</p>
<svelte:fragment slot="footer">
@@ -31,13 +31,7 @@
}
</script>
<Modal
title="Delete sessions"
bind:show={showDelete}
onSubmit={deleteSession}
icon="exclamation"
state="warning"
headerDivider={false}>
<Modal title="Delete sessions" bind:show={showDelete} onSubmit={deleteSession}>
<p>Are you sure you want to delete this session?</p>
<svelte:fragment slot="footer">
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
@@ -29,14 +29,7 @@
};
</script>
<Modal
title="Delete user"
bind:show={showDelete}
onSubmit={deleteUser}
icon="exclamation"
state="warning"
headerDivider={false}
bind:error>
<Modal title="Delete user" bind:show={showDelete} onSubmit={deleteUser} bind:error>
<p data-private>Are you sure you want to delete <b>{$user.name}</b> from '{$project.name}'?</p>
<svelte:fragment slot="footer">
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
@@ -141,14 +141,7 @@
</div>
</FloatingActionBar>
<Modal
title="Delete identity"
icon="exclamation"
state="warning"
bind:show={showDelete}
onSubmit={handleDelete}
headerDivider={false}
closable={!deleting}>
<Modal title="Delete identity" bind:show={showDelete} onSubmit={handleDelete} closable={!deleting}>
<p class="text" data-private>
Are you sure you want to delete <b>{selectedIds.length}</b>
{selectedIds.length > 1 ? 'identities' : 'identity'}?
@@ -2,15 +2,6 @@
import { page } from '$app/stores';
import { base } from '$app/paths';
import { AvatarInitials, EmptySearch } from '$lib/components';
import {
Table,
TableHeader,
TableBody,
TableRowLink,
TableCellHead,
TableCellText,
TableCell
} from '$lib/elements/table';
import { Button } from '$lib/elements/forms';
import { Container } from '$lib/layout';
import DeleteMembership from '../deleteMembership.svelte';
@@ -18,6 +9,7 @@
import type { PageData } from './$types';
import { trackEvent } from '$lib/actions/analytics';
import { toLocaleDateTime } from '$lib/helpers/date';
import { Table, Layout } from '@appwrite.io/pink-svelte';
export let data: PageData;
@@ -29,43 +21,43 @@
<Container>
{#if data.memberships.total}
<Table>
<TableHeader>
<TableCellHead>Name</TableCellHead>
<TableCellHead onlyDesktop>Roles</TableCellHead>
<TableCellHead onlyDesktop>Joined</TableCellHead>
<TableCellHead width={30} />
</TableHeader>
<TableBody>
{#each data.memberships.memberships as membership}
<TableRowLink
href={`${base}/project-${project}/auth/teams/team-${membership.teamId}`}>
<TableCellText title="Name">
<div class="u-flex u-cross-center u-gap-12">
<AvatarInitials size={32} name={membership.teamName} />
<span>{membership.teamName ? membership.teamName : 'n/a'}</span>
</div>
</TableCellText>
<TableCellText onlyDesktop title="Roles">{membership.roles}</TableCellText>
<TableCellText onlyDesktop title="Joined">
{toLocaleDateTime(membership.joined)}
</TableCellText>
<TableCell width={30}>
<button
class="button is-only-icon is-text"
aria-label="Delete item"
on:click|preventDefault={() => {
selectedMembership = membership;
showDelete = true;
trackEvent('click_delete_membership');
}}>
<span class="icon-trash" aria-hidden="true" />
</button>
</TableCell>
</TableRowLink>
{/each}
</TableBody>
</Table>
<Table.Root>
<svelte:fragment slot="header">
<Table.Header.Cell>Name</Table.Header.Cell>
<Table.Header.Cell>Roles</Table.Header.Cell>
<Table.Header.Cell>Joined</Table.Header.Cell>
<Table.Header.Cell />
</svelte:fragment>
{#each data.memberships.memberships as membership}
<Table.Link
href={`${base}/project-${project}/auth/teams/team-${membership.teamId}`}>
<Table.Cell>
<Layout.Stack direction="row" alignItems="center">
<AvatarInitials size={32} name={membership.teamName} />
<span>{membership.teamName ? membership.teamName : 'n/a'}</span>
</Layout.Stack>
</Table.Cell>
<Table.Cell>
{membership.roles}
</Table.Cell>
<Table.Cell>
{toLocaleDateTime(membership.joined)}
</Table.Cell>
<Table.Cell>
<button
class="button is-only-icon is-text"
aria-label="Delete item"
on:click|preventDefault={() => {
selectedMembership = membership;
showDelete = true;
trackEvent('click_delete_membership');
}}>
<span class="icon-trash" aria-hidden="true" />
</button>
</Table.Cell>
</Table.Link>
{/each}
</Table.Root>
{:else}
<EmptySearch>
<div class="u-flex u-flex-vertical u-cross-center u-gap-24">
@@ -143,14 +143,7 @@
</div>
</FloatingActionBar>
<Modal
title="Delete target"
icon="exclamation"
state="warning"
bind:show={showDelete}
onSubmit={handleDelete}
headerDivider={false}
closable={!deleting}>
<Modal title="Delete target" bind:show={showDelete} onSubmit={handleDelete} closable={!deleting}>
<p class="text" data-private>
Are you sure you want to delete <b>{selectedIds.length}</b>
{selectedIds.length > 1 ? 'targets' : 'target'}?
@@ -3,7 +3,7 @@
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { CardGrid, Heading } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, Form, FormItem, FormItemPart, InputText } from '$lib/elements/forms';
import { Button, Form, InputText } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { onMount } from 'svelte';
@@ -59,40 +59,32 @@
<ul class="form-list">
{#if prefs}
{#each prefs as [key, value], index}
<FormItem isMultiple>
<InputText
id={`key-${index}`}
isMultiple
fullWidth
bind:value={key}
placeholder="Enter key"
label="Key"
required />
<InputText
id={`key-${index}`}
bind:value={key}
placeholder="Enter key"
label="Key"
required />
<InputText
id={`value-${index}`}
isMultiple
fullWidth
bind:value
placeholder="Enter value"
label="Value"
required />
<FormItemPart alignEnd>
<Button
text
disabled={(!key || !value) && index === 0}
on:click={() => {
if (index === 0 && prefs?.length === 1) {
prefs = [['', '']];
} else {
prefs.splice(index, 1);
prefs = prefs;
}
}}>
<span class="icon-x" aria-hidden="true" />
</Button>
</FormItemPart>
</FormItem>
<InputText
id={`value-${index}`}
bind:value
placeholder="Enter value"
label="Value"
required />
<Button
text
disabled={(!key || !value) && index === 0}
on:click={() => {
if (index === 0 && prefs?.length === 1) {
prefs = [['', '']];
} else {
prefs.splice(index, 1);
prefs = prefs;
}
}}>
<span class="icon-x" aria-hidden="true" />
</Button>
{/each}
{/if}
</ul>
@@ -36,6 +36,11 @@
}
close();
}
$: if (!showCreate) {
console.log('test');
selectedVar = null;
}
</script>
<Modal
@@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Empty, PaginationWithLimit, ViewSelector } from '$lib/components';
import { Empty, PaginationWithLimit, SearchQuery, ViewSelector } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { Container, ContainerHeader } from '$lib/layout';
import type { Models } from '@appwrite.io/console';
@@ -13,9 +13,8 @@
import { columns } from './store';
import Table from './table.svelte';
import { registerCommands } from '$lib/commandCenter';
import { tooltip } from '$lib/actions/tooltip';
import { canWriteDatabases } from '$lib/stores/roles';
import { Tooltip } from '@appwrite.io/pink-svelte';
import { Layout, Tooltip } from '@appwrite.io/pink-svelte';
export let data: PageData;
@@ -44,36 +43,27 @@
</script>
<Container>
<ContainerHeader
title="Databases"
total={data?.databases?.total}
on:data={(data) => {
isCreationDisabled = data.detail.isButtonDisabled;
}}>
<svelte:fragment>
<div class="u-flex u-gap-16 u-cross-center u-flex-wrap">
<ViewSelector
{columns}
view={data.view}
hideColumns={!data.databases.total}
hideView={!data.databases.total} />
{#if $canWriteDatabases}
<Tooltip disabled={!isCreationDisabled}>
<div>
<Button
on:click={() => (showCreate = true)}
event="create_database"
disabled={isCreationDisabled}>
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create database</span>
</Button>
</div>
<span slot="tooltip">Upgrade to add more databases</span>
</Tooltip>
{/if}
</div>
</svelte:fragment>
</ContainerHeader>
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center">
<SearchQuery search={data.search} placeholder="Search databases" />
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<ViewSelector
{columns}
view={data.view}
hideColumns={!data.databases.total}
hideView={!data.databases.total} />
{#if $canWriteDatabases}
<Button
on:click={() => (showCreate = true)}
event="create_database"
disabled={isCreationDisabled}>
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create database</span>
</Button>
{/if}
</Layout.Stack>
</Layout.Stack>
{#if data.databases.total}
{#if data.view === 'grid'}
@@ -1,25 +1,32 @@
import { CARD_LIMIT, Dependencies } from '$lib/constants';
import { getLimit, getPage, getView, pageToOffset, View } from '$lib/helpers/load';
import { getLimit, getPage, getSearch, getView, pageToOffset, View } from '$lib/helpers/load';
import { sdk } from '$lib/stores/sdk';
import { type Models, Query } from '@appwrite.io/console';
import { timeFromNow } from '$lib/helpers/date';
import type { PageLoad } from './$types';
import type { BackupPolicy } from '$lib/sdk/backups';
import { isSelfHosted } from '$lib/system';
export const load: PageLoad = async ({ url, route, depends }) => {
depends(Dependencies.DATABASES);
const page = getPage(url);
const search = getSearch(url);
const limit = getLimit(url, route, CARD_LIMIT);
const view = getView(url, route, View.Grid);
const offset = pageToOffset(page, limit);
const { databases, policies, lastBackups } = await fetchDatabasesAndBackups(limit, offset);
const { databases, policies, lastBackups } = await fetchDatabasesAndBackups(
limit,
offset,
search
);
return {
offset,
limit,
view,
search,
policies,
databases,
lastBackups
@@ -27,12 +34,11 @@ export const load: PageLoad = async ({ url, route, depends }) => {
};
// TODO: @itznotabug we should improve this!
async function fetchDatabasesAndBackups(limit: number, offset: number) {
const databases = await sdk.forProject.databases.list([
Query.limit(limit),
Query.offset(offset),
Query.orderDesc('$createdAt')
]);
async function fetchDatabasesAndBackups(limit: number, offset: number, search: string) {
const databases = await sdk.forProject.databases.list(
[Query.limit(limit), Query.offset(offset), Query.orderDesc('$createdAt')],
search || undefined
);
const [policies, lastBackups] = await Promise.all([
await fetchPolicies(databases),
@@ -43,6 +49,8 @@ async function fetchDatabasesAndBackups(limit: number, offset: number) {
}
async function fetchPolicies(databases: Models.DatabaseList) {
if (isSelfHosted) return {};
const databasePolicies: Record<string, BackupPolicy[]> = {};
await Promise.all(
@@ -68,6 +76,8 @@ async function fetchPolicies(databases: Models.DatabaseList) {
}
async function fetchLastBackups(databases: Models.DatabaseList) {
if (isSelfHosted) return {};
const lastBackups: Record<string, string> = {};
await Promise.all(
@@ -1,5 +1,5 @@
<script lang="ts">
import { Empty, PaginationWithLimit } from '$lib/components';
import { Empty, PaginationWithLimit, SearchQuery, ViewSelector } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { Container, GridHeader } from '$lib/layout';
import { columns, showCreate } from './store';
@@ -7,24 +7,30 @@
import Grid from './grid.svelte';
import type { PageData } from './$types';
import { canWriteCollections } from '$lib/stores/roles';
import { Layout } from '@appwrite.io/pink-svelte';
export let data: PageData;
</script>
<Container>
<GridHeader
title="Collections"
{columns}
view={data.view}
hideColumns={!data.collections.total}
hideView={!data.collections.total}>
{#if $canWriteCollections}
<Button on:click={() => ($showCreate = true)} event="create_collection">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create collection</span>
</Button>
{/if}
</GridHeader>
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center">
<SearchQuery search={data.search} placeholder="Search collections" />
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<ViewSelector
{columns}
view={data.view}
hideColumns={!data.collections.total}
hideView={!data.collections.total} />
{#if $canWriteCollections}
<Button on:click={() => ($showCreate = true)} event="create_collection">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create collection</span>
</Button>
{/if}
</Layout.Stack>
</Layout.Stack>
{#if data.collections.total}
{#if data.view === 'grid'}
@@ -1,24 +1,26 @@
import { Query } from '@appwrite.io/console';
import { sdk } from '$lib/stores/sdk';
import { getLimit, getPage, getView, pageToOffset, View } from '$lib/helpers/load';
import { getLimit, getPage, getSearch, getView, pageToOffset, View } from '$lib/helpers/load';
import type { PageLoad } from './$types';
import { CARD_LIMIT, Dependencies } from '$lib/constants';
export const load: PageLoad = async ({ params, url, route, depends }) => {
depends(Dependencies.COLLECTIONS);
const page = getPage(url);
const search = getSearch(url);
const limit = getLimit(url, route, CARD_LIMIT);
const view = getView(url, route, View.Grid);
const offset = pageToOffset(page, limit);
const collections = await sdk.forProject.databases.listCollections(params.database, [
Query.limit(limit),
Query.offset(offset),
Query.orderDesc('')
]);
const collections = await sdk.forProject.databases.listCollections(
params.database,
[Query.limit(limit), Query.offset(offset), Query.orderDesc('')],
search || undefined
);
return {
offset,
limit,
search,
view,
collections
};
@@ -231,7 +231,6 @@
<Modal
title="Create backup policy"
size="big"
onSubmit={createPolicies}
bind:show={$showCreatePolicy}
bind:error={policyCreateError}>
@@ -3,6 +3,7 @@ import { CARD_LIMIT, Dependencies } from '$lib/constants';
import { sdk } from '$lib/stores/sdk';
import { Query } from '@appwrite.io/console';
import type { BackupArchive, BackupArchiveList, BackupPolicyList } from '$lib/sdk/backups';
import { isCloud } from '$lib/system';
export const load = async ({ params, url, route, depends }) => {
depends(Dependencies.BACKUPS);
@@ -14,24 +15,26 @@ export const load = async ({ params, url, route, depends }) => {
let backups: BackupArchiveList = { total: 0, archives: [] };
let policies: BackupPolicyList = { total: 0, policies: [] };
try {
[backups, policies] = await Promise.all([
sdk.forProject.backups.listArchives([
Query.limit(limit),
Query.offset(offset),
Query.orderDesc('$createdAt'),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', params.database)
]),
if (isCloud) {
try {
[backups, policies] = await Promise.all([
sdk.forProject.backups.listArchives([
Query.limit(limit),
Query.offset(offset),
Query.orderDesc('$createdAt'),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', params.database)
]),
sdk.forProject.backups.listPolicies([
Query.orderDesc('$createdAt'),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', params.database)
])
]);
} catch (e) {
// ignore
sdk.forProject.backups.listPolicies([
Query.orderDesc('$createdAt'),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', params.database)
])
]);
} catch (e) {
// ignore
}
}
const archivesByPolicy = groupArchivesByPolicy(backups.archives);
@@ -11,8 +11,6 @@
import { parseExpression } from 'cron-parser';
import { toLocaleDateTime } from '$lib/helpers/date';
import { tooltip } from '$lib/actions/tooltip';
import EmptyDark from '$lib/images/backups/backups-dark.png';
import EmptyLight from '$lib/images/backups/backups-light.png';
import type { BackupPolicy, BackupPolicyList } from '$lib/sdk/backups';
@@ -287,14 +285,7 @@
</Card>
</div>
<Modal
title="Delete policy"
icon="exclamation"
state="warning"
size="small"
bind:show={showDelete}
headerDivider={false}
onSubmit={deletePolicy}>
<Modal title="Delete policy" bind:show={showDelete} onSubmit={deletePolicy}>
<FormList>
<div class="u-flex-vertical u-gap-16">
<p class="text" data-private>
@@ -310,7 +301,6 @@
<div class="input-check-box-friction">
<InputCheckbox
required
size="small"
id="delete_policy"
bind:checked={confirmedDeletion}
label="I understand and confirm" />
@@ -2,7 +2,6 @@
import { onMount } from 'svelte';
import { trackEvent } from '$lib/actions/analytics';
import { InnerModal } from '$lib/components';
import { FormItem } from '$lib/elements/forms';
import TextCounter from '$lib/elements/forms/textCounter.svelte';
export let id: string;
@@ -61,22 +60,20 @@
</svelte:fragment>
<svelte:fragment slot="content">
<div class="form u-gap-8">
<FormItem>
<div class="input-text-wrapper">
<input
id="id"
placeholder="Enter ID"
maxlength={36}
{pattern}
autocomplete="off"
type="text"
class="input-text"
bind:value={id}
bind:this={element}
on:invalid={handleInvalid} />
<TextCounter count={id?.length ?? 0} max={36} />
</div>
</FormItem>
<div class="input-text-wrapper">
<input
id="id"
placeholder="Enter ID"
maxlength={36}
{pattern}
autocomplete="off"
type="text"
class="input-text"
bind:value={id}
bind:this={element}
on:invalid={handleInvalid} />
<TextCounter count={id?.length ?? 0} max={36} />
</div>
<div
class="u-flex u-gap-4 u-margin-block-start-8 u-small"
class:u-color-text-warning={icon === 'exclamation'}>
@@ -11,7 +11,6 @@
TableRow,
TableScroll
} from '$lib/elements/table';
import { tooltip } from '$lib/actions/tooltip';
import RestoreModal from './restoreModal.svelte';
import type { PageData } from './$types';
import { timeFromNow, toLocaleDateTime } from '$lib/helpers/date';
@@ -275,10 +274,7 @@
<Modal
title="Delete {selectedBackups.length ? 'backups' : 'backup'}"
icon="exclamation"
state="warning"
bind:show={showDelete}
headerDivider={false}
onSubmit={deleteBackups}>
<p class="text" data-private>
Are you sure you want to delete
@@ -296,7 +292,7 @@
</svelte:fragment>
</Modal>
<Modal headerDivider={true} title="Restore backup" bind:show={showRestore} onSubmit={restoreBackup}>
<Modal title="Restore backup" bind:show={showRestore} onSubmit={restoreBackup}>
<Card
isTile
class="restore-modal-inner-card u-width-full-line"
@@ -324,7 +320,7 @@
{#each restoreOptions as restoreOption}
<div class="u-width-full-line">
<LabelCard
padding="small"
padding="s"
name="restore"
value={restoreOption.id}
bind:group={selectedRestoreOption}>
@@ -352,7 +348,6 @@
bind:value={newDatabaseInfo.name}
autofocus
required />
{#if !showCustomId}
<div>
<Pill button on:click={() => (showCustomId = !showCustomId)}
@@ -375,7 +370,7 @@
<div class="input-check-box-friction">
<InputCheckbox
required
size="small"
size="s"
id="delete_policy"
bind:checked={confirmSameDbRestore}>
<svelte:fragment slot="description">
@@ -46,13 +46,7 @@
isRelationship(selectedAttribute) && selectedAttribute?.twoWay && !checked;
</script>
<Modal
title="Delete attribute"
icon="exclamation"
state="warning"
headerDivider={false}
bind:show={showDelete}
onSubmit={handleDelete}>
<Modal title="Delete attribute" bind:show={showDelete} onSubmit={handleDelete}>
<p class="text" data-private>
Are you sure you want to delete <b data-private>{selectedAttribute?.key}</b> from
<b data-private>{$collection?.name}</b>?

Some files were not shown because too many files have changed in this diff Show More