feat: add dedicated 402 budget limit error page

Replace generic error display for 402 responses with a dedicated
budget limit error page. Includes a 'Go to billing' CTA when an
organization is in context, and a 'Change organization' secondary
action on both console-level and project-level error pages.
This commit is contained in:
harsh mahajan
2026-05-20 10:55:25 +05:30
parent eaf7927a08
commit 838e066dbd
2 changed files with 95 additions and 16 deletions
+62 -13
View File
@@ -5,7 +5,12 @@
import { Button } from '$lib/elements/forms';
import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification';
import { Container } from '$lib/layout';
import { Typography } from '@appwrite.io/pink-svelte';
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
const isPaymentError = $derived(page.status === 402);
const billingUrl = $derived(
page.params.organization ? `${base}/organization-${page.params.organization}/billing` : null
);
$effect(() => {
const verifyEmailPath = resolve('/verify-email');
@@ -15,15 +20,59 @@
});
</script>
<Container>
<div>
<Typography.Title size="xl"
>{'status' in page.error
? page.error.status || 'Invalid Argument'
: 'Invalid Argument'}</Typography.Title>
<Typography.Title>{page.error.message}</Typography.Title>
</div>
<div>
<Button href={base}>Back to the console</Button>
</div>
</Container>
{#if isPaymentError}
<section class="budget-error">
<div class="budget-error__content">
<Layout.Stack gap="s" alignItems="center">
<Badge type="error" variant="secondary" content="Budget limit reached" />
<Layout.Stack gap="xs" alignItems="center">
<Typography.Title size="l" align="center">
Your organization has reached its budget limit
</Typography.Title>
<Typography.Text align="center">
Access to resources has been blocked. Update your billing settings to
restore access.
</Typography.Text>
</Layout.Stack>
<div class="u-margin-block-start-16">
<Layout.Stack direction="row" gap="s" justifyContent="center">
{#if billingUrl}
<Button href={billingUrl}>Go to billing</Button>
{/if}
<Button secondary href="{base}/account/organizations"
>Change organization</Button>
</Layout.Stack>
</div>
</Layout.Stack>
</div>
</section>
{:else}
<Container>
<div>
<Typography.Title size="xl"
>{'status' in page.error
? page.error.status || 'Invalid Argument'
: 'Invalid Argument'}</Typography.Title>
<Typography.Title>{page.error.message}</Typography.Title>
</div>
<div>
<Button href={base}>Back to the console</Button>
</div>
</Container>
{/if}
<style>
.budget-error {
min-height: calc(100vh - 48px - 2rem);
display: flex;
align-items: center;
justify-content: center;
padding: 4rem 1.5rem;
text-align: center;
}
.budget-error__content {
width: min(100%, 33rem);
max-width: 33rem;
}
</style>
@@ -1,12 +1,12 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/state';
import { Button } from '$lib/elements/forms';
import { currentPlan, organizationList } from '$lib/stores/organization';
import { currentPlan, organization, organizationList } from '$lib/stores/organization';
import SupportWizard from '$routes/(console)/supportWizard.svelte';
import { wizard } from '$lib/stores/wizard';
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
import type { Models } from '@appwrite.io/console';
function contactSupport() {
wizard.start(SupportWizard);
}
@@ -16,9 +16,39 @@
);
$: hasPremiumSupport = $currentPlan?.premiumSupport ?? allOrgsHavePremiumSupport ?? false;
$: orgId = $organization?.$id;
$: billingUrl = orgId ? `${base}/organization-${orgId}/billing` : null;
$: isPaymentError = page.status === 402;
</script>
{#if page.error.type === 'general_resource_blocked'}
{#if isPaymentError}
<section class="resource-blocked">
<div class="resource-blocked__content">
<Layout.Stack gap="s" alignItems="center">
<Badge type="error" variant="secondary" content="Budget limit reached" />
<Layout.Stack gap="xs" alignItems="center">
<Typography.Title size="l" align="center">
Your organization has reached its budget limit
</Typography.Title>
<Typography.Text align="center">
Access to this resource has been blocked. Update your billing settings to
restore access.
</Typography.Text>
</Layout.Stack>
<div class="u-margin-block-start-16">
<Layout.Stack direction="row" gap="s" justifyContent="center">
{#if billingUrl}
<Button href={billingUrl}>Go to billing</Button>
{/if}
<Button secondary href="{base}/account/organizations"
>Change organization</Button>
</Layout.Stack>
</div>
</Layout.Stack>
</div>
</section>
{:else if page.error.type === 'general_resource_blocked'}
<section class="resource-blocked">
<div class="resource-blocked__content">
<Layout.Stack gap="s" alignItems="center">