diff --git a/src/lib/components/fakeModal.svelte b/src/lib/components/fakeModal.svelte index f9ab788d6..74deb2ef7 100644 --- a/src/lib/components/fakeModal.svelte +++ b/src/lib/components/fakeModal.svelte @@ -16,7 +16,6 @@ return; }; export let title = ''; - export let description = ''; let backdrop: HTMLDivElement; diff --git a/src/lib/elements/forms/button.svelte b/src/lib/elements/forms/button.svelte index af466aa46..48e986b8d 100644 --- a/src/lib/elements/forms/button.svelte +++ b/src/lib/elements/forms/button.svelte @@ -15,6 +15,7 @@ export let external = false; export let href: string = null; export let fullWidth = false; + export let fullWidthMobile = false; export let ariaLabel: string = null; export let noMargin = false; export let event: string = null; @@ -47,6 +48,7 @@ text && 'is-text', danger && 'is-danger', fullWidth && 'is-full-width', + fullWidthMobile && 'is-full-width-mobile', noMargin && 'u-padding-inline-0', classes ] diff --git a/src/lib/layout/headerAlert.svelte b/src/lib/layout/headerAlert.svelte new file mode 100644 index 000000000..df1887aee --- /dev/null +++ b/src/lib/layout/headerAlert.svelte @@ -0,0 +1,26 @@ + + +
+
+
+
diff --git a/src/lib/layout/index.ts b/src/lib/layout/index.ts index 9537e351b..7b1c1ddc0 100644 --- a/src/lib/layout/index.ts +++ b/src/lib/layout/index.ts @@ -17,3 +17,4 @@ export { default as Activity } from './activity.svelte'; export { default as Progress } from './progress.svelte'; export { default as GridHeader } from './gridHeader.svelte'; export { default as ContainerHeader } from './containerHeader.svelte'; +export { default as HeaderAlert } from './headerAlert.svelte'; diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index 2fc95759e..e67dcbf86 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -37,6 +37,7 @@ export type Invoice = { to: string; status: string; dueAt: string; + clientSecret: string; }; export type InvoiceList = { @@ -44,6 +45,15 @@ export type InvoiceList = { total: number; }; +export type Coupon = { + $id: string; + code: string; + credits: number; + expiration: string; + status: string; // 'active' | 'disabled' | 'expired' + validity: number; +}; + export type Credit = { /** * Credit ID. @@ -159,8 +169,19 @@ export type AggregationList = { total: number; }; +export type AllowedRegions = + | 'eu-de' + | 'us-nyc' + | 'us-sfo' + | 'ap-in' + | 'eu-gb' + | 'eu-nl' + | 'ap-sg' + | 'ap-ca' + | 'ap-au'; + export type Region = { - $id: string; + $id: AllowedRegions; name: string; disabled: boolean; default: boolean; @@ -571,7 +592,7 @@ export class Billing { ); } - async getCoupon(couponId: string): Promise> { + async getCoupon(couponId: string): Promise { const path = `/console/coupons/${couponId}`; const params = { couponId diff --git a/src/routes/console/+layout.svelte b/src/routes/console/+layout.svelte index ee09e81f6..a01736043 100644 --- a/src/routes/console/+layout.svelte +++ b/src/routes/console/+layout.svelte @@ -1,7 +1,7 @@ @@ -40,13 +43,25 @@ title="Delete organization" onSubmit={deleteOrg} bind:show={showDelete} + bind:error icon="exclamation" state="warning" headerDivider={false}> -

- Are you sure you want to delete {$organization.name}? All projects ({$organization.total}) - and data associated with this organization will be deleted. This action is irreversible. -

+ {#if TEMPORARY_SET_FOR_DELETION} +

+ The organization {$organization.name} will be flagged for deletion. +

+ +

+ All existing projects will be paused and the organization will be deleted once your + upcoming invoice is processed on {toLocaleDate($organization.billingNextInvoiceDate)}. +

+ {:else} +

+ Are you sure you want to delete {$organization.name}? All projects ({$projects.total}) + and data associated with this organization will be deleted. This action is irreversible. +

+ {/if} diff --git a/src/routes/console/organization-[organization]/store.ts b/src/routes/console/organization-[organization]/store.ts index c753c34de..07b0ca110 100644 --- a/src/routes/console/organization-[organization]/store.ts +++ b/src/routes/console/organization-[organization]/store.ts @@ -1,3 +1,6 @@ -import { writable } from 'svelte/store'; +import { page } from '$app/stores'; +import type { Models } from '@appwrite.io/console'; +import { derived, writable } from 'svelte/store'; export const showExcess = writable(false); +export const projects = derived(page, ($page) => $page.data?.projects as Models.ProjectList); diff --git a/src/routes/console/organization-[organization]/wizard/step2.svelte b/src/routes/console/organization-[organization]/wizard/step2.svelte index df817841e..7eae737f7 100644 --- a/src/routes/console/organization-[organization]/wizard/step2.svelte +++ b/src/routes/console/organization-[organization]/wizard/step2.svelte @@ -5,12 +5,35 @@ import { sdk } from '$lib/stores/sdk'; import { onMount } from 'svelte'; import { createProject } from './store'; - import type { RegionList } from '$lib/sdk/billing'; + import type { Region, RegionList } from '$lib/sdk/billing'; + import { addNotification } from '$lib/stores/notifications'; + import type { Models } from '@appwrite.io/console'; let regions: RegionList; + let prefs: Models.Preferences; onMount(async () => { regions = await sdk.forConsole.billing.listRegions(); + prefs = await sdk.forConsole.account.getPrefs(); }); + + async function notifyRegion(selectedRegion: Region) { + try { + let newPrefs = { ...prefs }; + newPrefs.notifications = newPrefs.notifications ?? []; + newPrefs.notifications = [...newPrefs.notifications, selectedRegion.$id]; + const response = await sdk.forConsole.account.updatePrefs(newPrefs); + prefs = response.prefs; + addNotification({ + type: 'success', + isHtml: true, + message: `You will be notified when ${selectedRegion.name} region is available` + }); + } catch (error) { + console.log(error); + } + } + + $: notifications = prefs?.notifications ?? []; @@ -39,10 +62,17 @@ flag={region.flag} name={region.name} />

{region.name}

- - + {#if !notifications.includes(region.$id)} + { + notifyRegion(region); + }}> + + {/if} {:else} { $supportData = { @@ -25,7 +26,7 @@ }); async function handleSubmit() { - const response = await fetch('https://growth.appwrite.io/v1/support', { + const response = await fetch(`https://${VARS.GROWTH_ENDPOINT}/v1/support`, { method: 'POST', headers: { 'Content-Type': 'application/json' diff --git a/src/routes/console/wizard/cloudOrganization/addressDetails.svelte b/src/routes/console/wizard/cloudOrganization/addressDetails.svelte index 478de06d6..c502e545d 100644 --- a/src/routes/console/wizard/cloudOrganization/addressDetails.svelte +++ b/src/routes/console/wizard/cloudOrganization/addressDetails.svelte @@ -1,10 +1,10 @@ @@ -37,58 +34,97 @@ Billing address Add a billing address for your organization. - - Depending on your location, your billing address might need to be in the same country - associated with the payment method for your organization. - - - - - - - - - - - +

Billing address

+ +
+ {#if addressList?.total} + {#each addressList.billingAddresses as address} +
+ +
+

{address.streetAddress}

+ {#if address?.addressLine2} +

{address.addressLine2}

+ {/if} +

{address.city}

+

{address.state}

+

{address.postalCode}

+

{address.country}

+
+
+
+ {/each} + {/if} + +
+ {#if addressList?.total} + + Add new billing address + + {/if} + {#if $changeOrganizationTier.billingAddressId === null} + + + + + + + + + + + + {/if} +
+
diff --git a/src/routes/console/wizard/cloudOrganizationChangeTier/choosePlan.svelte b/src/routes/console/wizard/cloudOrganizationChangeTier/choosePlan.svelte index 847840580..100615b30 100644 --- a/src/routes/console/wizard/cloudOrganizationChangeTier/choosePlan.svelte +++ b/src/routes/console/wizard/cloudOrganizationChangeTier/choosePlan.svelte @@ -41,14 +41,15 @@ (plan) => plan.$id === $changeOrganizationTier.billingPlan ); + const totBandwidth = usage?.bandwidth?.length > 0 ? usage.bandwidth[0].value : 0; + const totUsers = usage?.users?.length > 0 ? usage.users[0].value : 0; $changeOrganizationTier.limitOverflow = { - bandwidth: - usage.bandwidth[0] > plan.bandwidth ? usage.bandwidth[0] - plan.bandwidth : 0, + bandwidth: totBandwidth > plan.bandwidth ? totBandwidth - plan.bandwidth : 0, storage: usage.storage[0] > sizeToBytes(plan.storage, 'GB') ? usage.storage[0] - plan.storage : 0, - users: usage.users[0] > plan.users ? usage.users[0] - plan.users : 0, + users: totUsers > plan.users ? totUsers - plan.users : 0, executions: usage.executions[0] > plan.executions ? usage.executions[0] - plan.executions : 0, members: members.total > plan.members ? members.total - (plan.members || Infinity) : 0 diff --git a/src/routes/console/wizard/cloudOrganizationChangeTier/confirmDetails.svelte b/src/routes/console/wizard/cloudOrganizationChangeTier/confirmDetails.svelte index 789b100d6..3b403fc01 100644 --- a/src/routes/console/wizard/cloudOrganizationChangeTier/confirmDetails.svelte +++ b/src/routes/console/wizard/cloudOrganizationChangeTier/confirmDetails.svelte @@ -4,6 +4,7 @@ import { Button, FormList, InputText, InputTextarea } from '$lib/elements/forms'; import { toLocaleDate } from '$lib/helpers/date'; import { WizardStep } from '$lib/layout'; + import type { Coupon } from '$lib/sdk/billing'; import { plansInfo } from '$lib/stores/billing'; import { organization } from '$lib/stores/organization'; import { sdk } from '$lib/stores/sdk'; @@ -21,6 +22,12 @@ let coupon: string = null; let comment: string = null; + let couponData: Partial = { + code: null, + status: null, + credits: null + }; + let showCoupon: boolean = false; async function fetchCard() { try { const card = await sdk.forConsole.billing.getPaymentMethod( @@ -32,15 +39,30 @@ } } - async function applyCredit() { + async function addCoupon() { try { - await sdk.forConsole.billing.getCoupon(coupon); + const response = await sdk.forConsole.billing.getCoupon(coupon); coupon = null; + couponData = response; + showCoupon = true; + $changeOrganizationTier.couponCode = response.$id; } catch (error) { console.log(error); + couponData.code = coupon; + couponData.status = 'error'; + showCoupon = true; } } + function removeCoupon() { + couponData = { + code: null, + status: null, + credits: null + }; + showCoupon = false; + } + $: downgradeToStarter = $changeOrganizationTier.billingPlan === 'tier-0'; $: if (!$isUpgrade) { $changeOrganizationFinalAction = 'Confirm plan change'; @@ -55,7 +77,6 @@ there is a specific reason you chose to change your plan at this time, please let us know.
- - + - + + {#if showCoupon} + {#if couponData?.status === 'error'} +
+ + + {couponData.code} is not a valid promo code + +
+ {:else} +
+
+ + + {couponData.code} applied (-${couponData.credits}) + +
+ +
+ {/if} + {/if}
{#if $changeOrganizationTier.billingPlan !== 'tier-0'} diff --git a/src/routes/console/wizard/cloudOrganizationChangeTier/store.ts b/src/routes/console/wizard/cloudOrganizationChangeTier/store.ts index 9edfd2a3e..25f69bf5d 100644 --- a/src/routes/console/wizard/cloudOrganizationChangeTier/store.ts +++ b/src/routes/console/wizard/cloudOrganizationChangeTier/store.ts @@ -11,7 +11,8 @@ export const changeOrganizationTier = writable<{ id?: string; billingPlan: Tier; paymentMethodId: string; - billingAddress: Address; + billingAddressId: string; + billingAddress?: Address; billingBudget?: number; collaborators?: string[]; isOverLimit?: boolean; @@ -23,12 +24,15 @@ export const changeOrganizationTier = writable<{ members?: number; }; taxId?: string; + feedbackMessage?: string; + couponCode?: string; }>({ id: null, billingPlan: 'tier-1', paymentMethodId: null, collaborators: [], isOverLimit: false, + billingAddressId: null, billingAddress: { $id: null, streetAddress: null, @@ -40,13 +44,3 @@ export const changeOrganizationTier = writable<{ }, taxId: null }); - -export const currentBillingAddress = writable
({ - $id: null, - streetAddress: null, - addressLine2: null, - city: null, - state: null, - postalCode: null, - country: null -});