diff --git a/src/lib/components/SMS/RecipientList.svelte b/src/lib/components/SMS/RecipientList.svelte new file mode 100644 index 0000000..e56f0f9 --- /dev/null +++ b/src/lib/components/SMS/RecipientList.svelte @@ -0,0 +1,64 @@ + + + + + + + + + + + + + {#each recipients as resident, index} + + + + + {/each} + +
+ { + checked = checked.map(() => currentTarget.checked); + }} + /> + Name
{ + checked[index] = currentTarget.checked; + }} + />{resident.name}
diff --git a/src/lib/components/SMS/index.ts b/src/lib/components/SMS/index.ts new file mode 100644 index 0000000..1752485 --- /dev/null +++ b/src/lib/components/SMS/index.ts @@ -0,0 +1,2 @@ +export * from './RecipientList.svelte'; +export { default as RecipientList } from './RecipientList.svelte'; diff --git a/src/routes/app/settings/+page.server.ts b/src/routes/app/settings/+page.server.ts index 4f86407..3a0c553 100644 --- a/src/routes/app/settings/+page.server.ts +++ b/src/routes/app/settings/+page.server.ts @@ -1,5 +1,4 @@ import { PhoneRegex } from '$lib/regex'; -import { encrypt } from '$lib/server/crypto/encryption.js'; import { logger } from '$lib/server/logger'; import { prisma } from '$lib/server/prisma'; import { fail, type Actions } from '@sveltejs/kit'; @@ -67,9 +66,9 @@ export const actions = { tenantId: tenantId, twilioConfig: { create: { - accountSID: encrypt(accountSID), - authToken: encrypt(authToken), - phoneNumber: encrypt(phoneNumber), + accountSID: accountSID, + authToken: authToken, + phoneNumber: phoneNumber, }, }, }, diff --git a/src/routes/app/sms/+page.server.ts b/src/routes/app/sms/+page.server.ts index cb65687..1a3aff0 100644 --- a/src/routes/app/sms/+page.server.ts +++ b/src/routes/app/sms/+page.server.ts @@ -1,4 +1,4 @@ -import { PhoneRegex } from '$lib/regex'; +import type { Recipient } from '$lib/components/SMS'; import { logger } from '$lib/server/logger'; import { prisma } from '$lib/server/prisma/index.js'; import { fail, type Actions } from '@sveltejs/kit'; @@ -31,8 +31,20 @@ export const load = async (event) => { logger.warn(validationError.message); } + const residents = await prisma.resident.findMany({ + where: { + tenantId: event.locals.tenant.id, + }, + select: { + id: true, + name: true, + phoneNumber: true, + }, + }); + return { isTwilioConfigured: success, + residents: residents, }; }; @@ -40,22 +52,18 @@ export const actions = { default: async (event) => { const form = await event.request.formData(); - if (!form.has('phone')) { - return fail(400, { error: 'phone_missing' }); + if (!form.has('recipients')) { + return fail(400, { error: 'recipients_missing' }); } - if (!form.get('message')) { + if (!form.has('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 recipients: Recipient[] = JSON.parse(form.get('recipients') as string); + if (!Array.isArray(recipients)) { + return fail(400, { error: 'invalid_recipients' }); } + logger.info(recipients); const message = form.get('message'); if (typeof message !== 'string') { @@ -78,17 +86,20 @@ export const actions = { const client = twilio(config.accountSID, config.authToken); - try { - const result = await client.messages.create({ - to: phone, - body: message, - from: config.phoneNumber, - }); - logger.debug(result); - } catch (e) { - logger.error(e); - fail(500, { success: false }); + for (const recipient of recipients) { + try { + const result = await client.messages.create({ + to: recipient.phone, + body: message, + from: config.phoneNumber, + }); + logger.debug(result); + } catch (e) { + logger.error(e); + fail(500, { success: false }); + } } + return { success: true, }; diff --git a/src/routes/app/sms/+page.svelte b/src/routes/app/sms/+page.svelte index 36087c6..073fef3 100644 --- a/src/routes/app/sms/+page.svelte +++ b/src/routes/app/sms/+page.svelte @@ -2,11 +2,12 @@ import { enhance } from '$app/forms'; import { goto } from '$app/navigation'; import { Button } from '$lib/components/Actions'; - import { Textarea, TextInput } from '$lib/components/DataInput'; + import { Textarea } from '$lib/components/DataInput'; import { Alert } from '$lib/components/Feedback'; import { Link } from '$lib/components/Navigation'; + import { RecipientList, type Recipient } from '$lib/components/SMS'; import { messages } from '$lib/i18n'; - import { CircleX, MessageCircleMore, PhoneOutgoing, TriangleAlert } from 'lucide-svelte'; + import { CircleX, MessageCircleMore, TriangleAlert } from 'lucide-svelte'; import type { ActionData, PageData } from './$types'; type Props = { @@ -14,73 +15,66 @@ form: ActionData; }; let { data, form }: Props = $props(); + + let recipients = $derived( + data.residents.map((r) => ({ id: r.id, name: r.name, phone: r.phoneNumber })) + ); + let selectedRecipients: Recipient[] = $state([]); -{#snippet PhoneLabel()} - {messages.sms_label_phone()} -{/snippet} - -{#snippet MessageLabel()} - {messages.sms_label_message()} -{/snippet} - -
-
- {#if form?.error} - - {#snippet icon()} - - {/snippet} - {form.error} - - {:else if !data.isTwilioConfigured} - - {#snippet icon()} - - {/snippet} - - Twilio must be configured on the goto('/app/settings')} - >Settings page - - - {/if} -
-

{messages.sms_prompt()}

+
+ {#if form?.error} + + {#snippet icon()} + + {/snippet} + {form.error} + + {:else if !data.isTwilioConfigured} + + {#snippet icon()} + + {/snippet} + + Twilio must be configured on the goto('/app/settings')} + >Settings page + + + {/if} +

{messages.sms_prompt()}

+
+
+

Recipients

+
+ +
-
- +
+ +
- -