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)}> + +
+ {: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} +
+ {displayName} +
+ {/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: {} + } +};