- {#if $activeHeaderAlert?.show} - - {/if} {#if $page.data?.header} {/if} @@ -247,4 +248,14 @@ grid-template-columns: auto 1fr !important; } } + // + //:global(main.has-alert > header) { + // top: 70px; + //} + //:global(main.has-alert > div nav) { + // @media (min-width: 1024px) { + // top: calc(48px + 70px) !important; + // height: calc(100vh - (48px + 70px)) !important; + // } + //} diff --git a/src/lib/layout/unauthenticated.svelte b/src/lib/layout/unauthenticated.svelte index ba694c3d9..5497d4991 100644 --- a/src/lib/layout/unauthenticated.svelte +++ b/src/lib/layout/unauthenticated.svelte @@ -270,7 +270,7 @@ } .tag-line { - font-family: 'Aeonik Pro'; + font-family: 'Aeonik Pro', 'Inter', sans-serif; font-size: 4rem; font-style: normal; font-weight: 400; diff --git a/src/lib/layout/usage.svelte b/src/lib/layout/usage.svelte index 71d376898..3077d1f74 100644 --- a/src/lib/layout/usage.svelte +++ b/src/lib/layout/usage.svelte @@ -78,32 +78,35 @@ export let count: Models.Metric[]; export let countMetadata: MetricMetadata; export let path: string = null; + export let hidePeriodSelect = false; -
- goto(`${path}/${e.detail}`)} - id="period" - options={[ - { - label: '24 hours', - value: '24h' - }, - { - label: '30 days', - value: '30d' - }, - { - label: '90 days', - value: '90d' - } - ]} - value={$page.params.period ?? '30d'} /> -
+ {#if !hidePeriodSelect} +
+ goto(`${path}/${e.detail}`)} + id="period" + options={[ + { + label: '24 hours', + value: '24h' + }, + { + label: '30 days', + value: '30d' + }, + { + label: '90 days', + value: '90d' + } + ]} + value={$page.params.period ?? '30d'} /> +
+ {/if} {#if count} diff --git a/src/lib/layout/wizard.svelte b/src/lib/layout/wizard.svelte index 850b882ed..4e5e49078 100644 --- a/src/lib/layout/wizard.svelte +++ b/src/lib/layout/wizard.svelte @@ -14,7 +14,7 @@ invertColumns?: boolean; hideFooter?: boolean; column?: boolean; - columnSize?: 's' | 'm'; + columnSize?: 's' | 'm' | 'l'; onExit?: () => void; } | { @@ -25,7 +25,7 @@ invertColumns?: boolean; hideFooter?: boolean; column?: boolean; - columnSize?: 's' | 'm'; + columnSize?: 's' | 'm' | 'l'; onExit?: () => void; }; diff --git a/src/lib/pages/domains/wizard/step1.svelte b/src/lib/pages/domains/wizard/step1.svelte index 5a7150194..0e8750d15 100644 --- a/src/lib/pages/domains/wizard/step1.svelte +++ b/src/lib/pages/domains/wizard/step1.svelte @@ -1,6 +1,5 @@ - -{#if hasInstallations} - {#await loadInstallations()} -
-
    - -
-
    - -
-
- {:then installations} - - { - return { - label: entry.organization, - value: entry.$id - }; - })} - on:change={() => { - search = ''; - installation.set( - installations.find((entry) => entry.$id === selectedInstallation) - ); - }} - bind:value={selectedInstallation} /> - - - {/await} -

- Manage organization configuration in your project settings. -

- {#if selectedInstallation} - {#await loadRepositories(selectedInstallation, search)} -
-
-
- {:then response} - {#if response?.length} -
    - {#each response as repo, i} -
  • -
    -
    - {#if action === 'select'} - repository.set(repo)} - value={repo.id} /> - {/if} -
    - {#if repo.runtime} - {repo.name} - {/if} -
    -
    - {repo.name} - {#if repo.private} -
    - {#if action === 'button'} -
    - -
    - {/if} -
    -
    -
  • - {/each} -
- {:else if search} - -
-
- Sorry we couldn't find "{search}" -

There are no repositories that match your search.

-
-
- -
-
-
- {:else} - - {/if} - {/await} - {/if} -{:else} -
- - - - -
-{/if} - - diff --git a/src/lib/wizards/functions/components/specificationsTooltip.svelte b/src/lib/wizards/functions/components/specificationsTooltip.svelte deleted file mode 100644 index 279f5a0f1..000000000 --- a/src/lib/wizards/functions/components/specificationsTooltip.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - -
-

Additional CPU and RAM are available for Pro and Scale teams.

-
- Learn more - -
-
- - diff --git a/src/lib/wizards/functions/connectExisting.svelte b/src/lib/wizards/functions/connectExisting.svelte deleted file mode 100644 index a619870d2..000000000 --- a/src/lib/wizards/functions/connectExisting.svelte +++ /dev/null @@ -1,81 +0,0 @@ - - - diff --git a/src/lib/wizards/functions/cover.svelte b/src/lib/wizards/functions/cover.svelte deleted file mode 100644 index d31eaae83..000000000 --- a/src/lib/wizards/functions/cover.svelte +++ /dev/null @@ -1,317 +0,0 @@ - - - - - - Create function -
-
-
-
- Connect Git repository -

- Create and deploy a function with a connected git repository. -

-
- -
- {#if isSelfHosted && !isVcsEnabled} -
-
- - Connect your self-hosted instance to Git - -

- Configure your self-hosted instance to connect your function to - a Git repository. - Learn more. -

-
-
- {/if} -
- -
-
- Quick start -

Use a starter template.

-
    - {#await Promise.all([$baseRuntimesList, $starterTemplate])} - {#each Array(6) as _i} -
  • - -
  • - {/each} - {:then [response, quickStart]} - {@const runtimes = new Map( - response.runtimes.map((r) => [r.$id, r]) - )} - {@const templates = quickStart.runtimes.filter((template) => - runtimes.has(template.name) - )} - {#each templates.slice(0, 6) as template} - {@const runtimeDetail = runtimes.get(template.name)} -
  • - -
  • - {/each} - - {#if templates.length < 6} - -
  • - - - -
  • - More runtimes coming soon -
    - {/if} - {/await} -
-
- - -
-
- Templates -

- Find the right template for your use case. -

- -
    - {#await $featuredTemplatesList} - {#each Array(3) as _i} -
  • - -
  • - {/each} - {:then templatesListWithoutStarter} - {#each templatesListWithoutStarter.templates as template} -
  • - -
  • - {/each} - {/await} -
-
- -
-
-

- You can also create a function - or using the CLI. - Learn more. -

-
-
- - - diff --git a/src/lib/wizards/functions/createGit.svelte b/src/lib/wizards/functions/createGit.svelte deleted file mode 100644 index 751487c0e..000000000 --- a/src/lib/wizards/functions/createGit.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - - diff --git a/src/lib/wizards/functions/createManual.svelte b/src/lib/wizards/functions/createManual.svelte deleted file mode 100644 index 586b580c6..000000000 --- a/src/lib/wizards/functions/createManual.svelte +++ /dev/null @@ -1,109 +0,0 @@ - - - diff --git a/src/lib/wizards/functions/createTemplate.svelte b/src/lib/wizards/functions/createTemplate.svelte deleted file mode 100644 index ae703bc45..000000000 --- a/src/lib/wizards/functions/createTemplate.svelte +++ /dev/null @@ -1,128 +0,0 @@ - - - diff --git a/src/lib/wizards/functions/steps/createRepository.svelte b/src/lib/wizards/functions/steps/createRepository.svelte deleted file mode 100644 index aecb74761..000000000 --- a/src/lib/wizards/functions/steps/createRepository.svelte +++ /dev/null @@ -1,179 +0,0 @@ - - - - Repository - - Select a Git repository that will trigger your function deployments when updated. - - - {#if $templateConfig.repositoryBehaviour === 'existing'} - - {:else} - {#await loadInstallations()} -
-
-
- {:then installations} - {#if hasInstallations} - - { - return { - label: entry.organization, - value: entry.$id - }; - })} - on:change={() => { - $installation = installations.find( - (entry) => entry.$id === selectedInstallationId - ); - }} - bind:value={selectedInstallationId} /> - - {:else} -
- - - - -
- {/if} - {#if $installation} - -
-
- -
-
-
- {$installation.organization}/{$templateConfig.repositoryName} -
-
-
-
- - - - -
-
- {/if} - {/await} - {/if} - diff --git a/src/lib/wizards/functions/steps/executeAccess.svelte b/src/lib/wizards/functions/steps/executeAccess.svelte deleted file mode 100644 index 64f2ec6d9..000000000 --- a/src/lib/wizards/functions/steps/executeAccess.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - - Permissions - - Choose who can execute this function using the client API. For more information, visit our - - Permissions guide. - - - - diff --git a/src/lib/wizards/functions/steps/functionConfiguration.svelte b/src/lib/wizards/functions/steps/functionConfiguration.svelte deleted file mode 100644 index cafa196af..000000000 --- a/src/lib/wizards/functions/steps/functionConfiguration.svelte +++ /dev/null @@ -1,120 +0,0 @@ - - - - Configuration - - Set your deployment configuration and any build commands here. - - - - {#if !showCustomId} -
- (showCustomId = !showCustomId)}> - -
- {:else} - - {/if} - - {#if detectingRuntime} - - {:else} - - {/if} - - - - - Build commands - (optional) - - - - - -
-
diff --git a/src/lib/wizards/functions/steps/gitConfiguration.svelte b/src/lib/wizards/functions/steps/gitConfiguration.svelte deleted file mode 100644 index 8788b50cc..000000000 --- a/src/lib/wizards/functions/steps/gitConfiguration.svelte +++ /dev/null @@ -1,94 +0,0 @@ - - - - Branch - - Choose the Git branch that will trigger your function deployments when updated. - - - -
- -
{$installation.organization}/{$repository.name}
-
- {#await loadBranches()} -
-
-
- {:then branches} - {@const options = - branches - ?.map((branch) => { - return { - value: branch.name, - label: branch.name - }; - }) - ?.sort((a, b) => { - return a.label > b.label ? 1 : -1; - }) ?? []} -
- - { - $choices.branch = event.detail.value; - }} - interactiveOutput - name="branches" - {options} /> - - - -
- {/await} - -

- Visit your repository on GitHub. -

- diff --git a/src/lib/wizards/functions/steps/manualConfiguration.svelte b/src/lib/wizards/functions/steps/manualConfiguration.svelte deleted file mode 100644 index 11e86decb..000000000 --- a/src/lib/wizards/functions/steps/manualConfiguration.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - - Configuration - - Set your deployment configuration and any build commands here. - - - - - - - - Build commands - (optional) - - - - - - - diff --git a/src/lib/wizards/functions/steps/manualDetails.svelte b/src/lib/wizards/functions/steps/manualDetails.svelte deleted file mode 100644 index 9167474d0..000000000 --- a/src/lib/wizards/functions/steps/manualDetails.svelte +++ /dev/null @@ -1,84 +0,0 @@ - - - - Details - Create and deploy your function manually. - - - - - - - - {#if !showCustomId} -
- (showCustomId = !showCustomId)}> - -
- {:else} - - {/if} -
-
diff --git a/src/lib/wizards/functions/steps/selectRepository.svelte b/src/lib/wizards/functions/steps/selectRepository.svelte deleted file mode 100644 index 8ae8a1b14..000000000 --- a/src/lib/wizards/functions/steps/selectRepository.svelte +++ /dev/null @@ -1,23 +0,0 @@ - - - - Repository - - Select a Git repository that will trigger your function deployments when updated. - - - diff --git a/src/lib/wizards/functions/steps/templateConfiguration.svelte b/src/lib/wizards/functions/steps/templateConfiguration.svelte deleted file mode 100644 index 190e338e0..000000000 --- a/src/lib/wizards/functions/steps/templateConfiguration.svelte +++ /dev/null @@ -1,128 +0,0 @@ - - - - {$template.name} - - {$template.tagline} - - - - {#await loadRuntimes()} - - {:then options} - - {/await} - {#await loadSpecifications()} - - {:then specificationOptions} - - {/await} - - {#if !showCustomId} -
- (showCustomId = !showCustomId)}> - -
- {:else} - - {/if} -
-
diff --git a/src/lib/wizards/functions/steps/templateDeployment.svelte b/src/lib/wizards/functions/steps/templateDeployment.svelte deleted file mode 100644 index 524ce1272..000000000 --- a/src/lib/wizards/functions/steps/templateDeployment.svelte +++ /dev/null @@ -1,62 +0,0 @@ - - - - Deployment - - -

Connect with Git Recommended

- - Create a new repository - Clone the template to a newly created repository in your organization. - - - Add to existing repository - Clone the template to an existing repository in your organization. - - -

Quick start

- - Connect later - Deploy now and continue development via CLI, or connect Git from your function settings. - -
-
diff --git a/src/lib/wizards/functions/steps/templatePermissions.svelte b/src/lib/wizards/functions/steps/templatePermissions.svelte deleted file mode 100644 index 42a4474f1..000000000 --- a/src/lib/wizards/functions/steps/templatePermissions.svelte +++ /dev/null @@ -1,90 +0,0 @@ - - - - Permissions - - Enable recommended scopes and execute access for when your function is deployed. - -

Execute permissions

- - - - {#if templateScopes.length > 0} -

Function scopes

- - {#each templateScopes as scope, i} - - {#if i < templateScopes.length - 1} -
- {/if} - {/each} -
- {/if} -
diff --git a/src/lib/wizards/functions/steps/templateVariables.svelte b/src/lib/wizards/functions/steps/templateVariables.svelte deleted file mode 100644 index c7db15bd4..000000000 --- a/src/lib/wizards/functions/steps/templateVariables.svelte +++ /dev/null @@ -1,124 +0,0 @@ - - - - Variables - - Edit the values of the environment variables that will be passed to your function at - runtime. - - {#if $template?.variables?.length} - {#if requiredVariables?.length} - - - Required variables - - {requiredVariables.length} - - - - {#each requiredVariables as variable} -
- - - {@html variable.description} - -
- {/each} -
-
-
- {/if} - - {#if optionalVariables?.length} - - - Optional variables - - {optionalVariables.length} - - - - {#each optionalVariables as variable} -
- - - - {@html variable.description} - -
- {/each} -
-
-
- {/if} - {:else} - -

There are no environment variables to configure.

-
- {/if} -
diff --git a/src/lib/wizards/functions/store.ts b/src/lib/wizards/functions/store.ts deleted file mode 100644 index 6613438a2..000000000 --- a/src/lib/wizards/functions/store.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { page } from '$app/stores'; -import type { WizardStepsType } from '$lib/layout/wizardWithSteps.svelte'; -import type { Models } from '@appwrite.io/console'; -import { derived, writable, type Writable } from 'svelte/store'; - -export const template = writable(); -export const templateConfig = writable<{ - $id: string; - name: string; - runtime: string; - variables: { [key: string]: unknown }; - repositoryBehaviour: 'new' | 'existing' | 'manual'; - repositoryName?: string; - repositoryPrivate?: boolean; - repositoryId: string; - execute?: boolean; - scopes?: string[]; - specification?: string; -}>(); -export const repository = writable(); -export const installation = writable(); -export const choices = writable<{ - branch: string; - rootDir: string; - silentMode: boolean; -}>({ - branch: null, - rootDir: null, - silentMode: null -}); - -export const installations = derived( - page, - ($page) => $page.data.installations as Models.InstallationList -); - -const initialCreateFunction: Partial = { - $id: null, - name: null, - entrypoint: null, - execute: [], - runtime: null, - commands: null -}; - -function createFunctionStore() { - const store = writable>({ - ...initialCreateFunction - }); - - const reset = () => { - store.set({ ...initialCreateFunction }); - }; - - return { - ...store, - reset - }; -} -export const createFunction = createFunctionStore(); - -export const createFunctionDeployment = writable(); - -export const templateStepsComponents: Writable = writable(new Map()); diff --git a/src/routes/(console)/+layout.ts b/src/routes/(console)/+layout.ts index d4d311286..447f41d90 100644 --- a/src/routes/(console)/+layout.ts +++ b/src/routes/(console)/+layout.ts @@ -34,7 +34,9 @@ export const load: LayoutLoad = async ({ params, fetch, depends, parent }) => { }, new Map()); } - const organizations = await sdk.forConsole.teams.list(); + const organizations = !isCloud + ? await sdk.forConsole.teams.list() + : await sdk.forConsole.billing.listOrganization(); let projects = []; let currentOrgId = params.organization ? params.organization : prefs.organization; diff --git a/src/routes/(console)/account/organizations/+page.ts b/src/routes/(console)/account/organizations/+page.ts index 0b89b1d30..c1734a28c 100644 --- a/src/routes/(console)/account/organizations/+page.ts +++ b/src/routes/(console)/account/organizations/+page.ts @@ -3,19 +3,22 @@ import { sdk } from '$lib/stores/sdk'; import { getLimit, getPage, pageToOffset } from '$lib/helpers/load'; import { CARD_LIMIT } from '$lib/constants'; import type { PageLoad } from './$types'; +import { isCloud } from '$lib/system'; export const load: PageLoad = async ({ url, route }) => { const page = getPage(url); const limit = getLimit(url, route, CARD_LIMIT); const offset = pageToOffset(page, limit); + const queries = [Query.offset(offset), Query.limit(limit), Query.orderDesc('')]; + + const organizations = !isCloud + ? await sdk.forConsole.teams.list(queries) + : await sdk.forConsole.billing.listOrganization(queries); + return { offset, limit, - organizations: await sdk.forConsole.teams.list([ - Query.offset(offset), - Query.limit(limit), - Query.orderDesc('') - ]) + organizations }; }; diff --git a/src/routes/(console)/account/payments/+page.svelte b/src/routes/(console)/account/payments/+page.svelte index 8eb25c9e2..c46855761 100644 --- a/src/routes/(console)/account/payments/+page.svelte +++ b/src/routes/(console)/account/payments/+page.svelte @@ -19,9 +19,7 @@ -
- Payment details -
+ Payment details
diff --git a/src/routes/(console)/account/payments/billingAddress.svelte b/src/routes/(console)/account/payments/billingAddress.svelte index a5617e7a7..d3129ce7c 100644 --- a/src/routes/(console)/account/payments/billingAddress.svelte +++ b/src/routes/(console)/account/payments/billingAddress.svelte @@ -1,14 +1,6 @@ - Billing Address + Billing address View or update your billing address. This address will be included in your invoices from Appwrite. {#if $addressList.total && countryList?.total} - - - Billing address - - - - - {#each $addressList.billingAddresses as address, i} - {@const country = countryList?.countries?.find( - (c) => c.code === address.country - )} - {@const linkedOrgs = orgList?.filter( - (org) => address.$id === org.billingAddressId - )} + + {#each $addressList.billingAddresses as address} + {@const country = countryList?.countries?.find( + (c) => c.code === address.country + )} + {@const linkedOrgs = orgList?.filter( + (org) => address.$id === org.billingAddressId + )} - - -
-

{address.streetAddress}

- {#if address?.addressLine2} -

{address.addressLine2}

- {/if} -

{address.city}

-

{address.state}

-

{address.postalCode}

-

{country ? country.name : address.country}

-
-
- - {#if linkedOrgs?.length > 0} - - (showLinked[i] = !showLinked[i])}> - linked to organization - - -

+ + + {address.streetAddress}, + {#if address?.addressLine2} + {address.addressLine2}, + {/if} + {address.city}, + {#if address?.state} + {address.state}, + {/if} + {#if address?.postalCode} + {address.postalCode}, + {/if} + {country ? country.name : address.country} + + + {#if linkedOrgs?.length > 0} + + + + linked to organization + + + + This billing address is linked to the following organizations: -

-
+ + {#each linkedOrgs as org} - {org.name} - + {/each} -
-
-
- {/if} -
- - - - - { - showEdit = true; - selectedAddress = address; - showDropdown[i] = false; - }}> - Edit - - { - showDelete = true; - selectedAddress = address; - selectedLinkedOrgs = linkedOrgs; - showDropdown[i] = false; - }}> - Delete - + + - - -
- {/each} -
-
+ + {/if} + + + + + + { + showEdit = true; + selectedAddress = address; + }}> + Edit + + { + showDelete = true; + selectedAddress = address; + selectedLinkedOrgs = linkedOrgs; + }}> + Delete + + + + + + {/each} + - +
+ +
{:else} (show = true)}>

Add a billing address

diff --git a/src/routes/(console)/account/payments/deleteAddressModal.svelte b/src/routes/(console)/account/payments/deleteAddressModal.svelte index a25cf15c7..059beab3c 100644 --- a/src/routes/(console)/account/payments/deleteAddressModal.svelte +++ b/src/routes/(console)/account/payments/deleteAddressModal.svelte @@ -8,6 +8,7 @@ import { addNotification } from '$lib/stores/notifications'; import type { Organization } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; + import { Layout, Link } from '@appwrite.io/pink-svelte'; export let showDelete = false; export let selectedAddress: Address; @@ -48,13 +49,13 @@ This billing address is set as the default for the following organisations. As they have upcoming invoices it cannot be deleted from your account.

-
    + {#each linkedOrgs as org} -
  • - {org.name} -
  • + + {org.name} + {/each} -
+ {:else}

Are you sure you want to delete this billing address from your account?

{/if} diff --git a/src/routes/(console)/account/payments/deletePaymentModal.svelte b/src/routes/(console)/account/payments/deletePaymentModal.svelte index 9e5b43498..63efeca25 100644 --- a/src/routes/(console)/account/payments/deletePaymentModal.svelte +++ b/src/routes/(console)/account/payments/deletePaymentModal.svelte @@ -9,6 +9,7 @@ import { addNotification } from '$lib/stores/notifications'; import type { Organization } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; + import { Layout, Link, Typography } from '@appwrite.io/pink-svelte'; export let linkedOrgs: Organization[] = []; export let showDelete = false; @@ -40,23 +41,23 @@ bind:open={showDelete} bind:error> {#if linkedOrgs.length === 1} -

+ This payment method is set as the default for the {linkedOrgs[0].name}. As it has upcoming invoices it cannot be deleted from your account. -

+ {:else if linkedOrgs.length > 1} -

+ This payment method is set as the default for the following organisations. As they have upcoming invoices it cannot be deleted from your account. -

-
    + + {#each linkedOrgs as org} -
  • - {org.name} -
  • + + {org.name} + {/each} -
+ {:else}

Are you sure you want to delete this payment method from your account?

{/if} diff --git a/src/routes/(console)/account/payments/editAddressModal.svelte b/src/routes/(console)/account/payments/editAddressModal.svelte index a70f9d8e7..74938b4af 100644 --- a/src/routes/(console)/account/payments/editAddressModal.svelte +++ b/src/routes/(console)/account/payments/editAddressModal.svelte @@ -51,10 +51,10 @@ type: 'success', message: `Address has been added` }); - trackEvent(Submit.BillingAddressCreate); + trackEvent(Submit.BillingAddressUpdate); } catch (e) { error = e.message; - trackError(e, Submit.BillingAddressCreate); + trackError(e, Submit.BillingAddressUpdate); } } diff --git a/src/routes/(console)/account/payments/editPaymentModal.svelte b/src/routes/(console)/account/payments/editPaymentModal.svelte index bcd68d08c..c054ec557 100644 --- a/src/routes/(console)/account/payments/editPaymentModal.svelte +++ b/src/routes/(console)/account/payments/editPaymentModal.svelte @@ -1,5 +1,5 @@ Create organization - Appwrite - - Create organization - -
- + + + +
- - -

- For more details on our plans, visit our - . -

- - {#if billingPlan !== BillingPlan.FREE} - - - {#if !couponData?.code} +
+
+ + For more details on our plans, visit our + pricing page. + + +
+ {#if selectedPlan !== BillingPlan.FREE} + +
+ +
+ {#if !selectedCoupon?.code} {/if} {/if} - - - {#if billingPlan !== BillingPlan.FREE} - - {:else} - - {/if} - -
- - + + + + {#if selectedPlan !== BillingPlan.FREE} + + {:else} + + {/if} + + - -
+
+ - + diff --git a/src/routes/(console)/create-organization/+page.ts b/src/routes/(console)/create-organization/+page.ts new file mode 100644 index 000000000..6c3cfe2f0 --- /dev/null +++ b/src/routes/(console)/create-organization/+page.ts @@ -0,0 +1,50 @@ +import { BillingPlan } from '$lib/constants'; +import { sdk } from '$lib/stores/sdk'; +import type { PageLoad } from './$types'; +import type { Coupon } from '$lib/sdk/billing'; +import type { Organization } from '$lib/stores/organization'; + +export const load: PageLoad = async ({ url, parent }) => { + const { organizations } = await parent(); + const [coupon, paymentMethods] = await Promise.all([ + getCoupon(url), + sdk.forConsole.billing.listPaymentMethods() + ]); + let plan = getPlanFromUrl(url); + const hasFreeOrganizations = organizations.teams?.some( + (org) => (org as Organization)?.billingPlan === BillingPlan.FREE + ); + if (plan === BillingPlan.FREE && hasFreeOrganizations) { + plan = BillingPlan.PRO; + } + + return { + plan, + coupon, + hasFreeOrganizations, + paymentMethods, + name: url.searchParams.get('name') ?? '' + }; +}; + +function getPlanFromUrl(url: URL): BillingPlan | null { + if (url.searchParams.has('plan')) { + const plan = url.searchParams.get('plan'); + if (plan && plan in BillingPlan) { + return plan as BillingPlan; + } + } + return BillingPlan.FREE; +} + +async function getCoupon(url: URL): Promise { + if (url.searchParams.has('code')) { + const coupon = url.searchParams.get('code'); + try { + return sdk.forConsole.billing.getCoupon(coupon); + } catch (e) { + return null; + } + } + return null; +} diff --git a/src/routes/(console)/createOrganization.svelte b/src/routes/(console)/createOrganization.svelte index 6547a5c51..9896ef451 100644 --- a/src/routes/(console)/createOrganization.svelte +++ b/src/routes/(console)/createOrganization.svelte @@ -8,9 +8,9 @@ import { Dependencies } from '$lib/constants'; import { Submit, trackEvent, trackError } from '$lib/actions/analytics'; import { ID } from '@appwrite.io/console'; - import Alert from '$lib/components/alert.svelte'; import { isCloud } from '$lib/system'; import { base } from '$app/paths'; + import { Alert } from '@appwrite.io/pink-svelte'; export let show = false; @@ -39,18 +39,17 @@ } - + {#if isCloud} - - Get ready for Appwrite Pro + We will soon introduce the much-anticipated Pro plan. Your account will continue to have access to one free organization. If you manage more than one organization, you will need to either upgrade to the Pro plan, transfer your projects to a Pro organization, or migrate to self-hosting. - + - + {/if} - import { Card, Layout, Typography, Input, Tag, Icon, Button } from '@appwrite.io/pink-svelte'; - import { IconPencil } from '@appwrite.io/pink-icons-svelte'; - import { CustomId } from '$lib/components/index.js'; + import { Card } from '@appwrite.io/pink-svelte'; import type { RegionList } from '$lib/sdk/billing'; - import { onMount } from 'svelte'; import { isCloud } from '$lib/system'; import { sdk } from '$lib/stores/sdk'; - import { isValueOfStringEnum } from '$lib/helpers/types'; import { Flag, ID, Region } from '@appwrite.io/console'; import Loading from './loading.svelte'; import { BillingPlan, Dependencies } from '$lib/constants'; @@ -15,27 +11,15 @@ import { base } from '$app/paths'; import { addNotification } from '$lib/stores/notifications'; import { tierToPlan } from '$lib/stores/billing'; + import CreateProject from '$lib/layout/createProject.svelte'; - let showCustomId = false; let isLoading = false; let id: string; let startAnimation = false; let projectName = ''; + let region = Region.Default; export let data: { regions: RegionList | null }; - onMount(() => { - if (isCloud) { - if (data.regions) { - data.regions.regions.forEach((region) => fetch(getFlagUrl(region.flag))); - } - } - }); - - function getFlagUrl(countryCode: string) { - if (!isValueOfStringEnum(Flag, countryCode)) return ''; - return sdk.forProject.avatars.getFlag(countryCode, 22, 15, 100)?.toString(); - } - async function createProject() { isLoading = true; @@ -72,7 +56,7 @@ id ?? ID.unique(), projectName, teamId, - Region.Default + region ); trackEvent(Submit.ProjectCreate, { customId: !!id, @@ -99,28 +83,6 @@ } } } - - function getRegions() { - if (!data.regions) { - return; - } - return data.regions.regions - .filter((region) => region.$id !== 'default') - .sort((regionA, regionB) => { - if (regionA.disabled && !regionB.disabled) { - return 1; - } - return regionA.name > regionB.name ? 1 : -1; - }) - .map((region) => { - return { - label: region.name, - value: region.$id, - leadingHtml: `Region flag`, - disabled: region.disabled - }; - }); - } @@ -144,57 +106,13 @@ height="22" class="u-only-dark" alt="Appwrite Logo" /> -
- - Create your project - - - - - - {#if !showCustomId} -
- { - showCustomId = true; - }}> Project ID -
- {/if} - -
- {#if data.regions} - - Region cannot be changed after creation - - {/if} -
-
- - Create - -
-
+ + {/if}
diff --git a/src/routes/(console)/onboarding/create-project/loading.svelte b/src/routes/(console)/onboarding/create-project/loading.svelte index 6d817bde4..23c467c2b 100644 --- a/src/routes/(console)/onboarding/create-project/loading.svelte +++ b/src/routes/(console)/onboarding/create-project/loading.svelte @@ -197,7 +197,7 @@ --> diff --git a/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte b/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte index ab4b51c7a..bd8077e29 100644 --- a/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte +++ b/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte @@ -2,13 +2,7 @@ import { sdk } from '$lib/stores/sdk'; import { invalidate } from '$app/navigation'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { - CardGrid, - CreditCardBrandImage, - CreditCardInfo, - DropList, - DropListItem - } from '$lib/components'; + import { CardGrid, CreditCardBrandImage, CreditCardInfo } from '$lib/components'; import { BillingPlan, Dependencies } from '$lib/constants'; import { addNotification } from '$lib/stores/notifications'; import { organization } from '$lib/stores/organization'; @@ -21,11 +15,25 @@ import EditPaymentModal from '$routes/(console)/account/payments/editPaymentModal.svelte'; import PaymentModal from '$lib/components/billing/paymentModal.svelte'; import { user } from '$lib/stores/user'; - import { Icon, Tooltip } from '@appwrite.io/pink-svelte'; - import { IconPlus } from '@appwrite.io/pink-icons-svelte'; - - let showDropdown = false; - let showDropdownBackup = false; + import { + ActionMenu, + Card, + Divider, + Icon, + Layout, + Popover, + Table, + Tooltip, + Typography + } from '@appwrite.io/pink-svelte'; + import { + IconDotsHorizontal, + IconInfo, + IconPencil, + IconPlus, + IconSwitchHorizontal, + IconTrash + } from '@appwrite.io/pink-icons-svelte'; let showPayment = false; let showEdit = false; @@ -97,204 +105,178 @@ Payment methods View or update your organization payment methods here. -
- {#if $organization?.paymentMethodId} - - - - - {#if defaultPaymentMethod.userId === $user.$id} - { - isSelectedBackup = false; - showEdit = true; - showDropdown = false; - }}> - Edit - - {/if} - { - isSelectedBackup = false; - showReplace = true; - showDropdown = false; - }}> - Replace - - { - isSelectedBackup = false; - showDelete = true; - showDropdown = false; - }}> - Remove - - - - - {:else} - {@const filteredPaymentMethods = $paymentMethods.paymentMethods.filter( - (o) => !!o.last4 && o.$id !== $organization?.backupPaymentMethodId - )} -
-
-
- - - - {#if $paymentMethods.total} - {#each filteredPaymentMethods as paymentMethod} - { - showDropdown = false; - addPaymentMethod(paymentMethod?.$id); - }}> - -

- Card ending in {paymentMethod.last4} -

- -
-
- {/each} - {/if} - (showPayment = true)}> - Add new payment method - -
-
-
-
- Add a payment method -
-
-
- {/if} -
- {#if $organization?.billingPlan !== BillingPlan.FREE && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION} -
- {#if $organization?.backupPaymentMethodId} -

Backup

- - - - - {#if backupPaymentMethod.userId === $user.$id} - + {#if defaultPaymentMethod?.userId === $user?.$id} + { + isSelectedBackup = false; showEdit = true; - isSelectedBackup = true; - showDropdownBackup = false; }}> Edit - + {/if} - { + isSelectedBackup = false; showReplace = true; - isSelectedBackup = true; - showDropdownBackup = false; }}> Replace - - + { + isSelectedBackup = false; showDelete = true; - isSelectedBackup = true; - showDropdownBackup = false; }}> - Delete - - - - - {:else} - {@const filteredPaymentMethods = $paymentMethods.paymentMethods.filter( - (o) => !!o.last4 && o.$id !== $organization?.paymentMethodId - )} -
- -
- - - -
- - {#if $paymentMethods.total} - {#each filteredPaymentMethods as paymentMethod} - + {#if backupPaymentMethod?.userId === $user?.$id} + { - showDropdownBackup = true; - addBackupPaymentMethod(paymentMethod?.$id); + isSelectedBackup = true; + showEdit = true; }}> - -

- Card ending in {paymentMethod.last4} -

- -
-
- {/each} - {/if} - (showPayment = true)}> - Add new payment method - -
-
-
+ Edit + + {/if} + { + isSelectedBackup = true; + showReplace = true; + }}> + Replace + + { + isSelectedBackup = true; + showDelete = true; + }}> + Remove + + + + + {/if} -
+ + {#if !$organization?.backupPaymentMethodId} + {@const filteredPaymentMethods = $paymentMethods.paymentMethods.filter( + (o) => !!o.last4 && o.$id !== $organization?.paymentMethodId + )} +
+ + + + + + + If your default payment fails, your backup method will be + charged automatically. + + + + + {#if $paymentMethods.total} + {#each filteredPaymentMethods as paymentMethod} + addBackupPaymentMethod(paymentMethod?.$id)}> + + + Card ending in {paymentMethod.last4} + + + {/each} + {/if} + + (showPayment = true)}> + Add new payment method + + + +
+ {/if} + {:else} + {@const filteredPaymentMethods = $paymentMethods.paymentMethods.filter( + (o) => !!o.last4 && o.$id !== $organization?.backupPaymentMethodId + )} + + + + + + {#if $paymentMethods.total} + {#each filteredPaymentMethods as paymentMethod} + addPaymentMethod(paymentMethod?.$id)}> + + + Card ending in {paymentMethod.last4} + + + {/each} + {/if} + + (showPayment = true)}> + Add new payment method + + + + Add a payment method + + {/if}
diff --git a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte index 2353a55d5..b03133998 100644 --- a/src/routes/(console)/organization-[organization]/billing/planSummary.svelte +++ b/src/routes/(console)/organization-[organization]/billing/planSummary.svelte @@ -1,6 +1,6 @@ {#if $organization} @@ -40,166 +64,165 @@ $organization?.billingNextInvoiceDate )}

- - -
-
- - {tierToPlan($organization?.billingPlan)?.name} plan -
- {isTrial || - $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION - ? formatCurrency(0) - : currentPlan - ? formatCurrency(currentPlan?.price) - : ''} -
-
+ + + + + {tierToPlan($organization?.billingPlan)?.name} plan + + + {isTrial || $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION + ? formatCurrency(0) + : currentPlan + ? formatCurrency(currentPlan?.price) + : ''} + + - {#if $organization?.billingPlan !== BillingPlan.FREE && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION && extraUsage > 0} - - - Add-ons{extraMembers ? extraAddons + 1 : extraAddons} -
- -
-
- -
- - {formatCurrency(extraUsage >= 0 ? extraUsage : 0)} - -
-
+ {#if $organization?.billingPlan !== BillingPlan.FREE && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION && extraUsage > 0} + + + {formatCurrency(extraUsage >= 0 ? extraUsage : 0)} + + + {#if extraMembers} + + + Additional members + {formatCurrency( + extraMembers * + (currentPlan?.addons?.member?.price ?? 0) + )} + + + {extraMembers} + + + {/if} + {#if currentInvoice?.usage} + {#each currentInvoice.usage as excess, i} + {#if i > 0 || extraMembers} + + {/if} + {#if ['storage', 'bandwidth'].includes(excess.name)} + {@const excessValue = humanFileSize(excess.value)} + + + {usageNameToLabel( + excess.name + )} + {formatCurrency( + excess.amount + )} + + + + + + {formatNumberWithCommas( + excess.value ?? 0 + )} bytes + + {excessValue.value ?? + 0}{excessValue.unit} + + + + + {/if} + {#if ['users', 'executions'].includes(excess.name)} + + + {usageNameToLabel( + excess.name + )} + {formatCurrency( + excess.amount + )} + + + + + {formatNumberWithCommas(excess.value)} + + {abbreviateNumber(excess.value)} + + + + {/if} + {/each} + {/if} + + + {/if} -
    - {#if extraMembers} -
  • -
    -
    - Additional members -
    -
    - {formatCurrency( - extraMembers * - (currentPlan?.addons?.member?.price ?? - 0) - )} -
    -
    -
    -
    - {extraMembers} -
    -
    -
  • - {/if} - {#if currentInvoice?.usage} - {#each currentInvoice.usage as excess, i} -
  • 0 || extraMembers}> - {#if ['storage', 'bandwidth'].includes(excess.name)} - {@const excessValue = humanFileSize( - excess.value - )} -
    -
    - {excess.name} -
    - {formatCurrency(excess.amount)} -
    -
    - {excessValue.value ?? 0}{excessValue.unit} -
    - {/if} - {#if ['users', 'executions'].includes(excess.name)} -
    -
    - {excess.name} -
    - {formatCurrency(excess.amount)} -
    -
    - {abbreviateNumber(excess.value)} -
    - {/if} -
  • - {/each} - {/if} -
-
- {/if} + {#if $organization?.billingPlan !== BillingPlan.FREE && availableCredit > 0} + + + + Credits to be applied + + + {formatCurrency( + Math.min(availableCredit, currentInvoice?.amount ?? 0) + )} + + + {/if} - {#if $organization?.billingPlan !== BillingPlan.FREE && availableCredit > 0} - - - - - Credits to be applied - -
- -{formatCurrency( - Math.min(availableCredit, currentInvoice?.amount ?? 0) - )} -
-
- {/if} - - {#if $organization?.billingPlan !== BillingPlan.FREE && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION} - - Current total (USD) - - - -
- {formatCurrency( - Math.max( - (currentInvoice?.amount ?? 0) - - Math.min( - availableCredit, - currentInvoice?.amount ?? 0 - ), - 0 - ) - )} -
-
- {/if} -
-
-
+ {#if $organization?.billingPlan !== BillingPlan.FREE && $organization?.billingPlan !== BillingPlan.GITHUB_EDUCATION} + + + + + Current total (USD) + + + + Estimates are updated daily and may differ from your + final invoice. + + + + + + {formatCurrency( + Math.max( + (currentInvoice?.amount ?? 0) - + Math.min(availableCredit, currentInvoice?.amount ?? 0), + 0 + ) + )} + + + {/if} + + {#if $organization?.billingPlan === BillingPlan.FREE || $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION} @@ -212,7 +235,7 @@ disabled={$organization?.markedForDeletion} href={$upgradeURL} on:click={() => - trackEvent('click_organization_upgrade', { + trackEvent(Click.OrganizationClickUpgrade, { from: 'button', source: 'billing_tab' })}> diff --git a/src/routes/(console)/organization-[organization]/billing/replaceAddress.svelte b/src/routes/(console)/organization-[organization]/billing/replaceAddress.svelte index 2018801ab..2f5c85539 100644 --- a/src/routes/(console)/organization-[organization]/billing/replaceAddress.svelte +++ b/src/routes/(console)/organization-[organization]/billing/replaceAddress.svelte @@ -1,7 +1,7 @@ diff --git a/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte b/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte index ee76f3afa..fb82105a8 100644 --- a/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte +++ b/src/routes/(console)/organization-[organization]/billing/retryPaymentModal.svelte @@ -7,12 +7,7 @@ import { addNotification } from '$lib/stores/notifications'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { page } from '$app/stores'; - import { - confirmPayment, - initializeStripe, - isStripeInitialized, - submitStripeCard - } from '$lib/stores/stripe'; + import { confirmPayment, isStripeInitialized, submitStripeCard } from '$lib/stores/stripe'; import { organization } from '$lib/stores/organization'; import { toLocaleDate } from '$lib/helpers/date'; import { PaymentBoxes } from '$lib/components/billing'; @@ -98,15 +93,10 @@ } } - $: if (paymentMethodId === null && !$isStripeInitialized) { - initializeStripe(); - } - + $: filteredMethods = $paymentMethods?.paymentMethods.filter((method) => !!method?.last4); $: if (paymentMethodId) { isStripeInitialized.set(false); } - $: filteredMethods = $paymentMethods?.paymentMethods.filter((method) => !!method?.last4); - $: if (!show) { invoice = null; } diff --git a/src/routes/(console)/organization-[organization]/billing/wizard/paymentDetails.svelte b/src/routes/(console)/organization-[organization]/billing/wizard/paymentDetails.svelte index 8a142ac57..456f446ca 100644 --- a/src/routes/(console)/organization-[organization]/billing/wizard/paymentDetails.svelte +++ b/src/routes/(console)/organization-[organization]/billing/wizard/paymentDetails.svelte @@ -5,7 +5,7 @@ import type { PaymentList } from '$lib/sdk/billing'; import { invalidate } from '$app/navigation'; import { Dependencies } from '$lib/constants'; - import { initializeStripe, isStripeInitialized, submitStripeCard } from '$lib/stores/stripe'; + import { isStripeInitialized, submitStripeCard } from '$lib/stores/stripe'; import { sdk } from '$lib/stores/sdk'; import { PaymentBoxes } from '$lib/components/billing'; import { addCreditWizardStore } from '../store'; @@ -30,10 +30,6 @@ } } - $: if ($addCreditWizardStore.paymentMethodId === null && !$isStripeInitialized) { - initializeStripe(); - } - $: if ($addCreditWizardStore.paymentMethodId) { isStripeInitialized.set(false); } @@ -49,10 +45,8 @@

Payment method

- - - +
diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte index f17e1b9cc..8fc9c44f4 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.svelte +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.svelte @@ -3,7 +3,6 @@ import { base } from '$app/paths'; import { page } from '$app/stores'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { Alert } from '$lib/components'; import { EstimatedTotalBox, PlanComparisonBox, @@ -12,114 +11,48 @@ import PlanExcess from '$lib/components/billing/planExcess.svelte'; import PlanSelection from '$lib/components/billing/planSelection.svelte'; import ValidateCreditModal from '$lib/components/billing/validateCreditModal.svelte'; - import Default from '$lib/components/roles/default.svelte'; import { BillingPlan, Dependencies, feedbackDowngradeOptions } from '$lib/constants'; - import { - Button, - Form, - FormList, - InputSelect, - InputTags, - InputTextarea, - Label - } from '$lib/elements/forms'; + import { Button, Form, InputSelect, InputTags, InputTextarea } from '$lib/elements/forms'; import { formatCurrency } from '$lib/helpers/numbers.js'; - import { - WizardSecondaryContainer, - WizardSecondaryContent, - WizardSecondaryFooter - } from '$lib/layout'; + import { Wizard } from '$lib/layout'; import { type Coupon, type PaymentList } from '$lib/sdk/billing'; - import { plansInfo, tierToPlan, type Tier } from '$lib/stores/billing'; + import { plansInfo, tierToPlan } from '$lib/stores/billing'; import { addNotification } from '$lib/stores/notifications'; - import { - currentPlan, - organization, - organizationList, - type Organization - } from '$lib/stores/organization'; + import { organization } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; import { user } from '$lib/stores/user'; import { VARS } from '$lib/system'; import { IconPlus } from '@appwrite.io/pink-icons-svelte'; - import { Icon } from '@appwrite.io/pink-svelte'; - import { onMount } from 'svelte'; + import { Alert, Fieldset, Icon, Layout, Link, Typography } from '@appwrite.io/pink-svelte'; import { writable } from 'svelte/store'; export let data; - $: anyOrgFree = $organizationList.teams?.find( - (org) => (org as Organization)?.billingPlan === BillingPlan.FREE - ); - const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$/i; + let selectedPlan: BillingPlan = data.plan as BillingPlan; + let selectedCoupon: Partial | null = data.coupon; let previousPage: string = base; let showExitModal = false; - afterNavigate(({ from }) => { - previousPage = from?.url?.pathname || previousPage; - }); - let formComponent: Form; let isSubmitting = writable(false); - let methods: PaymentList; - let billingPlan: Tier = $organization.billingPlan; - let paymentMethodId: string; + let paymentMethodId: string = + data.organization.paymentMethodId ?? + data.paymentMethods.paymentMethods.find((method) => !!method?.last4)?.$id; let collaborators: string[] = data?.members?.memberships ?.map((m) => { if (m.userEmail !== $user.email) return m.userEmail; }) ?.filter(Boolean) ?? []; - let couponData: Partial = { - code: null, - status: null, - credits: null - }; let taxId: string; let billingBudget: number; let showCreditModal = false; - let feedbackDowngradeReason: string; let feedbackMessage: string; - let selfService: boolean; - onMount(async () => { - if ($page.url.searchParams.has('code')) { - const coupon = $page.url.searchParams.get('code'); - try { - const response = await sdk.forConsole.billing.getCoupon(coupon); - couponData = response; - } catch (e) { - couponData = { - code: null, - status: null, - credits: null - }; - } - } - if ($page.url.searchParams.has('plan')) { - const plan = $page.url.searchParams.get('plan'); - if (plan && plan in BillingPlan) { - billingPlan = plan as BillingPlan; - } - } - if ($organization?.billingPlan === BillingPlan.SCALE) { - billingPlan = BillingPlan.SCALE; - } else { - billingPlan = BillingPlan.PRO; - } - - selfService = $currentPlan?.selfService ?? true; + afterNavigate(({ from }) => { + previousPage = from?.url?.pathname || previousPage; }); - async function loadPaymentMethods() { - methods = await sdk.forConsole.billing.listPaymentMethods(); - - paymentMethodId = - $organization?.paymentMethodId ?? - methods.paymentMethods.find((method) => !!method?.last4)?.$id ?? - null; - } - async function handleSubmit() { if (isDowngrade) { await downgrade(); @@ -131,8 +64,8 @@ async function downgrade() { try { await sdk.forConsole.billing.updatePlan( - $organization.$id, - billingPlan, + data.organization.$id, + selectedPlan, paymentMethodId, null ); @@ -143,14 +76,14 @@ 'Content-Type': 'application/json' }, body: JSON.stringify({ - from: tierToPlan($organization.billingPlan).name, - to: tierToPlan(billingPlan).name, - email: $user.email, + from: tierToPlan(data.organization.billingPlan).name, + to: tierToPlan(selectedPlan).name, + email: data.account.email, reason: feedbackDowngradeOptions.find( (option) => option.value === feedbackDowngradeReason )?.label, - orgId: $organization.$id, - userId: $user.$id, + orgId: data.organization.$id, + userId: data.account.$id, message: feedbackMessage ?? '' }) }); @@ -161,12 +94,12 @@ isHtml: true, message: ` ${$organization.name} will change to ${ - tierToPlan(billingPlan).name + tierToPlan(selectedPlan).name } plan at the end of the current billing cycle.` }); trackEvent(Submit.OrganizationDowngrade, { - plan: tierToPlan(billingPlan)?.name + plan: tierToPlan(selectedPlan)?.name }); } catch (e) { addNotification({ @@ -180,15 +113,15 @@ async function upgrade() { try { const org = await sdk.forConsole.billing.updatePlan( - $organization.$id, - billingPlan, + data.organization.$id, + selectedPlan, paymentMethodId, null ); //Add coupon - if (couponData?.code) { - await sdk.forConsole.billing.addCredit(org.$id, couponData.code); + if (selectedCoupon?.code) { + await sdk.forConsole.billing.addCredit(org.$id, selectedCoupon.code); trackEvent(Submit.CreditRedeem); } @@ -230,7 +163,7 @@ }); trackEvent(Submit.OrganizationUpgrade, { - plan: tierToPlan(billingPlan)?.name + plan: tierToPlan(selectedPlan)?.name }); } catch (e) { addNotification({ @@ -241,124 +174,132 @@ } } - $: isUpgrade = billingPlan > $organization.billingPlan; - $: isDowngrade = billingPlan < $organization.billingPlan; - $: if (billingPlan !== BillingPlan.FREE) { - loadPaymentMethods(); - } - $: isButtonDisabled = $organization.billingPlan === billingPlan; + $: isUpgrade = data.plan > data.organization.billingPlan; + $: isDowngrade = data.plan < data.organization.billingPlan; + $: isButtonDisabled = $organization.billingPlan === selectedPlan; + + $: console.log(data.paymentMethods); Change plan - Appwrite - - Change plan - -
- -

- For more details on our plans, visit our - . -

- {#if !selfService} - Your contract is not eligible for manual changes. Please reach out to schedule - a call or setup a dialog. - {/if} - - - {#if isDowngrade} - {#if billingPlan === BillingPlan.FREE} - - {:else if billingPlan === BillingPlan.PRO && $organization.billingPlan === BillingPlan.SCALE && collaborators?.length > 0} - {@const extraMembers = collaborators?.length ?? 0} - - - Your monthly payments will be adjusted for the Pro plan - - After switching plans, - you will be charged {formatCurrency( - extraMembers * - ($plansInfo?.get(billingPlan)?.addons?.member?.price ?? 0) - )} monthly for {extraMembers} team members. This will be reflected in - your next invoice. - + + + +
+ + For more details on our plans, visit our + pricing page. + + {#if !data.selfService} + + Your contract is not eligible for manual changes. Please reach out to + schedule a call or setup a dialog. + {/if} - {/if} + + + {#if isDowngrade} + {#if selectedPlan === BillingPlan.FREE} + + {:else if selectedPlan === BillingPlan.PRO && data.organization.billingPlan === BillingPlan.SCALE && collaborators?.length > 0} + {@const extraMembers = collaborators?.length ?? 0} + {@const price = formatCurrency( + extraMembers * + ($plansInfo?.get(selectedPlan)?.addons?.member?.price ?? 0) + )} + + + Your monthly payments will be adjusted for the Pro plan + + After switching plans, + you will be charged {price} monthly for {extraMembers} team members. + This will be reflected in your next invoice. + + {/if} + {/if} +
+ - {#if billingPlan !== BillingPlan.FREE && $organization.billingPlan === BillingPlan.FREE} - + {#if selectedPlan !== BillingPlan.FREE && data.organization.billingPlan === BillingPlan.FREE} + +
- - {#if !couponData?.code} - - {/if} - +
+ {#if !selectedCoupon?.code} + + {/if} {/if} - {#if isDowngrade && billingPlan === BillingPlan.FREE} - - - - + {#if isDowngrade && selectedPlan === BillingPlan.FREE} +
+ + + + +
{/if} - - - {#if billingPlan !== BillingPlan.FREE && $organization.billingPlan !== billingPlan && $organization.billingPlan !== BillingPlan.CUSTOM} - - {:else if $organization.billingPlan !== BillingPlan.CUSTOM} - - {/if} - -
- - + + + + {#if selectedPlan !== BillingPlan.FREE && data.organization.billingPlan !== selectedPlan && data.organization.billingPlan !== BillingPlan.CUSTOM} + + {:else if data.organization.billingPlan !== BillingPlan.CUSTOM} + + {/if} + + - -
+ + - + diff --git a/src/routes/(console)/organization-[organization]/change-plan/+page.ts b/src/routes/(console)/organization-[organization]/change-plan/+page.ts index e14b3d468..e3d79c69e 100644 --- a/src/routes/(console)/organization-[organization]/change-plan/+page.ts +++ b/src/routes/(console)/organization-[organization]/change-plan/+page.ts @@ -1,11 +1,58 @@ -import { Dependencies } from '$lib/constants'; +import { BillingPlan, Dependencies } from '$lib/constants'; +import { sdk } from '$lib/stores/sdk'; import type { PageLoad } from './$types'; +import type { Coupon } from '$lib/sdk/billing'; +import type { Organization } from '$lib/stores/organization'; -export const load: PageLoad = async ({ depends, parent }) => { - const { members } = await parent(); +export const load: PageLoad = async ({ depends, parent, url }) => { + const { members, organization, currentPlan, organizations } = await parent(); + depends(Dependencies.UPGRADE_PLAN); + + const [coupon, paymentMethods] = await Promise.all([ + getCoupon(url), + sdk.forConsole.billing.listPaymentMethods() + ]); + + let plan = getPlanFromUrl(url); + + if (organization?.billingPlan === BillingPlan.SCALE) { + plan = BillingPlan.SCALE; + } else { + plan = BillingPlan.PRO; + } + + const selfService = currentPlan?.selfService ?? true; + const hasFreeOrgs = organizations.teams?.some( + (org) => (org as Organization)?.billingPlan === BillingPlan.FREE + ); - depends(Dependencies.ORGANIZATION); return { - members + members, + plan, + coupon, + selfService, + hasFreeOrgs, + paymentMethods }; }; + +function getPlanFromUrl(url: URL): BillingPlan | null { + if (url.searchParams.has('plan')) { + const plan = url.searchParams.get('plan'); + if (plan && plan in BillingPlan) { + return plan as BillingPlan; + } + } +} + +async function getCoupon(url: URL): Promise { + if (url.searchParams.has('code')) { + const coupon = url.searchParams.get('code'); + try { + return sdk.forConsole.billing.getCoupon(coupon); + } catch (e) { + return null; + } + } + return null; +} diff --git a/src/routes/(console)/organization-[organization]/createProjectCloud.svelte b/src/routes/(console)/organization-[organization]/createProjectCloud.svelte index c42d662c6..9eeeeb96d 100644 --- a/src/routes/(console)/organization-[organization]/createProjectCloud.svelte +++ b/src/routes/(console)/organization-[organization]/createProjectCloud.svelte @@ -1,21 +1,22 @@ - + diff --git a/src/routes/(console)/organization-[organization]/domains/+page.svelte b/src/routes/(console)/organization-[organization]/domains/+page.svelte index 2eeff4d7d..4abfa7f3c 100644 --- a/src/routes/(console)/organization-[organization]/domains/+page.svelte +++ b/src/routes/(console)/organization-[organization]/domains/+page.svelte @@ -29,6 +29,7 @@ import SearchQuery from '$lib/components/searchQuery.svelte'; import { app } from '$lib/stores/app'; import type { Domain } from '$lib/sdk/domains'; + import { Click, trackEvent } from '$lib/actions/analytics'; export let data; @@ -42,7 +43,13 @@ - @@ -63,18 +70,9 @@ - - - - {domain.domain} - - - - + + {domain.domain} + {domain?.registrar || '-'} {domain?.nameservers || '-'} @@ -86,7 +84,7 @@ {domain.autoRenewal ? 'On' : 'Off'} - + +
+ + + + + + + + diff --git a/src/routes/(console)/project-[project]/databases/database-[database]/backups/policy.svelte b/src/routes/(console)/project-[project]/databases/database-[database]/backups/policy.svelte index ad7224c2c..9feb4a792 100644 --- a/src/routes/(console)/project-[project]/databases/database-[database]/backups/policy.svelte +++ b/src/routes/(console)/project-[project]/databases/database-[database]/backups/policy.svelte @@ -15,7 +15,7 @@ import EmptyLight from '$lib/images/backups/backups-light.png'; import type { BackupPolicy, BackupPolicyList } from '$lib/sdk/backups'; import { backupFrequencies } from '$lib/helpers/backups'; - import { trackEvent } from '$lib/actions/analytics'; + import { Click, trackEvent } from '$lib/actions/analytics'; import { Icon, Tooltip } from '@appwrite.io/pink-svelte'; import { IconPlus } from '@appwrite.io/pink-icons-svelte'; @@ -168,7 +168,7 @@ showDelete = true; selectedPolicy = policy; showDropdown[index] = false; - trackEvent('click_policy_delete'); + trackEvent(Click.PolicyDeleteClick); }}> Delete diff --git a/src/routes/(console)/project-[project]/databases/database-[database]/backups/restoreModal.svelte b/src/routes/(console)/project-[project]/databases/database-[database]/backups/restoreModal.svelte index 706415d3d..10d7252fc 100644 --- a/src/routes/(console)/project-[project]/databases/database-[database]/backups/restoreModal.svelte +++ b/src/routes/(console)/project-[project]/databases/database-[database]/backups/restoreModal.svelte @@ -1,6 +1,6 @@ @@ -22,7 +23,12 @@ - + diff --git a/src/routes/(console)/project-[project]/databases/database-[database]/collection-[collection]/subNavigation.svelte b/src/routes/(console)/project-[project]/databases/database-[database]/collection-[collection]/subNavigation.svelte index 66b5fc058..abdf465a2 100644 --- a/src/routes/(console)/project-[project]/databases/database-[database]/collection-[collection]/subNavigation.svelte +++ b/src/routes/(console)/project-[project]/databases/database-[database]/collection-[collection]/subNavigation.svelte @@ -4,8 +4,13 @@ import { showCreate } from '../store'; import type { PageData } from './$types'; import { showSubNavigation } from '$lib/stores/layout'; - import { Icon, Sidebar, Navbar, Layout, Link } from '@appwrite.io/pink-svelte'; - import { IconChevronDown, IconDatabase, IconTable } from '@appwrite.io/pink-icons-svelte'; + import { Icon, Sidebar, Navbar, Layout, Link, ActionMenu } from '@appwrite.io/pink-svelte'; + import { + IconChevronDown, + IconDatabase, + IconPlus, + IconTable + } from '@appwrite.io/pink-icons-svelte'; import { isTabletViewport } from '$lib/stores/viewport'; import { BottomSheet } from '$lib/components'; @@ -15,7 +20,7 @@ $: collectionId = $page.params.collection; $: sortedCollections = data?.allCollections?.collections?.sort((a, b) => - a.$updatedAt > b.$updatedAt ? -1 : 1 + a.name.localeCompare(b.name) ); $: selectedCollection = sortedCollections?.find( @@ -49,7 +54,7 @@ {@const isSelected = collectionId === collection.$id}
  • {/if}
  • - + + { + $showCreate = true; + $showSubNavigation = false; + }} + leadingIcon={IconPlus}> + Create collection + +