Skip to content

Commit a351210

Browse files
committed
Add subscribers list route
1 parent 67d536d commit a351210

5 files changed

Lines changed: 232 additions & 6 deletions

File tree

src/lib/actions/analytics.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,5 @@ export enum Submit {
286286
MessagingTopicCreate = 'submit_messaging_topic_create',
287287
MessagingTopicDelete = 'submit_messaging_topic_delete',
288288
MessagingTopicUpdateName = 'submit_messaging_topic_update_name',
289+
MessagingTopicSubscriberAdd = 'submit_messaging_topic_subscriber_add'
289290
}

src/lib/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ export enum Dependencies {
4343
COLLECTIONS = 'dependency:collections',
4444
RUNTIMES = 'dependency:runtimes',
4545
CONSOLE_VARIABLES = 'dependency:console_variables',
46-
MESSAGING_TOPIC = 'dependency:messaging_topic'
46+
MESSAGING_TOPIC = 'dependency:messaging_topic',
47+
MESSAGING_TOPIC_SUBSCRIBERS = 'dependency:messaging_topic_subscribers'
4748
}
4849

4950
export const scopes: {

src/routes/console/project-[project]/messaging/topics/topic-[topic]/header.svelte

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
title: 'Overview',
1515
event: 'overview'
1616
},
17-
// {
18-
// href: `${path}/memberships`,
19-
// title: 'Memberships',
20-
// event: 'memberships'
21-
// },
17+
{
18+
href: `${path}/subscribers`,
19+
title: 'Subscribers',
20+
event: 'subscribers'
21+
}
2222
// {
2323
// href: `${path}/sessions`,
2424
// title: 'Sessions',
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<script lang="ts">
2+
import { page } from '$app/stores';
3+
import {
4+
TableHeader,
5+
TableBody,
6+
TableRowLink,
7+
TableCellHead,
8+
TableCellText,
9+
TableCell,
10+
TableScroll,
11+
} from '$lib/elements/table';
12+
import { Button } from '$lib/elements/forms';
13+
import {
14+
Empty,
15+
EmptySearch,
16+
SearchQuery,
17+
PaginationWithLimit,
18+
Heading,
19+
} from '$lib/components';
20+
import { toLocaleDateTime } from '$lib/helpers/date';
21+
import { Container } from '$lib/layout';
22+
import { base } from '$app/paths';
23+
import { ID, type Models } from '@appwrite.io/console';
24+
import type { PageData } from './$types';
25+
import { writable } from 'svelte/store';
26+
import { sdk } from '$lib/stores/sdk';
27+
import { addNotification } from '$lib/stores/notifications';
28+
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
29+
import { invalidate } from '$app/navigation';
30+
import { Dependencies } from '$lib/constants';
31+
import type { Subscriber } from './+page';
32+
33+
export let data: PageData;
34+
let users = writable(new Map<string, Models.User<Models.Preferences>>());
35+
let selectedSubscriber: Subscriber | null = null;
36+
let showAdd = false;
37+
let showDelete = false;
38+
39+
const project = $page.params.project;
40+
41+
async function add(event: CustomEvent<string[]>) {
42+
const userIds = event.detail;
43+
44+
async function addSubscriber(userId: string) {
45+
try {
46+
await sdk.forProject.client.call(
47+
'POST',
48+
new URL(
49+
`${sdk.forProject.client.config.endpoint}/messaging/topics/${$page.params.topic}/subscribers`
50+
),
51+
{
52+
'X-Appwrite-Project': sdk.forProject.client.config.project,
53+
'content-type': 'application/json',
54+
'X-Appwrite-Mode': 'admin'
55+
},
56+
{
57+
subscriberId: ID.unique(),
58+
topicId: $page.params.topic,
59+
targetId: userId
60+
}
61+
);
62+
trackEvent(Submit.MessagingTopicCreate);
63+
await invalidate(Dependencies.MESSAGING_TOPIC_SUBSCRIBERS);
64+
} catch (e) {
65+
trackError(e, Submit.MessagingTopicCreate);
66+
}
67+
}
68+
69+
const promises = userIds.map(addSubscriber);
70+
71+
await Promise.all(promises);
72+
73+
showAdd = false;
74+
addNotification({
75+
type: 'success',
76+
message: `Added ${userIds.length} subscriber${userIds.length > 1 ? 's' : ''} to topic}`
77+
});
78+
}
79+
80+
$: console.log($users);
81+
</script>
82+
83+
<Container>
84+
<div class="u-flex u-flex-vertical">
85+
<div class="u-flex u-main-space-between">
86+
<Heading tag="h2" size="5">Subscribers</Heading>
87+
<div class="is-only-mobile">
88+
<Button on:click={() => (showAdd = true)} event="create_subscriber">
89+
<span class="icon-plus" aria-hidden="true" />
90+
<span class="text">Create topic</span>
91+
</Button>
92+
</div>
93+
</div>
94+
<!-- TODO: fix width of search input in mobile -->
95+
<SearchQuery search={data.search} placeholder="Search by name">
96+
<div class="u-flex u-gap-16 is-not-mobile">
97+
<Button on:click={() => (showAdd = true)} event="create_subscriber">
98+
<span class="icon-plus" aria-hidden="true" />
99+
<span class="text">Add subscriber</span>
100+
</Button>
101+
</div>
102+
</SearchQuery>
103+
</div>
104+
{#if data.subscribers.total}
105+
<TableScroll>
106+
<TableHeader>
107+
<TableCellHead>Name</TableCellHead>
108+
<TableCellHead onlyDesktop>Created</TableCellHead>
109+
<TableCellHead width={30} />
110+
</TableHeader>
111+
<TableBody>
112+
{#each data.subscribers.subscribers as subscriber}
113+
{@const user = data.targetsById.get(subscriber.$id)}
114+
<TableRowLink
115+
href={`${base}/console/project-${project}/auth/users/user-${user.$id}`}>
116+
<!-- <TableCell title="ID">
117+
<Id value={topic.$id}>
118+
{topic.$id}
119+
</Id>
120+
</TableCell> -->
121+
<TableCell title="Name">
122+
{user.name ?? '-'}
123+
</TableCell>
124+
<TableCellText onlyDesktop title="Created">
125+
{toLocaleDateTime(subscriber.$createdAt)}
126+
</TableCellText>
127+
<TableCell>
128+
<button
129+
class="button is-only-icon is-text"
130+
aria-label="Delete item"
131+
on:click|preventDefault={() => {
132+
selectedSubscriber = subscriber;
133+
showDelete = true;
134+
trackEvent('click_delete_subscriber');
135+
}}>
136+
<span class="icon-trash" aria-hidden="true" />
137+
</button>
138+
</TableCell>
139+
</TableRowLink>
140+
{/each}
141+
</TableBody>
142+
</TableScroll>
143+
144+
<PaginationWithLimit
145+
name="Subscribers"
146+
limit={data.limit}
147+
offset={data.offset}
148+
total={data.subscribers.total} />
149+
<!-- TODO: remove data.search != 'empty' when the API is ready with data -->
150+
{:else if data.search && data.search != 'empty'}
151+
<EmptySearch>
152+
<div class="u-text-center">
153+
<b>Sorry, we couldn't find '{data.search}'</b>
154+
<p>There are no subscribers that match your search.</p>
155+
</div>
156+
<Button
157+
secondary
158+
href={`/console/project-${$page.params.project}/messaging/topics/topic-${$page.params.topic}/subscribers`}>
159+
Clear Search
160+
</Button>
161+
</EmptySearch>
162+
{:else}
163+
<!-- TODO: update docs link -->
164+
<Empty
165+
single
166+
on:click={() => (showAdd = true)}
167+
href="https://appwrite.io/docs/references/cloud/client-web/teams"
168+
target="subscriber" />
169+
{/if}
170+
</Container>
171+
172+
<!-- TODO: handle create -->
173+
<!-- <UserModal bind:show={showAdd} on:submit={add} bind:users /> -->
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { sdk } from '$lib/stores/sdk';
2+
import { getLimit, getPage, getSearch, pageToOffset } from '$lib/helpers/load';
3+
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
4+
import type { PageLoad } from './$types';
5+
import type { Models } from '@appwrite.io/console';
6+
7+
export type Subscriber = {
8+
$id: string;
9+
$createdAt: string;
10+
targetId: string;
11+
topicId: string;
12+
};
13+
14+
export const load: PageLoad = async ({ params, url, route, depends }) => {
15+
depends(Dependencies.MESSAGING_TOPIC_SUBSCRIBERS);
16+
const page = getPage(url);
17+
const limit = getLimit(url, route, PAGE_LIMIT);
18+
const offset = pageToOffset(page, limit);
19+
const search = getSearch(url);
20+
21+
// TODO: remove when the API is ready with data
22+
// This allows us to mock w/ data and when search returns 0 results
23+
const subscribers: { subscribers: Subscriber[]; total: number } =
24+
await sdk.forProject.client.call(
25+
'GET',
26+
new URL(
27+
`${sdk.forProject.client.config.endpoint}/messaging/topics/${params.topic}/subscribers`
28+
),
29+
{
30+
'X-Appwrite-Project': sdk.forProject.client.config.project,
31+
'content-type': 'application/json',
32+
'X-Appwrite-Mode': 'admin'
33+
}
34+
);
35+
36+
const targetsById = new Map<string, Models.User<Models.Preferences>>();
37+
const userPromises = subscribers.subscribers.map((subscriber) =>
38+
sdk.forProject.users.get(subscriber.targetId)
39+
);
40+
for await (const user of userPromises) {
41+
targetsById.set(user.$id, user);
42+
}
43+
44+
return {
45+
offset,
46+
limit,
47+
search,
48+
subscribers,
49+
targetsById
50+
};
51+
};

0 commit comments

Comments
 (0)