import { fail, redirect } from '@sveltejs/kit'; import { pbUser, createPbClient } from '$lib/server/pocketbase'; import { createServices } from '$lib/server/services'; import { getPlatformFlags } from '$lib/server/platform'; import { pbAdmin } from '$lib/server/pocketbase'; export async function load(event) { const session = event.locals.user; const demoMode = (await getPlatformFlags()).demo; const isDemoParent = session?.username === 'demo'; const famSlug = event.params.fam; const username = event.params.username; // Demo parent visiting a child's route → load child kanban view. const demoParentVisitingChild = demoMode && isDemoParent && session?.username !== username; // Parent (role=parent, session auth) → admin overview. // Branch on role, NOT presence: a child who OTP-logged-in also has a // `users` session (locals.user) and must not hit the parent path. if (session && session.role === 'parent' && !demoParentVisitingChild) { if (!(demoMode && isDemoParent)) { if (session.name && session.username && session.username !== username) { throw redirect(303, `/${famSlug}/${session.username}`); } } const s = createServices(pbUser(event), session); const [ members, templates, assigned, summary, fam, rewards, bonusConfigs, completions, settings ] = await Promise.all([ s.crud.list('members', session.famId), s.crud.list('chore-templates', session.famId), s.crud.list('assigned-chores', session.famId), s.fam.weeklySummary(session.famId), s.fam.get(session.famId), s.crud.list('rewards', session.famId), s.crud.list('bonus-configs', session.famId), s.crud.list('completions', session.famId), s.settings.get(session.famId) ]); return { role: 'parent', members, templates, assigned, summary, fam, famSlug, rewards, bonusConfigs, completions, settings }; } // Child (users auth, role=child) → kanban. // Also demo parent visiting a child's route. // A child should always land on their own kanban route. if (!(demoMode && isDemoParent)) { if (session?.username && session.username !== username) { throw redirect(303, `/${famSlug}/${session.username}`); } } const pbToken = event.cookies.get('pb_token') || ''; const famId = session?.famId || ''; // When a demo parent visits a child's route, session.id is the parent's PB // record. Look up the actual child user by username so chores load correctly. const childId = demoParentVisitingChild ? (await pbAdmin.getList('users', `famId='${famId}' && username='${famSlug}:${username}'`))?.[0]?.id || '' : session?.id || ''; const empty = { role: 'child' as const, token: pbToken, memberId: childId, famId, verified: false, memberName: session?.name || '', memberColor: session?.color || '', templates: [] as never[], assigned: [] as never[], completions: [] as never[], rewards: [] as never[], bonusConfigs: [] as never[], tallies: {} as Record, payday: 1, paydayTime: '18:00', timezone: 'auto' }; if (!pbToken || !famId) return empty; try { const s = createServices(createPbClient(pbToken), session ?? undefined); const chores = await s.chores.myChores(famId); return { role: 'child', token: pbToken, memberId: childId, famId, verified: true, memberName: session?.name || '', memberColor: session?.color || '', templates: chores.templates || [], assigned: chores.assigned || [], completions: chores.completions || [], rewards: chores.rewards || [], bonusConfigs: chores.bonusConfigs || [], tallies: {} as Record, payday: chores.payday, paydayTime: chores.paydayTime || '18:00', timezone: chores.timezone || 'auto' }; } catch { return empty; } } export const actions = { claim: async (event) => { if (!event.locals.user) throw redirect(303, '/login'); const famId = event.locals.user.famId; const fd = await event.request.formData(); const rewardId = fd.get('id') as string; try { const s = createServices(pbUser(event), event.locals.user); const record = await s.rewards.approve(famId, rewardId); return { record }; } catch (e) { return { error: e instanceof Error ? e.message : 'Failed to claim reward' }; } }, issueAll: async (event) => { if (!event.locals.user) throw redirect(303, '/login'); const famId = event.locals.user.famId; const fd = await event.request.formData(); const memberId = fd.get('memberId') as string; try { const s = createServices(pbUser(event), event.locals.user); const result = await s.rewards.issueAll(famId, memberId); return { count: result.count }; } catch (e) { return { error: e instanceof Error ? e.message : 'Failed to issue rewards' }; } }, revoke: async (event) => { if (!event.locals.user) throw redirect(303, '/login'); const famId = event.locals.user.famId; const fd = await event.request.formData(); const completionId = fd.get('id') as string; try { const s = createServices(pbUser(event), event.locals.user); await s.completions.revoke(famId, completionId); return { revoked: true, id: completionId }; } catch (e) { return { error: e instanceof Error ? e.message : 'Failed to revoke' }; } }, trigger: async (event) => { if (!event.locals.user) throw redirect(303, '/login'); const famId = event.locals.user.famId; const fd = await event.request.formData(); const configId = fd.get('configId') as string; const memberId = fd.get('memberId') as string; if (!configId) return { error: 'Config ID required' }; try { const s = createServices(pbUser(event), event.locals.user); const result = await s.bonuses.trigger(famId, configId, memberId || undefined); return { records: result.records || [] }; } catch (e) { return { error: e instanceof Error ? e.message : 'Failed to trigger' }; } } };