Skip to content

Commit 1ffe431

Browse files
committed
Add support for adding a subscriber to a topic
1 parent e1ffcea commit 1ffe431

11 files changed

Lines changed: 515 additions & 143 deletions

File tree

src/lib/actions/analytics.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,5 +291,6 @@ export enum Submit {
291291
MessagingTopicCreate = 'submit_messaging_topic_create',
292292
MessagingTopicDelete = 'submit_messaging_topic_delete',
293293
MessagingTopicUpdateName = 'submit_messaging_topic_update_name',
294-
MessagingTopicSubscriberAdd = 'submit_messaging_topic_subscriber_add'
294+
MessagingTopicSubscriberAdd = 'submit_messaging_topic_subscriber_add',
295+
MessagingTopicSubscriberDelete = 'submit_messaging_topic_subscriber_delete'
295296
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<script lang="ts">
2+
import { DropList, DropListItem } from '$lib/components';
3+
import { createEventDispatcher } from 'svelte';
4+
import { targetsById } from './wizard/store';
5+
import UserTargetsModal from './userTargetsModal.svelte';
6+
import type { ProviderTypes } from './providerType.svelte';
7+
import TopicsModal from './topicsModal.svelte';
8+
import { topicsById } from './store';
9+
10+
export let showDropdown: boolean;
11+
export let showUserTargets: boolean;
12+
export let showTopics: boolean;
13+
export let providerType: ProviderTypes = null;
14+
15+
const dispatch = createEventDispatcher();
16+
17+
$: if (showUserTargets || showTopics) {
18+
showDropdown = false;
19+
}
20+
</script>
21+
22+
<DropList bind:show={showDropdown} placement="bottom-end" fixed>
23+
<slot />
24+
<svelte:fragment slot="list">
25+
<DropListItem on:click={() => (showTopics = true)}>Select topics</DropListItem>
26+
<DropListItem on:click={() => (showUserTargets = true)}>Select targets</DropListItem>
27+
</svelte:fragment>
28+
</DropList>
29+
30+
<TopicsModal
31+
bind:show={showTopics}
32+
bind:topicsById={$topicsById}
33+
on:update={(e) => {
34+
showTopics = false;
35+
dispatch('addTopics', e.detail);
36+
}} />
37+
<UserTargetsModal
38+
{providerType}
39+
bind:show={showUserTargets}
40+
bind:targetsById={$targetsById}
41+
on:update={(e) => {
42+
showUserTargets = false;
43+
dispatch('addTargets', e.detail);
44+
}} />

src/routes/console/project-[project]/messaging/store.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ export const columns = writable<Column[]>([
1313
{ id: 'scheduledAt', title: 'Scheduled at', show: true, width: 120 }
1414
]);
1515

16+
export const targetsById = writable<Record<string, Target>>({});
17+
export const topicsById = writable<Record<string, Topic>>({});
18+
1619
// TODO: remove this when the SDK and API are ready
1720
export type Message = {
1821
$id: string;
@@ -236,3 +239,25 @@ export const providersById: { [providerId: string]: Provider } = {
236239
options: {}
237240
}
238241
};
242+
243+
// TODO: remove when sdk has the model
244+
export type Topic = {
245+
$id: string;
246+
$createdAt: string;
247+
$updatedAt: string;
248+
providerId: string;
249+
name: string;
250+
total: number;
251+
description: string;
252+
};
253+
254+
export type Target = {
255+
$id: string;
256+
$createdAt: string;
257+
$updatedAt: string;
258+
name: string;
259+
userId: string;
260+
providerId: string;
261+
providerType: ProviderTypes;
262+
identifier: string;
263+
};

src/routes/console/project-[project]/messaging/topics/+page.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,7 @@ import { Query } from '@appwrite.io/console';
22
import { sdk } from '$lib/stores/sdk';
33
import { getLimit, getPage, getSearch, pageToOffset } from '$lib/helpers/load';
44
import { PAGE_LIMIT } from '$lib/constants';
5-
6-
// TODO: remove when sdk has the model
7-
export type Topic = {
8-
$id: string;
9-
$createdAt: string;
10-
$updatedAt: string;
11-
providerId: string;
12-
name: string;
13-
total: number;
14-
description: string;
15-
};
5+
import type { Topic } from '../store';
166

177
export const load = async ({ url, route }) => {
188
const page = getPage(url);

src/routes/console/project-[project]/messaging/topics/topic-[topic]/store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { derived } from 'svelte/store';
22
import { page } from '$app/stores';
3-
import type { Topic } from '../+page';
3+
import type { Topic } from '../../store';
44

55
export const topic = derived(
66
page,
Lines changed: 56 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,75 @@
11
<script lang="ts">
22
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';
123
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';
4+
import { Empty, EmptySearch, SearchQuery, PaginationWithLimit, Heading } from '$lib/components';
215
import { Container } from '$lib/layout';
22-
import { base } from '$app/paths';
23-
import { ID, type Models } from '@appwrite.io/console';
6+
import { ID } from '@appwrite.io/console';
247
import type { PageData } from './$types';
25-
import { writable } from 'svelte/store';
268
import { sdk } from '$lib/stores/sdk';
279
import { addNotification } from '$lib/stores/notifications';
2810
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
2911
import { invalidate } from '$app/navigation';
3012
import { Dependencies } from '$lib/constants';
31-
import type { Subscriber } from './+page';
13+
import Table from './table.svelte';
14+
import { type Target, targetsById } from '../../../store';
15+
import UserTargetsModal from '../../../userTargetsModal.svelte';
16+
import { onMount } from 'svelte';
3217
3318
export let data: PageData;
34-
let users = writable(new Map<string, Models.User<Models.Preferences>>());
35-
let selectedSubscriber: Subscriber | null = null;
3619
let showAdd = false;
37-
let showDelete = false;
3820
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-
}
21+
onMount(() => {
22+
$targetsById = {};
23+
for (const subscriber of $page.data.subscribers.subscribers) {
24+
const { target } = subscriber;
25+
$targetsById[target.$id] = target;
6726
}
27+
console.log($targetsById);
28+
});
6829
69-
const promises = userIds.map(addSubscriber);
30+
async function addTargets(event: CustomEvent<Record<string, Target>>) {
31+
showAdd = false;
32+
$targetsById = event.detail;
33+
async function addSubscriber(targetId: string) {
34+
await sdk.forProject.client.call(
35+
'POST',
36+
new URL(
37+
`${sdk.forProject.client.config.endpoint}/messaging/topics/${$page.params.topic}/subscribers`
38+
),
39+
{
40+
'X-Appwrite-Project': sdk.forProject.client.config.project,
41+
'content-type': 'application/json',
42+
'X-Appwrite-Mode': 'admin'
43+
},
44+
{
45+
subscriberId: ID.unique(),
46+
topicId: $page.params.topic,
47+
targetId: targetId
48+
}
49+
);
50+
}
7051
71-
await Promise.all(promises);
52+
const targetIds = Object.keys($targetsById);
53+
const promises = targetIds.map(addSubscriber);
7254
73-
showAdd = false;
74-
addNotification({
75-
type: 'success',
76-
message: `Added ${userIds.length} subscriber${userIds.length > 1 ? 's' : ''} to topic}`
77-
});
55+
try {
56+
await Promise.all(promises);
57+
addNotification({
58+
type: 'success',
59+
message: `Added ${targetIds.length} subscriber${
60+
targetIds.length > 1 ? 's' : ''
61+
} to topic`
62+
});
63+
trackEvent(Submit.MessagingTopicSubscriberAdd);
64+
await invalidate(Dependencies.MESSAGING_TOPIC_SUBSCRIBERS);
65+
} catch (error) {
66+
addNotification({
67+
type: 'error',
68+
message: error.message
69+
});
70+
trackError(error, Submit.MessagingTopicSubscriberAdd);
71+
}
7872
}
79-
80-
$: console.log($users);
8173
</script>
8274

8375
<Container>
@@ -87,7 +79,7 @@
8779
<div class="is-only-mobile">
8880
<Button on:click={() => (showAdd = true)} event="create_subscriber">
8981
<span class="icon-plus" aria-hidden="true" />
90-
<span class="text">Create topic</span>
82+
<span class="text">Add subscriber</span>
9183
</Button>
9284
</div>
9385
</div>
@@ -102,44 +94,7 @@
10294
</SearchQuery>
10395
</div>
10496
{#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>
97+
<Table {data} />
14398

14499
<PaginationWithLimit
145100
name="Subscribers"
@@ -169,5 +124,4 @@
169124
{/if}
170125
</Container>
171126

172-
<!-- TODO: handle create -->
173-
<!-- <UserModal bind:show={showAdd} on:submit={add} bind:users /> -->
127+
<UserTargetsModal bind:show={showAdd} bind:targetsById={$targetsById} on:update={addTargets} />

src/routes/console/project-[project]/messaging/topics/topic-[topic]/subscribers/+page.ts

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,27 @@ import { sdk } from '$lib/stores/sdk';
22
import { getLimit, getPage, getSearch, pageToOffset } from '$lib/helpers/load';
33
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
44
import type { PageLoad } from './$types';
5-
import type { Models } from '@appwrite.io/console';
5+
import type { Target } from '../../../store';
66

77
export type Subscriber = {
88
$id: string;
99
$createdAt: string;
10+
$updatedAt: string;
1011
targetId: string;
12+
target: Target;
13+
userName: string;
1114
topicId: string;
1215
};
1316

14-
export const load: PageLoad = async ({ params, url, route, depends }) => {
17+
export const load: PageLoad = async ({ params, url, route, depends, parent }) => {
1518
depends(Dependencies.MESSAGING_TOPIC_SUBSCRIBERS);
1619
const page = getPage(url);
1720
const limit = getLimit(url, route, PAGE_LIMIT);
1821
const offset = pageToOffset(page, limit);
1922
const search = getSearch(url);
2023

24+
const { topic } = await parent();
25+
2126
// TODO: remove when the API is ready with data
2227
// This allows us to mock w/ data and when search returns 0 results
2328
const subscribers: { subscribers: Subscriber[]; total: number } =
@@ -33,19 +38,11 @@ export const load: PageLoad = async ({ params, url, route, depends }) => {
3338
}
3439
);
3540

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-
4441
return {
4542
offset,
4643
limit,
4744
search,
48-
subscribers,
49-
targetsById
45+
topic,
46+
subscribers
5047
};
5148
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { writable } from 'svelte/store';
2+
import type { Column } from '$lib/components/viewSelector.svelte';
3+
4+
export const columns = writable<Column[]>([
5+
{ id: '$id', title: 'Subscriber ID', show: true, width: 140 },
6+
{ id: 'userName', title: 'Name', show: true, width: 100 },
7+
{ id: 'targetId', title: 'Target ID', show: true, width: 140 },
8+
{ id: 'target', title: 'Target', show: true, width: 140 },
9+
{ id: 'type', title: 'Type', show: true, width: 80 },
10+
{ id: '$createdAt', title: 'Created', show: true, width: 100 }
11+
]);

0 commit comments

Comments
 (0)