mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: payment failed
This commit is contained in:
@@ -19,6 +19,9 @@
|
||||
import CreateOrganizationCloud from '$routes/console/createOrganizationCloud.svelte';
|
||||
import { feedback } from '$lib/stores/feedback';
|
||||
import { Feedback } from '$lib/components/feedback';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { failedInvoice } from '$lib/stores/billing';
|
||||
import { diffDays } from '$lib/helpers/date';
|
||||
|
||||
let showDropdown = false;
|
||||
let showSupport = false;
|
||||
@@ -73,6 +76,21 @@
|
||||
|
||||
<div class="main-header-end">
|
||||
<nav class="u-flex is-only-desktop u-cross-center">
|
||||
{#if $failedInvoice}
|
||||
{@const daysPassed = diffDays(new Date($failedInvoice.dueAt), new Date())}
|
||||
{#if daysPassed >= 15}
|
||||
<Pill danger>
|
||||
<span class="icon-exclamation-circle" aria-hidden="true" />
|
||||
<span>Organization at risk</span>
|
||||
</Pill>
|
||||
{:else if daysPassed >= 30}
|
||||
<Pill danger>
|
||||
<span class="icon-exclamation-circle" aria-hidden="true" />
|
||||
<span>read-only access</span>
|
||||
</Pill>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if $feedback.notification}
|
||||
<div class="u-flex u-cross-center">
|
||||
<div class="pulse-notification" />
|
||||
|
||||
@@ -42,7 +42,11 @@
|
||||
<main
|
||||
class:grid-with-side={showSideNavigation}
|
||||
class:is-open={isOpen}
|
||||
class:u-hide={$wizard.show || $log.show}>
|
||||
class:u-hide={$wizard.show || $log.show}
|
||||
class:is-fixed-layout={$$slots.alert}>
|
||||
{#if $$slots.alert}
|
||||
<slot name="alert" />
|
||||
{/if}
|
||||
<header class="main-header u-padding-inline-end-0">
|
||||
<button
|
||||
class:u-hide={!showSideNavigation}
|
||||
|
||||
@@ -3,7 +3,9 @@ import { derived, get } from 'svelte/store';
|
||||
import { sdk } from './sdk';
|
||||
import { limitRates } from '$lib/constants';
|
||||
import { organization } from './organization';
|
||||
import type { PaymentList } from '$lib/sdk/billing';
|
||||
import type { Invoice, PaymentList } from '$lib/sdk/billing';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { cachedStore } from '$lib/helpers/cache';
|
||||
|
||||
export type Tier = 'tier-0' | 'tier-1' | 'tier-2';
|
||||
|
||||
@@ -31,6 +33,27 @@ export function getServiceLimit(serviceId: string) {
|
||||
return limitRates?.[get(organization)?.billingPlan]?.find((l) => l.id === serviceId);
|
||||
}
|
||||
|
||||
export const failedInvoice = cachedStore<
|
||||
false | Invoice,
|
||||
{
|
||||
load: (orgId: string) => Promise<void>;
|
||||
}
|
||||
>('failedInvoice', function ({ set }) {
|
||||
return {
|
||||
load: async (orgId) => {
|
||||
if (!isCloud) set(false);
|
||||
const invoices = await sdk.forConsole.billing.listInvoices(orgId);
|
||||
const failedInvoices = invoices.invoices.filter((i) => i.status === 'failed');
|
||||
// const failedInvoices = invoices.invoices;
|
||||
if (failedInvoices.length > 0) {
|
||||
const firstFailed = failedInvoices[0];
|
||||
//TODO: if firstFailed is older than 30 days, set readonly to true!
|
||||
set(firstFailed);
|
||||
} else set(false);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
export const tierFree = {
|
||||
price: 0,
|
||||
name: 'Free',
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { loading } from '../store';
|
||||
import Create from './createOrganization.svelte';
|
||||
import { failedInvoice } from '$lib/stores/billing';
|
||||
import { diffDays, toLocaleDate } from '$lib/helpers/date';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
let isOpen = false;
|
||||
|
||||
onMount(() => {
|
||||
loading.set(false);
|
||||
|
||||
setInterval(() => {
|
||||
checkForFeedback(INTERVAL);
|
||||
}, INTERVAL);
|
||||
@@ -54,6 +56,38 @@
|
||||
!$page?.params.organization &&
|
||||
!$page.url.pathname.includes('/console/account') &&
|
||||
!$page.url.pathname.includes('/console/onboarding')}>
|
||||
<svelte:fragment slot="alert">
|
||||
{#if $failedInvoice}
|
||||
{@const daysPassed = diffDays(new Date($failedInvoice.dueAt), new Date())}
|
||||
<section class="alert is-action is-action-and-top-sticky is-danger u-sep-block-end">
|
||||
<div class="alert-grid">
|
||||
<span class="icon-info" aria-hidden="true" />
|
||||
<div class="alert-content">
|
||||
<h6 class="alert-title">Your projects are at risk</h6>
|
||||
<p class="alert-message">
|
||||
{#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 The paid
|
||||
projects within this organization will be disabled if no action is taken
|
||||
within 30 days.
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
<div class="alert-buttons u-flex u-gap-16 u-cross-child-center">
|
||||
<a
|
||||
href={`${base}/console/organization-${$failedInvoice.teamId}/billing`}
|
||||
class="button is-secondary is-full-width-mobile">
|
||||
<span class="text">Update billing details</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<Header slot="header" />
|
||||
<SideNavigation slot="side" bind:isOpen />
|
||||
<slot />
|
||||
|
||||
@@ -4,11 +4,17 @@ import { sdk } from '$lib/stores/sdk';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { failedInvoice } from '$lib/stores/billing';
|
||||
import { isCloud } from '$lib/system';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
depends(Dependencies.ORGANIZATION);
|
||||
depends(Dependencies.PAYMENT_METHODS);
|
||||
|
||||
if (isCloud) {
|
||||
await failedInvoice.load(params.organization);
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
header: Header,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import AvailableCredit from './availableCredit.svelte';
|
||||
import PaymentHistory from './paymentHistory.svelte';
|
||||
import { Alert, Heading } from '$lib/components';
|
||||
import { paymentMethods } from '$lib/stores/billing';
|
||||
import { failedInvoice, paymentMethods } from '$lib/stores/billing';
|
||||
import type { PaymentMethodData } from '$lib/sdk/billing';
|
||||
|
||||
$: defaultPaymentMethod = $paymentMethods.paymentMethods.find(
|
||||
@@ -18,7 +18,15 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
{#if defaultPaymentMethod.failed}
|
||||
{#if $failedInvoice}
|
||||
<Alert type="error">
|
||||
<svelte:fragment slot="title">
|
||||
The scheduled payment for {$organization.name} failed
|
||||
</svelte:fragment>
|
||||
To avoid service disruptions in your projects, please verify your payment details and update
|
||||
them if necessary.
|
||||
</Alert>
|
||||
{:else if defaultPaymentMethod.failed}
|
||||
<Alert type="error">
|
||||
<svelte:fragment slot="title">
|
||||
The default payment method for {$organization.name} has expired
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Dependencies } from '$lib/constants';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ params, parent, depends }) => {
|
||||
export const load: PageLoad = async ({ parent, depends }) => {
|
||||
await parent();
|
||||
depends(Dependencies.PAYMENT_METHODS);
|
||||
depends(Dependencies.ORGANIZATION);
|
||||
|
||||
@@ -3,6 +3,8 @@ import { sdk } from '$lib/stores/sdk';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { failedInvoice } from '$lib/stores/billing';
|
||||
import { isCloud } from '$lib/system';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
depends(Dependencies.PROJECT);
|
||||
@@ -13,6 +15,10 @@ export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
localStorage.setItem('organization', project.teamId);
|
||||
preferences.loadTeamPrefs(project.teamId);
|
||||
|
||||
if (isCloud) {
|
||||
await failedInvoice.load(project.teamId);
|
||||
}
|
||||
|
||||
return {
|
||||
project,
|
||||
organization: await sdk.forConsole.teams.get(project.teamId)
|
||||
|
||||
Reference in New Issue
Block a user