use custom emoji for payday

This commit is contained in:
JCEEE
2026-09-21 08:35:13 +01:00
parent 8e4eef06f5
commit a8c674f8b3
8 changed files with 58 additions and 1 deletions
+1
View File
@@ -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 };
}
+3
View File
@@ -479,6 +479,9 @@ async function ensureUserFields(): Promise<void> {
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] });
}
+4
View File
@@ -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 || ''
};
}
+1
View File
@@ -8,4 +8,5 @@ export interface SessionUser {
pattern?: string;
themeSize?: string;
themeOpacity?: string;
partyEmoji?: string;
}
@@ -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 || [],
@@ -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}
<div class="payday-party" role="status" onclick={() => (paydayParty = false)}>
<span class="party-cat">🐖</span>
<span class="party-cat">{partyEmoji}</span>
<div class="party-body">
<p class="party-title">PAYDAY!!</p>
<p class="party-sub">Your cash is ready — go collect it! 🎉</p>
@@ -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) {
@@ -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 = [
'🐖', '🐱', '🐶', '🦄', '🐸', '🐵', '🦊', '🐼',
'🦁', '🐯', '🐰', '🐨', '🐷', '🐮', '🦖', '🐙',
'🚀', '🌈', '⭐', '🎮', '⚽', '🍕', '🍦', '🎉'
];
</script>
<ViewHeader title="Preferences" subtitle={data.verified ? data.memberName : ''} hero />
@@ -145,6 +153,28 @@
<input type="hidden" name="color" value={memberColor} />
</div>
<div style="margin-bottom:1rem">
<label style="display:block;font-size:0.85rem;color:#374151;margin-bottom:0.25rem">
Party emoji — leads your payday celebration 🎉
</label>
<div style="display:flex;gap:0.3rem;flex-wrap:wrap">
{#each partyEmojis as e}
<button
type="button"
onclick={() => (memberEmoji = e)}
aria-label="Pick {e}"
style="font-size:1.5rem;line-height:1;padding:0.25rem;border-radius:8px;border:{e ===
memberEmoji
? '2px solid #111827'
: '2px solid transparent'};background:{e === memberEmoji
? '#f3f4f6'
: 'transparent'};cursor:pointer"
>{e}</button>
{/each}
</div>
<input type="hidden" name="partyEmoji" value={memberEmoji} />
</div>
{#if data.role === 'parent'}
<div style="margin-bottom:1rem">
<label style="display:block;font-size:0.85rem;color:#374151;margin-bottom:0.25rem">