diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts
index 8a15993fcf..2b71457969 100644
--- a/src/lib/actions/analytics.ts
+++ b/src/lib/actions/analytics.ts
@@ -287,5 +287,8 @@ export enum Submit {
SmsResetTemplate = 'submit_sms_reset_template',
SmsUpdateInviteTemplate = 'submit_sms_update_invite_template',
SmsUpdateLoginTemplate = 'submit_sms_update_login_template',
- SmsUpdateVerificationTemplate = 'submit_sms_update_verification_template'
+ SmsUpdateVerificationTemplate = 'submit_sms_update_verification_template',
+ MessagingProviderCreate = 'submit_messaging_provider_create',
+ MessagingProviderDelete = 'submit_messaging_provider_delete',
+ MessagingProviderUpdate = 'submit_messaging_provider_update'
}
diff --git a/src/lib/components/drop.svelte b/src/lib/components/drop.svelte
index d6c899df21..f61ff6a246 100644
--- a/src/lib/components/drop.svelte
+++ b/src/lib/components/drop.svelte
@@ -13,6 +13,7 @@
export let noStyle = false;
export let fullWidth = false;
export let fixed = false;
+ export let display = 'block';
const dispatch = createEventDispatcher<{
blur: undefined;
@@ -100,7 +101,11 @@
-
+
diff --git a/src/lib/components/labelCard.svelte b/src/lib/components/labelCard.svelte
index 9d30f9b0c1..461e5a53f7 100644
--- a/src/lib/components/labelCard.svelte
+++ b/src/lib/components/labelCard.svelte
@@ -1,5 +1,7 @@
+
+
+
+ {#if data.providers.total}
+
+
+
+ {:else if data.search && data.search != 'empty'}
+
+
+
Sorry, we couldn't find '{data.search}'
+
There are no providers that match your search.
+
+
+
+ {:else}
+
+
+
+
+ Create your first provider to get started.
+
+
+ Need a hand? Learn more in our documentation.
+
+
+
+
+
+
+
+
+
+ {/if}
+
diff --git a/src/routes/console/project-[project]/messaging/providers/+page.ts b/src/routes/console/project-[project]/messaging/providers/+page.ts
new file mode 100644
index 0000000000..45f465b7d3
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/+page.ts
@@ -0,0 +1,77 @@
+import { Query } from '@appwrite.io/console';
+import { sdk } from '$lib/stores/sdk';
+import {
+ View,
+ getLimit,
+ getPage,
+ getQuery,
+ getSearch,
+ getView,
+ pageToOffset
+} from '$lib/helpers/load';
+import { Dependencies, PAGE_LIMIT } from '$lib/constants';
+import { providersById, type Provider } from '../store';
+import { queries, queryParamToMap } from '$lib/components/filters/store';
+
+const providers = Object.values(providersById);
+
+let data: { providers: Provider[]; total: number } = {
+ providers: [...providers],
+ total: providers.length
+};
+
+export const load = async ({ depends, url, route }) => {
+ depends(Dependencies.MESSAGING_PROVIDERS);
+
+ const page = getPage(url);
+ const search = getSearch(url);
+ const view = getView(url, route, View.Grid);
+ const limit = getLimit(url, route, PAGE_LIMIT);
+ const offset = pageToOffset(page, limit);
+ const query = getQuery(url);
+
+ const parsedQueries = queryParamToMap(query || '[]');
+ queries.set(parsedQueries);
+
+ // TODO: get rid of demo data
+ let providers: { providers: Provider[]; total: number } = { providers: [], total: 0 };
+ if (search == 'demo') {
+ providers = 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/providers'),
+ {
+ 'X-Appwrite-Project': sdk.forProject.client.config.project,
+ 'content-type': 'application/json',
+ 'X-Appwrite-Mode': 'admin'
+ },
+ params
+ );
+
+ providers = response;
+ }
+
+ return {
+ offset,
+ limit,
+ search,
+ query,
+ page,
+ view,
+ providers
+ };
+};
diff --git a/src/routes/console/project-[project]/messaging/providers/create.svelte b/src/routes/console/project-[project]/messaging/providers/create.svelte
new file mode 100644
index 0000000000..d828be10c8
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/create.svelte
@@ -0,0 +1,274 @@
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/createProviderDropdown.svelte b/src/routes/console/project-[project]/messaging/providers/createProviderDropdown.svelte
new file mode 100644
index 0000000000..e829f4927c
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/createProviderDropdown.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+ {#each Object.entries(providers) as [type, option]}
+ {
+ if (
+ type !== ProviderTypes.Email &&
+ type !== ProviderTypes.Sms &&
+ type !== ProviderTypes.Push
+ )
+ return;
+ $providerType = type;
+ const p = Object.keys(providers[type].providers).shift();
+ if (p && isValueOfStringEnum(Providers, p)) {
+ $provider = p;
+ }
+ showCreateDropdown = false;
+ wizard.start(Create);
+ }}>
+ {option.name}
+
+ {/each}
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/provider-[provider]/+layout.svelte b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/+layout.svelte
new file mode 100644
index 0000000000..944197a93a
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/+layout.svelte
@@ -0,0 +1,5 @@
+
+ Provider - Appwrite
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/provider-[provider]/+layout.ts b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/+layout.ts
new file mode 100644
index 0000000000..07531b6b5f
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/+layout.ts
@@ -0,0 +1,31 @@
+import type { LayoutLoad } from './$types';
+import Breadcrumbs from './breadcrumbs.svelte';
+import Header from './header.svelte';
+import { sdk } from '$lib/stores/sdk';
+import { Dependencies } from '$lib/constants';
+import { error } from '@sveltejs/kit';
+
+export const load: LayoutLoad = async ({ params, depends }) => {
+ depends(Dependencies.MESSAGING_PROVIDER);
+
+ const response = await sdk.forProject.client.call(
+ 'GET',
+ new URL(sdk.forProject.client.config.endpoint + '/messaging/providers/' + params.provider),
+ {
+ 'X-Appwrite-Project': sdk.forProject.client.config.project,
+ 'content-type': 'application/json',
+ 'X-Appwrite-Mode': 'admin'
+ }
+ );
+
+
+ try {
+ return {
+ header: Header,
+ breadcrumbs: Breadcrumbs,
+ provider: response
+ };
+ } catch (e) {
+ throw error(e.code, e.message);
+ }
+};
diff --git a/src/routes/console/project-[project]/messaging/providers/provider-[provider]/+page.svelte b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/+page.svelte
new file mode 100644
index 0000000000..2262f62ac1
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/+page.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/provider-[provider]/breadcrumbs.svelte b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/breadcrumbs.svelte
new file mode 100644
index 0000000000..5a3f26f90d
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/breadcrumbs.svelte
@@ -0,0 +1,27 @@
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/provider-[provider]/dangerZone.svelte b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/dangerZone.svelte
new file mode 100644
index 0000000000..7befc15c0e
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/dangerZone.svelte
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+ Delete provider
+
+ The provider's instance will be permanently deleted. This action is irreversible.
+
+
+
+ {$provider.name}
+
+
+ Last updated: {toLocaleDateTime($provider.$updatedAt)}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/provider-[provider]/deleteProvider.svelte b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/deleteProvider.svelte
new file mode 100644
index 0000000000..e32cbf6aae
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/deleteProvider.svelte
@@ -0,0 +1,59 @@
+
+
+
+
+ Are you sure you want to delete {$provider.name} from '{$project.name}'?
+
+
+
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/provider-[provider]/header.svelte b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/header.svelte
new file mode 100644
index 0000000000..d78a0f0d43
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/header.svelte
@@ -0,0 +1,17 @@
+
+
+
+
+
+ {$provider?.name ? $provider?.name : '-'}
+
+ {$provider?.$id}
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/provider-[provider]/store.ts b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/store.ts
new file mode 100644
index 0000000000..a69272a70c
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/store.ts
@@ -0,0 +1,9 @@
+import { derived } from 'svelte/store';
+import { page } from '$app/stores';
+import type { Provider } from '../../store';
+
+export const provider = derived(
+ page,
+ // TODO: Set actual type
+ ($page) => $page.data.provider as Provider
+);
diff --git a/src/routes/console/project-[project]/messaging/providers/provider-[provider]/updateName.svelte b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/updateName.svelte
new file mode 100644
index 0000000000..f77b626ca1
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/updateName.svelte
@@ -0,0 +1,51 @@
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/provider-[provider]/updateStatus.svelte b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/updateStatus.svelte
new file mode 100644
index 0000000000..f4fddcc744
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/provider-[provider]/updateStatus.svelte
@@ -0,0 +1,367 @@
+
+
+
+
+
+
+
+
+
Provider:
+
Channel:
+
Created: {toLocaleDateTime($provider.$createdAt)}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/store.ts b/src/routes/console/project-[project]/messaging/providers/store.ts
new file mode 100644
index 0000000000..714db9d9a7
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/store.ts
@@ -0,0 +1,381 @@
+import { writable } from 'svelte/store';
+import type { Column } from '$lib/helpers/types';
+import { Providers } from '../provider.svelte';
+import { ProviderTypes } from '../providerType.svelte';
+
+export let showCreate = writable(false);
+
+export const columns = writable
([
+ { id: '$id', title: 'Provider ID', type: 'string', show: true },
+ { id: 'name', title: 'Name', type: 'string', show: true },
+ { id: 'provider', title: 'Provider', type: 'string', show: true },
+ { id: 'type', title: 'Type', type: 'string', show: true },
+ { id: 'enabled', title: 'Status', type: 'boolean', show: true }
+]);
+
+type ProvidersMap = {
+ [key in ProviderTypes]: {
+ name: string;
+ text: string;
+ icon: string;
+ providers: {
+ [key in Providers]?: {
+ imageIcon: string;
+ title: string;
+ description: string;
+ configure: {
+ label: string;
+ name: string;
+ type: 'text' | 'phone' | 'email' | 'domain' | 'file' | 'switch';
+ placeholder?: string;
+ description?: string;
+ popover?: string[];
+ allowedFileExtensions?: string[];
+ }[];
+ };
+ };
+ };
+};
+
+export const providers: ProvidersMap = {
+ [ProviderTypes.Sms]: {
+ name: 'SMS',
+ text: 'SMS',
+ icon: 'annotation',
+ providers: {
+ [Providers.Twilio]: {
+ imageIcon: 'twilio',
+ title: 'Twilio',
+ description: '',
+ configure: [
+ {
+ label: 'Account SID',
+ name: 'accountSid',
+ type: 'text',
+ placeholder: 'Enter Account SID',
+ popover: [
+ 'How to get the Account SID?',
+ 'Head to Twilio console -> Account info -> Account SID.'
+ ]
+ },
+ {
+ label: 'Auth token',
+ name: 'authToken',
+ type: 'text',
+ placeholder: 'Enter Auth token',
+ popover: [
+ 'How to get the Auth token?',
+ 'Head to Twilio console -> Account info -> Auth Token.'
+ ]
+ },
+ {
+ label: 'Sender number',
+ name: 'from',
+ type: 'phone',
+ placeholder: 'Enter phone',
+ popover: [
+ 'How to get sender number?',
+ 'Head to Twilio console -> Account info -> My Twilio phone number.',
+ 'If you have multiple Twilio phone numbers, you can select one as the default number.'
+ ]
+ }
+ ]
+ },
+ [Providers.Msg91]: {
+ imageIcon: 'msg91',
+ title: 'MSG91',
+ description: '',
+ configure: [
+ {
+ label: 'Auth key',
+ name: 'authKey',
+ type: 'text',
+ placeholder: 'Enter auth key',
+ popover: [
+ 'How to get the Auth key?',
+ 'Create an account in MSG91.',
+ 'Click to open the Username dropdown -> Authkey -> Verify your mobile number -> Create Authkey.'
+ ]
+ },
+ {
+ label: 'Sender ID',
+ name: 'senderId',
+ type: 'text',
+ placeholder: 'Enter sender ID',
+ popover: [
+ 'How to create a Sender ID?',
+ 'Head to MSG91 dashboard -> SMS -> Sender ID -> Create sender ID.'
+ ]
+ },
+ {
+ label: 'Sender number',
+ name: 'from',
+ type: 'phone',
+ placeholder: 'Enter phone'
+ }
+ ]
+ },
+ [Providers.Telesign]: {
+ imageIcon: 'telesign',
+ title: 'Telesign',
+ description: '',
+ configure: [
+ {
+ label: 'Username',
+ name: 'username',
+ type: 'text',
+ placeholder: 'Enter username'
+ },
+ {
+ label: 'Password',
+ name: 'password',
+ type: 'text',
+ placeholder: 'Enter password'
+ },
+ {
+ label: 'Sender number',
+ name: 'from',
+ type: 'phone',
+ placeholder: 'Enter phone'
+ }
+ ]
+ },
+ [Providers.Textmagic]: {
+ imageIcon: 'textmagic',
+ title: 'Textmagic',
+ description: '',
+ configure: [
+ {
+ label: 'API key',
+ name: 'apiKey',
+ type: 'text',
+ placeholder: 'Enter API key',
+ popover: [
+ 'How to get the API key?',
+ 'Create an account in Textmagic.',
+ 'Head to TextMagic dashboard -> API Settings -> Add new API key.'
+ ]
+ },
+ {
+ label: 'Username',
+ name: 'username',
+ type: 'text',
+ placeholder: 'Enter username'
+ },
+ {
+ label: 'Sender number',
+ name: 'from',
+ type: 'phone',
+ placeholder: 'Enter phone'
+ }
+ ]
+ },
+ [Providers.Vonage]: {
+ imageIcon: 'vonage',
+ title: 'Vonage',
+ description: '',
+ configure: [
+ {
+ label: 'API key',
+ name: 'apiKey',
+ type: 'text',
+ placeholder: 'Enter API key',
+ popover: [
+ 'How to get the API key?',
+ 'Create an account in Vonage.',
+ 'Head to Vonage dashboard and copy the API key.'
+ ]
+ },
+ {
+ label: 'API secret',
+ name: 'apiSecret',
+ type: 'text',
+ placeholder: 'Enter API secret',
+ popover: [
+ 'How to get the API secret?',
+ 'Head to Vonage dashboard and copy the API secret.'
+ ]
+ },
+ {
+ label: 'Sender number',
+ name: 'from',
+ type: 'phone',
+ placeholder: 'Enter phone'
+ }
+ ]
+ }
+ }
+ },
+ [ProviderTypes.Email]: {
+ name: 'Email',
+ text: 'emails',
+ icon: 'mail',
+ providers: {
+ [Providers.Mailgun]: {
+ imageIcon: 'mailgun',
+ title: 'Mailgun',
+ description: '',
+ configure: [
+ {
+ label: 'API key',
+ name: 'apiKey',
+ type: 'text',
+ placeholder: 'Enter API key',
+ popover: [
+ 'How to get the API key?',
+ 'Create an account in Mailgun.',
+ 'Head to Profile -> API Security -> Add new key.'
+ ]
+ },
+ {
+ label: 'Domain',
+ name: 'domain',
+ type: 'domain',
+ placeholder: 'Enter domain',
+ popover: [
+ 'How to create a domain?',
+ 'Head to Sending -> Domains -> Add new domain.',
+ 'Follow Mailgun instructions to verify the domain name.'
+ ]
+ },
+ {
+ label: 'EU region',
+ name: 'isEuRegion',
+ type: 'switch',
+ description:
+ 'Enable the EU region setting if your domain is within the European Union.'
+ },
+ {
+ label: 'Sender email',
+ name: 'from',
+ type: 'email',
+ placeholder: 'Enter email'
+ }
+ ]
+ },
+ [Providers.Sendgrid]: {
+ imageIcon: 'sendgrid',
+ title: 'Sendgrid',
+ description: '',
+ configure: [
+ {
+ label: 'API key',
+ name: 'apiKey',
+ type: 'text',
+ placeholder: 'Enter API key',
+ popover: [
+ 'How to get the API key?',
+ 'Create an account in Mailgun.',
+ 'Head to Profile -> API Security -> Add new key.'
+ ]
+ },
+ {
+ label: 'Sender email',
+ name: 'from',
+ type: 'email',
+ placeholder: 'Enter email'
+ }
+ ]
+ }
+ }
+ },
+ [ProviderTypes.Push]: {
+ name: 'Push notification',
+ text: 'notifications',
+ icon: 'device-mobile',
+ providers: {
+ [Providers.FCM]: {
+ imageIcon: 'firebase',
+ title: 'FCM',
+ description: 'Firebase Cloud Messaging',
+ configure: [
+ {
+ label: 'Server key (.json file)',
+ name: 'serverKey',
+ type: 'file',
+ allowedFileExtensions: ['json'],
+ placeholder: 'Enter server key',
+ popover: [
+ 'How to get the FCM server key?',
+ 'Head to Project settings -> Service accounts -> Generate new private key.',
+ 'Generating the new key will result in the download of a JSON file.'
+ ]
+ }
+ ]
+ },
+ [Providers.APNS]: {
+ imageIcon: 'apple',
+ title: 'APNS',
+ description: 'Apple Push Notification Service',
+ configure: [
+ {
+ label: 'Team ID',
+ name: 'teamId',
+ type: 'text',
+ placeholder: 'Enter team ID',
+ popover: [
+ 'How to get the team ID?',
+ 'Head to Apple Developer Member Center -> Membership details -> Team ID.'
+ ]
+ },
+ {
+ label: 'Bundle ID',
+ name: 'bundleId',
+ type: 'text',
+ placeholder: 'Enter bundle ID',
+ popover: [
+ 'How to get the bundle ID?',
+ 'Head to Apple Developer Member Center -> Certificates, Identifiers & Profiles -> Identifiers.',
+ `
+
+

+
+
+ `
+ ]
+ },
+ {
+ label: 'Authentication key ID',
+ name: 'authKeyId',
+ type: 'text',
+ placeholder: 'Enter key ID',
+ popover: [
+ 'How to get the auth key ID?',
+ 'Head to Apple Developer Member Center -> Certificates, Identifiers & Profiles -> Keys.',
+ 'Click on your key to view details.'
+ ]
+ },
+ {
+ label: 'Auth key (.p8 file)',
+ name: 'authKey',
+ type: 'file',
+ allowedFileExtensions: ['p8'],
+ popover: [
+ 'How to get the authentication key?',
+ 'Head to Apple Developer Member Center (under Program resources) -> Certificates, Identifiers & Profiles -> Keys.',
+ 'Create a key and give it a name. Enable the Apple Push Notifications service (APNS), and register your key.'
+ ]
+ }
+ ]
+ }
+ // [Providers.MQTT]: {
+ // imageIcon: 'mqtt',
+ // title: 'MQTT',
+ // description: 'Message Queuing Telemtry Transport'
+ // }
+ }
+ }
+};
diff --git a/src/routes/console/project-[project]/messaging/providers/table.svelte b/src/routes/console/project-[project]/messaging/providers/table.svelte
new file mode 100644
index 0000000000..2921c40ecf
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/table.svelte
@@ -0,0 +1,170 @@
+
+
+
+
+ d.$id)} />
+ {#each $columns as column}
+ {#if column.show}
+ {column.title}
+ {/if}
+ {/each}
+
+
+ {#each data.providers.providers as provider (provider.$id)}
+
+
+ {#each $columns as column}
+ {#if column.show}
+ {#if column.id === '$id'}
+ {#key $columns}
+
+ {provider.$id}
+
+ {/key}
+ {:else if column.id === 'provider'}
+
+
+
+ {:else if column.id === 'type'}
+
+
+
+ {:else if column.id === 'enabled'}
+
+
+ {#if provider.enabled}
+
+ {/if}
+
+ {provider.enabled ? 'enabled' : 'disabled'}
+
+
+
+ {:else}
+
+ {provider[column.id]}
+
+ {/if}
+ {/if}
+ {/each}
+
+ {/each}
+
+
+
+ 0}>
+
+
+
{selectedIds.length}
+
+
+ {selectedIds.length > 1 ? 'providers' : 'provider'}
+
+ selected
+
+
+
+
+
+
+
+
+
+
+
+
+ Are you sure you want to delete {selectedIds.length}
+ {selectedIds.length > 1 ? 'providers' : 'provider'}?
+
+
+
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/update.svelte b/src/routes/console/project-[project]/messaging/providers/update.svelte
new file mode 100644
index 0000000000..4f3c14c44f
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/update.svelte
@@ -0,0 +1,268 @@
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/wizard/configure.svelte b/src/routes/console/project-[project]/messaging/providers/wizard/configure.svelte
new file mode 100644
index 0000000000..404b4f69ad
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/wizard/configure.svelte
@@ -0,0 +1,147 @@
+
+
+
+ Configure
+
+ Set up the credentials below to enable {providers[$providerType].providers[$provider].title}
+ for sending
+ {providers[$providerType].text}.
+
+
+ {#each inputs as input}
+ {#if input.type === 'text'}
+
+
+ {@html input.popover?.join('
')}
+
+
+ {:else if input.type === 'email'}
+
+
+
+ {@html input.popover?.join('
')}
+
+
+
+ {:else if input.type === 'domain'}
+
+
+
+ {@html input.popover?.join('
')}
+
+
+
+ {:else if input.type === 'phone'}
+
+
+
+ {@html input.popover?.join('
')}
+
+
+
+ {:else if input.type === 'file'}
+
+
+
+ {@html input.popover?.join('
')}
+
+
+
+ {:else if input.type === 'switch'}
+
+
+ {input.description}
+
+
+ {/if}
+ {/each}
+
+
+ Need a hand?
+
+
+
+
+
+
+ Read the full guide in the documentation
+
+
+
+
+
+
+
+
+
+ Invite a team member to complete this step
+
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/wizard/provider.svelte b/src/routes/console/project-[project]/messaging/providers/wizard/provider.svelte
new file mode 100644
index 0000000000..1f9e238a1a
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/wizard/provider.svelte
@@ -0,0 +1,161 @@
+
+
+
+ Provider
+
+
+
+ {#if !showCustomId}
+
+
(showCustomId = !showCustomId)}
+ >
+ Provider ID
+
+
+ {:else}
+
+ {/if}
+
+ Select a provider you would like to enable for sending {providers[$providerType].text}.
+
+
+ {#each Object.entries(providers[$providerType].providers) as [value, option]}
+
+ {option.title}
+ {#if option.description}
+ {option.description}
+ {/if}
+
+ {/each}
+
+
+
diff --git a/src/routes/console/project-[project]/messaging/providers/wizard/store.ts b/src/routes/console/project-[project]/messaging/providers/wizard/store.ts
new file mode 100644
index 0000000000..9f47f8a41a
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/wizard/store.ts
@@ -0,0 +1,103 @@
+import type { Providers } from '../../provider.svelte';
+import type { ProviderTypes } from '../../providerType.svelte';
+import { writable } from 'svelte/store';
+
+type ProviderParams = {
+ providerId: string;
+ name: string;
+ default: boolean;
+ enabled: boolean;
+};
+
+/**
+ * SMS providers
+ */
+
+export type TwilioProviderParams = ProviderParams & {
+ accountSid: string;
+ authToken: string;
+ from: string;
+};
+
+export type Msg91ProviderParams = ProviderParams & {
+ from: string;
+ senderId: string;
+ authKey: string;
+};
+
+export type TelesignProviderParams = ProviderParams & {
+ from: string;
+ username: string;
+ password: string;
+};
+
+export type TextmagicProviderParams = ProviderParams & {
+ from: string;
+ username: string;
+ apiKey: string;
+};
+
+export type VonageProviderParams = ProviderParams & {
+ from: string;
+ apiKey: string;
+ apiSecret: string;
+};
+
+/**
+ * Email providers
+ */
+
+export type MailgunProviderParams = ProviderParams & {
+ isEuRegion: boolean;
+ from: string;
+ apiKey: string;
+ domain: string;
+};
+
+export type SendgridProviderParams = ProviderParams & {
+ from: string;
+ apiKey: string;
+};
+
+/**
+ * Push providers
+ */
+
+export type FCMProviderParams = ProviderParams & {
+ serverKey: string;
+};
+
+export type APNSProviderParams = ProviderParams & {
+ authKey: string;
+ authKeyId: string;
+ teamId: string;
+ bundleId: string;
+};
+
+export type MQTTProviderParams = ProviderParams & {
+ serverKey: string;
+};
+
+export const providerType = writable(null);
+export const provider = writable(null);
+export const providerParams = writable<{
+ twilio: Partial;
+ msg91: Partial;
+ telesign: Partial;
+ textmagic: Partial;
+ vonage: Partial;
+ mailgun: Partial;
+ sendgrid: Partial;
+ fcm: Partial;
+ apns: Partial;
+}>({
+ twilio: null,
+ msg91: null,
+ telesign: null,
+ textmagic: null,
+ vonage: null,
+ mailgun: null,
+ sendgrid: null,
+ fcm: null,
+ apns: null
+});
diff --git a/src/routes/console/project-[project]/messaging/providers/wizard/store.ts.bak b/src/routes/console/project-[project]/messaging/providers/wizard/store.ts.bak
new file mode 100644
index 0000000000..0ee3ba46ca
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/wizard/store.ts.bak
@@ -0,0 +1,161 @@
+import { writable } from 'svelte/store';
+import type { Column } from '$lib/components/viewSelector.svelte';
+
+export let showCreate = writable(false);
+
+export const columns = writable([
+ { id: '$id', title: 'Provider ID', show: true },
+ { id: 'name', title: 'Name', show: true },
+ { id: 'provider', title: 'Provider', show: true },
+ { id: 'channel', title: 'Channel', show: true },
+ { id: 'status', title: 'Status', show: true }
+]);
+
+export type Instruction = {
+ text: string;
+ input: {
+ label: string;
+ name: string;
+ type: 'text' | 'domain' | 'email';
+ placeholder: string;
+ };
+};
+
+export const providers = {
+ sms: {
+ name: 'SMS',
+ text: 'SMS',
+ icon: 'annotation',
+ providers: {
+ twilio: {
+ imageIcon: 'twilio',
+ title: 'Twilio',
+ description: ''
+ },
+ msg91: {
+ imageIcon: 'msg91',
+ title: 'MSG91',
+ description: ''
+ },
+ telesign: {
+ imageIcon: 'telesign',
+ title: 'Telesign',
+ description: ''
+ },
+ textmagic: {
+ imageIcon: 'textmagic',
+ title: 'Textmagic',
+ description: ''
+ },
+ vonage: {
+ imageIcon: 'vonage',
+ title: 'Vonage',
+ description: ''
+ }
+ }
+ },
+ email: {
+ name: 'Email',
+ text: 'emails',
+ icon: 'mail',
+ providers: {
+ mailgun: {
+ imageIcon: 'mailgun',
+ title: 'Mailgun',
+ description: '',
+ initialize: [
+ {
+ text: 'Before you can create a Mailgun provider, you need to first create a Mailgun account.'
+ },
+ {
+ text: 'Head to your Profile > API Security.'
+ },
+ {
+ text: 'Generate a key and give it a name. Copy and paste it in the field below.',
+ input: {
+ label: 'API key',
+ name: 'apiKey',
+ type: 'text',
+ placeholder: 'Enter API key'
+ }
+ },
+ {
+ // TODO: Update link to domain verification
+ text: 'Head to Sending > Domains and click on \'Add New Domain\'. Verify your domain by following the instructions.',
+ input: {
+ label: 'Base URL',
+ name: 'baseUrl',
+ type: 'text',
+ placeholder: 'Enter base URL'
+ }
+ }
+ ],
+ configure: [
+ {
+ text: 'Provide a display name your recipient will see when they receive your emails.',
+ input: {
+ label: 'From',
+ name: 'from',
+ type: 'text',
+ placeholder: 'Enter name'
+ }
+ },
+ {
+ text: 'Provide an email address that will be visible to the recipient as the senders email address for this message.',
+ input: {
+ label: 'From email address',
+ name: 'email',
+ type: 'email',
+ placeholder: 'Enter email'
+ }
+ },
+ {
+ text: 'Provide an email address for users to use when replying to your emails.',
+ input: {
+ label: 'Reply to',
+ name: 'replyTo',
+ type: 'email',
+ placeholder: 'Enter email'
+ }
+ },
+ {
+ text: 'Provide the domain as it is registered on Mailgun.',
+ input: {
+ label: 'Domain',
+ name: 'domain',
+ type: 'domain',
+ placeholder: 'Enter domain'
+ }
+ }
+ ]
+ },
+ sendgrid: {
+ imageIcon: 'sendgrid',
+ title: 'Sendgrid',
+ description: ''
+ }
+ }
+ },
+ push: {
+ name: 'Push notification',
+ text: 'notifications',
+ icon: 'device-mobile',
+ providers: {
+ fcm: {
+ imageIcon: 'firebase',
+ title: 'FCM',
+ description: 'Firebase Cloud Messaging'
+ },
+ apns: {
+ imageIcon: 'apple',
+ title: 'APNS',
+ description: 'Apple Push Notification Service'
+ },
+ mqtt: {
+ imageIcon: 'mqtt',
+ title: 'MQTT',
+ description: 'Message Queuing Telemtry Transport'
+ }
+ }
+ }
+};
diff --git a/static/images/apns-bundle-id.png b/static/images/apns-bundle-id.png
new file mode 100644
index 0000000000..90fde1eff2
Binary files /dev/null and b/static/images/apns-bundle-id.png differ