diff --git a/src/lib/commandCenter/commands.ts b/src/lib/commandCenter/commands.ts
index f12bbe2ed8..470a610129 100644
--- a/src/lib/commandCenter/commands.ts
+++ b/src/lib/commandCenter/commands.ts
@@ -17,6 +17,7 @@ const groups = [
'platforms',
'databases',
'functions',
+ 'messaging',
'storage',
'domains',
'webhooks',
diff --git a/src/routes/console/project-[project]/messaging/+layout.svelte b/src/routes/console/project-[project]/messaging/+layout.svelte
new file mode 100644
index 0000000000..2b8b30178b
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/+layout.svelte
@@ -0,0 +1,62 @@
+
+
+
+ Messaging - Appwrite
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/+layout.ts b/src/routes/console/project-[project]/messaging/+layout.ts
new file mode 100644
index 0000000000..7db345058e
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/+layout.ts
@@ -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
+ };
+};
diff --git a/src/routes/console/project-[project]/messaging/+page.svelte b/src/routes/console/project-[project]/messaging/+page.svelte
new file mode 100644
index 0000000000..f2b675613c
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/+page.svelte
@@ -0,0 +1,229 @@
+
+
+
+
+
+
Messages
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if data.messages.total}
+
+
+ d.$id)} />
+ {#each $columns as column}
+ {#if column.show}
+ {column.title}
+ {/if}
+ {/each}
+
+
+ {#each data.messages.messages as message}
+
+
+
+ {#each $columns as column (column.id)}
+ {#if column.show}
+ {#if column.id === '$id'}
+ {#key $columns}
+
+ {message.$id}
+
+ {/key}
+ {:else if column.id === 'message'}
+
+ {#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}
+
+ {:else if column.id === 'providerType'}
+
+
+
+ {:else if column.id === 'status'}
+
+ {
+ e.preventDefault();
+ errors = message.deliveryErrors;
+ showFailed = true;
+ }} />
+
+ {:else if column.type === 'datetime'}
+
+ {#if !message[column.id]}
+ -
+ {:else}
+ {toLocaleDateTime(message[column.id])}
+ {/if}
+
+ {:else}
+
+ {message[column.id]}
+
+ {/if}
+ {/if}
+ {/each}
+
+ {/each}
+
+
+
+ 0}>
+
+
+
{selected.length}
+
+
+ {selected.length > 1 ? 'messages' : 'message'}
+
+ selected
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {:else if data.search && data.search != 'empty'}
+
+
+
Sorry, we couldn't find '{data.search}'
+
There are no messages that match your search.
+
+
+
+
+
+
+
+ {:else}
+
+ ($showCreate = true)} />
+ {/if}
+
+
+
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/+page.ts b/src/routes/console/project-[project]/messaging/+page.ts
new file mode 100644
index 0000000000..25c1e1cb89
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/+page.ts
@@ -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
+ };
+};
diff --git a/src/routes/console/project-[project]/messaging/breadcrumbs.svelte b/src/routes/console/project-[project]/messaging/breadcrumbs.svelte
new file mode 100644
index 0000000000..9e28bee679
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/breadcrumbs.svelte
@@ -0,0 +1,22 @@
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/create.svelte b/src/routes/console/project-[project]/messaging/create.svelte
new file mode 100644
index 0000000000..49a81af91f
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/create.svelte
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+ {#if !showCustomId}
+
+
(showCustomId = !showCustomId)}>
+
+ Bucket ID
+
+
+ {:else}
+
+ {/if}
+
+
+
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/failedModal.svelte b/src/routes/console/project-[project]/messaging/failedModal.svelte
new file mode 100644
index 0000000000..6ee183ffd1
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/failedModal.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+
Some messages failed to send.
+
+
+
+
+
+
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/header.svelte b/src/routes/console/project-[project]/messaging/header.svelte
new file mode 100644
index 0000000000..9534915abb
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/header.svelte
@@ -0,0 +1,44 @@
+
+
+
+
+ Messaging
+
+
+ {#each tabs as tab}
+
+ {tab.title}
+
+ {/each}
+
+
diff --git a/src/routes/console/project-[project]/messaging/messageStatusPill.svelte b/src/routes/console/project-[project]/messaging/messageStatusPill.svelte
new file mode 100644
index 0000000000..46b309abc7
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/messageStatusPill.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {#if status === 'sent'}
+
+ {:else if status === 'scheduled'}
+
+ {/if}
+
+ {status}
+
+
diff --git a/src/routes/console/project-[project]/messaging/provider.svelte b/src/routes/console/project-[project]/messaging/provider.svelte
new file mode 100644
index 0000000000..5e2da923a0
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/provider.svelte
@@ -0,0 +1,96 @@
+
+
+
+
+{#if icon === ''}
+ Invalid provider
+{:else}
+
+ {#if !noIcon}
+
+

+
+ {/if}
+
+ {displayName}
+
+
+{/if}
diff --git a/src/routes/console/project-[project]/messaging/providerType.svelte b/src/routes/console/project-[project]/messaging/providerType.svelte
new file mode 100644
index 0000000000..b7db1514f9
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providerType.svelte
@@ -0,0 +1,59 @@
+
+
+
+
+{#if text === ''}
+ Invalid provider type
+{:else}
+
+ {#if !noIcon}
+
+
+
+ {/if}
+
+ {text}
+
+
+{/if}
diff --git a/src/routes/console/project-[project]/messaging/store.ts b/src/routes/console/project-[project]/messaging/store.ts
new file mode 100644
index 0000000000..6a08966c05
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/store.ts
@@ -0,0 +1,240 @@
+import type { Column } from '$lib/helpers/types';
+import { Providers } from './provider.svelte';
+import { ProviderTypes } from './providerType.svelte';
+import { writable } from 'svelte/store';
+
+export const showCreate = writable(false);
+
+export const columns = writable([
+ { id: '$id', title: 'Message ID', type: 'string', show: true, width: 140 },
+ { id: 'description', title: 'Description', type: 'string', show: true, width: 140 },
+ { id: 'message', title: 'Message', type: 'string', show: false, width: 140 },
+ { id: 'providerType', title: 'Type', type: 'string', show: true, width: 100 },
+ { id: 'status', title: 'Status', type: 'string', show: true, width: 120 },
+ { id: 'scheduledAt', title: 'Scheduled at', type: 'datetime', show: true, width: 120 },
+ { id: 'deliveredAt', title: 'Delivered at', type: 'datetime', show: false, width: 120 }
+]);
+
+// TODO: remove this when the SDK and API are ready
+export type Message = {
+ $id: string;
+ $createdAt: string;
+ $updatedAt: string;
+ topics: string[];
+ users: string[];
+ targets: string[];
+ scheduledAt: string;
+ deliveredAt: string;
+ deliveryErrors: string[];
+ deliveredTo: number;
+ status: string;
+ providerType: ProviderTypes;
+ description: string;
+ data: {
+ content?: string;
+ body?: string;
+ subject?: string;
+ title?: string;
+ };
+};
+
+// TODO: remove this when the SDK and API are ready
+export const messages: { messages: Message[]; total: number } = {
+ messages: [
+ {
+ $id: '637a40ba7a703e3936e1',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ description: 'Welcome',
+ providerType: ProviderTypes.Push,
+ topics: [],
+ users: ['user-1', 'user-2'],
+ targets: [],
+ scheduledAt: '2021-03-01T00:00:00.000Z',
+ deliveredAt: '2021-03-01T00:00:00.000Z',
+ deliveryErrors: [],
+ deliveredTo: 2,
+ status: 'sent',
+ data: {
+ title: 'Welcome to the Cloud',
+ body: 'Detailed body'
+ }
+ },
+ {
+ $id: '637a40ba7a703e3936e2',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ description: 'Public beta announcement',
+ providerType: ProviderTypes.Sms,
+ topics: [],
+ users: ['user-1', 'user-2'],
+ targets: [],
+ scheduledAt: '2021-03-01T00:00:00.000Z',
+ deliveredAt: '2021-03-01T00:00:00.000Z',
+ deliveryErrors: [],
+ deliveredTo: 2,
+ status: 'scheduled',
+ data: {
+ content: 'Cloud is live on public beta'
+ }
+ },
+ {
+ $id: '637a40ba7a703e3936e3',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ description: 'Welcome',
+ providerType: ProviderTypes.Email,
+ topics: [],
+ users: ['user-1', 'user-2'],
+ targets: [],
+ scheduledAt: '',
+ deliveredAt: '',
+ deliveryErrors: [],
+ deliveredTo: 2,
+ status: 'draft',
+ data: {
+ subject: 'Welcome to the Cloud',
+ content: 'Detailed content'
+ }
+ }
+ ],
+ total: 3
+};
+
+// TODO: remove this when the SDK and API are ready
+export type Provider = {
+ $id: string;
+ $createdAt: string;
+ $updatedAt: string;
+ name: string;
+ provider: Providers;
+ default: boolean;
+ enabled: boolean;
+ type: ProviderTypes;
+ credentials: object;
+ options: object;
+};
+
+// TODO: remove this when the SDK and API are ready
+export const providersById: { [providerId: string]: Provider } = {
+ '637a40ba7a703e3936e1': {
+ $id: '637a40ba7a703e3936e1',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ name: 'My Firebase',
+ provider: Providers.FCM,
+ type: ProviderTypes.Push,
+ default: false,
+ enabled: true,
+ credentials: {},
+ options: {}
+ },
+ '637a40ba7a703e3936e2': {
+ $id: '637a40ba7a703e3936e2',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ name: 'My APNS',
+ provider: Providers.APNS,
+ type: ProviderTypes.Push,
+ default: false,
+ enabled: false,
+ credentials: {},
+ options: {}
+ },
+ '637a40ba7a703e3936e3': {
+ $id: '637a40ba7a703e3936e3',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ name: 'My MQTT',
+ provider: Providers.MQTT,
+ type: ProviderTypes.Push,
+ default: false,
+ enabled: false,
+ credentials: {},
+ options: {}
+ },
+ '637a40ba7a703e3936e4': {
+ $id: '637a40ba7a703e3936e4',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ name: 'My Sendgrid',
+ provider: Providers.Sendgrid,
+ type: ProviderTypes.Email,
+ default: false,
+ enabled: false,
+ credentials: {},
+ options: {}
+ },
+ '637a40ba7a703e3936e5': {
+ $id: '637a40ba7a703e3936e5',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ name: 'My Mailgun',
+ provider: Providers.Mailgun,
+ type: ProviderTypes.Email,
+ default: false,
+ enabled: false,
+ credentials: {},
+ options: {}
+ },
+ '637a40ba7a703e3936e6': {
+ $id: '637a40ba7a703e3936e6',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ name: 'My Telesign',
+ provider: Providers.Telesign,
+ type: ProviderTypes.Sms,
+ default: false,
+ enabled: false,
+ credentials: {},
+ options: {}
+ },
+ '637a40ba7a703e3936e7': {
+ $id: '637a40ba7a703e3936e7',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ name: 'My Msg91',
+ provider: Providers.Msg91,
+ type: ProviderTypes.Sms,
+ default: false,
+ enabled: false,
+ credentials: {},
+ options: {}
+ },
+ '637a40ba7a703e3936e8': {
+ $id: '637a40ba7a703e3936e8',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ name: 'My Textmagic',
+ provider: Providers.Textmagic,
+ type: ProviderTypes.Sms,
+ default: false,
+ enabled: false,
+ credentials: {},
+ options: {}
+ },
+ '637a40ba7a703e3936e9': {
+ $id: '637a40ba7a703e3936e9',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ name: 'My Vonage',
+ provider: Providers.Vonage,
+ type: ProviderTypes.Sms,
+ default: false,
+ enabled: false,
+ credentials: {},
+ options: {}
+ },
+ '637a40ba7a703e3936f0': {
+ $id: '637a40ba7a703e3936f0',
+ $createdAt: '2021-08-31T12:00:00.000Z',
+ $updatedAt: '2021-08-31T12:00:00.000Z',
+ name: 'My Twilio',
+ provider: Providers.Twilio,
+ type: ProviderTypes.Sms,
+ default: false,
+ enabled: false,
+ credentials: {},
+ options: {}
+ }
+};