Skip to content

Commit 838e066

Browse files
committed
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.
1 parent eaf7927 commit 838e066

2 files changed

Lines changed: 95 additions & 16 deletions

File tree

src/routes/(console)/+error.svelte

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
import { Button } from '$lib/elements/forms';
66
import { isVerifyEmailRedirectError } from '$lib/helpers/emailVerification';
77
import { Container } from '$lib/layout';
8-
import { Typography } from '@appwrite.io/pink-svelte';
8+
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
9+
10+
const isPaymentError = $derived(page.status === 402);
11+
const billingUrl = $derived(
12+
page.params.organization ? `${base}/organization-${page.params.organization}/billing` : null
13+
);
914
1015
$effect(() => {
1116
const verifyEmailPath = resolve('/verify-email');
@@ -15,15 +20,59 @@
1520
});
1621
</script>
1722

18-
<Container>
19-
<div>
20-
<Typography.Title size="xl"
21-
>{'status' in page.error
22-
? page.error.status || 'Invalid Argument'
23-
: 'Invalid Argument'}</Typography.Title>
24-
<Typography.Title>{page.error.message}</Typography.Title>
25-
</div>
26-
<div>
27-
<Button href={base}>Back to the console</Button>
28-
</div>
29-
</Container>
23+
{#if isPaymentError}
24+
<section class="budget-error">
25+
<div class="budget-error__content">
26+
<Layout.Stack gap="s" alignItems="center">
27+
<Badge type="error" variant="secondary" content="Budget limit reached" />
28+
<Layout.Stack gap="xs" alignItems="center">
29+
<Typography.Title size="l" align="center">
30+
Your organization has reached its budget limit
31+
</Typography.Title>
32+
<Typography.Text align="center">
33+
Access to resources has been blocked. Update your billing settings to
34+
restore access.
35+
</Typography.Text>
36+
</Layout.Stack>
37+
<div class="u-margin-block-start-16">
38+
<Layout.Stack direction="row" gap="s" justifyContent="center">
39+
{#if billingUrl}
40+
<Button href={billingUrl}>Go to billing</Button>
41+
{/if}
42+
<Button secondary href="{base}/account/organizations"
43+
>Change organization</Button>
44+
</Layout.Stack>
45+
</div>
46+
</Layout.Stack>
47+
</div>
48+
</section>
49+
{:else}
50+
<Container>
51+
<div>
52+
<Typography.Title size="xl"
53+
>{'status' in page.error
54+
? page.error.status || 'Invalid Argument'
55+
: 'Invalid Argument'}</Typography.Title>
56+
<Typography.Title>{page.error.message}</Typography.Title>
57+
</div>
58+
<div>
59+
<Button href={base}>Back to the console</Button>
60+
</div>
61+
</Container>
62+
{/if}
63+
64+
<style>
65+
.budget-error {
66+
min-height: calc(100vh - 48px - 2rem);
67+
display: flex;
68+
align-items: center;
69+
justify-content: center;
70+
padding: 4rem 1.5rem;
71+
text-align: center;
72+
}
73+
74+
.budget-error__content {
75+
width: min(100%, 33rem);
76+
max-width: 33rem;
77+
}
78+
</style>

src/routes/(console)/project-[region]-[project]/+error.svelte

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<script lang="ts">
2+
import { base } from '$app/paths';
23
import { page } from '$app/state';
34
import { Button } from '$lib/elements/forms';
4-
import { currentPlan, organizationList } from '$lib/stores/organization';
5+
import { currentPlan, organization, organizationList } from '$lib/stores/organization';
56
import SupportWizard from '$routes/(console)/supportWizard.svelte';
67
import { wizard } from '$lib/stores/wizard';
78
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
89
import type { Models } from '@appwrite.io/console';
9-
1010
function contactSupport() {
1111
wizard.start(SupportWizard);
1212
}
@@ -16,9 +16,39 @@
1616
);
1717
1818
$: hasPremiumSupport = $currentPlan?.premiumSupport ?? allOrgsHavePremiumSupport ?? false;
19+
20+
$: orgId = $organization?.$id;
21+
$: billingUrl = orgId ? `${base}/organization-${orgId}/billing` : null;
22+
$: isPaymentError = page.status === 402;
1923
</script>
2024

21-
{#if page.error.type === 'general_resource_blocked'}
25+
{#if isPaymentError}
26+
<section class="resource-blocked">
27+
<div class="resource-blocked__content">
28+
<Layout.Stack gap="s" alignItems="center">
29+
<Badge type="error" variant="secondary" content="Budget limit reached" />
30+
<Layout.Stack gap="xs" alignItems="center">
31+
<Typography.Title size="l" align="center">
32+
Your organization has reached its budget limit
33+
</Typography.Title>
34+
<Typography.Text align="center">
35+
Access to this resource has been blocked. Update your billing settings to
36+
restore access.
37+
</Typography.Text>
38+
</Layout.Stack>
39+
<div class="u-margin-block-start-16">
40+
<Layout.Stack direction="row" gap="s" justifyContent="center">
41+
{#if billingUrl}
42+
<Button href={billingUrl}>Go to billing</Button>
43+
{/if}
44+
<Button secondary href="{base}/account/organizations"
45+
>Change organization</Button>
46+
</Layout.Stack>
47+
</div>
48+
</Layout.Stack>
49+
</div>
50+
</section>
51+
{:else if page.error.type === 'general_resource_blocked'}
2252
<section class="resource-blocked">
2353
<div class="resource-blocked__content">
2454
<Layout.Stack gap="s" alignItems="center">

0 commit comments

Comments
 (0)