Skip to content

Commit 0d60d15

Browse files
committed
Implement Custom Domains Wizard and List
1 parent 4b00b0b commit 0d60d15

File tree

13 files changed

+502
-91
lines changed

13 files changed

+502
-91
lines changed

src/routes/console/project-[project]/settings/_createDomain.svelte

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 135 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,159 @@
11
<script lang="ts">
22
import { page } from '$app/stores';
3+
import { Empty } from '$lib/components';
34
import { Pill } from '$lib/elements';
45
import { Button } from '$lib/elements/forms';
56
import {
67
Table,
78
TableBody,
9+
TableCell,
810
TableCellHead,
911
TableCellText,
1012
TableHeader,
1113
TableRow
1214
} from '$lib/elements/table';
1315
import { Container } from '$lib/layout';
16+
import { addNotification } from '$lib/stores/notifications';
1417
import { sdkForConsole } from '$lib/stores/sdk';
15-
import CreateDomain from '../_createDomain.svelte';
18+
import { wizard } from '$lib/stores/wizard';
19+
import Create from './_create.svelte';
20+
import Delete from './_delete.svelte';
21+
import { domainList } from './store';
22+
import type { Models } from '@aw-labs/appwrite-console';
1623
1724
const projectId = $page.params.project;
18-
const listDomains = () => sdkForConsole.projects.listDomains(projectId);
25+
let showDelete = false;
26+
let selectedDomain: Models.Domain;
27+
let isVerifying = {};
1928
20-
let request = listDomains();
21-
let addDomain = false;
29+
const openWizard = () => {
30+
wizard.start(Create);
31+
};
32+
33+
const refreshDomain = async (domain: Models.Domain, i: number) => {
34+
const domainId = domain.$id;
35+
try {
36+
isVerifying[domainId] = true;
37+
38+
if (domain.verification) {
39+
await domainList.load(projectId);
40+
return;
41+
}
42+
const result = await sdkForConsole.projects.updateDomainVerification(
43+
projectId,
44+
domainId
45+
);
46+
$domainList.domains[i] = result;
47+
} catch (error) {
48+
addNotification({
49+
message: error.message,
50+
type: 'error'
51+
});
52+
} finally {
53+
isVerifying[domainId] = false;
54+
}
55+
};
2256
</script>
2357

2458
<Container>
25-
<h1>Custom Domains</h1>
26-
{#await request}
59+
<div class="u-flex u-gap-12 common-section u-main-space-between">
60+
<h2 class="heading-level-5">Custom Domains</h2>
61+
62+
<Button on:click={openWizard}>
63+
<span class="icon-plus" aria-hidden="true" /> <span class="text">Create domain</span>
64+
</Button>
65+
</div>
66+
{#await domainList.load(projectId)}
2767
<div aria-busy="true" />
28-
{:then response}
29-
<Table>
30-
<TableHeader>
31-
<TableCellHead width={96} />
32-
<TableCellHead>Domain</TableCellHead>
33-
<TableCellHead>TLS</TableCellHead>
34-
</TableHeader>
35-
<TableBody>
36-
{#each response.domains as domain}
37-
<TableRow>
38-
<TableCellText title="Scopes">
39-
<Pill danger={!domain.verification} success={domain.verification}>
40-
{domain.verification ? 'verified' : 'unverified'}
41-
</Pill>
42-
</TableCellText>
43-
<TableCellText title="Domain">
44-
{domain.domain}
45-
</TableCellText>
46-
<TableCellText title="TLS">
47-
{#if domain.certificateId}
48-
Verified
49-
{:else if domain.verification}
50-
In Progress
51-
{:else}
52-
Pending Verification
53-
{/if}
54-
</TableCellText>
55-
</TableRow>
56-
{/each}
57-
</TableBody>
58-
</Table>
68+
{:then}
69+
{#if $domainList.total}
70+
<Table>
71+
<TableHeader>
72+
<TableCellHead />
73+
<TableCellHead>Domain Name</TableCellHead>
74+
<TableCellHead>TLS</TableCellHead>
75+
<TableCellHead />
76+
</TableHeader>
77+
<TableBody>
78+
{#each $domainList.domains as domain, i}
79+
<TableRow>
80+
<TableCellText title="Status">
81+
<Pill warning={!domain.verification} success={domain.verification}>
82+
{domain.verification ? 'verified' : 'unverified'}
83+
</Pill>
84+
</TableCellText>
85+
<TableCellText title="Domain">
86+
{domain.domain}
87+
</TableCellText>
88+
<TableCellText title="TLS">
89+
<Pill
90+
warning={!domain.verification}
91+
success={domain.certificateId != ''}>
92+
{!domain.verification
93+
? 'pending verification'
94+
: domain.certificateId == ''
95+
? 'in progress'
96+
: 'enabled'}
97+
</Pill>
98+
<span class="tooltip-popup" role="tooltip">
99+
The process might take a while, click the retry button to see if
100+
it's finished
101+
</span>
102+
</TableCellText>
103+
<TableCell title="Actions">
104+
<div class="u-flex u-gap-8 u-cross-center u-main-end">
105+
{#if isVerifying[domain.$id]}
106+
<div
107+
class="loader"
108+
style="color: hsl(var(--color-neutral-50)); inline-size: 1.25rem; block-size: 1.25rem" />
109+
{:else if !domain.certificateId}
110+
<button
111+
class="button is-text is-only-icon u-padding-inline-0"
112+
style="--p-button-size: var(--button-size, 2.0rem);"
113+
aria-label="Verify item"
114+
on:click={() => {
115+
refreshDomain(domain, i);
116+
}}>
117+
<span class="icon-refresh" aria-hidden="true" />
118+
</button>
119+
{/if}
120+
<button
121+
class="button tooltip is-text is-only-icon u-padding-inline-0"
122+
style="--p-button-size: var(--button-size, 2.0rem);"
123+
aria-label="Delete item"
124+
on:click={async () => {
125+
showDelete = true;
126+
selectedDomain = domain;
127+
}}>
128+
<span class="icon-trash" aria-hidden="true" />
129+
<span class="tooltip-popup is-bottom" role="tooltip">
130+
Delete
131+
</span>
132+
</button>
133+
</div>
134+
</TableCell>
135+
</TableRow>
136+
{/each}
137+
</TableBody>
138+
</Table>
139+
{:else}
140+
<Empty dashed centered>
141+
<div class="u-flex u-flex-vertical u-cross-center">
142+
<div class="common-section">
143+
<Button secondary round on:click={openWizard}>
144+
<i class="icon-plus" />
145+
</Button>
146+
</div>
147+
<div class="common-section">
148+
<p>Create your first Domain to get started</p>
149+
</div>
150+
<div class="common-section">
151+
<Button secondary href="#">Documentation</Button>
152+
</div>
153+
</div>
154+
</Empty>
155+
{/if}
59156
{/await}
60-
61-
<Button on:click={() => (addDomain = true)}>Add Domain</Button>
62157
</Container>
63158

64-
<CreateDomain bind:show={addDomain} on:created={() => (request = listDomains())} />
159+
<Delete bind:showDelete bind:selectedDomain />
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<script lang="ts">
2+
import { Wizard } from '$lib/layout';
3+
import { beforeNavigate } from '$app/navigation';
4+
import { wizard } from '$lib/stores/wizard';
5+
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
6+
import { domain } from './wizard/store';
7+
import Step1 from './wizard/step1.svelte';
8+
import Step2 from './wizard/step2.svelte';
9+
import Step3 from './wizard/step3.svelte';
10+
import Step4 from './wizard/step4.svelte';
11+
import { onDestroy } from 'svelte';
12+
13+
onDestroy(() => {
14+
domain.set({ id: '', name: '' });
15+
});
16+
17+
beforeNavigate(() => {
18+
wizard.hide();
19+
});
20+
21+
const stepsComponents: WizardStepsType = new Map();
22+
stepsComponents.set(1, {
23+
label: 'Add your domain',
24+
component: Step1
25+
});
26+
stepsComponents.set(2, {
27+
label: 'Add a CNAME Record',
28+
component: Step2
29+
});
30+
stepsComponents.set(3, {
31+
label: 'Verify domain',
32+
component: Step3
33+
});
34+
stepsComponents.set(4, {
35+
label: 'SSL Certificate',
36+
component: Step4
37+
});
38+
</script>
39+
40+
<Wizard
41+
title="Create domain"
42+
steps={stepsComponents}
43+
finalAction="Go to console"
44+
on:finish={wizard.hide} />
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script lang="ts">
2+
import { Modal } from '$lib/components';
3+
import { Button, Form } from '$lib/elements/forms';
4+
import { project } from '../../store';
5+
import type { Models } from '@aw-labs/appwrite-console';
6+
import { sdkForConsole } from '$lib/stores/sdk';
7+
import { addNotification } from '$lib/stores/notifications';
8+
import { domainList } from './store';
9+
10+
export let showDelete = false;
11+
export let selectedDomain: Models.Domain;
12+
13+
const deleteDomain = async () => {
14+
try {
15+
await sdkForConsole.projects.deleteDomain($project.$id, selectedDomain.$id);
16+
domainList.load($project.$id);
17+
showDelete = false;
18+
addNotification({
19+
type: 'success',
20+
message: `${selectedDomain.domain} has been deleted`
21+
});
22+
} catch (error) {
23+
addNotification({
24+
type: 'error',
25+
message: error.message
26+
});
27+
}
28+
};
29+
</script>
30+
31+
<Form on:submit={deleteDomain}>
32+
<Modal bind:show={showDelete} warning>
33+
<svelte:fragment slot="header">Delete Domain</svelte:fragment>
34+
<p>
35+
Are you sure you want to delete <b>{selectedDomain.domain}</b> from '{$project.name}'?
36+
</p>
37+
<svelte:fragment slot="footer">
38+
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
39+
<Button secondary submit>Delete</Button>
40+
</svelte:fragment>
41+
</Modal>
42+
</Form>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { sdkForConsole } from '$lib/stores/sdk';
2+
import type { Models } from '@aw-labs/appwrite-console';
3+
import { cachedStore } from '$lib/helpers/cache';
4+
5+
export const domainList = cachedStore<
6+
Models.DomainList,
7+
{
8+
load: (projectId: string) => Promise<void>;
9+
}
10+
>('domainList', function ({ set }) {
11+
return {
12+
load: async (projectId) => {
13+
const response = await sdkForConsole.projects.listDomains(projectId);
14+
set(response);
15+
}
16+
};
17+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script lang="ts">
2+
import { Copy } from '$lib/components';
3+
import { domain } from './store';
4+
5+
const parts = $domain.name.split('.');
6+
const registerable = [parts[parts.length - 2], parts[parts.length - 1]].join('.');
7+
const cnameValue = $domain.name.replace('.' + registerable, '');
8+
</script>
9+
10+
<table class="table is-remove-outer-styles">
11+
<thead class="table-thead">
12+
<tr class="table-row">
13+
<th class="table-thead-col" style="--p-col-width:125">
14+
<span class="eyebrow-heading-3">Type</span>
15+
</th>
16+
<th class="table-thead-col">
17+
<span class="eyebrow-heading-3">Name</span>
18+
</th>
19+
<th class="table-thead-col">
20+
<span class="eyebrow-heading-3">Value</span>
21+
</th>
22+
</tr>
23+
</thead>
24+
<tbody class="table-tbody">
25+
<tr class="table-row">
26+
<td class="table-col" data-title="Type">
27+
<span class="text">CNAME</span>
28+
</td>
29+
<td class="table-col" data-title="Name">
30+
<span class="text">{cnameValue}</span>
31+
</td>
32+
<td class="table-col" data-title="Value">
33+
<div class="u-flex u-main-space-between u-cross-center">
34+
<span class="text">{$domain.name}</span>
35+
<Copy value={$domain.name}>
36+
<span class="icon-duplicate" aria-hidden="true" />
37+
</Copy>
38+
</div>
39+
</td>
40+
</tr>
41+
</tbody>
42+
</table>

0 commit comments

Comments
 (0)