Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add messaging topics route #597

Merged
merged 9 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/lib/actions/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,5 +292,11 @@ export enum Submit {
MessagingProviderDelete = 'submit_messaging_provider_delete',
MessagingProviderUpdate = 'submit_messaging_provider_update',
MessagingMessageCreate = 'submit_messaging_message_create',
MessagingMessageDelete = 'submit_messaging_message_delete'
MessagingMessageDelete = 'submit_messaging_message_delete',
MessagingTopicCreate = 'submit_messaging_topic_create',
MessagingTopicDelete = 'submit_messaging_topic_delete',
MessagingTopicUpdateName = 'submit_messaging_topic_update_name',
MessagingTopicUpdateDescription = 'submit_messaging_topic_update_description',
MessagingTopicSubscriberAdd = 'submit_messaging_topic_subscriber_add',
MessagingTopicSubscriberDelete = 'submit_messaging_topic_subscriber_delete'
}
5 changes: 4 additions & 1 deletion src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ export enum Dependencies {
CONSOLE_VARIABLES = 'dependency:console_variables',
MESSAGING_PROVIDERS = 'dependency:messaging_providers',
MESSAGING_PROVIDER = 'dependency:messaging_provider',
MESSAGING_MESSAGE = 'dependency:messaging_message'
MESSAGING_MESSAGE = 'dependency:messaging_message',
MESSAGING_TOPICS = 'dependency:messaging_topics',
MESSAGING_TOPIC = 'dependency:messaging_topic',
MESSAGING_TOPIC_SUBSCRIBERS = 'dependency:messaging_topic_subscribers'
}

export const scopes: {
Expand Down
44 changes: 44 additions & 0 deletions src/routes/console/project-[project]/messaging/actions.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script lang="ts">
import { DropList, DropListItem } from '$lib/components';
import { createEventDispatcher } from 'svelte';
import { targetsById } from './wizard/store';
import UserTargetsModal from './userTargetsModal.svelte';
import type { ProviderTypes } from './providerType.svelte';
import TopicsModal from './topicsModal.svelte';
import { topicsById } from './store';

export let showDropdown: boolean;
export let showUserTargets: boolean;
export let showTopics: boolean;
export let providerType: ProviderTypes = null;

const dispatch = createEventDispatcher();

$: if (showUserTargets || showTopics) {
showDropdown = false;
}
</script>

<DropList bind:show={showDropdown} placement="bottom-end" fixed>
<slot />
<svelte:fragment slot="list">
<DropListItem on:click={() => (showTopics = true)}>Select topics</DropListItem>
<DropListItem on:click={() => (showUserTargets = true)}>Select targets</DropListItem>
</svelte:fragment>
</DropList>

<TopicsModal
bind:show={showTopics}
bind:topicsById={$topicsById}
on:update={(e) => {
showTopics = false;
dispatch('addTopics', e.detail);
}} />
<UserTargetsModal
{providerType}
bind:show={showUserTargets}
bind:targetsById={$targetsById}
on:update={(e) => {
showUserTargets = false;
dispatch('addTargets', e.detail);
}} />
5 changes: 0 additions & 5 deletions src/routes/console/project-[project]/messaging/create.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script lang="ts">
import { onDestroy } from 'svelte';
import { Wizard } from '$lib/layout';
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
import Step1 from './wizard/step1.svelte';
Expand Down Expand Up @@ -97,10 +96,6 @@
}
}

onDestroy(() => {
console.log('destroy');
});

const stepsComponents: WizardStepsType = new Map();
stepsComponents.set(1, {
label: 'Message',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import Create from './create.svelte';
import { messageParams, providerType, targetsById } from './wizard/store';
import { ProviderTypes } from './providerType.svelte';
import { topicsById } from './store';

export let showCreateDropdown = false;
</script>
Expand All @@ -29,6 +30,7 @@
)
return;
$providerType = type;
$topicsById = {};
$targetsById = {};
const common = {
topics: [],
Expand Down
25 changes: 25 additions & 0 deletions src/routes/console/project-[project]/messaging/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export const columns = writable<Column[]>([
{ id: 'deliveredAt', title: 'Delivered at', type: 'datetime', show: false, width: 120 }
]);

export const targetsById = writable<Record<string, Target>>({});
export const topicsById = writable<Record<string, Topic>>({});

// TODO: remove this when the SDK and API are ready
export type Message = {
$id: string;
Expand Down Expand Up @@ -238,3 +241,25 @@ export const providersById: { [providerId: string]: Provider } = {
options: {}
}
};

// TODO: remove when sdk has the model
export type Topic = {
$id: string;
$createdAt: string;
$updatedAt: string;
providerId: string;
name: string;
total: number;
description: string;
};

export type Target = {
$id: string;
$createdAt: string;
$updatedAt: string;
name: string;
userId: string;
providerId: string;
providerType: ProviderTypes;
identifier: string;
};
103 changes: 103 additions & 0 deletions src/routes/console/project-[project]/messaging/topics/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<script lang="ts">
import { page } from '$app/stores';
import { Button } from '$lib/elements/forms';
import {
Empty,
EmptySearch,
SearchQuery,
PaginationWithLimit,
Heading,
ViewSelector
} from '$lib/components';
import Create from './create.svelte';
import { goto } from '$app/navigation';
import { Container } from '$lib/layout';
import { base } from '$app/paths';
import type { Models } from '@appwrite.io/console';
import type { PageData } from './$types';
import { columns, showCreate } from './store';
import { View } from '$lib/helpers/load';
import Filters from '$lib/components/filters/filters.svelte';
import Table from './table.svelte';

export let data: PageData;

const project = $page.params.project;
const topicCreated = async (event: CustomEvent<Models.Team<Record<string, unknown>>>) => {
await goto(`${base}/console/project-${project}/messaging/topics/topic-${event.detail.$id}`);
};
</script>

<Container>
<div class="u-flex u-flex-vertical">
<div class="u-flex u-main-space-between">
<Heading tag="h2" size="5">Topics</Heading>
<div class="is-only-mobile">
<Button on:click={() => ($showCreate = true)} event="create_topic">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create topic</span>
</Button>
</div>
</div>
<!-- TODO: fix width of search input in mobile -->
<SearchQuery search={data.search} placeholder="Search by name or ID">
<div class="u-flex u-gap-16 is-not-mobile">
<Filters query={data.query} {columns} />
<ViewSelector
view={View.Table}
{columns}
hideView
allowNoColumns
showColsTextMobile />
<Button on:click={() => ($showCreate = true)} event="create_topic">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create topic</span>
</Button>
</div>
</SearchQuery>
<div class="u-flex u-gap-16 is-only-mobile u-margin-block-start-16">
<div class="u-flex-basis-50-percent">
<!-- TODO: fix width -->
<ViewSelector
view={View.Table}
{columns}
hideView
allowNoColumns
showColsTextMobile />
</div>
<div class="u-flex-basis-50-percent">
<!-- TODO: fix width -->
<Filters query={data.query} {columns} />
</div>
</div>
</div>
{#if data.topics.total}
<Table {data} />

<PaginationWithLimit
name="Topics"
limit={data.limit}
offset={data.offset}
total={data.topics.total} />
<!-- TODO: remove data.search != 'empty' when the API is ready with data -->
{:else if data.search && data.search != 'empty'}
<EmptySearch>
<div class="u-text-center">
<b>Sorry, we couldn't find '{data.search}'</b>
<p>There are no topics that match your search.</p>
</div>
<Button secondary href={`/console/project-${$page.params.project}/messaging/topics`}>
Clear Search
</Button>
</EmptySearch>
{:else}
<!-- TODO: update docs link -->
<Empty
single
on:click={() => ($showCreate = true)}
href="https://appwrite.io/docs/references/cloud/client-web/teams"
target="topic" />
{/if}
</Container>

<Create bind:showCreate={$showCreate} on:created={topicCreated} />
53 changes: 53 additions & 0 deletions src/routes/console/project-[project]/messaging/topics/+page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Query } from '@appwrite.io/console';
import { sdk } from '$lib/stores/sdk';
import { getLimit, getPage, getQuery, getSearch, pageToOffset } from '$lib/helpers/load';
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
import { queryParamToMap, queries } from '$lib/components/filters/store';
import type { Topic } from '../store';

export const load = async ({ depends, url, route }) => {
depends(Dependencies.MESSAGING_TOPICS);
const page = getPage(url);
const search = getSearch(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
const query = getQuery(url);

const parsedQueries = queryParamToMap(query || '[]');
queries.set(parsedQueries);

const payload = {
queries: [
Query.limit(limit),
Query.offset(offset),
Query.orderDesc(''),
...parsedQueries.values()
]
};

if (search) {
payload['search'] = search;
}

// TODO: remove when the API is ready with data
// This allows us to mock w/ data and when search returns 0 results
const topics: { topics: Topic[]; total: number } = await sdk.forProject.client.call(
'GET',
new URL(sdk.forProject.client.config.endpoint + '/messaging/topics'),
{
'X-Appwrite-Project': sdk.forProject.client.config.project,
'content-type': 'application/json',
'X-Appwrite-Mode': 'admin'
},
payload
);

return {
offset,
limit,
search,
query,
page,
topics
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<script lang="ts">
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Modal, CustomId } from '$lib/components';
import { Pill } from '$lib/elements';
import { InputText, Button, FormList } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { ID } from '@appwrite.io/console';
import { createEventDispatcher } from 'svelte';

export let showCreate = false;

const dispatch = createEventDispatcher();

let name: string, description: string, id: string, error: string;
let showCustomId = false;

const create = async () => {
try {
const topic = await sdk.forProject.client.call(
'POST',
new URL(sdk.forProject.client.config.endpoint + '/messaging/topics'),
{
'X-Appwrite-Project': sdk.forProject.client.config.project,
'content-type': 'application/json',
'X-Appwrite-Mode': 'admin'
},
{
name,
description,
topicId: id ?? ID.unique()
}
);
name = '';
description = '';
showCreate = false;
showCustomId = false;
addNotification({
type: 'success',
message: `${topic.name} has been created`
});
trackEvent(Submit.MessagingTopicCreate, {
customId: !!id
});
dispatch('created', topic);
} catch (e) {
error = e.message;
trackError(e, Submit.MessagingTopicCreate);
}
};

$: if (!showCreate) {
showCustomId = false;
error = null;
}
</script>

<Modal title="Create topic" {error} size="big" bind:show={showCreate} onSubmit={create}>
<FormList>
<InputText
id="name"
label="Name"
placeholder="Enter name"
autofocus={true}
required
bind:value={name} />
<InputText
id="description"
label="Description"
placeholder="Enter description"
bind:value={description}>
</InputText>
{#if !showCustomId}
<div>
<Pill button on:click={() => (showCustomId = !showCustomId)}
><span class="icon-pencil" aria-hidden="true" />
<span class="text"> Topic ID </span>
</Pill>
</div>
{:else}
<CustomId bind:show={showCustomId} name="Topic" bind:id />
{/if}
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
<Button submit>Create</Button>
</svelte:fragment>
</Modal>
12 changes: 12 additions & 0 deletions src/routes/console/project-[project]/messaging/topics/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Column } from '$lib/helpers/types';
import { writable } from 'svelte/store';

export let showCreate = writable(false);

export const columns = writable<Column[]>([
{ id: '$id', title: 'Topic ID', type: 'string', show: true, width: 140 },
{ id: 'name', title: 'Name', type: 'string', show: true, width: 140 },
{ id: 'description', title: 'Description', type: 'string', show: true, width: 140 },
{ id: 'total', title: 'Subscribers', type: 'integer', show: true, width: 140 },
{ id: '$createdAt', title: 'Created', type: 'datetime', show: true, width: 140 }
]);
Loading