12 implement twilio sms #37

Merged
BenjaminPalko merged 26 commits from 12-implement-twilio-sms into master 2025-01-02 20:11:28 -05:00
4 changed files with 121 additions and 0 deletions
Showing only changes of commit ab347a47b2 - Show all commits

View file

@ -7,6 +7,7 @@
block?: boolean;
children: Snippet;
color?: DaisyColor;
full?: boolean;
glass?: boolean;
outline?: boolean;
onClick?: () => void;
@ -20,6 +21,7 @@
block = false,
children,
color,
full = false,
glass = false,
outline = false,
onClick,
@ -36,6 +38,7 @@
class:btn-outline={outline}
class:btn-block={block}
class:btn-wide={wide}
class:w-full={full}
class:glass
class:btn-xs={size === 'xs'}
class:btn-sm={size === 'sm'}

View file

@ -1,2 +1,4 @@
<script lang="ts">
</script>
<a href="/app/sms" class="btn btn-ghost">SMS</a>

View file

@ -0,0 +1,49 @@
import { PhoneRegex } from '$lib/regex';
import { logger } from '$lib/server/logger';
import { twilioClient } from '$lib/server/twilio';
import { TwilioConfig } from '$lib/server/twilio/twilio.config';
import { fail, type Actions } from '@sveltejs/kit';
import zod from 'zod';
export const actions = {
push: async (event) => {
const form = await event.request.formData();
if (!form.has('phone')) {
return fail(400, { error: 'phone_missing' });
}
if (!form.get('message')) {
return fail(400, { error: 'message_missing' });
}
const {
success: phoneSuccess,
data: phone,
error: phoneError,
} = zod.string().regex(PhoneRegex).safeParse(form.get('phone'));
if (!phoneSuccess) {
logger.error(phoneError);
return fail(400, { error: 'invalid_phone' });
}
const message = form.get('message');
if (typeof message !== 'string') {
return fail(400, { error: 'invalid_message' });
}
try {
const result = await twilioClient.messages.create({
to: phone,
body: message,
from: TwilioConfig.twilio_phone_number,
});
logger.debug(result);
} catch (e) {
logger.error(e);
fail(500, { success: false });
}
return {
success: true,
};
},
} satisfies Actions;

View file

@ -0,0 +1,67 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { Button } from '$lib/components/Actions';
import { Textarea, TextInput } from '$lib/components/DataInput';
import { fade } from 'svelte/transition';
import type { ActionData } from './$types';
type Props = {
form: ActionData;
};
let { form }: Props = $props();
</script>
{#snippet PhoneIcon()}
<i class="fi fi-sr-phone-flip"></i>
{/snippet}
{#snippet MessageLabel()}
<i class="fi fi-sr-comment-alt"></i> Message
{/snippet}
{#snippet alert(message: string)}
<div role="alert" class="alert alert-error absolute -top-20" transition:fade>
<i class="fi fi-bs-octagon-xmark h-6 w-6 shrink-0"></i>
<span>{message}</span>
</div>
{/snippet}
<div class="page" transition:fade>
<div class="card bg-base-200 p-4 shadow-xl">
<div class="card-title justify-center">
<h2 class="text-2xl font-semibold">Send a Message</h2>
{#if form?.error}
{@render alert(form.error)}
{/if}
</div>
<form id="sms" method="POST" action="?/push" use:enhance>
<div class="card-body">
<TextInput
type="tel"
name="phone"
start={PhoneIcon}
placeholder="Phone"
bordered
fade
/>
<Textarea
color="primary"
label={MessageLabel}
size="lg"
error={form?.error}
name="message"
form="sms"
/>
</div>
<div class="card-actions justify-center px-8">
<Button color="primary" type="submit" full>Send Message</Button>
</div>
</form>
</div>
</div>
<style>
.page {
@apply flex flex-col items-center justify-around gap-24 py-[10%];
}
</style>