mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: refactor invoice
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
</script>
|
||||
|
||||
{#if $organization?.markedForDeletion && !$page.url.pathname.includes('/console/account')}
|
||||
<HeaderAlert title="Organization flagged for deletion">
|
||||
<svelte:fragment>
|
||||
All existing projects in the {$organization.name} organization have been paused. This organization
|
||||
will be deleted once your upcoming invoice is processed successfully.
|
||||
</svelte:fragment>
|
||||
</HeaderAlert>
|
||||
{/if}
|
||||
@@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
import { actionRequiredInvoices } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { VARS } from '$lib/system';
|
||||
|
||||
const endpoint = VARS.APPWRITE_ENDPOINT ?? `${$page.url.origin}/v1`;
|
||||
</script>
|
||||
|
||||
{#if $actionRequiredInvoices && $actionRequiredInvoices.total && !$page.url.pathname.includes('/console/account')}
|
||||
<HeaderAlert title="Authorization required">
|
||||
Please authorize your upcoming payment for {$organization.name}. Your bank requires this
|
||||
security measure to proceed with payment.
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button
|
||||
text
|
||||
href={`${endpoint}/organizations/${$organization.$id}/invoices/${$actionRequiredInvoices.invoices[0].$id}/view`}>
|
||||
View invoice
|
||||
</Button>
|
||||
<Button
|
||||
secondary
|
||||
href={`${endpoint}/organizations/${$organization.$id}/billing?type=confirmation&invoice=${$actionRequiredInvoices.invoices[0].$id}`}>
|
||||
Authorize payment
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</HeaderAlert>
|
||||
{/if}
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { diffDays, toLocaleDate } from '$lib/helpers/date';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
import { failedInvoice } from '$lib/stores/billing';
|
||||
</script>
|
||||
|
||||
{#if $failedInvoice && !$page.url.pathname.includes('/console/account')}
|
||||
{@const daysPassed = diffDays(new Date($failedInvoice.dueAt), new Date())}
|
||||
<HeaderAlert title="Your projects are at risk">
|
||||
<svelte:fragment>
|
||||
{#if daysPassed > 30}
|
||||
Your scheduled payment on <b>{toLocaleDate($failedInvoice?.dueAt)}</b> failed. To resume
|
||||
write access of your organization, please update your billing details.
|
||||
{:else}
|
||||
Your scheduled payment on <b>{toLocaleDate($failedInvoice?.dueAt)}</b> failed. Access
|
||||
to paid projects within this organization will be disabled if no action is taken within
|
||||
30 days.
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button
|
||||
href={`${base}/console/organization-${$failedInvoice?.teamId}/billing#paymentMethods`}
|
||||
secondary
|
||||
fullWidthMobile>
|
||||
<span class="text">Update billing details</span>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</HeaderAlert>
|
||||
{/if}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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<InvoiceList>(null);
|
||||
|
||||
export type TierData = {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { SvelteComponent } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export type HeaderAlert = {
|
||||
show: boolean;
|
||||
component: typeof SvelteComponent<unknown>;
|
||||
importance: number;
|
||||
};
|
||||
|
||||
export type HeaderAlertStore = {
|
||||
components: HeaderAlert[];
|
||||
};
|
||||
|
||||
function createHeaderAlertStore() {
|
||||
const { subscribe, update, set } = writable<HeaderAlertStore>({
|
||||
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();
|
||||
@@ -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);
|
||||
</script>
|
||||
|
||||
<CommandCenter />
|
||||
@@ -441,29 +469,10 @@
|
||||
!$page.url.pathname.includes('/console/card') &&
|
||||
!$page.url.pathname.includes('/console/onboarding')}>
|
||||
<svelte:fragment slot="alert">
|
||||
{#if $failedInvoice && !$page.url.pathname.includes('/console/account')}
|
||||
{@const daysPassed = diffDays(new Date($failedInvoice.dueAt), new Date())}
|
||||
<HeaderAlert title="Your projects are at risk">
|
||||
<svelte:fragment>
|
||||
{#if daysPassed > 30}
|
||||
Your scheduled payment on <b>{toLocaleDate($failedInvoice?.dueAt)}</b> failed.
|
||||
To resume write access of your organization, please update your billing details.
|
||||
{:else}
|
||||
Your scheduled payment on <b>{toLocaleDate($failedInvoice?.dueAt)}</b> failed.
|
||||
Access to paid projects within this organization will be disabled if no action
|
||||
is taken within 30 days.
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button
|
||||
href={`${base}/console/organization-${$failedInvoice?.teamId}/billing#paymentMethods`}
|
||||
secondary
|
||||
fullWidthMobile>
|
||||
<span class="text">Update billing details</span>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</HeaderAlert>
|
||||
{#if selectedHeaderAlert?.show}
|
||||
<svelte:component this={selectedHeaderAlert.component} />
|
||||
{/if}
|
||||
|
||||
{#if $organization?.markedForDeletion && !$page.url.pathname.includes('/console/account')}
|
||||
<HeaderAlert title="Organization flagged for deletion">
|
||||
<svelte:fragment>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
);
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
@@ -65,7 +68,7 @@
|
||||
Credit balance <span class="inline-tag">${balance}</span>
|
||||
</h4>
|
||||
{#if creditList?.total}
|
||||
<Button secondary>
|
||||
<Button secondary on:click={handleCredits}>
|
||||
<span class="icon-plus" aria-hidden="true"></span>
|
||||
<span class="text">Add credits</span>
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user