Skip to content

Commit bc9694e

Browse files
committed
Implement Custom Domains Wizard and List
1 parent 60370d5 commit bc9694e

File tree

12 files changed

+493
-78
lines changed

12 files changed

+493
-78
lines changed

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

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 137 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,161 @@
11
<script lang="ts">
22
import { page } from '$app/stores';
3+
import { tooltip } from '$lib/actions/tooltip';
4+
import { Empty } from '$lib/components';
35
import { Pill } from '$lib/elements';
46
import { Button } from '$lib/elements/forms';
57
import {
68
Table,
79
TableBody,
10+
TableCell,
811
TableCellHead,
912
TableCellText,
1013
TableHeader,
1114
TableRow
1215
} from '$lib/elements/table';
1316
import { Container } from '$lib/layout';
17+
import { addNotification } from '$lib/stores/notifications';
1418
import { sdkForConsole } from '$lib/stores/sdk';
15-
import CreateDomain from '../_createDomain.svelte';
19+
import { wizard } from '$lib/stores/wizard';
20+
import Create from './_create.svelte';
21+
import Delete from './_delete.svelte';
22+
import { domainList } from './store';
23+
import type { Models } from '@aw-labs/appwrite-console';
1624
1725
const projectId = $page.params.project;
18-
const listDomains = () => sdkForConsole.projects.listDomains(projectId);
26+
let showDelete = false;
27+
let selectedDomain: Models.Domain;
28+
let isVerifying = {};
1929
20-
let request = listDomains();
21-
let addDomain = false;
30+
const openWizard = () => {
31+
wizard.start(Create);
32+
};
33+
34+
const refreshDomain = async (domain: Models.Domain, i: number) => {
35+
const domainId = domain.$id;
36+
try {
37+
isVerifying[domainId] = true;
38+
39+
if (domain.verification) {
40+
await domainList.load(projectId);
41+
return;
42+
}
43+
const result = await sdkForConsole.projects.updateDomainVerification(
44+
projectId,
45+
domainId
46+
);
47+
$domainList.domains[i] = result;
48+
} catch (error) {
49+
addNotification({
50+
message: error.message,
51+
type: 'error'
52+
});
53+
} finally {
54+
isVerifying[domainId] = false;
55+
}
56+
};
2257
</script>
2358

2459
<Container>
25-
<h1>Custom Domains</h1>
26-
{#await request}
60+
<div class="u-flex u-gap-12 common-section u-main-space-between">
61+
<h2 class="heading-level-5">Custom Domains</h2>
62+
63+
<Button on:click={openWizard}>
64+
<span class="icon-plus" aria-hidden="true" /> <span class="text">Create domain</span>
65+
</Button>
66+
</div>
67+
{#await domainList.load(projectId)}
2768
<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>
69+
{:then}
70+
{#if $domainList.total}
71+
<Table>
72+
<TableHeader>
73+
<TableCellHead />
74+
<TableCellHead>Domain Name</TableCellHead>
75+
<TableCellHead>TLS</TableCellHead>
76+
<TableCellHead />
77+
</TableHeader>
78+
<TableBody>
79+
{#each $domainList.domains as domain, i}
80+
<TableRow>
81+
<TableCellText title="Status">
82+
<Pill warning={!domain.verification} success={domain.verification}>
83+
{domain.verification ? 'verified' : 'unverified'}
84+
</Pill>
85+
</TableCellText>
86+
<TableCellText title="Domain">
87+
{domain.domain}
88+
</TableCellText>
89+
<TableCellText title="TLS">
90+
{#if domain.certificateId}
91+
<Pill success>enabled</Pill>
92+
{:else}
93+
<span
94+
use:tooltip={{
95+
content:
96+
"The process might take a while, click the retry button to see if it's finished"
97+
}}>
98+
<Pill warning>
99+
{!domain.verification
100+
? 'pending verification'
101+
: 'in progress'}
102+
</Pill>
103+
</span>
104+
{/if}
105+
</TableCellText>
106+
<TableCell title="Actions">
107+
<div class="u-flex u-gap-8 u-cross-center u-main-end">
108+
{#if isVerifying[domain.$id]}
109+
<!-- TODO: remove inline styles -->
110+
<div
111+
class="loader"
112+
style="color: hsl(var(--color-neutral-50)); inline-size: 1.25rem; block-size: 1.25rem" />
113+
{:else if !domain.certificateId}
114+
<!-- TODO: remove inline styles -->
115+
<button
116+
class="button is-text is-only-icon u-padding-inline-0"
117+
style="--p-button-size: var(--button-size, 2.0rem);"
118+
aria-label="Verify item"
119+
on:click={() => {
120+
refreshDomain(domain, i);
121+
}}>
122+
<span class="icon-refresh" aria-hidden="true" />
123+
</button>
124+
{/if}
125+
<!-- TODO: remove inline styles -->
126+
<button
127+
class="button tooltip is-text is-only-icon u-padding-inline-0"
128+
style="--p-button-size: var(--button-size, 2.0rem);"
129+
aria-label="Delete item"
130+
on:click={async () => {
131+
showDelete = true;
132+
selectedDomain = domain;
133+
}}>
134+
<span class="icon-trash" aria-hidden="true" />
135+
<span class="tooltip-popup is-bottom" role="tooltip">
136+
Delete
137+
</span>
138+
</button>
139+
</div>
140+
</TableCell>
141+
</TableRow>
142+
{/each}
143+
</TableBody>
144+
</Table>
145+
{:else}
146+
<Empty isButton single on:click={openWizard}>
147+
<div class="common-section">
148+
<div class="u-text-center common-section">
149+
<p>Create your first Domain to get started</p>
150+
</div>
151+
<div class="u-flex u-gap-16 common-section u-main-center">
152+
<Button external href="#/" text>Documentation</Button>
153+
<Button secondary>Create domain</Button>
154+
</div>
155+
</div>
156+
</Empty>
157+
{/if}
59158
{/await}
60-
61-
<Button on:click={() => (addDomain = true)}>Add Domain</Button>
62159
</Container>
63160

64-
<CreateDomain bind:show={addDomain} on:created={() => (request = listDomains())} />
161+
<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: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<script lang="ts">
2+
import { Copy } from '$lib/components';
3+
import {
4+
Table,
5+
TableBody,
6+
TableCell,
7+
TableCellHead,
8+
TableCellText,
9+
TableHeader,
10+
TableRow
11+
} from '$lib/elements/table';
12+
import { domain } from './store';
13+
14+
const parts = $domain.name.split('.');
15+
const registerable = [parts[parts.length - 2], parts[parts.length - 1]].join('.');
16+
const cnameValue = $domain.name.replace('.' + registerable, '');
17+
</script>
18+
19+
<Table noStyles noMargin>
20+
<TableHeader>
21+
<TableCellHead>Type</TableCellHead>
22+
<TableCellHead>Name</TableCellHead>
23+
<TableCellHead>Value</TableCellHead>
24+
</TableHeader>
25+
<TableBody>
26+
<TableRow>
27+
<TableCellText title="Type">CNAME</TableCellText>
28+
<TableCellText title="Name">{cnameValue}</TableCellText>
29+
<TableCell title="Value">
30+
<div class="u-flex u-main-space-between u-cross-center">
31+
<span class="text">{$domain.name}</span>
32+
<Copy value={$domain.name}>
33+
<span class="icon-duplicate" aria-hidden="true" />
34+
</Copy>
35+
</div>
36+
</TableCell>
37+
</TableRow>
38+
</TableBody>
39+
</Table>

0 commit comments

Comments
 (0)