Sorry, we couldn't find '{data.search}'
@@ -160,11 +153,33 @@
{: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
index c6fb523854..80a032040c 100644
--- a/src/routes/console/project-[project]/messaging/providers/+page.ts
+++ b/src/routes/console/project-[project]/messaging/providers/+page.ts
@@ -18,12 +18,39 @@ export const load = async ({ url, route }) => {
const limit = getLimit(url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
+ // 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('')]
+ };
+
+ 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,
page,
view,
- providers: data
+ providers
};
};
diff --git a/src/routes/console/project-[project]/messaging/providers/create.svelte b/src/routes/console/project-[project]/messaging/providers/create.svelte
index 65528f2c00..81241b514c 100644
--- a/src/routes/console/project-[project]/messaging/providers/create.svelte
+++ b/src/routes/console/project-[project]/messaging/providers/create.svelte
@@ -1,68 +1,275 @@
-
-
-
- {#if !showCustomId}
-
-
(showCustomId = !showCustomId)}
- >
- Team ID
-
-
- {:else}
-
- {/if}
-
-
-
-
-
-
+
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/store.ts b/src/routes/console/project-[project]/messaging/providers/store.ts
index fe1716cb0b..2fe1bd8df3 100644
--- a/src/routes/console/project-[project]/messaging/providers/store.ts
+++ b/src/routes/console/project-[project]/messaging/providers/store.ts
@@ -1,12 +1,381 @@
import { writable } from 'svelte/store';
import type { Column } from '$lib/components/viewSelector.svelte';
+import { Providers } from '../provider.svelte';
+import { ProviderTypes } from '../providerType.svelte';
export let showCreate = writable(false);
export const columns = writable
([
- { id: '$id', title: 'Provider ID', show: true, width: 140 },
- { id: 'name', title: 'Name', show: true, width: 140 },
- { id: 'provider', title: 'Provider', show: true, width: 120 },
- { id: 'channel', title: 'Channel', show: true, width: 100 },
- { id: 'status', title: 'Status', show: true, width: 120 }
+ { 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 }
]);
+
+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/wizard/step1.svelte b/src/routes/console/project-[project]/messaging/providers/wizard/step1.svelte
new file mode 100644
index 0000000000..1f9e238a1a
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/wizard/step1.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/step2.svelte b/src/routes/console/project-[project]/messaging/providers/wizard/step2.svelte
new file mode 100644
index 0000000000..7f1fce5d0e
--- /dev/null
+++ b/src/routes/console/project-[project]/messaging/providers/wizard/step2.svelte
@@ -0,0 +1,133 @@
+
+
+
+ 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/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/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