Skip to content

Commit

Permalink
Add messaging overview route
Browse files Browse the repository at this point in the history
  • Loading branch information
stnguyen90 committed Dec 20, 2023
1 parent ade620c commit a99082e
Show file tree
Hide file tree
Showing 13 changed files with 949 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/lib/commandCenter/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const groups = [
'platforms',
'databases',
'functions',
'messaging',
'storage',
'domains',
'webhooks',
Expand Down
62 changes: 62 additions & 0 deletions src/routes/console/project-[project]/messaging/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
import { project } from '../store';
import { showCreate } from './store';
// TODO: finalize the commands
$: $registerCommands([
{
label: 'Create message',
callback: () => {
if (!$page.url.pathname.endsWith('messaging')) {
goto(`/console/project-${$project.$id}/messaging`);
}
$showCreate = true;
},
keys: $page.url.pathname.endsWith('messaging') ? ['c'] : ['c', 'm'],
icon: 'plus',
group: 'messaging'
},
{
label: 'Go to topics',
callback() {
goto(`/console/project-${$project.$id}/messaging/topics`);
},
keys: ['g', 't'],
disabled:
$page.url.pathname.endsWith('topics') || $page.url.pathname.includes('message-'),
group: 'navigation',
rank: 10
},
{
label: 'Go to providers',
callback() {
goto(`/console/project-${$project.$id}/messaging/providers`);
},
keys: ['g', 'p'],
disabled:
$page.url.pathname.endsWith('topics') || $page.url.pathname.includes('message-'),
group: 'navigation',
rank: 10
}
// {
// label: 'Find messages',
// callback: () => {
// addSubPanel(BucketsPanel);
// },
// group: 'messaging',
// rank: -1
// }
]);
$: $updateCommandGroupRanks({ messaging: 200, navigation: 100 });
</script>

<svelte:head>
<title>Messaging - Appwrite</title>
</svelte:head>

<slot />
10 changes: 10 additions & 0 deletions src/routes/console/project-[project]/messaging/+layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Breadcrumbs from './breadcrumbs.svelte';
import Header from './header.svelte';
import type { LayoutLoad } from './$types';

export const load: LayoutLoad = async () => {
return {
header: Header,
breadcrumbs: Breadcrumbs
};
};
229 changes: 229 additions & 0 deletions src/routes/console/project-[project]/messaging/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import {
Empty,
EmptySearch,
FloatingActionBar,
Heading,
Id,
PaginationWithLimit,
SearchQuery,
ViewSelector
} from '$lib/components';
import { Button } from '$lib/elements/forms';
import {
TableBody,
TableCell,
TableCellCheck,
TableCellHead,
TableCellHeadCheck,
TableCellText,
TableHeader,
TableRowLink
} from '$lib/elements/table';
import TableScroll from '$lib/elements/table/tableScroll.svelte';
import { toLocaleDateTime } from '$lib/helpers/date';
import { Container } from '$lib/layout';
import type { Models } from '@appwrite.io/console';
import type { PageData } from './$types';
import Create from './create.svelte';
import { columns, showCreate } from './store';
import MessageStatusPill from './messageStatusPill.svelte';
import ProviderType, { ProviderTypes } from './providerType.svelte';
import Filters from '$lib/components/filters/filters.svelte';
import FailedModal from './failedModal.svelte';
export let data: PageData;
let selected: string[] = [];
let showDelete = false;
let showFailed = false;
let errors: string[] = [];
const project = $page.params.project;
async function messageCreated(event: CustomEvent<Models.Bucket>) {
$showCreate = false;
await goto(`${base}/console/project-${project}/messaging/message-${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">Messages</Heading>
<div class="is-only-mobile">
<Button on:click={() => ($showCreate = true)} event="create_message">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create message</span>
</Button>
</div>
</div>
<!-- TODO: fix width of search input in mobile -->
<SearchQuery search={data.search} placeholder="Search by channel, topic, provider, or ID">
<div class="u-flex u-gap-16 is-not-mobile">
<!-- TODO: make this not database-specific -->
<Filters query={data.query} {columns} />
<ViewSelector
view={data.view}
{columns}
hideView
allowNoColumns
showColsTextMobile />
<Button on:click={() => ($showCreate = true)} event="create_message">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create message</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={data.view}
{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.messages.total}
<TableScroll>
<TableHeader>
<TableCellHeadCheck
bind:selected
pageItemsIds={data.messages.messages.map((d) => d.$id)} />
{#each $columns as column}
{#if column.show}
<TableCellHead width={column.width}>{column.title}</TableCellHead>
{/if}
{/each}
</TableHeader>
<TableBody>
{#each data.messages.messages as message}
<TableRowLink
href={`${base}/console/project-${project}/messaging/message-${message.$id}`}>
<TableCellCheck bind:selectedIds={selected} id={message.$id} />

{#each $columns as column (column.id)}
{#if column.show}
{#if column.id === '$id'}
{#key $columns}
<TableCell title={column.title} width={column.width}>
<Id value={message.$id}>{message.$id}</Id>
</TableCell>
{/key}
{:else if column.id === 'message'}
<TableCellText title={column.title} width={column.width}>
{#if message.providerType === ProviderTypes.Push}
{message.data.title}
{:else if message.providerType === ProviderTypes.Sms}
{message.data.content}
{:else if message.providerType === ProviderTypes.Email}
{message.data.subject}
{:else}
Invalid provider
{/if}
</TableCellText>
{:else if column.id === 'providerType'}
<TableCellText title={column.title} width={column.width}>
<ProviderType type={message.providerType} size="s" />
</TableCellText>
{:else if column.id === 'status'}
<TableCellText onlyDesktop title="Status">
<MessageStatusPill
status={message.status}
on:click={(e) => {
e.preventDefault();
errors = message.deliveryErrors;
showFailed = true;
}} />
</TableCellText>
{:else if column.type === 'datetime'}
<TableCellText title={column.title} width={column.width}>
{#if !message[column.id]}
-
{:else}
{toLocaleDateTime(message[column.id])}
{/if}
</TableCellText>
{:else}
<TableCellText title={column.title} width={column.width}>
{message[column.id]}
</TableCellText>
{/if}
{/if}
{/each}
</TableRowLink>
{/each}
</TableBody>
</TableScroll>

<FloatingActionBar show={selected.length > 0}>
<div class="u-flex u-cross-center u-main-space-between actions">
<div class="u-flex u-cross-center u-gap-8">
<span class="indicator body-text-2 u-bold">{selected.length}</span>
<p>
<span class="is-only-desktop">
{selected.length > 1 ? 'messages' : 'message'}
</span>
selected
</p>
</div>

<div class="u-flex u-cross-center u-gap-8">
<Button text on:click={() => (selected = [])}>Cancel</Button>
<!-- TODO: handle delete -->
<Button secondary on:click={() => (showDelete = true)}>
<p>Delete</p>
</Button>
</div>
</div>
</FloatingActionBar>

<PaginationWithLimit
name="Messages"
limit={data.limit}
offset={data.offset}
total={data.messages.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 messages that match your search.</p>
</div>
<div class="u-flex u-gap-16">
<!-- TODO: update docs link -->
<Button
external
href="https://appwrite.io/docs/products/storage/upload-download"
text>
Documentation
</Button>
<Button secondary href={`/console/project-${$page.params.project}/messaging`}>
Clear search
</Button>
</div>
</EmptySearch>
{:else}
<!-- TODO: update docs link -->
<Empty
single
href="https://appwrite.io/docs"
target="message"
on:click={() => ($showCreate = true)} />
{/if}
</Container>

<FailedModal bind:show={showFailed} {errors} />

<!-- TODO: handle create -->
<Create bind:showCreate={$showCreate} on:created={messageCreated} />
72 changes: 72 additions & 0 deletions src/routes/console/project-[project]/messaging/+page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
View,
getLimit,
getPage,
getQuery,
getSearch,
getView,
pageToOffset
} from '$lib/helpers/load';
import { CARD_LIMIT } from '$lib/constants';
import type { PageLoad } from './$types';
import { messages as data, providersById } from './store';
import type { Message } from './store';
import { Query } from '@appwrite.io/console';
import { sdk } from '$lib/stores/sdk';
import { queries, queryParamToMap } from '$lib/components/filters/store';

export const load: PageLoad = async ({ url, route }) => {
const page = getPage(url);
const search = getSearch(url);
const view = getView(url, route, View.Grid);
const limit = getLimit(url, route, CARD_LIMIT);
const offset = pageToOffset(page, limit);
const query = getQuery(url);

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

// TODO: remove when the API is ready with data
// This allows us to mock w/ data and when search returns 0 results
let messages: { messages: Message[]; total: number } = { messages: [], total: 0 };
if (search === 'demo') {
messages = data;
} else {
const params = {
queries: [
Query.limit(limit),
Query.offset(offset),
Query.orderDesc(''),
...parsedQueries.values()
]
};

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

const response = await sdk.forProject.client.call(
'GET',
new URL(sdk.forProject.client.config.endpoint + '/messaging/messages'),
{
'X-Appwrite-Project': sdk.forProject.client.config.project,
'content-type': 'application/json',
'X-Appwrite-Mode': 'admin'
},
params
);

messages = response;
}

return {
offset,
limit,
search,
query,
page,
view,
messages,
providersById
};
};
Loading

0 comments on commit a99082e

Please sign in to comment.