Merge remote-tracking branch 'origin/main' into feat-project-changes-2
@@ -14,12 +14,15 @@ export async function enterCreditCard(page: Page) {
|
||||
state: 'visible'
|
||||
});
|
||||
await page.getByPlaceholder('cardholder').fill('Test User');
|
||||
const stripe = page.frameLocator('[title="Secure payment input frame"]');
|
||||
const stripe = page.locator('[title="Secure payment input frame"]').nth(0).contentFrame();
|
||||
await stripe.locator('id=Field-numberInput').fill('4242424242424242');
|
||||
await stripe.locator('id=Field-expiryInput').fill('1250');
|
||||
await stripe.locator('id=Field-cvcInput').fill('123');
|
||||
await stripe.locator('id=Field-countryInput').selectOption('DE');
|
||||
await dialog.getByRole('button', { name: 'Add', exact: true }).click();
|
||||
await page.locator('id=state-picker').click(); // open dropdown
|
||||
await page.getByRole('option', { name: 'Alabama' }).click();
|
||||
await dialog.getByRole('button', { name: 'Add', exact: true }).click();
|
||||
await dialog.waitFor({
|
||||
state: 'hidden'
|
||||
});
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/svelte": "^1.1.24",
|
||||
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2289",
|
||||
"@appwrite.io/console": "^1.10.0",
|
||||
"@appwrite.io/pink-icons": "0.25.0",
|
||||
"@appwrite.io/pink-icons-svelte": "^2.0.0-RC.1",
|
||||
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@e6b154b",
|
||||
"@appwrite.io/pink-legacy": "^1.0.3",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@95bdb79",
|
||||
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@e6b154b",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sentry/sveltekit": "^8.38.0",
|
||||
"@stripe/stripe-js": "^3.5.0",
|
||||
|
||||
@@ -12,20 +12,20 @@ importers:
|
||||
specifier: ^1.1.24
|
||||
version: 1.1.24(svelte@5.25.3)(zod@3.24.3)
|
||||
'@appwrite.io/console':
|
||||
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2289
|
||||
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2289
|
||||
specifier: ^1.10.0
|
||||
version: 1.10.0
|
||||
'@appwrite.io/pink-icons':
|
||||
specifier: 0.25.0
|
||||
version: 0.25.0
|
||||
'@appwrite.io/pink-icons-svelte':
|
||||
specifier: ^2.0.0-RC.1
|
||||
version: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-icons-svelte@12707b9(svelte@5.25.3)
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@e6b154b
|
||||
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@e6b154b(svelte@5.25.3)
|
||||
'@appwrite.io/pink-legacy':
|
||||
specifier: ^1.0.3
|
||||
version: 1.0.3
|
||||
'@appwrite.io/pink-svelte':
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@95bdb79
|
||||
version: https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@95bdb79(svelte@5.25.3)
|
||||
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@e6b154b
|
||||
version: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@e6b154b(svelte@5.25.3)
|
||||
'@popperjs/core':
|
||||
specifier: ^2.11.8
|
||||
version: 2.11.8
|
||||
@@ -257,17 +257,16 @@ packages:
|
||||
'@analytics/type-utils@0.6.2':
|
||||
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2289':
|
||||
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2289}
|
||||
version: 1.9.0
|
||||
'@appwrite.io/console@1.10.0':
|
||||
resolution: {integrity: sha512-7ErAZgS0djmB41ZFXPTlJH5kBNvJeKv2UHOEcI6uAOyXO4UoVWlhQk0D/3oktNylwOsr/K/98EWfsUDeYbq68w==}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1':
|
||||
resolution: {integrity: sha512-iLFlV55hj8mGuAbmxJGenxN5RaZMmVT4GJb9dv/MP1xBAtYibFq7JvBcxm18qV2KU8c31Rntf+Ub4GL7HwqTYg==}
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-icons-svelte@12707b9':
|
||||
resolution: {tarball: https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-icons-svelte@12707b9}
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@e6b154b':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@e6b154b}
|
||||
version: 2.0.0-RC.1
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
@@ -281,8 +280,8 @@ packages:
|
||||
'@appwrite.io/pink-legacy@1.0.3':
|
||||
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@95bdb79':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@95bdb79}
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@e6b154b':
|
||||
resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@e6b154b}
|
||||
version: 2.0.0-RC.2
|
||||
peerDependencies:
|
||||
svelte: ^4.0.0
|
||||
@@ -3646,13 +3645,13 @@ snapshots:
|
||||
|
||||
'@analytics/type-utils@0.6.2': {}
|
||||
|
||||
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@2289': {}
|
||||
'@appwrite.io/console@1.10.0': {}
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)':
|
||||
dependencies:
|
||||
svelte: 5.25.3
|
||||
|
||||
'@appwrite.io/pink-icons-svelte@https://try-module.cloud/module/@appwrite/%40appwrite.io%2Fpink-icons-svelte@12707b9(svelte@5.25.3)':
|
||||
'@appwrite.io/pink-icons-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@e6b154b(svelte@5.25.3)':
|
||||
dependencies:
|
||||
svelte: 5.25.3
|
||||
|
||||
@@ -3665,7 +3664,7 @@ snapshots:
|
||||
'@appwrite.io/pink-icons': 1.0.0
|
||||
the-new-css-reset: 1.11.3
|
||||
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@95bdb79(svelte@5.25.3)':
|
||||
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@e6b154b(svelte@5.25.3)':
|
||||
dependencies:
|
||||
'@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3)
|
||||
'@floating-ui/dom': 1.6.13
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
<meta
|
||||
name="description"
|
||||
content="Appwrite is an open-source platform for building applications at any scale, using your preferred programming languages and tools." />
|
||||
<link rel="icon" type="image/svg+xml" href="/console/logos/appwrite-icon.svg" />
|
||||
<link rel="mask-icon" type="image/png" href="/console/logos/appwrite-icon.png" />
|
||||
<link rel="stylesheet" href="/console/css/loading.css" />
|
||||
<link rel="icon" type="image/svg+xml" href="%sveltekit.assets%/logos/appwrite-icon.svg" />
|
||||
<link rel="mask-icon" type="image/png" href="%sveltekit.assets%/logos/appwrite-icon.png" />
|
||||
<link rel="stylesheet" href="%sveltekit.assets%/css/loading.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
@@ -40,13 +40,13 @@
|
||||
<div></div>
|
||||
</div>
|
||||
<img
|
||||
src="/console/images/appwrite-logo-light.svg"
|
||||
src="%sveltekit.assets%/images/appwrite-logo-light.svg"
|
||||
width="120"
|
||||
height="22"
|
||||
class="logo-light"
|
||||
alt="Appwrite Logo" />
|
||||
<img
|
||||
src="/console/images/appwrite-logo-dark.svg"
|
||||
src="%sveltekit.assets%/images/appwrite-logo-dark.svg"
|
||||
width="120"
|
||||
height="22"
|
||||
class="logo-dark"
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
onMount(() => {
|
||||
projects = page.data.projects?.projects || [];
|
||||
projects = page.data.allProjects?.projects || [];
|
||||
});
|
||||
|
||||
let projectsToArchive = $derived(
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import { initializeStripe, unmountPaymentElement } from '$lib/stores/stripe';
|
||||
import { Badge, Card, Layout } from '@appwrite.io/pink-svelte';
|
||||
import type { PaymentMethodData } from '$lib/sdk/billing';
|
||||
import type { PaymentMethod } from '@stripe/stripe-js';
|
||||
import StatePicker from './statePicker.svelte';
|
||||
|
||||
export let methods: PaymentMethodData[];
|
||||
export let group: string;
|
||||
@@ -14,6 +16,9 @@
|
||||
export let disabledCondition: string = null;
|
||||
export let setAsDefault = false;
|
||||
export let showSetAsDefault = false;
|
||||
export let showState = false;
|
||||
export let paymentMethod: PaymentMethod | null = null;
|
||||
export let state: string = '';
|
||||
|
||||
let element: HTMLDivElement;
|
||||
let loader: HTMLDivElement;
|
||||
@@ -79,25 +84,29 @@
|
||||
{/each}
|
||||
<Card.Selector title="Add new payment method" name="$new" bind:group value="$new" />
|
||||
{#if group === '$new'}
|
||||
<InputText
|
||||
id="name"
|
||||
label="Cardholder name"
|
||||
placeholder="Cardholder name"
|
||||
bind:value={name}
|
||||
required
|
||||
autofocus={true} />
|
||||
{#if showState}
|
||||
<StatePicker card={paymentMethod} bind:state />
|
||||
{:else}
|
||||
<InputText
|
||||
id="name"
|
||||
label="Cardholder name"
|
||||
placeholder="Cardholder name"
|
||||
bind:value={name}
|
||||
required
|
||||
autofocus={true} />
|
||||
|
||||
<div class="aw-stripe-container" data-private>
|
||||
<div class="loader-container" bind:this={loader}>
|
||||
<div class="loader"></div>
|
||||
<div class="aw-stripe-container" data-private>
|
||||
<div class="loader-container" bind:this={loader}>
|
||||
<div class="loader"></div>
|
||||
</div>
|
||||
<div bind:this={element}></div>
|
||||
</div>
|
||||
<div bind:this={element}></div>
|
||||
</div>
|
||||
{#if showSetAsDefault}
|
||||
<InputChoice
|
||||
bind:value={setAsDefault}
|
||||
id="default"
|
||||
label="Set as default payment method for this organization" />
|
||||
{#if showSetAsDefault}
|
||||
<InputChoice
|
||||
bind:value={setAsDefault}
|
||||
id="default"
|
||||
label="Set as default payment method for this organization" />
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
import { FakeModal } from '$lib/components';
|
||||
import { InputText, Button } from '$lib/elements/forms';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { initializeStripe, submitStripeCard } from '$lib/stores/stripe';
|
||||
import { initializeStripe, setPaymentMethod, submitStripeCard } from '$lib/stores/stripe';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { page } from '$app/state';
|
||||
import { Spinner } from '@appwrite.io/pink-svelte';
|
||||
import type { PaymentMethod } from '@stripe/stripe-js';
|
||||
import StatePicker from './statePicker.svelte';
|
||||
|
||||
export let show = false;
|
||||
|
||||
@@ -16,10 +18,36 @@
|
||||
let name: string;
|
||||
let error: string;
|
||||
let modal: FakeModal;
|
||||
let showState: boolean = false;
|
||||
let state: string = '';
|
||||
let paymentMethod: PaymentMethod | null = null;
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
if (showState && !state) {
|
||||
throw Error('Please select a state');
|
||||
}
|
||||
|
||||
if (showState) {
|
||||
const card = await setPaymentMethod(paymentMethod.id, name, state);
|
||||
modal.closeModal();
|
||||
await invalidate(Dependencies.PAYMENT_METHODS);
|
||||
dispatch('submit', card);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'A new payment method has been added to your account'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const card = await submitStripeCard(name, page?.params?.organization ?? null);
|
||||
if (card && Object.hasOwn(card, 'id')) {
|
||||
if ((card as PaymentMethod).card.country === 'US') {
|
||||
paymentMethod = card as PaymentMethod;
|
||||
showState = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
modal.closeModal();
|
||||
await invalidate(Dependencies.PAYMENT_METHODS);
|
||||
dispatch('submit', card);
|
||||
@@ -73,28 +101,30 @@
|
||||
bind:error
|
||||
onSubmit={handleSubmit}>
|
||||
<slot />
|
||||
<InputText
|
||||
id="name"
|
||||
required
|
||||
autofocus={true}
|
||||
bind:value={name}
|
||||
label="Cardholder name"
|
||||
placeholder="Cardholder name" />
|
||||
|
||||
<div class="aw-stripe-container" data-private>
|
||||
{#if isLoading}
|
||||
<div class="loader-element">
|
||||
<Spinner />
|
||||
{#if showState}
|
||||
<StatePicker card={paymentMethod} bind:state />
|
||||
{:else}
|
||||
<InputText
|
||||
id="name"
|
||||
required
|
||||
autofocus={true}
|
||||
bind:value={name}
|
||||
label="Cardholder name"
|
||||
placeholder="Cardholder name" />
|
||||
<div class="aw-stripe-container" data-private>
|
||||
{#if isLoading}
|
||||
<div class="loader-element">
|
||||
<Spinner />
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
style:display={isLoading ? 'none' : 'initial'}
|
||||
class="stripe-element"
|
||||
bind:this={element}>
|
||||
<!-- Stripe will create form elements here -->
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
style:display={isLoading ? 'none' : 'initial'}
|
||||
class="stripe-element"
|
||||
bind:this={element}>
|
||||
<!-- Stripe will create form elements here -->
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<slot name="end"></slot>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
export const states: Array<{ name: string; abbreviation: string }> = [
|
||||
{
|
||||
name: 'Alabama',
|
||||
abbreviation: 'AL'
|
||||
},
|
||||
{
|
||||
name: 'Alaska',
|
||||
abbreviation: 'AK'
|
||||
},
|
||||
{
|
||||
name: 'American Samoa',
|
||||
abbreviation: 'AS'
|
||||
},
|
||||
{
|
||||
name: 'Arizona',
|
||||
abbreviation: 'AZ'
|
||||
},
|
||||
{
|
||||
name: 'Arkansas',
|
||||
abbreviation: 'AR'
|
||||
},
|
||||
{
|
||||
name: 'California',
|
||||
abbreviation: 'CA'
|
||||
},
|
||||
{
|
||||
name: 'Colorado',
|
||||
abbreviation: 'CO'
|
||||
},
|
||||
{
|
||||
name: 'Connecticut',
|
||||
abbreviation: 'CT'
|
||||
},
|
||||
{
|
||||
name: 'Delaware',
|
||||
abbreviation: 'DE'
|
||||
},
|
||||
{
|
||||
name: 'District Of Columbia',
|
||||
abbreviation: 'DC'
|
||||
},
|
||||
{
|
||||
name: 'Federated States Of Micronesia',
|
||||
abbreviation: 'FM'
|
||||
},
|
||||
{
|
||||
name: 'Florida',
|
||||
abbreviation: 'FL'
|
||||
},
|
||||
{
|
||||
name: 'Georgia',
|
||||
abbreviation: 'GA'
|
||||
},
|
||||
{
|
||||
name: 'Guam',
|
||||
abbreviation: 'GU'
|
||||
},
|
||||
{
|
||||
name: 'Hawaii',
|
||||
abbreviation: 'HI'
|
||||
},
|
||||
{
|
||||
name: 'Idaho',
|
||||
abbreviation: 'ID'
|
||||
},
|
||||
{
|
||||
name: 'Illinois',
|
||||
abbreviation: 'IL'
|
||||
},
|
||||
{
|
||||
name: 'Indiana',
|
||||
abbreviation: 'IN'
|
||||
},
|
||||
{
|
||||
name: 'Iowa',
|
||||
abbreviation: 'IA'
|
||||
},
|
||||
{
|
||||
name: 'Kansas',
|
||||
abbreviation: 'KS'
|
||||
},
|
||||
{
|
||||
name: 'Kentucky',
|
||||
abbreviation: 'KY'
|
||||
},
|
||||
{
|
||||
name: 'Louisiana',
|
||||
abbreviation: 'LA'
|
||||
},
|
||||
{
|
||||
name: 'Maine',
|
||||
abbreviation: 'ME'
|
||||
},
|
||||
{
|
||||
name: 'Marshall Islands',
|
||||
abbreviation: 'MH'
|
||||
},
|
||||
{
|
||||
name: 'Maryland',
|
||||
abbreviation: 'MD'
|
||||
},
|
||||
{
|
||||
name: 'Massachusetts',
|
||||
abbreviation: 'MA'
|
||||
},
|
||||
{
|
||||
name: 'Michigan',
|
||||
abbreviation: 'MI'
|
||||
},
|
||||
{
|
||||
name: 'Minnesota',
|
||||
abbreviation: 'MN'
|
||||
},
|
||||
{
|
||||
name: 'Mississippi',
|
||||
abbreviation: 'MS'
|
||||
},
|
||||
{
|
||||
name: 'Missouri',
|
||||
abbreviation: 'MO'
|
||||
},
|
||||
{
|
||||
name: 'Montana',
|
||||
abbreviation: 'MT'
|
||||
},
|
||||
{
|
||||
name: 'Nebraska',
|
||||
abbreviation: 'NE'
|
||||
},
|
||||
{
|
||||
name: 'Nevada',
|
||||
abbreviation: 'NV'
|
||||
},
|
||||
{
|
||||
name: 'New Hampshire',
|
||||
abbreviation: 'NH'
|
||||
},
|
||||
{
|
||||
name: 'New Jersey',
|
||||
abbreviation: 'NJ'
|
||||
},
|
||||
{
|
||||
name: 'New Mexico',
|
||||
abbreviation: 'NM'
|
||||
},
|
||||
{
|
||||
name: 'New York',
|
||||
abbreviation: 'NY'
|
||||
},
|
||||
{
|
||||
name: 'North Carolina',
|
||||
abbreviation: 'NC'
|
||||
},
|
||||
{
|
||||
name: 'North Dakota',
|
||||
abbreviation: 'ND'
|
||||
},
|
||||
{
|
||||
name: 'Northern Mariana Islands',
|
||||
abbreviation: 'MP'
|
||||
},
|
||||
{
|
||||
name: 'Ohio',
|
||||
abbreviation: 'OH'
|
||||
},
|
||||
{
|
||||
name: 'Oklahoma',
|
||||
abbreviation: 'OK'
|
||||
},
|
||||
{
|
||||
name: 'Oregon',
|
||||
abbreviation: 'OR'
|
||||
},
|
||||
{
|
||||
name: 'Palau',
|
||||
abbreviation: 'PW'
|
||||
},
|
||||
{
|
||||
name: 'Pennsylvania',
|
||||
abbreviation: 'PA'
|
||||
},
|
||||
{
|
||||
name: 'Puerto Rico',
|
||||
abbreviation: 'PR'
|
||||
},
|
||||
{
|
||||
name: 'Rhode Island',
|
||||
abbreviation: 'RI'
|
||||
},
|
||||
{
|
||||
name: 'South Carolina',
|
||||
abbreviation: 'SC'
|
||||
},
|
||||
{
|
||||
name: 'South Dakota',
|
||||
abbreviation: 'SD'
|
||||
},
|
||||
{
|
||||
name: 'Tennessee',
|
||||
abbreviation: 'TN'
|
||||
},
|
||||
{
|
||||
name: 'Texas',
|
||||
abbreviation: 'TX'
|
||||
},
|
||||
{
|
||||
name: 'Utah',
|
||||
abbreviation: 'UT'
|
||||
},
|
||||
{
|
||||
name: 'Vermont',
|
||||
abbreviation: 'VT'
|
||||
},
|
||||
{
|
||||
name: 'Virgin Islands',
|
||||
abbreviation: 'VI'
|
||||
},
|
||||
{
|
||||
name: 'Virginia',
|
||||
abbreviation: 'VA'
|
||||
},
|
||||
{
|
||||
name: 'Washington',
|
||||
abbreviation: 'WA'
|
||||
},
|
||||
{
|
||||
name: 'West Virginia',
|
||||
abbreviation: 'WV'
|
||||
},
|
||||
{
|
||||
name: 'Wisconsin',
|
||||
abbreviation: 'WI'
|
||||
},
|
||||
{
|
||||
name: 'Wyoming',
|
||||
abbreviation: 'WY'
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { states } from './state';
|
||||
import { InputSelect } from '$lib/elements/forms';
|
||||
import type { PaymentMethod } from '@stripe/stripe-js';
|
||||
import { Alert, Card, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { CreditCardBrandImage } from '../index.js';
|
||||
|
||||
export let state: string = '';
|
||||
export let card: PaymentMethod | null = null;
|
||||
</script>
|
||||
|
||||
<Layout.Stack direction="column" gap="m">
|
||||
{#if card}
|
||||
<Card.Base variant="secondary" padding="s">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="s">
|
||||
<CreditCardBrandImage brand={card.card?.brand} />
|
||||
<span>ending in {card.card?.last4}</span>
|
||||
</Layout.Stack>
|
||||
<Typography.Text size="s">
|
||||
{card.card.country}
|
||||
{card.billing_details.address.postal_code}
|
||||
</Typography.Text>
|
||||
</Card.Base>
|
||||
{/if}
|
||||
|
||||
<Alert.Inline status="info" title="State is required for US payment methods">
|
||||
<Typography.Text size="s">
|
||||
To complete the billing information, select your state so we can apply the correct taxes
|
||||
and meet U.S. legal requirements.
|
||||
</Typography.Text>
|
||||
</Alert.Inline>
|
||||
|
||||
<InputSelect
|
||||
bind:value={state}
|
||||
required
|
||||
label="State"
|
||||
placeholder="Select a state"
|
||||
id="state-picker"
|
||||
options={states.map((state) => ({
|
||||
label: state.name,
|
||||
value: state.abbreviation,
|
||||
id: state.abbreviation.toLowerCase()
|
||||
}))} />
|
||||
</Layout.Stack>
|
||||
@@ -8,7 +8,8 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconCode, IconAndroid, IconFlutter, IconApple } from '@appwrite.io/pink-icons-svelte';
|
||||
import Prism from 'prismjs';
|
||||
import 'prismjs/components/prism-dart';
|
||||
import 'prismjs/components/prism-kotlin';
|
||||
@@ -24,6 +25,21 @@
|
||||
|
||||
export let label: string = null;
|
||||
export let labelIcon: 'code' | 'android' | 'flutter' | 'apple' = null;
|
||||
|
||||
function getIcon(iconName: string) {
|
||||
switch (iconName) {
|
||||
case 'code':
|
||||
return IconCode;
|
||||
case 'android':
|
||||
return IconAndroid;
|
||||
case 'flutter':
|
||||
return IconFlutter;
|
||||
case 'apple':
|
||||
return IconApple;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export let code: string;
|
||||
export let language: 'js' | 'html' | 'dart' | 'kotlin' | 'json' | 'sh' | 'yml' | 'swift';
|
||||
export let withLineNumbers = false;
|
||||
@@ -49,12 +65,11 @@
|
||||
<div
|
||||
class="controls u-position-absolute u-inset-inline-end-8 u-inset-block-start-8 u-flex u-gap-8">
|
||||
{#if label}
|
||||
<Pill>
|
||||
<Badge variant="secondary" content={label}>
|
||||
{#if labelIcon}
|
||||
<span class={`icon-${labelIcon}`} aria-hidden="true"></span>
|
||||
<Icon icon={getIcon(labelIcon)} size="s" slot="start" />
|
||||
{/if}
|
||||
{label}
|
||||
</Pill>
|
||||
</Badge>
|
||||
{/if}
|
||||
{#if withCopy}
|
||||
<Copy value={code}>
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
migrations.migrations.forEach(updateOrAddItem);
|
||||
});
|
||||
|
||||
return sdk.forConsole.client.subscribe('console', (response) => {
|
||||
return sdk.forConsoleIn(page.params.region).client.subscribe('console', (response) => {
|
||||
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
|
||||
if (response.events.includes('migrations.*')) {
|
||||
updateOrAddItem(response.payload as Payload);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { at, empty } from '$lib/helpers/array';
|
||||
import { singular } from '$lib/helpers/string';
|
||||
@@ -295,13 +294,12 @@
|
||||
<Typography.Text variant="m-500">Choose an attribute (optional)</Typography.Text>
|
||||
<Layout.Stack gap="s" wrap="wrap" direction="row">
|
||||
{#each available.attributes as attribute}
|
||||
<Pill
|
||||
<Tag
|
||||
disabled={showInput}
|
||||
selected={selected.attribute === attribute}
|
||||
button
|
||||
on:click={() => select('attribute', attribute)}>
|
||||
{attribute}
|
||||
</Pill>
|
||||
</Tag>
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -64,8 +64,8 @@
|
||||
onMount(() => {
|
||||
value = column?.array ? [] : null;
|
||||
if (column?.type === 'datetime') {
|
||||
const today = new Date();
|
||||
value = today.toISOString();
|
||||
const now = new Date();
|
||||
value = now.toISOString().slice(0, 16);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -85,7 +85,11 @@
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<form on:submit|preventDefault={addFilterAndReset}>
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
addFilterAndReset();
|
||||
}}>
|
||||
<Layout.Stack gap="s" direction="row" alignItems="flex-start">
|
||||
<InputSelect
|
||||
id="column"
|
||||
@@ -137,7 +141,7 @@
|
||||
bind:value />
|
||||
{:else if column.type === 'datetime'}
|
||||
{#key value}
|
||||
<InputDateTime id="value" bind:value step={60} />
|
||||
<InputDateTime id="value" bind:value step={60} type="datetime-local" />
|
||||
{/key}
|
||||
{:else}
|
||||
<InputText id="value" bind:value placeholder="Enter value" />
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
bind:value />
|
||||
{:else if column.type === 'datetime'}
|
||||
{#key value}
|
||||
<InputDateTime id="value" bind:value step={60} />
|
||||
<InputDateTime id="value" bind:value step={60} type="datetime-local" />
|
||||
{/key}
|
||||
{:else}
|
||||
<InputText id="value" bind:value placeholder="Enter value" />
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
</Link.Button>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="middle" class:icons={state === 'icons'}>
|
||||
<div slot="middle" class="middle-container" class:icons={state === 'icons'}>
|
||||
{#if progressCard}
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
@@ -240,26 +240,27 @@
|
||||
<span slot="tooltip">{projectOption.name}</span>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
<div class="only-mobile divider">
|
||||
<Divider />
|
||||
</div>
|
||||
<div class="only-mobile">
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
href={`/console/project-${project.region}-${project.$id}/settings`}
|
||||
on:click={() => {
|
||||
trackEvent('click_menu_settings');
|
||||
}}
|
||||
class="link"
|
||||
><span class="link-icon"><Icon icon={IconCog} size="s" /></span
|
||||
><span
|
||||
class:no-text={state === 'icons'}
|
||||
class:has-text={state === 'open'}
|
||||
class="link-text">Settings</span
|
||||
></a>
|
||||
<span slot="tooltip">Settings</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{#if project}
|
||||
<div class="mobile-tablet-settings">
|
||||
<Tooltip placement="right" disabled={state !== 'icons'}>
|
||||
<a
|
||||
href={`/console/project-${project.region}-${project.$id}/settings`}
|
||||
on:click={() => {
|
||||
trackEvent('click_menu_settings');
|
||||
sideBarIsOpen = false;
|
||||
}}
|
||||
class="link"
|
||||
class:active={isSelected('/settings') && !isSelected('sites')}
|
||||
><span class="link-icon"><Icon icon={IconCog} size="s" /></span
|
||||
><span
|
||||
class:no-text={state === 'icons'}
|
||||
class:has-text={state === 'open'}
|
||||
class="link-text">Settings</span
|
||||
></a>
|
||||
<span slot="tooltip">Settings</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{:else if $isSmallViewport}
|
||||
<div class="action-buttons">
|
||||
@@ -359,6 +360,29 @@
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.middle-container {
|
||||
flex: 1;
|
||||
overflow-y: visible;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.mobile-tablet-settings {
|
||||
display: block;
|
||||
margin-top: var(--space-6, 12px);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 1px;
|
||||
background: var(--border-neutral, #ededf0);
|
||||
margin-bottom: var(--space-6, 12px);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
@@ -578,12 +602,6 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
@media (min-width: 1024px) {
|
||||
height: var(--base-32, 32px);
|
||||
}
|
||||
}
|
||||
|
||||
:global(button.collapse) {
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
disabled?: boolean;
|
||||
leadingIcon?: ComponentType;
|
||||
leadingHtml?: string;
|
||||
badge?: string;
|
||||
}[];
|
||||
|
||||
let element: HTMLSelectElement;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { default as Pill } from './pill.svelte';
|
||||
export { default as SelectSearchItem } from './selectSearchItem.svelte';
|
||||
export { default as Flag } from './flag.svelte';
|
||||
export { default as SelectSearchCheckbox } from './selectSearchCheckbox.svelte';
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
export let disabled = false;
|
||||
export let selected = false;
|
||||
export let success = false;
|
||||
export let warning = false;
|
||||
export let danger = false;
|
||||
export let info = false;
|
||||
export let button = false;
|
||||
export let submit = false;
|
||||
export let external = false;
|
||||
export let href: string = null;
|
||||
export let event: string = null;
|
||||
export let eventData: Record<string, unknown> = {};
|
||||
export let style = '';
|
||||
let classes = '';
|
||||
export { classes as class };
|
||||
|
||||
function track() {
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
trackEvent(`click_${event}`, {
|
||||
from: 'tag',
|
||||
...eventData
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if href}
|
||||
<a
|
||||
{style}
|
||||
{href}
|
||||
target={external ? '_blank' : '_self'}
|
||||
rel={external ? 'noopener noreferrer' : ''}
|
||||
class="tag {classes}"
|
||||
class:is-disabled={disabled}
|
||||
class:is-selected={selected}
|
||||
class:is-success={success}
|
||||
class:is-warning={warning}
|
||||
class:is-danger={danger}
|
||||
class:is-info={info}>
|
||||
<slot />
|
||||
</a>
|
||||
{:else if button}
|
||||
<button
|
||||
on:click
|
||||
on:click={track}
|
||||
{style}
|
||||
{disabled}
|
||||
type={submit ? 'submit' : 'button'}
|
||||
class="tag {classes}"
|
||||
class:is-disabled={disabled}
|
||||
class:is-selected={selected}
|
||||
class:is-success={success}
|
||||
class:is-warning={warning}
|
||||
class:is-danger={danger}
|
||||
class:is-info={info}>
|
||||
<slot />
|
||||
</button>
|
||||
{:else}
|
||||
<div
|
||||
class="tag {classes}"
|
||||
{style}
|
||||
class:is-disabled={disabled}
|
||||
class:is-selected={selected}
|
||||
class:is-success={success}
|
||||
class:is-warning={warning}
|
||||
class:is-danger={danger}
|
||||
class:is-info={info}>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
/* button and anchor tags seem to have special dimens, this should be auto fixed in pink2. */
|
||||
:global(.tag):where(button, a) {
|
||||
--p-tag-height: 2.125rem;
|
||||
--p-tag-content-height: 2.024rem;
|
||||
}
|
||||
</style>
|
||||
@@ -6,6 +6,7 @@ type RegionOption = {
|
||||
value: string;
|
||||
disabled: boolean;
|
||||
leadingHtml: string;
|
||||
badge?: string;
|
||||
};
|
||||
|
||||
export function filterRegions(regions: Models.ConsoleRegion[]): RegionOption[] {
|
||||
@@ -22,6 +23,7 @@ export function filterRegions(regions: Models.ConsoleRegion[]): RegionOption[] {
|
||||
label: region.name,
|
||||
value: region.$id,
|
||||
disabled: region.disabled || !region.available,
|
||||
leadingHtml: `<img src='${getFlagUrl(region.flag)}' alt='Region flag'/>`
|
||||
leadingHtml: `<img src='${getFlagUrl(region.flag)}' alt='Region flag'/>`,
|
||||
badge: region.disabled || !region.available ? 'Coming soon' : undefined
|
||||
}));
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 415 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 717 KiB |
|
Before Width: | Height: | Size: 263 KiB |
@@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { DropList } from '$lib/components';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Link, Pill } from '$lib/elements';
|
||||
import { Link } from '$lib/elements';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
import {
|
||||
checkForProjectLimitation,
|
||||
@@ -125,13 +127,19 @@
|
||||
{#if isCloud && isLimited}
|
||||
<DropList bind:show={showDropdown} width="16">
|
||||
{#if hasProjectLimitation}
|
||||
<Pill button on:click={() => (showDropdown = !showDropdown)}>
|
||||
<span class="icon-info"></span>{total}/{limit} created
|
||||
</Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content={`${total}/${limit} created`}
|
||||
on:click={() => (showDropdown = !showDropdown)}>
|
||||
<Icon icon={IconInfo} size="s" slot="start" />
|
||||
</Badge>
|
||||
{:else if $organization?.billingPlan !== BillingPlan.SCALE}
|
||||
<Pill button on:click={() => (showDropdown = !showDropdown)}>
|
||||
<span class="icon-info"></span>Limits applied
|
||||
</Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content="Limits applied"
|
||||
on:click={() => (showDropdown = !showDropdown)}>
|
||||
<Icon icon={IconInfo} size="s" slot="start" />
|
||||
</Badge>
|
||||
{/if}
|
||||
<svelte:fragment slot="list">
|
||||
<slot name="tooltip" {limit} {tier} {title} {upgradeMethod} {hasUsageFees}>
|
||||
|
||||
@@ -74,7 +74,12 @@
|
||||
<Divider vertical />
|
||||
</span>
|
||||
{/if}
|
||||
<Badge size="xs" variant="secondary" content="BETA" />
|
||||
<Badge
|
||||
size="xs"
|
||||
type="success"
|
||||
variant="secondary"
|
||||
content="Generally Available"
|
||||
style="white-space: nowrap;" />
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -83,9 +88,15 @@
|
||||
justifyContent={$isSmallViewport ? 'flex-start' : 'flex-end'}
|
||||
alignItems="center">
|
||||
{#if isCloud}
|
||||
{#if !$isSmallViewport}<Badge size="xs" variant="secondary" content="BETA" /><Icon
|
||||
size="s"
|
||||
icon={IconCloud} />
|
||||
{#if !$isSmallViewport}
|
||||
<Badge
|
||||
size="xs"
|
||||
type="success"
|
||||
variant="secondary"
|
||||
content="Generally Available"
|
||||
style="white-space: nowrap;" />
|
||||
|
||||
<Icon size="s" icon={IconCloud} />
|
||||
{#if $version}
|
||||
<Link.Anchor
|
||||
size="s"
|
||||
|
||||
@@ -124,9 +124,18 @@
|
||||
|
||||
const progressCard = function getProgressCard() {
|
||||
if (selectedProject && !hasOnboardingDismissed(selectedProject.$id, $user)) {
|
||||
const { platforms, pingCount } = selectedProject;
|
||||
let percentage = 33;
|
||||
|
||||
if (platforms.length > 0 && pingCount === 0) {
|
||||
percentage = 66;
|
||||
} else if (pingCount > 0) {
|
||||
percentage = 100;
|
||||
}
|
||||
|
||||
return {
|
||||
title: 'Get started',
|
||||
percentage: selectedProject && selectedProject.platforms.length ? 100 : 33
|
||||
percentage
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1201,7 +1201,8 @@ export class Billing {
|
||||
async setPaymentMethod(
|
||||
paymentMethodId: string,
|
||||
providerMethodId: string | PaymentMethod,
|
||||
name: string
|
||||
name: string,
|
||||
state: string | undefined = undefined
|
||||
): Promise<PaymentMethodData> {
|
||||
const path = `/account/payment-methods/${paymentMethodId}/provider`;
|
||||
const params = {
|
||||
@@ -1209,6 +1210,10 @@ export class Billing {
|
||||
providerMethodId,
|
||||
name
|
||||
};
|
||||
|
||||
if (state !== undefined) {
|
||||
params['state'] = state;
|
||||
}
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return await this.client.call(
|
||||
'patch',
|
||||
|
||||
@@ -69,7 +69,7 @@ export const roles = [
|
||||
|
||||
export const teamStatusReadonly = 'readonly';
|
||||
export const billingLimitOutstandingInvoice = 'outstanding_invoice';
|
||||
export const billingProjectsLimitDate = '2024-09-01';
|
||||
export const billingProjectsLimitDate = '2025-09-01';
|
||||
|
||||
export const paymentMethods = derived(page, ($page) => $page.data.paymentMethods as PaymentList);
|
||||
export const addressList = derived(page, ($page) => $page.data.addressList as AddressesList);
|
||||
|
||||
@@ -8,27 +8,30 @@ import { page } from '$app/state';
|
||||
import { user } from '$lib/stores/user';
|
||||
import deepEqual from 'deep-equal';
|
||||
|
||||
type Preferences = {
|
||||
type ConsolePreferences = {
|
||||
limit?: number;
|
||||
view?: View;
|
||||
columns?: string[];
|
||||
};
|
||||
} /* support a strict + flexible preference type for TS compatibility */ & Record<
|
||||
string,
|
||||
string | number | boolean | object | null | unknown
|
||||
>;
|
||||
|
||||
type TeamPreferences = {
|
||||
names?: string[];
|
||||
};
|
||||
|
||||
type PreferencesStore = {
|
||||
[key: string]: Preferences;
|
||||
type ConsolePreferencesStore = {
|
||||
[key: string]: ConsolePreferences;
|
||||
collections?: {
|
||||
[key: string]: Preferences['columns'];
|
||||
[key: string]: ConsolePreferences['columns'];
|
||||
};
|
||||
displayNames?: {
|
||||
[key: string]: TeamPreferences['names'];
|
||||
};
|
||||
} & { hideAiDisclaimer?: boolean };
|
||||
|
||||
async function updateConsolePreferences(store: PreferencesStore): Promise<void> {
|
||||
async function updateConsolePreferences(store: ConsolePreferencesStore): Promise<void> {
|
||||
const currentPreferences = get(user)?.prefs ?? (await sdk.forConsole.account.getPrefs());
|
||||
if (!currentPreferences?.console || Array.isArray(currentPreferences.console)) {
|
||||
currentPreferences.console = {};
|
||||
@@ -43,8 +46,8 @@ async function updateConsolePreferences(store: PreferencesStore): Promise<void>
|
||||
}
|
||||
|
||||
function createPreferences() {
|
||||
const { subscribe, set, update } = writable<PreferencesStore>({});
|
||||
let preferences: PreferencesStore = {};
|
||||
const { subscribe, set, update } = writable<ConsolePreferencesStore>({});
|
||||
let preferences: ConsolePreferencesStore = {};
|
||||
|
||||
if (browser) {
|
||||
// fresh fetch.
|
||||
@@ -73,9 +76,9 @@ function createPreferences() {
|
||||
/**
|
||||
* Update the local store and then synchronizes them on user prefs.
|
||||
*/
|
||||
function updateAndSync(callback: (prefs: PreferencesStore) => void): Promise<void> {
|
||||
let oldPrefsSnapshot: PreferencesStore;
|
||||
let newPrefsSnapshot: PreferencesStore;
|
||||
function updateAndSync(callback: (prefs: ConsolePreferencesStore) => void): Promise<void> {
|
||||
let oldPrefsSnapshot: ConsolePreferencesStore;
|
||||
let newPrefsSnapshot: ConsolePreferencesStore;
|
||||
|
||||
update((currentPrefs) => {
|
||||
oldPrefsSnapshot = structuredClone(currentPrefs);
|
||||
@@ -96,7 +99,7 @@ function createPreferences() {
|
||||
subscribe,
|
||||
set,
|
||||
update,
|
||||
get: (route?: Page['route']): Preferences => {
|
||||
get: (route?: Page['route']): ConsolePreferences => {
|
||||
const parsedRoute = route ?? page.route;
|
||||
return (
|
||||
preferences?.[parsedRoute.id] ?? {
|
||||
@@ -106,10 +109,10 @@ function createPreferences() {
|
||||
}
|
||||
);
|
||||
},
|
||||
getCustomCollectionColumns: (collectionId: string): Preferences['columns'] => {
|
||||
getCustomCollectionColumns: (collectionId: string): ConsolePreferences['columns'] => {
|
||||
return preferences?.collections?.[collectionId] ?? [];
|
||||
},
|
||||
setLimit: (limit: Preferences['limit']) =>
|
||||
setLimit: (limit: ConsolePreferences['limit']) =>
|
||||
updateAndSync((n) => {
|
||||
const path = page.route.id;
|
||||
|
||||
@@ -122,7 +125,7 @@ function createPreferences() {
|
||||
|
||||
return n;
|
||||
}),
|
||||
setView: (view: Preferences['view']) =>
|
||||
setView: (view: ConsolePreferences['view']) =>
|
||||
updateAndSync((n) => {
|
||||
const path = page.route.id;
|
||||
|
||||
@@ -135,7 +138,7 @@ function createPreferences() {
|
||||
|
||||
return n;
|
||||
}),
|
||||
setColumns: (columns: Preferences['columns']) =>
|
||||
setColumns: (columns: ConsolePreferences['columns']) =>
|
||||
updateAndSync((n) => {
|
||||
const path = page.route.id;
|
||||
|
||||
@@ -148,7 +151,10 @@ function createPreferences() {
|
||||
|
||||
return n;
|
||||
}),
|
||||
setCustomCollectionColumns: (collectionId: string, columns: Preferences['columns']) =>
|
||||
setCustomCollectionColumns: (
|
||||
collectionId: string,
|
||||
columns: ConsolePreferences['columns']
|
||||
) =>
|
||||
updateAndSync((n) => {
|
||||
if (!n?.collections?.[collectionId]) {
|
||||
n ??= {};
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import type { Appearance, Stripe, StripeElement, StripeElements } from '@stripe/stripe-js';
|
||||
import type {
|
||||
Appearance,
|
||||
PaymentMethod,
|
||||
Stripe,
|
||||
StripeElement,
|
||||
StripeElements
|
||||
} from '@stripe/stripe-js';
|
||||
import { sdk } from './sdk';
|
||||
import { app } from './app';
|
||||
import { get, writable } from 'svelte/store';
|
||||
@@ -83,7 +89,8 @@ export async function submitStripeCard(name: string, organizationId?: string) {
|
||||
billing_details: {
|
||||
name
|
||||
}
|
||||
}
|
||||
},
|
||||
expand: ['payment_method']
|
||||
},
|
||||
redirect: 'if_required'
|
||||
});
|
||||
@@ -95,9 +102,13 @@ export async function submitStripeCard(name: string, organizationId?: string) {
|
||||
}
|
||||
|
||||
if (setupIntent && setupIntent.status === 'succeeded') {
|
||||
if ((setupIntent.payment_method as PaymentMethod).card.country === 'US') {
|
||||
// need to get state
|
||||
return setupIntent.payment_method as PaymentMethod;
|
||||
}
|
||||
const method = await sdk.forConsole.billing.setPaymentMethod(
|
||||
paymentMethod.$id,
|
||||
setupIntent.payment_method,
|
||||
(setupIntent.payment_method as PaymentMethod).id,
|
||||
name
|
||||
);
|
||||
paymentElement.destroy();
|
||||
@@ -115,6 +126,32 @@ export async function submitStripeCard(name: string, organizationId?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function setPaymentMethod(providerMethodId: string, name: string, state: string) {
|
||||
if (!paymentMethod) {
|
||||
addNotification({
|
||||
title: 'Error',
|
||||
message: 'No payment method found. Please try again.',
|
||||
type: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const method = await sdk.forConsole.billing.setPaymentMethod(
|
||||
paymentMethod.$id,
|
||||
providerMethodId,
|
||||
name,
|
||||
state
|
||||
);
|
||||
paymentElement.destroy();
|
||||
isStripeInitialized.set(false);
|
||||
trackEvent(Submit.PaymentMethodCreate);
|
||||
return method;
|
||||
} catch (e) {
|
||||
trackError(e, Submit.PaymentMethodCreate);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export async function confirmPayment(
|
||||
orgId: string,
|
||||
clientSecret: string,
|
||||
|
||||
@@ -297,7 +297,7 @@
|
||||
if (currentOrganizationId === org.$id) return;
|
||||
if (isCloud) {
|
||||
currentOrganizationId = org.$id;
|
||||
checkForProjectsLimit(org, data.projects?.projects?.length || 0);
|
||||
checkForProjectsLimit(org, data.allProjects?.projects?.length || 0);
|
||||
checkForEnterpriseTrial(org);
|
||||
await checkForUsageLimit(org);
|
||||
checkForMarkedForDeletion(org);
|
||||
|
||||
@@ -56,7 +56,7 @@ export const load: LayoutLoad = async ({ depends, parent }) => {
|
||||
organizations,
|
||||
consoleVariables,
|
||||
version: versionData?.version ?? null,
|
||||
projects
|
||||
allProjects: projects
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { PageData } from './$types';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge } from '@appwrite.io/pink-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
import { daysLeftInTrial, plansInfo, tierToPlan } from '$lib/stores/billing';
|
||||
@@ -75,8 +75,10 @@
|
||||
{#if organization?.billingPlan === BillingPlan.FREE || organization?.billingPlan === BillingPlan.GITHUB_EDUCATION}
|
||||
<Tooltip>
|
||||
<div class="u-flex u-cross-center">
|
||||
<Pill class="eyebrow-heading-3"
|
||||
>{tierToPlan(organization?.billingPlan)?.name}</Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content={tierToPlan(organization?.billingPlan)?.name}
|
||||
class="eyebrow-heading-3" />
|
||||
</div>
|
||||
<span slot="tooltip"
|
||||
>You are limited to 1 free organization per account</span>
|
||||
@@ -85,7 +87,10 @@
|
||||
{#if organization?.billingTrialStartDate && $daysLeftInTrial > 0 && organization.billingPlan !== BillingPlan.FREE && $plansInfo.get(organization.billingPlan)?.trialDays}
|
||||
<Tooltip>
|
||||
<div class="u-flex u-cross-center">
|
||||
<Pill class="eyebrow-heading-3">TRIAL</Pill>
|
||||
<Badge
|
||||
class="eyebrow-heading-3"
|
||||
variant="secondary"
|
||||
content="TRIAL" />
|
||||
</div>
|
||||
<span slot="tooltip"
|
||||
>{`Your trial ends on ${toLocaleDate(
|
||||
|
||||
@@ -3,28 +3,28 @@ import { isSameDay } from '$lib/helpers/date';
|
||||
import { type BottomModalAlertItem, showBottomModalAlert } from '$lib/stores/bottom-alerts';
|
||||
import AutoIncrementDark from '$lib/images/promos/auto-increment-dark.png';
|
||||
import AutoIncrementLight from '$lib/images/promos/auto-increment-light.png';
|
||||
import EncryptedDatabasesDark from '$lib/images/promos/encrypted-attribute-dark.png';
|
||||
import EncryptedDatabasesLight from '$lib/images/promos/encrypted-attribute-light.png';
|
||||
import OptInRelationDark from '$lib/images/promos/opt-relation-dark.png';
|
||||
import OptInRelationLight from '$lib/images/promos/opt-relation-light.png';
|
||||
import AtomicNumericOperationsDark from '$lib/images/promos/atomic-numeric-operations-dark.png';
|
||||
import AtomicNumericOperationsLight from '$lib/images/promos/atomic-numeric-operations-light.png';
|
||||
import AppwriteGeneralAvailabiltyLight from '$lib/images/promos/appwrite-general-availability-light.png';
|
||||
import AppwriteGeneralAvailabiltyDark from '$lib/images/promos/appwrite-general-availability-dark.png';
|
||||
|
||||
const listOfPromotions: BottomModalAlertItem[] = [];
|
||||
|
||||
if (isCloud) {
|
||||
const encryptedAttributePromo: BottomModalAlertItem = {
|
||||
id: 'modal:encrypted_attribute_announcement',
|
||||
const appwriteGeneralAvailabiltyPromo: BottomModalAlertItem = {
|
||||
id: 'modal:appwrite_general_availability_announcement',
|
||||
src: {
|
||||
dark: EncryptedDatabasesDark,
|
||||
light: EncryptedDatabasesLight
|
||||
dark: AppwriteGeneralAvailabiltyDark,
|
||||
light: AppwriteGeneralAvailabiltyLight
|
||||
},
|
||||
title: 'Introducing Encrypted string attribute support',
|
||||
message: 'Encrypt string attributes at rest, directly from the Appwrite Console.',
|
||||
title: 'Now Generally Available',
|
||||
message: 'After 26 months of Appwrite Cloud, we are ready to remove the beta tag.',
|
||||
plan: 'free',
|
||||
importance: 8,
|
||||
scope: 'project',
|
||||
scope: 'everywhere',
|
||||
cta: {
|
||||
text: 'Read announcement',
|
||||
link: () => 'https://appwrite.io/blog/post/announcing-encrypted-string-attributes',
|
||||
text: 'Learn more',
|
||||
link: () => 'https://apwr.dev/ygTXfxA',
|
||||
external: true,
|
||||
hideOnClick: true
|
||||
},
|
||||
@@ -50,27 +50,31 @@ if (isCloud) {
|
||||
},
|
||||
show: true
|
||||
};
|
||||
const optInRelationPromo: BottomModalAlertItem = {
|
||||
id: 'modal:opt_in_relation_announcement',
|
||||
const atomicNumericOperationsPromo: BottomModalAlertItem = {
|
||||
id: 'modal:atomic_numeric_operations_announcement',
|
||||
src: {
|
||||
dark: OptInRelationDark,
|
||||
light: OptInRelationLight
|
||||
dark: AtomicNumericOperationsDark,
|
||||
light: AtomicNumericOperationsLight
|
||||
},
|
||||
title: 'Introducing Opt-in relationship loading',
|
||||
title: 'Announcing Atomic Numeric Operations',
|
||||
message:
|
||||
'Gain full control over which related documents to fetch and drastically reduce payload sizes.',
|
||||
'Safely update numeric fields directly on the server, without conflicts or race conditions.',
|
||||
plan: 'free',
|
||||
importance: 8,
|
||||
scope: 'project',
|
||||
cta: {
|
||||
text: 'Read announcement',
|
||||
link: () => 'https://appwrite.io/blog/post/announcing-opt-in-relationship-loading',
|
||||
link: () => 'https://appwrite.io/blog/post/announcing-atomic-numeric-operations',
|
||||
external: true,
|
||||
hideOnClick: true
|
||||
},
|
||||
show: false
|
||||
show: true
|
||||
};
|
||||
listOfPromotions.push(optInRelationPromo, autoIncrementPromo, encryptedAttributePromo);
|
||||
listOfPromotions.push(
|
||||
appwriteGeneralAvailabiltyPromo,
|
||||
atomicNumericOperationsPromo,
|
||||
autoIncrementPromo
|
||||
);
|
||||
}
|
||||
|
||||
export function addBottomModalAlerts() {
|
||||
|
||||
@@ -16,16 +16,17 @@
|
||||
PaginationWithLimit
|
||||
} from '$lib/components';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Submit, trackError, trackEvent, Click } from '$lib/actions/analytics';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { loading } from '$routes/store';
|
||||
import { ID, Region, type Models } from '@appwrite.io/console';
|
||||
import { openImportWizard } from '../project-[region]-[project]/settings/migrations/(import)';
|
||||
import { billingProjectsLimitDate, readOnly } from '$lib/stores/billing';
|
||||
import { billingProjectsLimitDate, readOnly, upgradeURL } from '$lib/stores/billing';
|
||||
import { onMount, type ComponentType } from 'svelte';
|
||||
import { canWriteProjects } from '$lib/stores/roles';
|
||||
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
|
||||
import { Badge, Icon, Typography, Alert, Tag, Tooltip } from '@appwrite.io/pink-svelte';
|
||||
import { isSmallViewport } from '$lib/stores/viewport';
|
||||
import {
|
||||
IconAndroid,
|
||||
IconApple,
|
||||
@@ -129,7 +130,9 @@
|
||||
}
|
||||
|
||||
function formatName(name: string, limit: number = 19) {
|
||||
return name ? (name.length > limit ? `${name.slice(0, limit)}...` : name) : '-';
|
||||
const mobileLimit = 16;
|
||||
const actualLimit = $isSmallViewport ? mobileLimit : limit;
|
||||
return name ? (name.length > actualLimit ? `${name.slice(0, actualLimit)}...` : name) : '-';
|
||||
}
|
||||
|
||||
$: projectsToArchive = data.projects.projects.filter(
|
||||
@@ -164,7 +167,7 @@
|
||||
</DropList>
|
||||
</div>
|
||||
|
||||
{#if isCloud && $currentPlan?.projects && $currentPlan?.projects > 0 && data.organization.projects.length > 0 && $canWriteProjects}
|
||||
{#if isCloud && $currentPlan?.projects && $currentPlan?.projects > 0 && data.organization.projects.length > 0 && data.projects.total > 2 && $canWriteProjects}
|
||||
<Alert.Inline
|
||||
title={`${data.projects.total - data.organization.projects.length} projects will be archived on ${toLocaleDate(billingProjectsLimitDate)}`}>
|
||||
<Typography.Text>
|
||||
@@ -181,6 +184,17 @@
|
||||
<Button secondary size="s" on:click={() => (showSelectProject = true)}>
|
||||
Manage projects
|
||||
</Button>
|
||||
<Button
|
||||
size="s"
|
||||
href={$upgradeURL}
|
||||
on:click={() => {
|
||||
trackEvent(Click.OrganizationClickUpgrade, {
|
||||
from: 'button',
|
||||
source: 'projects_archive_alert'
|
||||
});
|
||||
}}>
|
||||
Upgrade to Pro
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
@@ -219,6 +233,7 @@
|
||||
{#if isSetToArchive(project)}
|
||||
<Tag
|
||||
size="s"
|
||||
style="white-space: nowrap;"
|
||||
on:click={(event) => {
|
||||
event.preventDefault();
|
||||
showSelectProject = true;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
import { upgradeURL } from '$lib/stores/billing';
|
||||
import { Pill } from '$lib/elements';
|
||||
|
||||
import { Alert, Badge, Icon, Link, Table, Tooltip, Typography } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
{#if credit.status === 'expired'}
|
||||
<Tooltip>
|
||||
<span>
|
||||
<Pill>Expired</Pill>
|
||||
<Badge variant="secondary" content="Expired" />
|
||||
</span>
|
||||
<span slot="tooltip"
|
||||
>{toLocaleDate(credit.expiration)}</span>
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
href={`${endpoint}/organizations/${page.params.organization}/invoices/${invoice.$id}/download`}>
|
||||
Download PDF
|
||||
</ActionMenu.Item.Anchor>
|
||||
{#if status === 'overdue' || status === 'failed'}
|
||||
{#if status === 'overdue' || status === 'failed' || status === 'abandoned'}
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconRefresh}
|
||||
on:click={() => {
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { submitStripeCard } from '$lib/stores/stripe';
|
||||
import { setPaymentMethod, submitStripeCard } from '$lib/stores/stripe';
|
||||
import { onMount } from 'svelte';
|
||||
import type { PaymentList } from '$lib/sdk/billing';
|
||||
import type { PaymentList, PaymentMethodData } from '$lib/sdk/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { PaymentBoxes } from '$lib/components/billing';
|
||||
import type { PaymentMethod } from '@stripe/stripe-js';
|
||||
|
||||
export let organization: Organization;
|
||||
export let show = false;
|
||||
@@ -20,6 +21,9 @@
|
||||
let name: string;
|
||||
let error: string;
|
||||
let selectedPaymentMethodId: string;
|
||||
let showState: boolean = false;
|
||||
let state: string = '';
|
||||
let paymentMethod: PaymentMethod | null = null;
|
||||
|
||||
onMount(async () => {
|
||||
if (!organization.paymentMethodId && !organization.backupPaymentMethodId) {
|
||||
@@ -41,7 +45,24 @@
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
if (selectedPaymentMethodId === '$new') {
|
||||
const method = await submitStripeCard(name, organization.$id);
|
||||
if (showState && !state) {
|
||||
throw Error('Please select a state');
|
||||
}
|
||||
let method: PaymentMethodData;
|
||||
if (showState) {
|
||||
method = await setPaymentMethod(paymentMethod.id, name, state);
|
||||
} else {
|
||||
const card = await submitStripeCard(name, organization.$id);
|
||||
if (card && Object.hasOwn(card, 'id')) {
|
||||
if ((card as PaymentMethod).card.country === 'US') {
|
||||
paymentMethod = card as PaymentMethod;
|
||||
showState = true;
|
||||
return;
|
||||
}
|
||||
} else if (card && Object.hasOwn(card, '$id')) {
|
||||
method = card as PaymentMethodData;
|
||||
}
|
||||
}
|
||||
selectedPaymentMethodId = method.$id;
|
||||
}
|
||||
isBackup
|
||||
@@ -97,6 +118,9 @@
|
||||
<PaymentBoxes
|
||||
methods={filteredMethods}
|
||||
bind:name
|
||||
bind:paymentMethod
|
||||
bind:showState
|
||||
bind:state
|
||||
bind:group={selectedPaymentMethodId}
|
||||
defaultMethod={organization?.paymentMethodId}
|
||||
backupMethod={organization?.backupPaymentMethodId}
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
import { FakeModal } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { Invoice } from '$lib/sdk/billing';
|
||||
import type { Invoice, PaymentMethodData } from '$lib/sdk/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { page } from '$app/state';
|
||||
import { confirmPayment, isStripeInitialized, submitStripeCard } from '$lib/stores/stripe';
|
||||
import {
|
||||
confirmPayment,
|
||||
isStripeInitialized,
|
||||
setPaymentMethod,
|
||||
submitStripeCard
|
||||
} from '$lib/stores/stripe';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { PaymentBoxes } from '$lib/components/billing';
|
||||
@@ -16,6 +21,7 @@
|
||||
import { getApiEndpoint, sdk } from '$lib/stores/sdk';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import { base } from '$app/paths';
|
||||
import type { PaymentMethod } from '@stripe/stripe-js';
|
||||
|
||||
export let show = false;
|
||||
export let invoice: Invoice;
|
||||
@@ -24,6 +30,9 @@
|
||||
let name: string;
|
||||
let paymentMethodId: string;
|
||||
let setAsDefault = false;
|
||||
let showState: boolean = false;
|
||||
let state: string = '';
|
||||
let paymentMethod: PaymentMethod | null = null;
|
||||
const endpoint = getApiEndpoint();
|
||||
|
||||
onMount(async () => {
|
||||
@@ -48,7 +57,24 @@
|
||||
try {
|
||||
if (paymentMethodId === null) {
|
||||
try {
|
||||
const method = await submitStripeCard(name, $organization.$id);
|
||||
if (showState && !state) {
|
||||
throw Error('Please select a state');
|
||||
}
|
||||
let method: PaymentMethodData;
|
||||
if (showState) {
|
||||
method = await setPaymentMethod(paymentMethod.id, name, state);
|
||||
} else {
|
||||
const card = await submitStripeCard(name, $organization.$id);
|
||||
if (card && Object.hasOwn(card, 'id')) {
|
||||
if ((card as PaymentMethod).card.country === 'US') {
|
||||
paymentMethod = card as PaymentMethod;
|
||||
showState = true;
|
||||
return;
|
||||
}
|
||||
} else if (card && Object.hasOwn(card, '$id')) {
|
||||
method = card as PaymentMethodData;
|
||||
}
|
||||
}
|
||||
const card = await sdk.forConsole.billing.getPaymentMethod(method.$id);
|
||||
if (card?.last4) {
|
||||
paymentMethodId = card.$id;
|
||||
@@ -131,6 +157,9 @@
|
||||
</Button>
|
||||
|
||||
<PaymentBoxes
|
||||
bind:paymentMethod
|
||||
bind:showState
|
||||
bind:state
|
||||
methods={filteredMethods}
|
||||
defaultMethod={$organization?.paymentMethodId}
|
||||
backupMethod={$organization?.backupPaymentMethodId}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { onMount } from 'svelte';
|
||||
import type { PaymentList } from '$lib/sdk/billing';
|
||||
import type { PaymentList, PaymentMethodData } from '$lib/sdk/billing';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { isStripeInitialized, submitStripeCard } from '$lib/stores/stripe';
|
||||
import { isStripeInitialized, setPaymentMethod, submitStripeCard } from '$lib/stores/stripe';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { PaymentBoxes } from '$lib/components/billing';
|
||||
import { addCreditWizardStore } from '../store';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import type { PaymentMethod } from '@stripe/stripe-js';
|
||||
|
||||
let methods: PaymentList;
|
||||
let name: string;
|
||||
let showState: boolean = false;
|
||||
let state: string = '';
|
||||
let paymentMethod: PaymentMethod | null = null;
|
||||
|
||||
onMount(async () => {
|
||||
methods = await sdk.forConsole.billing.listPaymentMethods();
|
||||
@@ -21,7 +25,24 @@
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
const method = await submitStripeCard(name, $organization.$id);
|
||||
if (showState && !state) {
|
||||
throw Error('Please select a state');
|
||||
}
|
||||
let method: PaymentMethodData;
|
||||
if (showState) {
|
||||
method = await setPaymentMethod(paymentMethod.id, name, state);
|
||||
} else {
|
||||
const card = await submitStripeCard(name, $organization.$id);
|
||||
if (card && Object.hasOwn(card, 'id')) {
|
||||
if ((card as PaymentMethod).card.country === 'US') {
|
||||
paymentMethod = card as PaymentMethod;
|
||||
showState = true;
|
||||
return;
|
||||
}
|
||||
} else if (card && Object.hasOwn(card, '$id')) {
|
||||
method = card as PaymentMethodData;
|
||||
}
|
||||
}
|
||||
$addCreditWizardStore.paymentMethodId = method.$id;
|
||||
invalidate(Dependencies.PAYMENT_METHODS);
|
||||
} catch (e) {
|
||||
@@ -45,6 +66,9 @@
|
||||
|
||||
<p class="text"><b>Payment method</b></p>
|
||||
<PaymentBoxes
|
||||
bind:paymentMethod
|
||||
bind:showState
|
||||
bind:state
|
||||
methods={filteredMethods}
|
||||
bind:name
|
||||
bind:group={$addCreditWizardStore.paymentMethodId} />
|
||||
|
||||
@@ -5,12 +5,17 @@
|
||||
import { Link } from '$lib/elements';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
let { domain, retryVerification }: { domain: Models.Domain; retryVerification: () => void } =
|
||||
$props();
|
||||
let {
|
||||
domain,
|
||||
retryVerification
|
||||
}: {
|
||||
domain: Models.Domain;
|
||||
retryVerification: () => void;
|
||||
} = $props();
|
||||
|
||||
const isDomainVerified = domain.nameservers.toLowerCase() === 'appwrite';
|
||||
const isDomainVerified = $derived(domain.nameservers.toLowerCase() === 'appwrite');
|
||||
|
||||
const metrics = [
|
||||
const metrics = $derived([
|
||||
{
|
||||
value: isDomainVerified ? 'Verified' : 'Not verified',
|
||||
description: 'Status'
|
||||
@@ -40,7 +45,7 @@
|
||||
value: domain?.renewalPrice || '-',
|
||||
description: 'Renewal price'
|
||||
}
|
||||
];
|
||||
]);
|
||||
</script>
|
||||
|
||||
<Layout.Grid gap="m" columnsL={6} columns={3} columnsS={2} columnsXXS={1}>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
async function retryDomain() {
|
||||
try {
|
||||
error = null;
|
||||
const domain = await sdk.forConsole.domains.updateNameservers(selectedDomain.$id);
|
||||
if (domain.nameservers.toLowerCase() === 'appwrite') {
|
||||
show = false;
|
||||
|
||||
@@ -60,10 +60,11 @@
|
||||
label="Services ID"
|
||||
autofocus={true}
|
||||
placeholder="com.company.appname"
|
||||
bind:value={appId} />
|
||||
<InputText id="keyID" label="Key ID" placeholder="SHAB13ROFN" bind:value={keyID} />
|
||||
<InputText id="teamID" label="Team ID" placeholder="ELA2CD3AED" bind:value={teamID} />
|
||||
<InputTextarea id="p8" label="P8 File" placeholder="" bind:value={p8} />
|
||||
bind:value={appId}
|
||||
required />
|
||||
<InputText id="keyID" label="Key ID" placeholder="SHAB13ROFN" bind:value={keyID} required />
|
||||
<InputText id="teamID" label="Team ID" placeholder="ELA2CD3AED" bind:value={teamID} required />
|
||||
<InputTextarea id="p8" label="P8 File" placeholder="" bind:value={p8} required />
|
||||
<Alert.Inline status="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert.Inline>
|
||||
@@ -73,10 +74,13 @@
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && keyID && teamID && p8)}
|
||||
disabled={!appId ||
|
||||
!keyID ||
|
||||
!teamID ||
|
||||
!p8 ||
|
||||
(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -56,18 +56,21 @@
|
||||
label="Client ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
bind:value={appId}
|
||||
required />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
bind:value={clientSecret} />
|
||||
bind:value={clientSecret}
|
||||
required />
|
||||
<InputText
|
||||
id="domain"
|
||||
label="Auth0 Domain"
|
||||
placeholder="Your Auth0 domain"
|
||||
bind:value={auth0Domain} />
|
||||
placeholder="your-tenant.auth0.com"
|
||||
bind:value={auth0Domain}
|
||||
required />
|
||||
<Alert.Inline status="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert.Inline>
|
||||
@@ -77,10 +80,12 @@
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && clientSecret && auth0Domain)}
|
||||
disabled={!appId ||
|
||||
!clientSecret ||
|
||||
!auth0Domain ||
|
||||
(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -60,18 +60,21 @@
|
||||
label="Client ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
bind:value={appId}
|
||||
required />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
bind:value={clientSecret} />
|
||||
bind:value={clientSecret}
|
||||
required />
|
||||
<InputText
|
||||
id="domain"
|
||||
label="Authentik Base-Domain"
|
||||
placeholder="Your Authentik domain"
|
||||
bind:value={authentikDomain} />
|
||||
bind:value={authentikDomain}
|
||||
required />
|
||||
<Alert.Inline status="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert.Inline>
|
||||
@@ -81,10 +84,12 @@
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && clientSecret && authentikDomain)}
|
||||
disabled={!appId ||
|
||||
!clientSecret ||
|
||||
!authentikDomain ||
|
||||
(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -55,18 +55,16 @@
|
||||
label="App ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
bind:value={appId}
|
||||
required />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="App Secret"
|
||||
placeholder="Enter App Secret"
|
||||
minlength={0}
|
||||
bind:value={clientSecret} />
|
||||
<InputText
|
||||
id="endpoint"
|
||||
label="Endpoint (optional)"
|
||||
placeholder="Your endpoint"
|
||||
bind:value={endpoint} />
|
||||
bind:value={clientSecret}
|
||||
required />
|
||||
<InputText id="endpoint" label="Endpoint" placeholder="Your endpoint" bind:value={endpoint} />
|
||||
<Alert.Inline status="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert.Inline>
|
||||
@@ -76,10 +74,11 @@
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && clientSecret)}
|
||||
disabled={!appId ||
|
||||
!clientSecret ||
|
||||
(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -54,13 +54,15 @@
|
||||
label="App ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
bind:value={appId}
|
||||
required />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="App Secret"
|
||||
placeholder="Enter App Secret"
|
||||
minlength={0}
|
||||
bind:value={secret} />
|
||||
bind:value={secret}
|
||||
required />
|
||||
<Alert.Inline status="info">
|
||||
To complete the setup, create an OAuth2 client ID with "Web application" as the application
|
||||
type, then add this redirect URI to your {provider.name} configuration.
|
||||
@@ -74,9 +76,9 @@
|
||||
<Button
|
||||
disabled={!appId ||
|
||||
!secret ||
|
||||
(appId === provider.appId &&
|
||||
secret === provider.secret &&
|
||||
enabled === provider.enabled)}
|
||||
(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -55,13 +55,15 @@
|
||||
label="App ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
bind:value={appId}
|
||||
required />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="App Secret"
|
||||
placeholder="Enter App Secret"
|
||||
minlength={0}
|
||||
bind:value={secret} />
|
||||
bind:value={secret}
|
||||
required />
|
||||
<Alert.Inline status="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
</Alert.Inline>
|
||||
@@ -73,9 +75,9 @@
|
||||
<Button
|
||||
disabled={!appId ||
|
||||
!secret ||
|
||||
(appId === provider.appId &&
|
||||
secret === provider.secret &&
|
||||
enabled === provider.enabled)}
|
||||
(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -42,9 +42,8 @@
|
||||
clientSecret && tenantID ? JSON.stringify({ clientSecret, tenantID }) : provider.secret;
|
||||
</script>
|
||||
|
||||
<Modal {error} onSubmit={update} bind:show on:close>
|
||||
<svelte:fragment slot="title">{provider.name} OAuth2 settings</svelte:fragment>
|
||||
<p>
|
||||
<Modal {error} onSubmit={update} bind:show on:close title={`${provider.name} OAuth2 settings`}>
|
||||
<p slot="description">
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={oAuthProvider?.docs} target="_blank" rel="noopener noreferrer">
|
||||
@@ -57,13 +56,15 @@
|
||||
label="Application (client) ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
bind:value={appId}
|
||||
required />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
bind:value={clientSecret} />
|
||||
bind:value={clientSecret}
|
||||
required />
|
||||
<InputText
|
||||
id="tenant"
|
||||
label="Target Tenant"
|
||||
@@ -78,10 +79,11 @@
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && clientSecret && tenantID)}
|
||||
disabled={!appId ||
|
||||
!clientSecret ||
|
||||
(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -78,33 +78,39 @@
|
||||
label="Client ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
bind:value={appId}
|
||||
required />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
bind:value={clientSecret} />
|
||||
bind:value={clientSecret}
|
||||
required />
|
||||
<InputText
|
||||
id="well-known-endpoint"
|
||||
label="Well-Known Endpoint"
|
||||
placeholder="https://example.com/.well-known/openid-configuration"
|
||||
bind:value={wellKnownEndpoint} />
|
||||
bind:value={wellKnownEndpoint}
|
||||
required={!authorizationEndpoint && !tokenEndpoint && !userinfoEndpoint} />
|
||||
<InputText
|
||||
id="authorization-endpoint"
|
||||
label="Authorization Endpoint"
|
||||
placeholder="https://example.com/authorize"
|
||||
bind:value={authorizationEndpoint} />
|
||||
bind:value={authorizationEndpoint}
|
||||
required={!wellKnownEndpoint} />
|
||||
<InputText
|
||||
id="token-endpoint"
|
||||
label="Token Endpoint"
|
||||
placeholder="https://example.com/token"
|
||||
bind:value={tokenEndpoint} />
|
||||
bind:value={tokenEndpoint}
|
||||
required={!wellKnownEndpoint} />
|
||||
<InputText
|
||||
id="userinfo-endpoint"
|
||||
label="User Info Endpoint"
|
||||
placeholder="https://example.com/userinfo"
|
||||
bind:value={userinfoEndpoint} />
|
||||
bind:value={userinfoEndpoint}
|
||||
required={!wellKnownEndpoint} />
|
||||
|
||||
<Alert.Inline status="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
@@ -115,10 +121,13 @@
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && isValidSecret)}
|
||||
disabled={!appId ||
|
||||
!clientSecret ||
|
||||
(!wellKnownEndpoint &&
|
||||
(!authorizationEndpoint || !tokenEndpoint || !userinfoEndpoint)) ||
|
||||
(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -47,9 +47,14 @@
|
||||
: provider.secret;
|
||||
</script>
|
||||
|
||||
<Modal {error} onSubmit={update} size="l" bind:show on:close>
|
||||
<svelte:fragment slot="title">{provider.name} OAuth2 settings</svelte:fragment>
|
||||
<p>
|
||||
<Modal
|
||||
{error}
|
||||
onSubmit={update}
|
||||
size="m"
|
||||
bind:show
|
||||
on:close
|
||||
title={`${provider.name} OAuth2 settings`}>
|
||||
<p slot="description">
|
||||
To use {provider.name} authentication in your application, first fill in this form. For more
|
||||
info you can
|
||||
<a class="link" href={oAuthProvider?.docs} target="_blank" rel="noopener noreferrer"
|
||||
@@ -61,23 +66,27 @@
|
||||
label="Client ID"
|
||||
autofocus={true}
|
||||
placeholder="Enter ID"
|
||||
bind:value={appId} />
|
||||
bind:value={appId}
|
||||
required />
|
||||
<InputPassword
|
||||
id="secret"
|
||||
label="Client Secret"
|
||||
placeholder="Enter Client Secret"
|
||||
minlength={0}
|
||||
bind:value={clientSecret} />
|
||||
bind:value={clientSecret}
|
||||
required />
|
||||
<InputText
|
||||
id="domain"
|
||||
label="Okta Domain"
|
||||
placeholder="dev-1337.okta.com"
|
||||
bind:value={oktaDomain} />
|
||||
bind:value={oktaDomain}
|
||||
required />
|
||||
<InputText
|
||||
id="serverID"
|
||||
label="Authorization Server ID"
|
||||
placeholder="default"
|
||||
bind:value={authorizationServerId} />
|
||||
bind:value={authorizationServerId}
|
||||
required />
|
||||
|
||||
<Alert.Inline status="info">
|
||||
To complete set up, add this OAuth2 redirect URI to your {provider.name} app configuration.
|
||||
@@ -88,10 +97,13 @@
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
|
||||
<Button
|
||||
disabled={(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId) ||
|
||||
!(appId && clientSecret && oktaDomain && authorizationServerId)}
|
||||
disabled={!appId ||
|
||||
!clientSecret ||
|
||||
!oktaDomain ||
|
||||
!authorizationServerId ||
|
||||
(secret === provider.secret &&
|
||||
enabled === provider.enabled &&
|
||||
appId === provider.appId)}
|
||||
submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { EmptySearch } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge } from '@appwrite.io/pink-svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
TableBody,
|
||||
@@ -77,7 +77,10 @@
|
||||
{session.osVersion}
|
||||
</p>
|
||||
{#if session.current}
|
||||
<Pill success>current session</Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
type="success"
|
||||
content="current session" />
|
||||
{/if}
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Modal, CustomId } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Button, InputText, InputSelect, InputPhone } from '$lib/elements/forms';
|
||||
import InputEmail from '$lib/elements/forms/inputEmail.svelte';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -116,10 +117,12 @@
|
||||
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}
|
||||
><span class="icon-pencil" aria-hidden="true"></span><span class="text">
|
||||
Target ID
|
||||
</span></Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content="Target ID"
|
||||
on:click={() => (showCustomId = !showCustomId)}>
|
||||
<Icon icon={IconPencil} size="s" slot="start" />
|
||||
</Badge>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomId bind:show={showCustomId} name="Target" bind:id autofocus={false} />
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
group: 'databases',
|
||||
icon: IconPlus,
|
||||
rank: page.url.pathname.endsWith('backups') ? 10 : 0,
|
||||
disabled: !isCloud || !$currentPlan.backupsEnabled
|
||||
disabled: !isCloud || !$currentPlan?.backupsEnabled
|
||||
},
|
||||
{
|
||||
label: 'Create manual backup',
|
||||
@@ -77,7 +77,7 @@
|
||||
group: 'databases',
|
||||
icon: IconPlus,
|
||||
rank: page.url.pathname.endsWith('backups') ? 10 : 0,
|
||||
disabled: !isCloud || !$currentPlan.backupsEnabled
|
||||
disabled: !isCloud || !$currentPlan?.backupsEnabled
|
||||
},
|
||||
{
|
||||
label: 'Go to collections',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { Pill } from '$lib/elements';
|
||||
import { DropList } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { IconInfo, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
@@ -41,12 +40,12 @@
|
||||
{#if title === 'Policies' && policiesCreated >= maxPolicies}
|
||||
<div style:height="40px;" style:padding-block-start="4px">
|
||||
<DropList bind:show={showDropdown} width="16">
|
||||
<Pill disabled={buttonDisabled} button on:click={() => (showDropdown = true)}>
|
||||
<Layout.Stack direction="row" gap="xs" alignItems="center" inline>
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
{policiesCreated}/{maxPolicies} created
|
||||
</Layout.Stack>
|
||||
</Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content={`${policiesCreated}/${maxPolicies} created`}
|
||||
on:click={() => (showDropdown = true)}>
|
||||
<Icon icon={IconInfo} size="s" slot="start" />
|
||||
</Badge>
|
||||
<svelte:fragment slot="list">
|
||||
<slot name="tooltip">
|
||||
<span>
|
||||
|
||||
@@ -29,7 +29,7 @@ export function buildWildcardAttributesQuery(
|
||||
): string[] {
|
||||
return [
|
||||
...(collection?.attributes
|
||||
?.filter((attr) => isRelationship(attr))
|
||||
?.filter((attr) => attr.status === 'available' && isRelationship(attr))
|
||||
?.map((attr) => Query.select([`${attr.key}.*`])) ?? []),
|
||||
|
||||
Query.select(['*'])
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { collection } from '../../store';
|
||||
import { page } from '$app/stores';
|
||||
import { page } from '$app/state';
|
||||
import { Alert } from '@appwrite.io/pink-svelte';
|
||||
|
||||
let showDelete = false;
|
||||
@@ -23,13 +23,15 @@
|
||||
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
const { $databaseId, $collectionId, $id: documentId } = $doc;
|
||||
|
||||
await sdk
|
||||
.forProject($page.params.region, $page.params.project)
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.databases.updateDocument(
|
||||
$doc.$databaseId,
|
||||
$doc.$collectionId,
|
||||
$doc.$id,
|
||||
$doc.data,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
documentId,
|
||||
undefined,
|
||||
permissions
|
||||
);
|
||||
await invalidate(Dependencies.DOCUMENT);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
import { CardContainer, GridItem1, Id } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge } from '@appwrite.io/pink-svelte';
|
||||
import { canWriteCollections } from '$lib/stores/roles';
|
||||
import type { PageData } from './$types';
|
||||
export let data: PageData;
|
||||
@@ -22,7 +22,7 @@
|
||||
<svelte:fragment slot="title">{collection.name}</svelte:fragment>
|
||||
<svelte:fragment slot="status">
|
||||
{#if !collection.enabled}
|
||||
<Pill>disabled</Pill>
|
||||
<Badge variant="secondary" content="disabled" />
|
||||
{/if}</svelte:fragment>
|
||||
|
||||
<Id value={collection.$id}>{collection.$id}</Id>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
import { CustomId, LabelCard } from '$lib/components';
|
||||
import { providers } from '../store';
|
||||
import { InputText } from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Providers } from '../../provider.svelte';
|
||||
import { SmtpEncryption } from '@appwrite.io/console';
|
||||
|
||||
@@ -151,10 +152,12 @@
|
||||
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}
|
||||
><span class="icon-pencil" aria-hidden="true"></span><span class="text">
|
||||
Provider ID
|
||||
</span></Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content="Provider ID"
|
||||
on:click={() => (showCustomId = !showCustomId)}>
|
||||
<Icon icon={IconPencil} size="s" slot="start" />
|
||||
</Badge>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomId bind:show={showCustomId} name="Provider" bind:id autofocus={false} />
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
InputText,
|
||||
InputTextarea
|
||||
} from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
import { CustomId, Modal } from '$lib/components';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
@@ -104,10 +105,12 @@
|
||||
</InputSwitch>
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}
|
||||
><span class="icon-pencil" aria-hidden="true"></span><span class="text">
|
||||
Message ID
|
||||
</span></Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content="Message ID"
|
||||
on:click={() => (showCustomId = !showCustomId)}>
|
||||
<Icon icon={IconPencil} size="s" slot="start" />
|
||||
</Badge>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomId
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
InputTextarea,
|
||||
Label
|
||||
} from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPencil, IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { CustomId, Modal } from '$lib/components';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
@@ -43,8 +44,6 @@
|
||||
import PushPhone from '../pushPhone.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import InputFilePicker from '$lib/elements/forms/inputFilePicker.svelte';
|
||||
import { Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
|
||||
import { page } from '$app/state';
|
||||
|
||||
let showCustomId = false;
|
||||
@@ -196,10 +195,12 @@
|
||||
</form>
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}
|
||||
><span class="icon-pencil" aria-hidden="true"></span><span class="text">
|
||||
Message ID
|
||||
</span></Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content="Message ID"
|
||||
on:click={() => (showCustomId = !showCustomId)}>
|
||||
<Icon icon={IconPencil} size="s" slot="start" />
|
||||
</Badge>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomId
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { messageParams, providerType } from './store';
|
||||
import { Button, InputEmail, InputRadio, InputTextarea } from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
|
||||
import { CustomId, Modal } from '$lib/components';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
@@ -83,10 +84,12 @@
|
||||
</div>
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}
|
||||
><span class="icon-pencil" aria-hidden="true"></span><span class="text">
|
||||
Message ID
|
||||
</span></Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
content="Message ID"
|
||||
on:click={() => (showCustomId = !showCustomId)}>
|
||||
<Icon icon={IconPencil} size="s" slot="start" />
|
||||
</Badge>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomId
|
||||
|
||||
@@ -120,10 +120,11 @@
|
||||
</script>
|
||||
|
||||
{#if data.platforms.platforms.length}
|
||||
<Table.Root columns={3} let:root>
|
||||
<Table.Root columns={4} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Platform type</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Identifier</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Last updated</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
{#each data.platforms.platforms as platform}
|
||||
@@ -137,6 +138,13 @@
|
||||
{PlatformTypes[platform.type]}
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
<Table.Cell {root}>
|
||||
{#if platform.type.includes('web') || platform.type === 'web'}
|
||||
{platform.hostname || '—'}
|
||||
{:else}
|
||||
{platform.key || platform.hostname || '—'}
|
||||
{/if}
|
||||
</Table.Cell>
|
||||
<Table.Cell {root}>
|
||||
{#if platform.$updatedAt}
|
||||
<DualTimeView time={platform.$updatedAt} />
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
JavascriptFrameworkIcon
|
||||
} from './components/index';
|
||||
import { extendedHostnameRegex } from '$lib/helpers/string';
|
||||
import { project } from '../../store';
|
||||
|
||||
export let key;
|
||||
|
||||
@@ -57,6 +58,7 @@
|
||||
const projectId = page.params.project;
|
||||
|
||||
const updateConfigCode = (prefix = '') => `${prefix}APPWRITE_PROJECT_ID = "${projectId}"
|
||||
${prefix}APPWRITE_PROJECT_NAME = "${$project.name}"
|
||||
${prefix}APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}"
|
||||
`;
|
||||
type FrameworkType = {
|
||||
@@ -126,11 +128,11 @@ ${prefix}APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.p
|
||||
smallIcon: IconAngular,
|
||||
portNumber: 4200,
|
||||
runCommand: 'npm run start',
|
||||
updateConfigCode: `appwriteEndpoint: '${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}',\nappwriteProjectId:'${projectId}'`
|
||||
updateConfigCode: `appwriteEndpoint: '${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}',\nappwriteProjectId: '${projectId}',\nappwriteProjectName: '${$project.name}'`
|
||||
},
|
||||
{
|
||||
key: 'js',
|
||||
label: 'Javascript',
|
||||
label: 'JavaScript',
|
||||
icon: JavascriptFrameworkIcon,
|
||||
smallIcon: IconJs,
|
||||
portNumber: 5173,
|
||||
@@ -294,11 +296,9 @@ ${prefix}APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.p
|
||||
to reflect the values below:</Typography.Text>
|
||||
{:else}
|
||||
<Typography.Text variant="m-500"
|
||||
>2. Add your Appwrite credentials to <InlineCode
|
||||
size="s"
|
||||
code=".env.example" /> then rename it to <InlineCode
|
||||
size="s"
|
||||
code=".env" /> if needed.</Typography.Text>
|
||||
>2. Copy the file <InlineCode size="s" code=".env.example" />, rename it
|
||||
to <InlineCode size="s" code=".env" />
|
||||
and update the configuration settings.</Typography.Text>
|
||||
{/if}
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
|
||||
@@ -56,9 +56,17 @@
|
||||
await goto(routeBase);
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
} else {
|
||||
await goto(
|
||||
`${routeBase}/add-domain/verify-${domainName}?rule=${rule.$id}&domain=${domain.$id}`
|
||||
);
|
||||
let redirect = `${routeBase}/add-domain/verify-${domainName}?rule=${rule.$id}`;
|
||||
|
||||
if (isCloud) {
|
||||
/**
|
||||
* Domains are only on cloud!
|
||||
* Self-hosted instances have rules.
|
||||
*/
|
||||
redirect += `&domain=${domain.$id}`;
|
||||
}
|
||||
|
||||
await goto(redirect);
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const columns = writable<Column[]>([
|
||||
{
|
||||
id: 'domain',
|
||||
title: 'Domain',
|
||||
type: 'string',
|
||||
format: 'string',
|
||||
width: { min: 200, max: 550 }
|
||||
},
|
||||
|
||||
{
|
||||
id: 'redirectUrl',
|
||||
title: 'Redirect to',
|
||||
type: 'string',
|
||||
width: { min: 120, max: 400 }
|
||||
},
|
||||
{
|
||||
id: 'deploymentVcsProviderBranch',
|
||||
title: 'Production branch',
|
||||
type: 'string',
|
||||
width: { min: 90, max: 220 }
|
||||
}
|
||||
]);
|
||||
@@ -15,7 +15,6 @@
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import DeleteDomainModal from './deleteDomainModal.svelte';
|
||||
import RetryDomainModal from './retryDomainModal.svelte';
|
||||
import { columns } from './store';
|
||||
import { regionalProtocol } from '../../store';
|
||||
|
||||
let {
|
||||
@@ -27,11 +26,21 @@
|
||||
let showDelete = $state(false);
|
||||
let showRetry = $state(false);
|
||||
let selectedDomain: Models.ProxyRule = $state(null);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
id: 'domain',
|
||||
title: 'Domain',
|
||||
type: 'string',
|
||||
format: 'string',
|
||||
width: { min: 200, max: 550 }
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<Table.Root columns={[...$columns, { id: 'actions', width: 40 }]} let:root>
|
||||
<Table.Root columns={[...columns, { id: 'actions', width: 40 }]} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
{#each $columns as { id, title }}
|
||||
{#each columns as { id, title }}
|
||||
<Table.Header.Cell column={id} {root}>
|
||||
{title}
|
||||
</Table.Header.Cell>
|
||||
@@ -40,7 +49,7 @@
|
||||
</svelte:fragment>
|
||||
{#each domains.rules as domain}
|
||||
<Table.Row.Base {root}>
|
||||
{#each $columns as column}
|
||||
{#each columns as column}
|
||||
<Table.Cell column={column.id} {root}>
|
||||
{#if column.id === 'domain'}
|
||||
<Layout.Stack direction="row" gap="xs">
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge, Icon } from '@appwrite.io/pink-svelte';
|
||||
import { IconCheckCircle, IconExclamationCircle } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
export let enabled: boolean;
|
||||
|
||||
$: style = `color: ${enabled ? 'hsl(var(--color-success-120))' : 'hsl(var(--color-danger-120))'}`;
|
||||
$: iconClass = (enabled ? 'icon-check-circle' : 'icon-exclamation-circle') + ' u-font-size-12';
|
||||
</script>
|
||||
|
||||
<Pill success={enabled} danger={!enabled} on:click>
|
||||
<span class="text u-font-size-12 u-trim">
|
||||
<span class={iconClass} aria-hidden="true" {style}></span>
|
||||
|
||||
<span>{enabled ? 'enabled' : 'stopped'}</span>
|
||||
</span>
|
||||
</Pill>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
type={enabled ? 'success' : 'error'}
|
||||
content={enabled ? 'enabled' : 'stopped'}
|
||||
on:click>
|
||||
<Icon icon={enabled ? IconCheckCircle : IconExclamationCircle} size="s" slot="start" />
|
||||
</Badge>
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
trackEvent(Submit.DomainUpdateVerification);
|
||||
} catch (e) {
|
||||
error =
|
||||
e.message ??
|
||||
'Domain verification failed. Please check your domain settings or try again later';
|
||||
trackError(e, Submit.DomainUpdateVerification);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Avatar, Empty, EmptySearch, PaginationWithLimit, SearchQuery } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Badge } from '@appwrite.io/pink-svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { calculateSize } from '$lib/helpers/sizeConvertion';
|
||||
import { Container } from '$lib/layout';
|
||||
@@ -129,7 +129,7 @@
|
||||
<span class="avatar is-size-small is-color-empty"></span>
|
||||
<span class="text u-trim">{file.name}</span>
|
||||
<div>
|
||||
<Pill warning>Pending</Pill>
|
||||
<Badge variant="secondary" type="warning" content="Pending" />
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Table.Cell>
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
import { page } from '$app/state';
|
||||
import UpdateVariablesModal from './updateVariablesModal.svelte';
|
||||
import SecretVariableModal from './secretVariableModal.svelte';
|
||||
import { Confirm } from '$lib/components';
|
||||
|
||||
export let variableList: Models.VariableList;
|
||||
export let globalVariableList: Models.VariableList | undefined = undefined;
|
||||
@@ -64,6 +65,8 @@
|
||||
let showEditorModal = false;
|
||||
let showUpdate = false;
|
||||
let showSecretModal = false;
|
||||
let showDeleteModal = false;
|
||||
let deleteError: string;
|
||||
let offset = 0;
|
||||
const limit = 10;
|
||||
|
||||
@@ -134,9 +137,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function handleVariableDeleted(variable: Models.Variable) {
|
||||
async function handleVariableDeleted() {
|
||||
try {
|
||||
await sdkDeleteVariable(variable.$id);
|
||||
await sdkDeleteVariable(selectedVar.$id);
|
||||
showDeleteModal = false;
|
||||
selectedVar = null;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$project.name} ${
|
||||
@@ -145,10 +150,7 @@
|
||||
});
|
||||
trackEvent(Submit.VariableDelete);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
deleteError = error.message;
|
||||
trackError(error, Submit.VariableDelete);
|
||||
}
|
||||
}
|
||||
@@ -402,7 +404,8 @@
|
||||
status="danger"
|
||||
trailingIcon={IconTrash}
|
||||
on:click={async (e) => {
|
||||
handleVariableDeleted(variable);
|
||||
selectedVar = variable;
|
||||
showDeleteModal = true;
|
||||
toggle(e);
|
||||
}}>
|
||||
Delete
|
||||
@@ -480,3 +483,13 @@
|
||||
{variableList}
|
||||
bind:show={showVariablesUpload} />
|
||||
{/if}
|
||||
|
||||
{#if showDeleteModal}
|
||||
<Confirm
|
||||
title="Delete variable"
|
||||
bind:open={showDeleteModal}
|
||||
bind:error={deleteError}
|
||||
onSubmit={handleVariableDeleted}>
|
||||
<p>Are you sure you want to delete this variable? This action is irreversible.</p>
|
||||
</Confirm>
|
||||
{/if}
|
||||
|
||||
@@ -26,7 +26,7 @@ const config = {
|
||||
precompress: true
|
||||
}),
|
||||
paths: {
|
||||
base: '/console'
|
||||
base: process.env.PREVIEW ? '' : '/console'
|
||||
}
|
||||
},
|
||||
vitePlugin: {
|
||||
|
||||