diff --git a/frontend/src/lib/server/hono.ts b/frontend/src/lib/server/hono.ts index b6cb0eb..1bc10aa 100644 --- a/frontend/src/lib/server/hono.ts +++ b/frontend/src/lib/server/hono.ts @@ -81,7 +81,11 @@ export const hono = { return request( 'PATCH', `/api/admin/${famId}/fam`, - { payday, ...(paydayTime !== undefined ? { paydayTime } : {}), ...(timezone !== undefined ? { timezone } : {}) }, + { + payday, + ...(paydayTime !== undefined ? { paydayTime } : {}), + ...(timezone !== undefined ? { timezone } : {}) + }, sessionHeaders(event) ); }, @@ -111,11 +115,11 @@ export const hono = { sessionHeaders(event) ); }, - async issueAllRewards(event: RequestEvent, famId: string, memberId: string, message?: string) { + async issueAllRewards(event: RequestEvent, famId: string, memberId: string) { return request( 'POST', `/api/admin/${famId}/rewards/issue-all`, - { memberId, message }, + { memberId }, sessionHeaders(event) ); }, @@ -196,14 +200,6 @@ export const hono = { sessionHeaders(event) ); }, - async sendMessage(event: RequestEvent, famId: string, memberId: string, message: string) { - return request( - 'POST', - `/api/admin/${famId}/send-message`, - { memberId, message }, - sessionHeaders(event) - ); - }, async memberChores(event: RequestEvent, famId: string, memberId: string) { return request( 'GET', diff --git a/frontend/src/routes/[fam]/[username]/+page.server.ts b/frontend/src/routes/[fam]/[username]/+page.server.ts index 4e5b87e..789cc1c 100644 --- a/frontend/src/routes/[fam]/[username]/+page.server.ts +++ b/frontend/src/routes/[fam]/[username]/+page.server.ts @@ -174,12 +174,8 @@ export const actions = { const famId = event.locals.session.famId; const fd = await event.request.formData(); const rewardId = fd.get('id') as string; - const message = fd.get('message') as string; try { const record = await hono.admin.claimReward(event, famId, rewardId); - if (message) { - await hono.admin.sendMessage(event, famId, record.memberId, message); - } return { record }; } catch (e) { return { error: e instanceof Error ? e.message : 'Failed to claim reward' }; @@ -191,9 +187,8 @@ export const actions = { const famId = event.locals.session.famId; const fd = await event.request.formData(); const memberId = fd.get('memberId') as string; - const message = fd.get('message') as string; try { - const result = await hono.admin.issueAllRewards(event, famId, memberId, message || undefined); + const result = await hono.admin.issueAllRewards(event, famId, memberId); return { count: result.count }; } catch (e) { return { error: e instanceof Error ? e.message : 'Failed to issue rewards' }; diff --git a/frontend/src/routes/[fam]/[username]/+page.svelte b/frontend/src/routes/[fam]/[username]/+page.svelte index f1b6fac..7f60f98 100644 --- a/frontend/src/routes/[fam]/[username]/+page.svelte +++ b/frontend/src/routes/[fam]/[username]/+page.svelte @@ -19,15 +19,6 @@ isCompleteForPeriod } from '../../../../../timezone.ts'; - interface Notification { - id: string; - famId: string; - memberId: string; - message: string; - read: boolean; - created: string; - } - let { data } = $props(); let role = $state(data.role || 'child'); @@ -44,15 +35,6 @@ let simulateEow = $state(!!(data.settings?.simulateEow ?? data.simulateEow)); let eowPreview = $state(null); - const responseTags = [ - '๐Ÿ‘ well done', - '๐Ÿ˜Š really pleased', - '๐ŸŽฏ you deserved that', - '๐Ÿ’ช great effort', - '๐Ÿ™ thanks' - ]; - let selectedMessage = $state>({}); - let customMessage = $state>({}); let toast = $state(''); let parentMembers = $derived(famStore.initialized ? famStore.members : data.members || []); @@ -515,33 +497,8 @@ loading = false; return; } - - await loadAndDismiss(); }); - async function loadAndDismiss() { - if (!deviceToken || !famId) return; - try { - const res = await fetch('/api/members/notifications', { - headers: { - 'x-device-token': deviceToken, - 'x-device-famid': famId - } - }); - if (!res.ok) return; - const all: Notification[] = await res.json(); - for (const n of all.filter((n) => !n.read)) { - await fetch(`/api/members/notifications/${n.id}/dismiss`, { - method: 'POST', - headers: { - 'x-device-token': deviceToken, - 'x-device-famid': famId - } - }); - } - } catch {} - } - function isCompleted(assignedChoreId: string, date: string): boolean { const a = assigned.find((x) => x.id === assignedChoreId); // Weekly chores are "done for the week" โ€” complete if completed any day @@ -822,7 +779,6 @@ }} > - @@ -840,7 +796,6 @@ }} > -
{r.label}
@@ -866,31 +821,6 @@
{/each} {/if} - -
-

Response:

-
- {#each responseTags as tag} - - {/each} -
- { - if (customMessage[m.id]) selectedMessage[m.id] = customMessage[m.id]; - }} - /> -
{/if} {/each} @@ -1569,41 +1499,6 @@ align-items: center; gap: 0.4rem; } - .respond-label { - font-size: 0.8rem; - color: #6b7280; - margin: 0.4rem 0 0.3rem; - } - .tags { - display: flex; - gap: 0.3rem; - flex-wrap: wrap; - } - .tag { - font-size: 0.75rem; - padding: 0.2rem 0.5rem; - border: 1px solid #d1d5db; - border-radius: 999px; - background: white; - cursor: pointer; - } - .tag:hover { - background: #f3f4f6; - } - .tag.selected { - background: #6366f1; - color: white; - border-color: #6366f1; - } - .custom-msg { - margin-top: 0.3rem; - padding: 0.3rem 0.5rem; - border: 1px solid #d1d5db; - border-radius: 4px; - font-size: 0.8rem; - width: 100%; - box-sizing: border-box; - } .error { color: #dc2626; diff --git a/proxy/src/index.ts b/proxy/src/index.ts index b363431..cf44790 100644 --- a/proxy/src/index.ts +++ b/proxy/src/index.ts @@ -1877,7 +1877,7 @@ app.post("/api/admin/:famId/rewards/issue-all", requireAdmin, async (c) => { try { const { famId } = c.req.param(); const body = await c.req.json(); - const { memberId, message } = body; + const { memberId } = body; if (!memberId) return c.json({ error: "memberId required" }, 400); const now = new Date().toISOString(); // Find all claimable rewards for this member (unclaimed or requested) @@ -1890,15 +1890,6 @@ app.post("/api/admin/:famId/rewards/issue-all", requireAdmin, async (c) => { await pb.update("rewards", r.id, { status: "claimed", claimedAt: now }); count++; } - // Send notification to member if message provided - if (message && count > 0) { - await pb.create("notifications", { - famId, - memberId, - message, - read: false, - }); - } return c.json({ count }); } catch (err) { return handleError(c, err); @@ -1928,8 +1919,6 @@ app.post( }, ); -// โ”€โ”€ Admin: Send notification message โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - app.get("/api/admin/:famId/profile", requireAdmin, async (c) => { try { const { famId } = c.req.param(); @@ -1978,25 +1967,6 @@ app.patch("/api/admin/:famId/profile", requireAdmin, async (c) => { } }); -app.post("/api/admin/:famId/send-message", requireAdmin, async (c) => { - try { - const { famId } = c.req.param(); - const body = await c.req.json(); - if (!body.memberId || !body.message) { - return c.json({ error: "memberId and message required" }, 400); - } - const record = await pb.create("notifications", { - famId, - memberId: body.memberId, - message: body.message, - read: false, - }); - return c.json(record); - } catch (err) { - return handleError(c, err); - } -}); - // โ”€โ”€ Member: Claim a reward โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ app.post("/api/members/rewards/:id/claim", requireDeviceToken, async (c) => { @@ -2024,19 +1994,6 @@ app.post("/api/members/rewards/:id/claim", requireDeviceToken, async (c) => { status: "requested", requestedAt: now, }); - // Create notification for admin - const members = await pb.getList( - "members", - `famId = '${famId}' && id = '${memberId}'`, - ); - const memberName = members.items?.[0]?.name || "Unknown"; - const msg = `Claim requested by ${memberName}`; - await pb.create("notifications", { - famId, - memberId, - message: msg, - read: false, - }); return c.json(record); } catch (err) { return handleError(c, err); @@ -2069,56 +2026,12 @@ app.post("/api/members/rewards/request-all", requireDeviceToken, async (c) => { }); count++; } - if (count > 0) { - const members = await pb.getList( - "members", - `famId = '${famId}' && id = '${memberId}'`, - ); - const memberName = members.items?.[0]?.name || "Unknown"; - const msg = `${count} claim(s) requested by ${memberName}`; - await pb.create("notifications", { - famId, - memberId, - message: msg, - read: false, - }); - } return c.json({ count }); } catch (err) { return handleError(c, err); } }); -// โ”€โ”€ Member: Notifications โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -app.get("/api/members/notifications", requireDeviceToken, async (c) => { - try { - const famId = c.get("famId"); - const memberId = c.get("memberId"); - const data = await pb.getList( - "notifications", - `famId = '${famId}' && memberId = '${memberId}'`, - ); - return c.json(data.items); - } catch (err) { - return handleError(c, err); - } -}); - -app.post( - "/api/members/notifications/:id/dismiss", - requireDeviceToken, - async (c) => { - try { - const { id } = c.req.param(); - const record = await pb.update("notifications", id, { read: true }); - return c.json(record); - } catch (err) { - return handleError(c, err); - } - }, -); - // โ”€โ”€ Payday: release this period's unpaid cash as an auto-claimed ask โ”€โ”€ async function releaseWeek(famId: string) { @@ -2178,15 +2091,6 @@ async function releaseWeek(famId: string) { }); } } - // Kid notice: the fun moment. - try { - await pb.create("notifications", { - famId, - memberId: m.id, - message: `You've earned ยฃ${total.toFixed(2)}! Go get it from your parent โญ`, - read: false, - }); - } catch {} breakdown.push({ memberId: m.id, name: m.name, diff --git a/proxy/src/migrate.ts b/proxy/src/migrate.ts index c8e2f5f..f7518e2 100644 --- a/proxy/src/migrate.ts +++ b/proxy/src/migrate.ts @@ -302,7 +302,9 @@ export async function migrate(): Promise { missing.push({ name: "settleDate", type: "text", required: false }); } if (missing.length) { - console.log("[migrate] Adding rewards.claimable/settleDate (payday gating)..."); + console.log( + "[migrate] Adding rewards.claimable/settleDate (payday gating)...", + ); rewardsCol2.fields.push(...missing); await updateCollection(rewardsCol2.id, { name: "rewards", @@ -370,47 +372,6 @@ export async function migrate(): Promise { } } - // โ”€โ”€ 4. Create notifications collection if missing โ”€โ”€ - const notifCol = await getCollection("notifications"); - if (!notifCol) { - const famsCol = await getCollection("fams"); - if (!famsCol) throw new Error("fams collection not found"); - const membersCol = await getCollection("members"); - if (!membersCol) throw new Error("members collection not found"); - console.log("[migrate] Creating notifications collection..."); - await createCollection({ - name: "notifications", - type: "base", - listRule: "", - viewRule: "", - createRule: null, - updateRule: null, - deleteRule: null, - fields: [ - { - name: "famId", - type: "relation", - required: true, - collectionId: famsCol.id, - maxSelect: 1, - cascadeDelete: false, - }, - { - name: "memberId", - type: "relation", - required: true, - collectionId: membersCol.id, - maxSelect: 1, - cascadeDelete: false, - }, - { name: "message", type: "text", required: true }, - { name: "read", type: "bool", required: false }, - ], - }); - } else { - console.log(` โ†ณ notifications already exists`); - } - // โ”€โ”€ 5. Add payday field to fams if missing โ”€โ”€ const famsCol = await getCollection("fams"); if (famsCol) { @@ -547,10 +508,15 @@ export async function migrate(): Promise { }); } if (fams.length) { - console.log(` โœ“ Backfilled timezone="auto" for ${fams.length} fam${fams.length > 1 ? "s" : ""}`); + console.log( + ` โœ“ Backfilled timezone="auto" for ${fams.length} fam${fams.length > 1 ? "s" : ""}`, + ); } } catch (err) { - console.log(" โ†ณ timezone backfill skipped:", err instanceof Error ? err.message : err); + console.log( + " โ†ณ timezone backfill skipped:", + err instanceof Error ? err.message : err, + ); } // โ”€โ”€ 6. Backfill completions.date โ€” strip timestamps to YYYY-MM-DD โ”€โ”€ @@ -1334,78 +1300,88 @@ export async function migrate(): Promise { } } - // โ”€โ”€ 7. Add todo fields to assigned_chores if missing โ”€โ”€ - const assignedCol = await getCollection("assigned_chores"); - if (assignedCol) { - let needsUpdate = false; + // โ”€โ”€ 7. Add todo fields to assigned_chores if missing โ”€โ”€ + const assignedCol = await getCollection("assigned_chores"); + if (assignedCol) { + let needsUpdate = false; - // 7a. Make templateId non-required (todos don't use templates) - const tplField = assignedCol.fields.find((f: any) => f.name === "templateId"); - if (tplField && tplField.required) { - console.log("[migrate] Making assigned_chores.templateId non-required..."); - tplField.required = false; - needsUpdate = true; - } + // 7a. Make templateId non-required (todos don't use templates) + const tplField = assignedCol.fields.find( + (f: any) => f.name === "templateId", + ); + if (tplField && tplField.required) { + console.log( + "[migrate] Making assigned_chores.templateId non-required...", + ); + tplField.required = false; + needsUpdate = true; + } - // 7b. Add isTodo, startDate, completeBy fields - const hasIsTodo = assignedCol.fields.some((f: any) => f.name === "isTodo"); - if (!hasIsTodo) { - console.log("[migrate] Adding todo fields to assigned_chores..."); - assignedCol.fields.push( - { name: "isTodo", type: "bool" }, - { name: "startDate", type: "text" }, - { name: "completeBy", type: "text" } - ); - needsUpdate = true; - } else { - console.log(` โ†ณ assigned_chores.isTodo already exists`); - } + // 7b. Add isTodo, startDate, completeBy fields + const hasIsTodo = assignedCol.fields.some((f: any) => f.name === "isTodo"); + if (!hasIsTodo) { + console.log("[migrate] Adding todo fields to assigned_chores..."); + assignedCol.fields.push( + { name: "isTodo", type: "bool" }, + { name: "startDate", type: "text" }, + { name: "completeBy", type: "text" }, + ); + needsUpdate = true; + } else { + console.log(` โ†ณ assigned_chores.isTodo already exists`); + } - if (needsUpdate) { - await updateCollection(assignedCol.id, { - name: "assigned_chores", - type: "base", - listRule: assignedCol.listRule, - viewRule: assignedCol.viewRule, - createRule: assignedCol.createRule, - updateRule: assignedCol.updateRule, - deleteRule: assignedCol.deleteRule, - fields: assignedCol.fields, - }); - } - } else { - console.log(` โ†ณ assigned_chores collection not found (will be created by seed)`); - } + if (needsUpdate) { + await updateCollection(assignedCol.id, { + name: "assigned_chores", + type: "base", + listRule: assignedCol.listRule, + viewRule: assignedCol.viewRule, + createRule: assignedCol.createRule, + updateRule: assignedCol.updateRule, + deleteRule: assignedCol.deleteRule, + fields: assignedCol.fields, + }); + } + } else { + console.log( + ` โ†ณ assigned_chores collection not found (will be created by seed)`, + ); + } - // โ”€โ”€ 8. Add completedAt timestamp to completions โ”€โ”€ - const complCol = await getCollection("completions"); - if (complCol) { - const hasCompletedAt = complCol.fields.some((f: any) => f.name === "completedAt"); - if (!hasCompletedAt) { - console.log("[migrate] Adding completions.completedAt..."); - complCol.fields.push({ - name: "completedAt", - type: "date", - required: false, - hidden: false, - }); - await updateCollection(complCol.id, { - name: "completions", - type: "base", - listRule: complCol.listRule, - viewRule: complCol.viewRule, - createRule: complCol.createRule, - updateRule: complCol.updateRule, - deleteRule: complCol.deleteRule, - fields: complCol.fields, - }); - console.log(" โœ“ completions.completedAt added"); - } else { - console.log(` โ†ณ completions.completedAt already exists`); - } - } else { - console.log(` โ†ณ completions collection not found (will be created by seed)`); - } + // โ”€โ”€ 8. Add completedAt timestamp to completions โ”€โ”€ + const complCol = await getCollection("completions"); + if (complCol) { + const hasCompletedAt = complCol.fields.some( + (f: any) => f.name === "completedAt", + ); + if (!hasCompletedAt) { + console.log("[migrate] Adding completions.completedAt..."); + complCol.fields.push({ + name: "completedAt", + type: "date", + required: false, + hidden: false, + }); + await updateCollection(complCol.id, { + name: "completions", + type: "base", + listRule: complCol.listRule, + viewRule: complCol.viewRule, + createRule: complCol.createRule, + updateRule: complCol.updateRule, + deleteRule: complCol.deleteRule, + fields: complCol.fields, + }); + console.log(" โœ“ completions.completedAt added"); + } else { + console.log(` โ†ณ completions.completedAt already exists`); + } + } else { + console.log( + ` โ†ณ completions collection not found (will be created by seed)`, + ); + } - console.log("[migrate] Done"); + console.log("[migrate] Done"); }