diff --git a/frontend/src/hooks.server.ts b/frontend/src/hooks.server.ts index 9ec589b..c8f4ec3 100644 --- a/frontend/src/hooks.server.ts +++ b/frontend/src/hooks.server.ts @@ -29,6 +29,7 @@ function sessionFrom(record: any, freshToken: string) { pattern: record.pattern || '', themeSize: record.themeSize || '', themeOpacity: record.themeOpacity || '', + partyEmoji: record.partyEmoji || '', token: freshToken } satisfies SessionUser & { token: string }; } diff --git a/frontend/src/lib/server/migrate.ts b/frontend/src/lib/server/migrate.ts index a66e1d8..87a2981 100644 --- a/frontend/src/lib/server/migrate.ts +++ b/frontend/src/lib/server/migrate.ts @@ -479,6 +479,9 @@ async function ensureUserFields(): Promise { if (!has('passwordResetExpiry')) { needed.push({ name: 'passwordResetExpiry', type: 'text', required: false }); } + if (!has('partyEmoji')) { + needed.push({ name: 'partyEmoji', type: 'text', required: false }); + } if (needed.length) { await updateCollection(usersCol.id, { ...usersCol, fields: [...usersCol.fields, ...needed] }); } diff --git a/frontend/src/lib/server/services/fam.ts b/frontend/src/lib/server/services/fam.ts index 044a5d1..4bb5032 100644 --- a/frontend/src/lib/server/services/fam.ts +++ b/frontend/src/lib/server/services/fam.ts @@ -76,6 +76,7 @@ export async function getProfile(pb: any, famId: string, userId: string) { pattern: rec.pattern || '', themeSize: rec.themeSize || '', themeOpacity: rec.themeOpacity || '', + partyEmoji: rec.partyEmoji || '', email: rec.email || '' }; } @@ -92,6 +93,8 @@ export async function updateProfile( if (data.pattern !== undefined) patch.pattern = data.pattern; if (data.themeSize !== undefined) patch.themeSize = data.themeSize; if (data.themeOpacity !== undefined) patch.themeOpacity = data.themeOpacity; + if (data.partyEmoji !== undefined) + patch.partyEmoji = String(data.partyEmoji).trim().slice(0, 8) || '🐖'; if (data.email !== undefined) patch.email = data.email; const rec = await pb.collection('users').update(userId, patch); return { @@ -101,6 +104,7 @@ export async function updateProfile( pattern: rec.pattern || '', themeSize: rec.themeSize || '', themeOpacity: rec.themeOpacity || '', + partyEmoji: rec.partyEmoji || '', email: rec.email || '' }; } diff --git a/frontend/src/lib/server/types.ts b/frontend/src/lib/server/types.ts index e731727..9cf3093 100644 --- a/frontend/src/lib/server/types.ts +++ b/frontend/src/lib/server/types.ts @@ -8,4 +8,5 @@ export interface SessionUser { pattern?: string; themeSize?: string; themeOpacity?: string; + partyEmoji?: string; } diff --git a/frontend/src/routes/[fam]/[username]/+page.server.ts b/frontend/src/routes/[fam]/[username]/+page.server.ts index c15b484..cb5e3dc 100644 --- a/frontend/src/routes/[fam]/[username]/+page.server.ts +++ b/frontend/src/routes/[fam]/[username]/+page.server.ts @@ -87,6 +87,7 @@ export async function load(event) { verified: false, memberName: session?.name || '', memberColor: session?.color || '', + partyEmoji: (session as any)?.partyEmoji || '', templates: [] as never[], assigned: [] as never[], completions: [] as never[], @@ -111,6 +112,7 @@ export async function load(event) { verified: true, memberName: session?.name || '', memberColor: session?.color || '', + partyEmoji: (session as any)?.partyEmoji || '', templates: chores.templates || [], assigned: chores.assigned || [], completions: chores.completions || [], diff --git a/frontend/src/routes/[fam]/[username]/+page.svelte b/frontend/src/routes/[fam]/[username]/+page.svelte index 3f2b090..a968d17 100644 --- a/frontend/src/routes/[fam]/[username]/+page.svelte +++ b/frontend/src/routes/[fam]/[username]/+page.svelte @@ -209,6 +209,15 @@ let famId = $state(data.famId || ''); let memberName = $state(data.memberName || ''); let memberColor = $state(data.memberColor || '#6366f1'); + // Party mascot: live member record first (updates without re-login), + // server load as fallback, pig as default. + let partyEmoji = $derived( + (famStore.initialized + ? (famStore.members as any[]).find((m) => m.id === memberId)?.partyEmoji + : null) || + (data as any).partyEmoji || + '🐖' + ); let editingName = $state(false); let nameInput = $state(''); let showColorPicker = $state(false); @@ -1285,7 +1294,7 @@ {/if} {#if paydayParty}
(paydayParty = false)}> - 🐖 + {partyEmoji}

PAYDAY!!

Your cash is ready — go collect it! 🎉

diff --git a/frontend/src/routes/[fam]/[username]/preferences/+page.server.ts b/frontend/src/routes/[fam]/[username]/preferences/+page.server.ts index 72f7cce..3eb667d 100644 --- a/frontend/src/routes/[fam]/[username]/preferences/+page.server.ts +++ b/frontend/src/routes/[fam]/[username]/preferences/+page.server.ts @@ -63,6 +63,7 @@ export async function load(event) { pattern: me.pattern || '', themeSize: me.themeSize || '', themeOpacity: me.themeOpacity || '', + partyEmoji: me.partyEmoji || '', email: me.email || '', hasPin: false, session: true @@ -94,6 +95,7 @@ export async function load(event) { pattern: session.pattern || '', themeSize: session.themeSize || '', themeOpacity: session.themeOpacity || '', + partyEmoji: session.partyEmoji || '', email: '', hasPin: await hasPin(session.id).catch(() => false), session: true @@ -112,6 +114,7 @@ export const actions = { const pattern = (fd.get('pattern') as string) || ''; const themeSize = (fd.get('themeSize') as string) || ''; const themeOpacity = (fd.get('themeOpacity') as string) || ''; + const partyEmoji = String(fd.get('partyEmoji') || '').trim().slice(0, 8); const s = servicesFor(event); const famId = session.famId; @@ -124,6 +127,7 @@ export const actions = { data.pattern = pattern; data.themeSize = themeSize; data.themeOpacity = themeOpacity; + if (partyEmoji) data.partyEmoji = partyEmoji; data.email = email || ''; const me = await s.fam.updateProfile(famId, data); return { @@ -133,6 +137,7 @@ export const actions = { pattern: me.pattern, themeSize: me.themeSize, themeOpacity: me.themeOpacity, + partyEmoji: me.partyEmoji, email: me.email }; } catch (e) { @@ -148,6 +153,7 @@ export const actions = { data.pattern = pattern; data.themeSize = themeSize; data.themeOpacity = themeOpacity; + if (partyEmoji) data.partyEmoji = partyEmoji; const me = await s.fam.updateProfile(famId, data); return { success: true, @@ -156,6 +162,7 @@ export const actions = { pattern: me.pattern, themeSize: me.themeSize, themeOpacity: me.themeOpacity, + partyEmoji: me.partyEmoji, email: '' }; } catch (e) { diff --git a/frontend/src/routes/[fam]/[username]/preferences/+page.svelte b/frontend/src/routes/[fam]/[username]/preferences/+page.svelte index e5c9161..6cee69a 100644 --- a/frontend/src/routes/[fam]/[username]/preferences/+page.svelte +++ b/frontend/src/routes/[fam]/[username]/preferences/+page.svelte @@ -70,6 +70,14 @@ '#8b5cf6', '#14b8a6' ]; + + // Payday-party mascot — the emoji that leads the child's payday celebration. + let memberEmoji = $state(data.partyEmoji || '🐖'); + const partyEmojis = [ + '🐖', '🐱', '🐶', '🦄', '🐸', '🐵', '🦊', '🐼', + '🦁', '🐯', '🐰', '🐨', '🐷', '🐮', '🦖', '🐙', + '🚀', '🌈', '⭐', '🎮', '⚽', '🍕', '🍦', '🎉' + ]; @@ -145,6 +153,28 @@
+
+ +
+ {#each partyEmojis as e} + + {/each} +
+ +
+ {#if data.role === 'parent'}