From 27492b1558bd0890c7409a29868634a728b4b2d6 Mon Sep 17 00:00:00 2001 From: JCEEE <0xjceee@proton.me> Date: Thu, 10 Sep 2026 11:31:40 +0100 Subject: [PATCH] send email on claim requests --- frontend/src/lib/server/email.ts | 33 +++++++++++++++-- .../api/members/rewards/[id]/claim/+server.ts | 36 +++++++++++++++++-- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/server/email.ts b/frontend/src/lib/server/email.ts index 1177a35..95350b9 100644 --- a/frontend/src/lib/server/email.ts +++ b/frontend/src/lib/server/email.ts @@ -123,7 +123,36 @@ export async function sendMentionEmail(opts: {

${escHtml(authorName)} mentioned you in your family chat:

${escHtml(message)}

Open your family dashboard to read and reply.

-

Talk soon,
The FamDone team

- ` +

Talk soon,
The FamDone team

+ ` + }); +} + +// A child asked to collect a reward — ping the parent(s) with the ask and a +// link straight to the Claims card on the admin dashboard. +export async function sendClaimRequestEmail(opts: { + to: string; + parentName: string; + childName: string; + rewardName: string; + rewardValue: string; + dashboardUrl: string; +}) { + const { to, parentName, childName, rewardName, rewardValue, dashboardUrl } = opts; + + await resend.emails.send({ + from: 'no-reply@walthamstow.xyz', + to, + subject: `${childName} asked to claim ${rewardName}`, + html: ` +

Hi ${escHtml(parentName)},

+

${escHtml(childName)} asked to collect a reward:

+

+ ${escHtml(rewardName)} + ${rewardValue ? `— ${escHtml(rewardValue)}` : ''} +

+

Open the Claims card on your dashboard to review and issue it.

+

Talk soon,
The FamDone team

+ ` }); } diff --git a/frontend/src/routes/api/members/rewards/[id]/claim/+server.ts b/frontend/src/routes/api/members/rewards/[id]/claim/+server.ts index d22d6d9..6ecaa05 100644 --- a/frontend/src/routes/api/members/rewards/[id]/claim/+server.ts +++ b/frontend/src/routes/api/members/rewards/[id]/claim/+server.ts @@ -1,7 +1,9 @@ import { json, error } from '@sveltejs/kit'; import type { RequestEvent } from '@sveltejs/kit'; -import { createPbClient } from '$lib/server/pocketbase'; +import { createPbClient, pbAdmin } from '$lib/server/pocketbase'; import { createServices } from '$lib/server/services'; +import { sendClaimRequestEmail } from '$lib/server/email'; +import { handleOf } from '@shared/slugify'; export async function POST(event: RequestEvent) { const u = event.locals.user; @@ -10,8 +12,36 @@ export async function POST(event: RequestEvent) { const id = event.params.id!; try { const s = createServices(pb, { id: u.id, role: u.role }); - return json(await s.rewards.claim(u.famId, id)); + const record = await s.rewards.claim(u.famId, id); + + // Tell the parent(s) a claim was requested — best-effort, so a mail + // outage can never fail the claim itself. + try { + const [parents, fam] = await Promise.all([ + pbAdmin.getList('users', `famId = '${u.famId}' && role = 'parent'`), + pbAdmin.getOne('fams', u.famId) + ]); + const label = record?.label || 'a reward'; + const idx = label.indexOf(' – '); + const rewardName = idx === -1 ? label : label.slice(0, idx); + const rewardValue = idx === -1 ? '' : label.slice(idx + 3); + for (const parent of parents) { + if (!parent.email) continue; + await sendClaimRequestEmail({ + to: parent.email, + parentName: parent.name || 'there', + childName: u.name || 'Someone', + rewardName, + rewardValue, + dashboardUrl: `${event.url.origin}/${fam?.slug || u.famId}/${handleOf(parent.username)}` + }); + } + } catch { + // Best-effort only — the claim already succeeded. + } + + return json(record); } catch (e) { return json({ error: e instanceof Error ? e.message : 'claim failed' }, { status: 400 }); } -} \ No newline at end of file +}