diff --git a/src/lib/components/billing/alerts/markedForDeletion.svelte b/src/lib/components/billing/alerts/markedForDeletion.svelte new file mode 100644 index 000000000..e6323850b --- /dev/null +++ b/src/lib/components/billing/alerts/markedForDeletion.svelte @@ -0,0 +1,14 @@ + + +{#if $organization?.markedForDeletion && !$page.url.pathname.includes('/console/account')} + + + All existing projects in the {$organization.name} organization have been paused. This organization + will be deleted once your upcoming invoice is processed successfully. + + +{/if} diff --git a/src/lib/components/billing/alerts/paymentAuthRequired.svelte b/src/lib/components/billing/alerts/paymentAuthRequired.svelte new file mode 100644 index 000000000..257b86d16 --- /dev/null +++ b/src/lib/components/billing/alerts/paymentAuthRequired.svelte @@ -0,0 +1,29 @@ + + +{#if $actionRequiredInvoices && $actionRequiredInvoices.total && !$page.url.pathname.includes('/console/account')} + + Please authorize your upcoming payment for {$organization.name}. Your bank requires this + security measure to proceed with payment. + + + + + +{/if} diff --git a/src/lib/components/billing/alerts/projectsAtRisk.svelte b/src/lib/components/billing/alerts/projectsAtRisk.svelte new file mode 100644 index 000000000..348fe3ebd --- /dev/null +++ b/src/lib/components/billing/alerts/projectsAtRisk.svelte @@ -0,0 +1,32 @@ + + +{#if $failedInvoice && !$page.url.pathname.includes('/console/account')} + {@const daysPassed = diffDays(new Date($failedInvoice.dueAt), new Date())} + + + {#if daysPassed > 30} + Your scheduled payment on {toLocaleDate($failedInvoice?.dueAt)} failed. To resume + write access of your organization, please update your billing details. + {:else} + Your scheduled payment on {toLocaleDate($failedInvoice?.dueAt)} failed. Access + to paid projects within this organization will be disabled if no action is taken within + 30 days. + {/if} + + + + + +{/if} diff --git a/src/lib/sdk/billing.ts b/src/lib/sdk/billing.ts index b2d22568c..09ec9294e 100644 --- a/src/lib/sdk/billing.ts +++ b/src/lib/sdk/billing.ts @@ -86,9 +86,9 @@ export type Credit = { */ credits: number; /** - * Used up credit amount + * Remaining up credit amount */ - creditsUsed: number; + creditsRemaining: number; /** * Credit expiration time in ISO 8601 format. */ diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index a07f8de1d..3dd4640e8 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -2,7 +2,7 @@ import { page } from '$app/stores'; import { derived, get, writable } from 'svelte/store'; import { sdk } from './sdk'; import { organization } from './organization'; -import type { AddressesList, Invoice, PaymentList, PlansInfo } from '$lib/sdk/billing'; +import type { InvoiceList, AddressesList, Invoice, PaymentList, PlansInfo } from '$lib/sdk/billing'; import { isCloud } from '$lib/system'; import { cachedStore } from '$lib/helpers/cache'; @@ -87,6 +87,8 @@ export const failedInvoice = cachedStore< }; }); +export const actionRequiredInvoices = writable(null); + export type TierData = { name: string; description: string; diff --git a/src/lib/stores/headerAlert.ts b/src/lib/stores/headerAlert.ts new file mode 100644 index 000000000..9b9cae830 --- /dev/null +++ b/src/lib/stores/headerAlert.ts @@ -0,0 +1,49 @@ +import type { SvelteComponent } from 'svelte'; +import { writable } from 'svelte/store'; + +export type HeaderAlert = { + show: boolean; + component: typeof SvelteComponent; + importance: number; +}; + +export type HeaderAlertStore = { + components: HeaderAlert[]; +}; + +function createHeaderAlertStore() { + const { subscribe, update, set } = writable({ + components: [] + }); + + return { + subscribe, + set, + add: (component: HeaderAlert) => { + update((n) => { + n.components.push(component); + n.components = n.components; + return n; + }); + }, + get: (): HeaderAlert => { + //return higest importance component + let component = { + show: false, + component: null, + importance: 0 + }; + update((n) => { + n.components.forEach((c) => { + if (c.importance > component.importance) { + component = c; + } + }); + return n; + }); + return component; + } + }; +} + +export const headerAlert = createHeaderAlertStore(); diff --git a/src/routes/console/+layout.svelte b/src/routes/console/+layout.svelte index 813f78833..38d186109 100644 --- a/src/routes/console/+layout.svelte +++ b/src/routes/console/+layout.svelte @@ -14,11 +14,11 @@ import { loading, requestedMigration } from '../store'; import Create from './createOrganization.svelte'; import { - failedInvoice, daysLeftInTrial, tierToPlan, readOnly, - showUsageRatesModal + showUsageRatesModal, + actionRequiredInvoices } from '$lib/stores/billing'; import { diffDays, toLocaleDate } from '$lib/helpers/date'; import { base } from '$app/paths'; @@ -40,9 +40,12 @@ import ExcesLimitModal from './organization-[organization]/excesLimitModal.svelte'; import { showExcess } from './organization-[organization]/store'; import UsageRates from './wizard/cloudOrganization/usageRates.svelte'; - import { Button } from '$lib/elements/forms'; import { consoleVariables, showPrereleaseModal } from './store'; import PreReleaseModal from './(billing-modal)/preReleaseModal.svelte'; + import { Query } from '@appwrite.io/console'; + import { headerAlert } from '$lib/stores/headerAlert'; + import MarkedForDeletion from '$lib/components/billing/alerts/markedForDeletion.svelte'; + import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequired.svelte'; function kebabToSentenceCase(str: string) { return str @@ -290,6 +293,14 @@ checkForTrialEnding(); paymentExpired(); checkForUsageLimit(); + if ($organization?.markedForDeletion) { + headerAlert.add({ + component: MarkedForDeletion, + show: true, + importance: 5 + }); + } + checkPaymentAuthorizationRequired(); } }); @@ -419,6 +430,20 @@ } } + async function checkPaymentAuthorizationRequired() { + if ($organization.billingPlan === 'tier-0') return; + $actionRequiredInvoices = await sdk.forConsole.billing.listInvoices($organization.$id, [ + Query.equal('status', 'requires_action') + ]); + if ($actionRequiredInvoices && $actionRequiredInvoices.total) { + headerAlert.add({ + component: PaymentAuthRequired, + show: true, + importance: 6 + }); + } + } + $: if (!$log.show) { $log.data = null; $log.func = null; @@ -429,6 +454,9 @@ } $registerSearchers(orgSearcher, projectsSearcher); + + $: selectedHeaderAlert = headerAlert.get(); + $: console.log(selectedHeaderAlert); @@ -441,29 +469,10 @@ !$page.url.pathname.includes('/console/card') && !$page.url.pathname.includes('/console/onboarding')}> - {#if $failedInvoice && !$page.url.pathname.includes('/console/account')} - {@const daysPassed = diffDays(new Date($failedInvoice.dueAt), new Date())} - - - {#if daysPassed > 30} - Your scheduled payment on {toLocaleDate($failedInvoice?.dueAt)} failed. - To resume write access of your organization, please update your billing details. - {:else} - Your scheduled payment on {toLocaleDate($failedInvoice?.dueAt)} failed. - Access to paid projects within this organization will be disabled if no action - is taken within 30 days. - {/if} - - - - - + {#if selectedHeaderAlert?.show} + {/if} + {#if $organization?.markedForDeletion && !$page.url.pathname.includes('/console/account')} diff --git a/src/routes/console/organization-[organization]/+layout.ts b/src/routes/console/organization-[organization]/+layout.ts index 0548d6cca..c030fa03c 100644 --- a/src/routes/console/organization-[organization]/+layout.ts +++ b/src/routes/console/organization-[organization]/+layout.ts @@ -6,6 +6,9 @@ import { error } from '@sveltejs/kit'; import type { LayoutLoad } from './$types'; import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; +import { headerAlert } from '$lib/stores/headerAlert'; +import ProjectsAtRisk from '$lib/components/billing/alerts/projectsAtRisk.svelte'; +import { get } from 'svelte/store'; export const load: LayoutLoad = async ({ params, depends }) => { depends(Dependencies.ORGANIZATION); @@ -13,6 +16,14 @@ export const load: LayoutLoad = async ({ params, depends }) => { if (isCloud) { await failedInvoice.load(params.organization); + + if (!get(failedInvoice)) { + headerAlert.add({ + show: true, + component: ProjectsAtRisk, + importance: 1 + }); + } } try { diff --git a/src/routes/console/organization-[organization]/billing/availableCredit.svelte b/src/routes/console/organization-[organization]/billing/availableCredit.svelte index 602d9eab0..8006745ce 100644 --- a/src/routes/console/organization-[organization]/billing/availableCredit.svelte +++ b/src/routes/console/organization-[organization]/billing/availableCredit.svelte @@ -52,7 +52,10 @@ request(); } - $: balance = creditList?.credits?.reduce((acc: number, curr: Credit) => acc + curr.credits, 0); + $: balance = creditList?.credits?.reduce( + (acc: number, curr: Credit) => acc + curr.creditsRemaining, + 0 + ); @@ -65,7 +68,7 @@ Credit balance ${balance} {#if creditList?.total} -