feat: refactor invoice

This commit is contained in:
Arman
2023-12-06 16:11:09 +01:00
parent c152162068
commit ca6cc46648
9 changed files with 179 additions and 30 deletions
@@ -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}
+2 -2
View File
@@ -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.
*/
+3 -1
View File
@@ -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;
+49
View File
@@ -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();
+34 -25
View File
@@ -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>