Skip to content

Commit

Permalink
Create a wizard to send email messages
Browse files Browse the repository at this point in the history
  • Loading branch information
stnguyen90 committed Dec 19, 2023
1 parent 38ebcfc commit 803ad12
Show file tree
Hide file tree
Showing 10 changed files with 626 additions and 85 deletions.
3 changes: 2 additions & 1 deletion src/lib/actions/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,5 +290,6 @@ export enum Submit {
SmsUpdateVerificationTemplate = 'submit_sms_update_verification_template',
MessagingProviderCreate = 'submit_messaging_provider_create',
MessagingProviderDelete = 'submit_messaging_provider_delete',
MessagingProviderUpdate = 'submit_messaging_provider_update'
MessagingProviderUpdate = 'submit_messaging_provider_update',
MessagingMessageCreate = 'submit_messaging_message_create'
}
58 changes: 36 additions & 22 deletions src/routes/console/project-[project]/messaging/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import {
Expand All @@ -21,16 +20,15 @@
TableCellHeadCheck,
TableCellText,
TableHeader,
TableRowLink
TableRowLink,
TableScroll
} from '$lib/elements/table';
import TableScroll from '$lib/elements/table/tableScroll.svelte';
import { toLocaleDateTime } from '$lib/helpers/date';
import { Container } from '$lib/layout';
import type { Models } from '@appwrite.io/console';
import type { PageData } from './$types';
import Create from './create.svelte';
import { columns, showCreate } from './store';
import MessageStatusPill from './messageStatusPill.svelte';
import CreateMessageDropdown from './createMessageDropdown.svelte';
import ProviderType, { ProviderTypes } from './providerType.svelte';
import Filters from '$lib/components/filters/filters.svelte';
import FailedModal from './failedModal.svelte';
Expand All @@ -40,24 +38,19 @@
let showDelete = false;
let showFailed = false;
let errors: string[] = [];
let showCreateDropdownMobile = false;
let showCreateDropdownDesktop = false;
let showCreateDropdownEmpty = false;
const project = $page.params.project;
async function messageCreated(event: CustomEvent<Models.Bucket>) {
$showCreate = false;
await goto(`${base}/console/project-${project}/messaging/message-${event.detail.$id}`);
}
</script>

<Container>
<div class="u-flex u-flex-vertical">
<div class="u-flex u-main-space-between">
<Heading tag="h2" size="5">Messages</Heading>
<div class="is-only-mobile">
<Button on:click={() => ($showCreate = true)} event="create_message">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create message</span>
</Button>
<CreateMessageDropdown bind:showCreateDropdown={showCreateDropdownMobile} />
</div>
</div>
<!-- TODO: fix width of search input in mobile -->
Expand All @@ -71,10 +64,7 @@
hideView
allowNoColumns
showColsTextMobile />
<Button on:click={() => ($showCreate = true)} event="create_message">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create message</span>
</Button>
<CreateMessageDropdown bind:showCreateDropdown={showCreateDropdownDesktop} />
</div>
</SearchQuery>
<div class="u-flex u-gap-16 is-only-mobile u-margin-block-start-16">
Expand Down Expand Up @@ -219,11 +209,35 @@
single
href="https://appwrite.io/docs"
target="message"
on:click={() => ($showCreate = true)} />
on:click={() => ($showCreate = true)}>
<div class="u-text-center">
<Heading size="7" tag="h2" trimmed={false}>
Create your first message to get started.
</Heading>
<p class="body-text-2 u-bold u-margin-block-start-4">
Need a hand? Learn more in our documentation.
</p>
</div>
<div class="u-flex u-flex-wrap u-gap-16 u-main-center">
<Button
external
href="https://appwrite.io/docs/references/cloud/client-web/messages"
text
event="empty_documentation"
ariaLabel={`create message`}>
Documentation
</Button>
<CreateMessageDropdown bind:showCreateDropdown={showCreateDropdownEmpty}>
<Button
secondary
on:click={() => (showCreateDropdownEmpty = !showCreateDropdownEmpty)}
event="create_message">
<span class="text">Create message</span>
</Button>
</CreateMessageDropdown>
</div>
</Empty>
{/if}
</Container>

<FailedModal bind:show={showFailed} {errors} />

<!-- TODO: handle create -->
<Create bind:showCreate={$showCreate} on:created={messageCreated} />
158 changes: 98 additions & 60 deletions src/routes/console/project-[project]/messaging/create.svelte
Original file line number Diff line number Diff line change
@@ -1,73 +1,111 @@
<script lang="ts">
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Modal, CustomId } from '$lib/components';
import { Pill } from '$lib/elements';
import { Button, InputText, FormList } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { onDestroy } from 'svelte';
import { Wizard } from '$lib/layout';
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
import Step1 from './wizard/step1.svelte';
import Step2 from './wizard/step2.svelte';
import Step3 from './wizard/step3.svelte';
import { sdk } from '$lib/stores/sdk';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { addNotification } from '$lib/stores/notifications';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { project } from '../store';
import { wizard } from '$lib/stores/wizard';
import { providerType, messageParams } from './wizard/store';
import { ProviderTypes } from './providerType.svelte';
import { ID } from '@appwrite.io/console';
import { createEventDispatcher } from 'svelte';
export let showCreate = false;
const dispatch = createEventDispatcher();
let name = '';
let id: string = null;
let showCustomId = false;
let error: string;
const create = async () => {
async function create() {
try {
const bucket = await sdk.forProject.storage.createBucket(id ? id : ID.unique(), name);
showCreate = false;
dispatch('created', bucket);
let response = { $id: '' };
const messageId = $messageParams[$providerType].messageId || ID.unique();
switch ($providerType) {
case ProviderTypes.Email:
response = await sdk.forProject.client.call(
'POST',
new URL(
sdk.forProject.client.config.endpoint + '/messaging/messages/email'
),
{
'X-Appwrite-Project': sdk.forProject.client.config.project,
'content-type': 'application/json',
'X-Appwrite-Mode': 'admin'
},
{
...$messageParams[$providerType],
messageId
}
);
break;
case ProviderTypes.Sms:
response = await sdk.forProject.client.call(
'POST',
new URL(sdk.forProject.client.config.endpoint + '/messaging/messages/sms'),
{
'X-Appwrite-Project': sdk.forProject.client.config.project,
'content-type': 'application/json',
'X-Appwrite-Mode': 'admin'
},
{
...$messageParams[$providerType],
messageId
}
);
break;
case ProviderTypes.Push:
response = await sdk.forProject.client.call(
'POST',
new URL(
sdk.forProject.client.config.endpoint + '/messaging/providers/telesign'
),
{
'X-Appwrite-Project': sdk.forProject.client.config.project,
'content-type': 'application/json',
'X-Appwrite-Mode': 'admin'
},
{
...$messageParams[$providerType],
messageId
}
);
break;
}
wizard.hide();
addNotification({
type: 'success',
message: `${name} has been created`
message: `The message has been sent.`
});
trackEvent(Submit.MessagingMessageCreate, {
providerType: $providerType
});
name = null;
trackEvent(Submit.BucketCreate, {
customId: !!id
await goto(`${base}/console/project-${$project.$id}/messaging/message-${response.$id}`);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
} catch (e) {
error = e.message;
trackError(e, Submit.BucketCreate);
trackError(error, Submit.MessagingProviderCreate);
}
};
$: if (!showCustomId) {
id = null;
}
$: if (!showCreate) {
showCustomId = false;
error = null;
}
</script>
<Modal title="Create message" {error} onSubmit={create} size="big" bind:show={showCreate}>
<FormList>
<InputText
id="name"
label="Name"
placeholder="New bucket"
bind:value={name}
autofocus
required />
onDestroy(() => {
console.log('destroy');
});
const stepsComponents: WizardStepsType = new Map();
stepsComponents.set(1, {
label: 'Message',
component: Step1
});
stepsComponents.set(2, {
label: 'Targets',
component: Step2
});
stepsComponents.set(3, {
label: 'Schedule',
component: Step3
});
</script>

{#if !showCustomId}
<div>
<Pill button on:click={() => (showCustomId = !showCustomId)}>
<span class="icon-pencil" aria-hidden="true" />
<span class="text"> Bucket ID </span>
</Pill>
</div>
{:else}
<CustomId bind:show={showCustomId} name="Bucket" bind:id />
{/if}
</FormList>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
<Button submit>Create</Button>
</svelte:fragment>
</Modal>
<Wizard title="Create message" steps={stepsComponents} on:finish={create} finalAction="Send" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script lang="ts">
import { DropList, DropListItem } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { wizard } from '$lib/stores/wizard';
import { providers } from './providers/store';
import Create from './create.svelte';
import { messageParams, providerType, targetsById } from './wizard/store';
import { ProviderTypes } from './providerType.svelte';
export let showCreateDropdown = false;
</script>

<DropList bind:show={showCreateDropdown} scrollable>
<slot>
<Button on:click={() => (showCreateDropdown = !showCreateDropdown)} event="create_message">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create message</span>
</Button>
</slot>
<svelte:fragment slot="list">
{#each Object.entries(providers) as [type, option]}
<DropListItem
icon={option.icon}
on:click={() => {
if (
type !== ProviderTypes.Email &&
type !== ProviderTypes.Sms &&
type !== ProviderTypes.Push
)
return;
$providerType = type;
$targetsById = {};
const common = {
topics: [],
users: [],
targets: []
};
switch (type) {
case ProviderTypes.Email:
$messageParams[$providerType] = {
...common,
subject: '',
content: ''
};
break;
case ProviderTypes.Sms:
$messageParams[$providerType] = {
...common,
content: ''
};
break;
case ProviderTypes.Push:
$messageParams[$providerType] = {
...common,
title: '',
body: ''
};
break;
}
showCreateDropdown = false;
wizard.start(Create);
}}>
{option.name}
</DropListItem>
{/each}
</svelte:fragment>
</DropList>
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@
userResultsById = {};
response.users.forEach((user) => {
if (providerType !== null) {
user.targets = user.targets.filter((target) => target.providerType === providerType);
user.targets = user.targets.filter(
(target) => target.providerType === providerType
);
}
userResultsById = {
...userResultsById,
Expand Down Expand Up @@ -200,7 +202,9 @@
<svelte:fragment slot="description">
<div class="u-inline-flex u-gap-8">
<span class="inline-tag u-normal"
><ProviderType type={target.providerType} noIcon /></span>
><ProviderType
type={target.providerType}
noIcon /></span>
{#if target.providerType !== ProviderTypes.Push}
{target.identifier}
{:else}
Expand Down
Loading

0 comments on commit 803ad12

Please sign in to comment.