mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Create a wizard to send email messages
This commit is contained in:
@@ -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'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import {
|
||||
@@ -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';
|
||||
@@ -40,13 +38,11 @@
|
||||
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>
|
||||
@@ -54,10 +50,7 @@
|
||||
<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 -->
|
||||
@@ -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">
|
||||
@@ -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} />
|
||||
|
||||
@@ -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.`
|
||||
});
|
||||
name = null;
|
||||
trackEvent(Submit.BucketCreate, {
|
||||
customId: !!id
|
||||
trackEvent(Submit.MessagingMessageCreate, {
|
||||
providerType: $providerType
|
||||
});
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.BucketCreate);
|
||||
await goto(`${base}/console/project-${$project.$id}/messaging/message-${response.$id}`);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.MessagingProviderCreate);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$: if (!showCustomId) {
|
||||
id = null;
|
||||
}
|
||||
$: if (!showCreate) {
|
||||
showCustomId = false;
|
||||
error = null;
|
||||
}
|
||||
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>
|
||||
|
||||
<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 />
|
||||
|
||||
{#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" />
|
||||
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
@@ -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}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<script context="module" lang="ts">
|
||||
export async function createEmailMessage(params: EmailMessageParams) {
|
||||
const 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'
|
||||
},
|
||||
params
|
||||
);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { messageParams, providerType, type EmailMessageParams, MessageStatuses } from './store';
|
||||
import {
|
||||
Button,
|
||||
FormList,
|
||||
InputEmail,
|
||||
InputRadio,
|
||||
InputSwitch,
|
||||
InputText,
|
||||
InputTextarea
|
||||
} from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { CustomId, Modal } from '$lib/components';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { ProviderTypes } from '../providerType.svelte';
|
||||
|
||||
let showCustomId = false;
|
||||
let showTest = false;
|
||||
let selected = 'self';
|
||||
let otherEmail = '';
|
||||
|
||||
async function sendTestEmail() {
|
||||
const email = selected === 'self' ? $user.email : otherEmail;
|
||||
console.log(email);
|
||||
|
||||
createEmailMessage({
|
||||
topics: $messageParams[ProviderTypes.Email]?.topics || [],
|
||||
targets: $messageParams[ProviderTypes.Email]?.targets || [],
|
||||
description: $messageParams[ProviderTypes.Email]?.description || 'Test message',
|
||||
status: MessageStatuses.PROCESSING,
|
||||
messageId: ID.unique(),
|
||||
// TODO: properly handle the test email address
|
||||
users: ['steven'],
|
||||
subject: $messageParams[ProviderTypes.Email]?.subject || '',
|
||||
content: $messageParams[ProviderTypes.Email]?.content || '',
|
||||
html: $messageParams[ProviderTypes.Email]?.html || false
|
||||
});
|
||||
}
|
||||
|
||||
$: otherEmail = selected === 'self' ? '' : otherEmail;
|
||||
</script>
|
||||
|
||||
<FormList>
|
||||
<InputText
|
||||
id="subject"
|
||||
label="Subject"
|
||||
placeholder="Enter subject"
|
||||
bind:value={$messageParams[$providerType]['subject']}>
|
||||
</InputText>
|
||||
<div class="u-colum-gap-2">
|
||||
<InputTextarea
|
||||
id="message"
|
||||
label="Message"
|
||||
placeholder="Type here..."
|
||||
bind:value={$messageParams[$providerType]['content']}>
|
||||
</InputTextarea>
|
||||
<!-- TODO: Add support for draft messages -->
|
||||
<!-- <div class="u-flex u-main-end">
|
||||
<Button text on:click={() => (showTest = true)}>Send test message</Button>
|
||||
</div> -->
|
||||
<Modal title="Send test message" bind:show={showTest} onSubmit={sendTestEmail} size="big">
|
||||
<slot />
|
||||
<InputRadio
|
||||
label={$user.email}
|
||||
bind:group={selected}
|
||||
value="self"
|
||||
id="self"
|
||||
name="selected" />
|
||||
<InputRadio
|
||||
label="Other"
|
||||
bind:group={selected}
|
||||
value="other"
|
||||
id="other"
|
||||
name="selected"
|
||||
fullWidth>
|
||||
<svelte:fragment slot="description">
|
||||
Enter the email address to which te test message will be
|
||||
<div
|
||||
on:click={() => (selected = 'other')}
|
||||
on:keyup|self={clickOnEnter}
|
||||
role="button"
|
||||
tabindex="0">
|
||||
<InputEmail
|
||||
showLabel={false}
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
bind:value={otherEmail} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</InputRadio>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showTest = false)}>Cancel</Button>
|
||||
<Button submit>Send</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
</div>
|
||||
<InputSwitch label="HTML mode" id="html" bind:value={$messageParams[$providerType]['html']}>
|
||||
<svelte:fragment slot="description">
|
||||
Enable the HTML mode if your message contains HTML tags.
|
||||
</svelte:fragment>
|
||||
</InputSwitch>
|
||||
<InputText
|
||||
id="description"
|
||||
label="Description"
|
||||
placeholder="Enter description"
|
||||
tooltip="Provide a summary of the message. Users won't see this description."
|
||||
bind:value={$messageParams[$providerType]['description']}>
|
||||
</InputText>
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}
|
||||
><span class="icon-pencil" aria-hidden="true" /><span class="text">
|
||||
Message ID
|
||||
</span></Pill>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomId
|
||||
bind:show={showCustomId}
|
||||
name="Message"
|
||||
bind:id={$messageParams[$providerType].messageId}
|
||||
autofocus={false} />
|
||||
{/if}
|
||||
</FormList>
|
||||
@@ -0,0 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { messageParams, providerType } from './store';
|
||||
import { providers } from '../providers/store';
|
||||
import EmailFormList from './emailFormList.svelte';
|
||||
|
||||
async function beforeSubmit() {
|
||||
console.log($messageParams[$providerType]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Message</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
<!-- TODO: update documentation link -->
|
||||
Create an {providers[$providerType].text} that will be displayed to your subscribers. Learn more
|
||||
in our documentation.
|
||||
</svelte:fragment>
|
||||
<EmailFormList />
|
||||
</WizardStep>
|
||||
@@ -0,0 +1,99 @@
|
||||
<script lang="ts">
|
||||
import { Card } from '$lib/components';
|
||||
import UserTargetsModal, { type Target } from '../userTargetsModal.svelte';
|
||||
import { ProviderTypes } from '../providerType.svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '$lib/elements/table';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { messageParams, providerType, targetsById } from './store';
|
||||
|
||||
let showDropdown = false;
|
||||
let targetIdsLength = 0;
|
||||
|
||||
function update(event: CustomEvent<Record<string, Target>>) {
|
||||
$targetsById = event.detail;
|
||||
showDropdown = false;
|
||||
}
|
||||
|
||||
function removeTarget(targetId: string) {
|
||||
const { [targetId]: _, ...rest } = $targetsById;
|
||||
$targetsById = rest;
|
||||
}
|
||||
|
||||
async function beforeSubmit() {
|
||||
$messageParams[$providerType].targets = Object.keys($targetsById);
|
||||
console.log($messageParams[$providerType]);
|
||||
}
|
||||
|
||||
$: targetIdsLength = Object.keys($targetsById).length;
|
||||
$: console.log(targetIdsLength);
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Targets</svelte:fragment>
|
||||
<!-- TODO: add support for topics -->
|
||||
<svelte:fragment slot="subtitle"
|
||||
>Select users to whom this message should be directed.</svelte:fragment>
|
||||
{#if targetIdsLength == 0}
|
||||
<Card>
|
||||
<div class="u-flex u-cross-center u-flex-vertical u-main-center u-flex">
|
||||
<Button secondary round on:click={() => (showDropdown = !showDropdown)}>
|
||||
<i class="icon-plus" />
|
||||
</Button>
|
||||
<div class="common-section">
|
||||
<span class="text"> Select recipients to get started</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
{:else}
|
||||
<div class="table-wrapper">
|
||||
<Table noMargin noStyles>
|
||||
<TableHeader>
|
||||
<TableCellHead width={140}>Target</TableCellHead>
|
||||
<TableCellHead width={32} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each Object.entries($targetsById) as [targetId, target] (targetId)}
|
||||
<TableRow>
|
||||
<TableCell title="Target">
|
||||
<div class="u-flex u-cross-center">
|
||||
<span class="text"
|
||||
>{target.name ? target.name : target.identifier}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell title="Remove" width={40}>
|
||||
<div class="u-flex u-main-end">
|
||||
<button
|
||||
class="button is-text is-only-icon"
|
||||
type="button"
|
||||
aria-label="delete"
|
||||
on:click={() => removeTarget(targetId)}>
|
||||
<span class="icon-x" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<Button text noMargin on:click={() => (showDropdown = !showDropdown)}>
|
||||
<span class="icon-plus" aria-hidden="true" />
|
||||
<span class="text">Add target</span>
|
||||
</Button>
|
||||
{/if}
|
||||
</WizardStep>
|
||||
|
||||
<UserTargetsModal
|
||||
providerType={ProviderTypes.Email}
|
||||
bind:show={showDropdown}
|
||||
bind:targetsById={$targetsById}
|
||||
on:update={update} />
|
||||
@@ -0,0 +1,101 @@
|
||||
<script lang="ts">
|
||||
import { FormList, InputDate, InputSelect, InputTime } from '$lib/elements/forms';
|
||||
import Helper from '$lib/elements/forms/helper.svelte';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { messageParams, providerType } from './store';
|
||||
|
||||
let when: 'now' | 'later' = 'now';
|
||||
let now = new Date();
|
||||
let timeZoneOffset: number;
|
||||
let minDate: string;
|
||||
let date: string;
|
||||
let time: string;
|
||||
let dateTime: Date;
|
||||
|
||||
const options = [
|
||||
{ label: 'Now', value: 'now' },
|
||||
{ label: 'Schedule', value: 'later' }
|
||||
];
|
||||
const formatOptions: Intl.DateTimeFormatOptions = {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hourCycle: 'h23',
|
||||
timeZoneName: 'longOffset'
|
||||
};
|
||||
|
||||
async function beforeSubmit() {
|
||||
if (when === 'later') {
|
||||
$messageParams[$providerType].scheduledAt = dateTime.toISOString();
|
||||
}
|
||||
console.log($messageParams[$providerType]);
|
||||
}
|
||||
|
||||
$: if (when === 'now') {
|
||||
date = time = '';
|
||||
}
|
||||
$: if (when === 'later') {
|
||||
now = new Date();
|
||||
}
|
||||
$: timeZoneOffset = now ? now.getTimezoneOffset() * 60 * 1000 : 0;
|
||||
$: minDate = new Date(now.getTime() - timeZoneOffset).toISOString().split('T')[0];
|
||||
$: minTime =
|
||||
date === minDate
|
||||
? new Date(now.getTime() - timeZoneOffset).toISOString().split('T')[1].substring(0, 5)
|
||||
: '00:00';
|
||||
$: dateTime = new Date(`${date}T${time}`);
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Schedule</svelte:fragment>
|
||||
<!-- TODO: Add link to docs -->
|
||||
<svelte:fragment slot="subtitle"
|
||||
>Schedule the time you want your users to receive this message. Learn more in our
|
||||
documentation.</svelte:fragment>
|
||||
<FormList>
|
||||
<div
|
||||
class="u-grid u-gap-24"
|
||||
style="grid-auto-rows: 1fr; grid-template-columns: 1fr 1fr 1fr;">
|
||||
<InputSelect id="when" label=" " {options} bind:value={when} />
|
||||
<InputDate
|
||||
id="date"
|
||||
label="Date"
|
||||
disabled={when === 'now'}
|
||||
required={when === 'later'}
|
||||
min={minDate}
|
||||
bind:value={date} />
|
||||
<InputTime
|
||||
id="time"
|
||||
label="Time"
|
||||
disabled={when === 'now'}
|
||||
required={when === 'later'}
|
||||
min={minTime}
|
||||
bind:value={time} />
|
||||
</div>
|
||||
</FormList>
|
||||
<Helper type="neutral">
|
||||
{#if when === 'now'}
|
||||
The message will be sent immediately
|
||||
{:else if !dateTime || isNaN(dateTime.getTime())}
|
||||
The message will be sent later
|
||||
{:else}
|
||||
The message will be sent at {dateTime.toLocaleString('en', formatOptions)}
|
||||
{/if}
|
||||
</Helper>
|
||||
</WizardStep>
|
||||
|
||||
<!-- <Modal
|
||||
title="Send message"
|
||||
bind:show={showDelete}
|
||||
onSubmit={deleteAccount}
|
||||
icon="exclamation"
|
||||
state="warning"
|
||||
headerDivider={false}>
|
||||
<p>Are you sure you want to delete your account?</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button secondary submit>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</Modal> -->
|
||||
@@ -0,0 +1,52 @@
|
||||
import { ProviderTypes } from '../providerType.svelte';
|
||||
import type { Target } from '../userTargetsModal.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export enum MessageStatuses {
|
||||
DRAFT = 'draft',
|
||||
PROCESSING = 'processing'
|
||||
}
|
||||
|
||||
export type MessageParams = {
|
||||
messageId: string;
|
||||
topics: string[];
|
||||
users: string[];
|
||||
targets: string[];
|
||||
description: string;
|
||||
status: MessageStatuses;
|
||||
scheduledAt?: string;
|
||||
};
|
||||
|
||||
export type EmailMessageParams = MessageParams & {
|
||||
subject: string;
|
||||
content: string;
|
||||
html: boolean;
|
||||
};
|
||||
|
||||
export type SMSMessageParams = MessageParams & {
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type PushMessageParams = MessageParams & {
|
||||
title: string;
|
||||
body: string;
|
||||
data: Record<string, string>;
|
||||
action?: string;
|
||||
icon?: string;
|
||||
sound?: string;
|
||||
color?: string;
|
||||
tag?: string;
|
||||
badge?: string;
|
||||
};
|
||||
|
||||
export const providerType = writable<ProviderTypes>(null);
|
||||
export const targetsById = writable<Record<string, Target>>({});
|
||||
export const messageParams = writable<{
|
||||
[ProviderTypes.Email]: Partial<EmailMessageParams>;
|
||||
[ProviderTypes.Sms]: Partial<SMSMessageParams>;
|
||||
[ProviderTypes.Push]: Partial<PushMessageParams>;
|
||||
}>({
|
||||
[ProviderTypes.Email]: null,
|
||||
[ProviderTypes.Sms]: null,
|
||||
[ProviderTypes.Push]: null
|
||||
});
|
||||
Reference in New Issue
Block a user