Skip to content

Commit b2c12f8

Browse files
authored
Merge pull request #2974 from appwrite/feat-custom-domain-limits-enforcement
feat: enforce custom domain plan limits in UI
2 parents d1729ef + 6c22336 commit b2c12f8

File tree

8 files changed

+186
-61
lines changed

8 files changed

+186
-61
lines changed

bun.lock

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"@melt-ui/svelte": "^0.86.6",
5656
"@playwright/test": "^1.58.2",
5757
"@sveltejs/adapter-static": "^3.0.10",
58-
"@sveltejs/kit": "^2.55.0",
58+
"@sveltejs/kit": "^2.57.1",
5959
"@sveltejs/vite-plugin-svelte": "^5.1.1",
6060
"@testing-library/dom": "^10.4.1",
6161
"@testing-library/jest-dom": "^6.9.1",

src/lib/components/billing/planComparisonBox.svelte

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,18 @@
8686
{formatNum(currentPlan.executions)} executions
8787
</span>
8888
</li>
89+
{#if currentPlan.domains > 0}
90+
<li class="list-item u-gap-4 u-cross-center">
91+
<span class="icon-arrow-down u-color-text-danger" aria-hidden="true"></span>
92+
<span class="text">
93+
Limited to {currentPlan.domains} custom {pluralize(
94+
currentPlan.domains,
95+
'domain'
96+
)}
97+
per project
98+
</span>
99+
</li>
100+
{/if}
89101
</ul>
90102
{:else}
91103
<ul class="un-order-list">
@@ -105,12 +117,22 @@
105117
<li>
106118
Limited to {formatNum(currentPlan.executions)} executions
107119
</li>
120+
{#if currentPlan.domains > 0}
121+
<li>
122+
Limited to {currentPlan.domains} custom {pluralize(
123+
currentPlan.domains,
124+
'domain'
125+
)} per project
126+
</li>
127+
{:else}
128+
<li>Unlimited custom domains</li>
129+
{/if}
108130
</ul>
109131
{/if}
110132
{:else if planHasGroup(selectedTab, BillingPlanGroup.Pro)}
111133
<Typography.Text>Everything in the Free plan, plus:</Typography.Text>
112134
<ul class="un-order-list">
113-
<li>Unlimited databases, buckets, functions</li>
135+
<li>Unlimited databases, buckets, functions, and custom domains</li>
114136
<li>Unlimited seats</li>
115137
<li>{currentPlan.bandwidth}GB bandwidth</li>
116138
<li>{currentPlan.storage}GB storage</li>

src/lib/stores/billing.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ export type PlanServices =
200200
| 'bandwidthAddon'
201201
| 'buckets'
202202
| 'databases'
203+
| 'domains'
203204
| 'executions'
204205
| 'executionsAddon'
205206
| 'fileSize'
@@ -316,6 +317,7 @@ export function checkForProjectLimitation(plan: string, id: PlanServices) {
316317

317318
switch (id) {
318319
case 'databases':
320+
case 'domains':
319321
case 'functions':
320322
case 'buckets':
321323
case 'members': // Only applies to Free plan now

src/routes/(console)/organization-[organization]/domains/+page.svelte

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@
3434
import { View } from '$lib/helpers/load';
3535
import type { Models } from '@appwrite.io/console';
3636
37-
export let data;
37+
let { data } = $props();
3838
39-
let showDelete = false;
40-
let showRetry = false;
41-
let selectedDomain: Models.Domain = null;
39+
let showDelete = $state(false);
40+
let showRetry = $state(false);
41+
let selectedDomain = $state<Models.Domain | null>(null);
4242
4343
const isDomainVerified = (domain: Models.Domain) => {
4444
return domain.nameservers.toLowerCase() === 'appwrite';

src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/+page.svelte

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,26 @@
66
import Container from '$lib/layout/container.svelte';
77
import type { Models } from '@appwrite.io/console';
88
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
9-
import { Card, Empty, Icon, Layout } from '@appwrite.io/pink-svelte';
9+
import { Card, Empty, Icon, Layout, Tooltip } from '@appwrite.io/pink-svelte';
1010
import DeleteDomainModal from './deleteDomainModal.svelte';
1111
import RetryDomainModal from './retryDomainModal.svelte';
1212
import SearchQuery from '$lib/components/searchQuery.svelte';
1313
import { app } from '$lib/stores/app';
1414
import { Click, trackEvent } from '$lib/actions/analytics';
15+
import {
16+
BODY_TOOLTIP_MAX_WIDTH,
17+
BODY_TOOLTIP_WRAPPER_STYLE_PRELINE
18+
} from '$lib/helpers/tooltipContent';
19+
import { isServiceLimited } from '$lib/stores/billing';
20+
import { organization } from '$lib/stores/organization';
1521
import Table from './table.svelte';
1622
1723
let { data } = $props();
1824
25+
const isDomainLimitReached = $derived(
26+
isServiceLimited('domains', $organization, data.proxyRules.total)
27+
);
28+
1929
let showDelete = $state(false);
2030
let showRetry = $state(false);
2131
let selectedProxyRule: Models.ProxyRule = null;
@@ -24,16 +34,28 @@
2434
<Container>
2535
<Layout.Stack direction="row" justifyContent="space-between">
2636
<SearchQuery placeholder="Search domain" />
27-
<Button
28-
href={`${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/domains/add-domain`}
29-
on:click={() => {
30-
trackEvent(Click.DomainCreateClick, {
31-
source: 'functions_domain_overview'
32-
});
33-
}}>
34-
<Icon icon={IconPlus} size="s" />
35-
Add domain
36-
</Button>
37+
<Tooltip disabled={!isDomainLimitReached} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
38+
<div>
39+
<Button
40+
disabled={isDomainLimitReached}
41+
href={isDomainLimitReached
42+
? undefined
43+
: `${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/domains/add-domain`}
44+
on:click={() => {
45+
trackEvent(Click.DomainCreateClick, {
46+
source: 'functions_domain_overview'
47+
});
48+
}}>
49+
<Icon icon={IconPlus} size="s" />
50+
Add domain
51+
</Button>
52+
</div>
53+
<svelte:fragment slot="tooltip">
54+
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
55+
You have reached the maximum number of custom domains for your plan.
56+
</div>
57+
</svelte:fragment>
58+
</Tooltip>
3759
</Layout.Stack>
3860
{#if data.proxyRules.total}
3961
<Table proxyRules={data.proxyRules} organizationDomains={data.organizationDomains} />
@@ -70,12 +92,24 @@
7092
size="s"
7193
ariaLabel="add domain">Documentation</Button>
7294

73-
<Button
74-
secondary
75-
href={`${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/domains/add-domain`}
76-
size="s">
77-
Add domain
78-
</Button>
95+
<Tooltip disabled={!isDomainLimitReached} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
96+
<div>
97+
<Button
98+
secondary
99+
disabled={isDomainLimitReached}
100+
href={isDomainLimitReached
101+
? undefined
102+
: `${base}/project-${page.params.region}-${page.params.project}/functions/function-${page.params.function}/domains/add-domain`}
103+
size="s">
104+
Add domain
105+
</Button>
106+
</div>
107+
<svelte:fragment slot="tooltip">
108+
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
109+
You have reached the maximum number of custom domains for your plan.
110+
</div>
111+
</svelte:fragment>
112+
</Tooltip>
79113
</svelte:fragment>
80114
</Empty>
81115
</Card.Base>

src/routes/(console)/project-[region]-[project]/settings/domains/+page.svelte

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,23 @@
55
import { Button } from '$lib/elements/forms';
66
import Container from '$lib/layout/container.svelte';
77
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
8-
import { Card, Empty, Icon } from '@appwrite.io/pink-svelte';
8+
import { Card, Empty, Icon, Tooltip } from '@appwrite.io/pink-svelte';
99
import { app } from '$lib/stores/app';
1010
import { Click, trackEvent } from '$lib/actions/analytics';
11+
import {
12+
BODY_TOOLTIP_MAX_WIDTH,
13+
BODY_TOOLTIP_WRAPPER_STYLE_PRELINE
14+
} from '$lib/helpers/tooltipContent';
15+
import { isServiceLimited } from '$lib/stores/billing';
16+
import { organization } from '$lib/stores/organization';
1117
import Table from './table.svelte';
1218
import { ResponsiveContainerHeader } from '$lib/layout';
1319
1420
let { data } = $props();
21+
22+
const isDomainLimitReached = $derived(
23+
isServiceLimited('domains', $organization, data.rules.total)
24+
);
1525
</script>
1626

1727
<Container>
@@ -20,16 +30,28 @@
2030
hideView
2131
searchPlaceholder="Search by domain"
2232
analyticsSource="settings_domain_overview">
23-
<Button
24-
href={`${base}/project-${page.params.region}-${page.params.project}/settings/domains/add-domain`}
25-
on:click={() => {
26-
trackEvent(Click.DomainCreateClick, {
27-
source: 'settings_domain_overview'
28-
});
29-
}}>
30-
<Icon icon={IconPlus} size="s" />
31-
Add domain
32-
</Button>
33+
<Tooltip disabled={!isDomainLimitReached} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
34+
<div>
35+
<Button
36+
disabled={isDomainLimitReached}
37+
href={isDomainLimitReached
38+
? undefined
39+
: `${base}/project-${page.params.region}-${page.params.project}/settings/domains/add-domain`}
40+
on:click={() => {
41+
trackEvent(Click.DomainCreateClick, {
42+
source: 'settings_domain_overview'
43+
});
44+
}}>
45+
<Icon icon={IconPlus} size="s" />
46+
Add domain
47+
</Button>
48+
</div>
49+
<svelte:fragment slot="tooltip">
50+
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
51+
You have reached the maximum number of custom domains for your plan.
52+
</div>
53+
</svelte:fragment>
54+
</Tooltip>
3355
</ResponsiveContainerHeader>
3456
{#if data.rules.total}
3557
<Table domains={data.rules} organizationDomains={data.organizationDomains} />
@@ -64,12 +86,24 @@
6486
size="s"
6587
ariaLabel="add domain">Documentation</Button>
6688

67-
<Button
68-
secondary
69-
href={`${base}/project-${page.params.region}-${page.params.project}/settings/domains/add-domain`}
70-
size="s">
71-
Add domain
72-
</Button>
89+
<Tooltip disabled={!isDomainLimitReached} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
90+
<div>
91+
<Button
92+
secondary
93+
disabled={isDomainLimitReached}
94+
href={isDomainLimitReached
95+
? undefined
96+
: `${base}/project-${page.params.region}-${page.params.project}/settings/domains/add-domain`}
97+
size="s">
98+
Add domain
99+
</Button>
100+
</div>
101+
<svelte:fragment slot="tooltip">
102+
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
103+
You have reached the maximum number of custom domains for your plan.
104+
</div>
105+
</svelte:fragment>
106+
</Tooltip>
73107
</svelte:fragment>
74108
</Empty>
75109
</Card.Base>

src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/+page.svelte

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,50 @@
55
import { Button } from '$lib/elements/forms';
66
import Container from '$lib/layout/container.svelte';
77
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
8-
import { Card, Empty, Icon, Layout } from '@appwrite.io/pink-svelte';
8+
import { Card, Empty, Icon, Layout, Tooltip } from '@appwrite.io/pink-svelte';
99
import SearchQuery from '$lib/components/searchQuery.svelte';
1010
import { app } from '$lib/stores/app';
1111
import { Click, trackEvent } from '$lib/actions/analytics';
12+
import {
13+
BODY_TOOLTIP_MAX_WIDTH,
14+
BODY_TOOLTIP_WRAPPER_STYLE_PRELINE
15+
} from '$lib/helpers/tooltipContent';
16+
import { isServiceLimited } from '$lib/stores/billing';
17+
import { organization } from '$lib/stores/organization';
1218
import Table from './table.svelte';
1319
14-
export let data;
20+
let { data } = $props();
21+
22+
const isDomainLimitReached = $derived(
23+
isServiceLimited('domains', $organization, data.proxyRules.total)
24+
);
1525
</script>
1626

1727
<Container>
1828
<Layout.Stack direction="row" justifyContent="space-between">
1929
<SearchQuery placeholder="Search domain" />
20-
<Button
21-
href={`${base}/project-${page.params.region}-${page.params.project}/sites/site-${page.params.site}/domains/add-domain`}
22-
on:click={() => {
23-
trackEvent(Click.DomainCreateClick, {
24-
source: 'sites_domain_overview'
25-
});
26-
}}>
27-
<Icon icon={IconPlus} size="s" />
28-
Add domain
29-
</Button>
30+
<Tooltip disabled={!isDomainLimitReached} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
31+
<div>
32+
<Button
33+
disabled={isDomainLimitReached}
34+
href={isDomainLimitReached
35+
? undefined
36+
: `${base}/project-${page.params.region}-${page.params.project}/sites/site-${page.params.site}/domains/add-domain`}
37+
on:click={() => {
38+
trackEvent(Click.DomainCreateClick, {
39+
source: 'sites_domain_overview'
40+
});
41+
}}>
42+
<Icon icon={IconPlus} size="s" />
43+
Add domain
44+
</Button>
45+
</div>
46+
<svelte:fragment slot="tooltip">
47+
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
48+
You have reached the maximum number of custom domains for your plan.
49+
</div>
50+
</svelte:fragment>
51+
</Tooltip>
3052
</Layout.Stack>
3153

3254
{#if data.proxyRules.total}
@@ -62,12 +84,24 @@
6284
size="s"
6385
ariaLabel="add domain">Documentation</Button>
6486

65-
<Button
66-
secondary
67-
href={`${base}/project-${page.params.region}-${page.params.project}/sites/site-${page.params.site}/domains/add-domain`}
68-
size="s">
69-
Add domain
70-
</Button>
87+
<Tooltip disabled={!isDomainLimitReached} maxWidth={BODY_TOOLTIP_MAX_WIDTH}>
88+
<div>
89+
<Button
90+
secondary
91+
disabled={isDomainLimitReached}
92+
href={isDomainLimitReached
93+
? undefined
94+
: `${base}/project-${page.params.region}-${page.params.project}/sites/site-${page.params.site}/domains/add-domain`}
95+
size="s">
96+
Add domain
97+
</Button>
98+
</div>
99+
<svelte:fragment slot="tooltip">
100+
<div style={BODY_TOOLTIP_WRAPPER_STYLE_PRELINE}>
101+
You have reached the maximum number of custom domains for your plan.
102+
</div>
103+
</svelte:fragment>
104+
</Tooltip>
71105
</svelte:fragment>
72106
</Empty>
73107
</Card.Base>

0 commit comments

Comments
 (0)