diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 47ccf8b69b..00d3105305 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -282,5 +282,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/labelCard.svelte b/src/lib/components/labelCard.svelte index 5565b392e4..a13f4c1d7e 100644 --- a/src/lib/components/labelCard.svelte +++ b/src/lib/components/labelCard.svelte @@ -1,10 +1,14 @@ @@ -47,10 +46,7 @@
Providers
- +
@@ -64,10 +60,7 @@ hideView allowNoColumns showColsTextMobile /> - +
@@ -148,7 +141,7 @@ limit={data.limit} offset={data.offset} total={data.providers.total} /> - {:else if data.search} + {:else if data.search && data.search != 'empty'}
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)} - > -
- {: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.', + ` +
+ Screenshot of Bundle ID in Apple +
+
+
+
+
+
` + ] + }, + { + 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)} + > +
+ {: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