add fixes to claims and seasons

This commit is contained in:
JCEEE
2026-07-30 11:12:35 +01:00
parent 57ef30d3a7
commit b254564436
24 changed files with 2517 additions and 704 deletions
+3
View File
@@ -36,4 +36,7 @@ export const memberApi = {
async claimReward(token: string, famId: string, rewardId: string) {
return memberFetch('POST', `/api/members/rewards/${rewardId}/claim`, token, famId);
},
async requestAllRewards(token: string, famId: string) {
return memberFetch<{ count: number }>('POST', '/api/members/rewards/request-all', token, famId);
},
};
+3 -3
View File
@@ -15,22 +15,22 @@
let navItems = $derived(isParent && memberName
? [
{ href: `/${famSlug}`, label: famName, icon: homeIcon },
{ href: `/${famSlug}/${memberName}`, label: 'Dashboard', icon: dashboardIcon },
{ href: `/${famSlug}/${memberName}/chores`, label: 'Chores', icon: choresIcon },
{ href: `/${famSlug}/${memberName}/rewards`, label: 'Rewards', icon: rewardsIcon },
{ href: `/${famSlug}/${memberName}/bonuses`, label: 'Bonuses', icon: bonusesIcon },
{ href: `/${famSlug}/${memberName}/preferences`, label: 'Preferences', icon: prefsIcon },
]
: showChildItems
? [
{ href: `/${famSlug}`, label: famName, icon: homeIcon },
{ href: `/${famSlug}/${memberName}`, label: 'Dashboard', icon: dashboardIcon },
{ href: `/${famSlug}/${memberName}/preferences`, label: 'Preferences', icon: prefsIcon },
]
: []
);
let footerItems = $derived([
{ href: `/${famSlug}`, label: famName, icon: homeIcon },
...(memberName ? [{ href: `/${famSlug}/${memberName}/preferences`, label: 'Preferences', icon: prefsIcon }] : []),
...(isParent && memberName ? [{ href: `/${famSlug}/${memberName}/settings`, label: 'Settings', icon: settingsIcon }] : []),
{ href: session ? '/logout' : '/login', label: session ? 'Log out' : 'Log in', icon: logoutIcon },
]);
+52 -1
View File
@@ -1,12 +1,36 @@
<script lang="ts">
let { announcement = '', children }: { announcement?: string; children?: any } = $props();
let {
announcement = '',
role = '',
seasons = [],
children,
}: {
announcement?: string
role?: string
seasons?: { id: string; name: string; color: string; active: boolean }[]
children?: any
} = $props();
</script>
<header class="topnav">
<div class="topnav-left">
{#if role}
<span class="role-pill {role}">{role}</span>
{/if}
</div>
<div class="topnav-announcement">
{#if announcement}<span class="announcement-text">{announcement}</span>{/if}
</div>
<div class="topnav-actions">
{#each seasons as s}
<span
class="season-pill"
class:active={s.active}
style="--season-color: {s.color}"
>
{s.name}
</span>
{/each}
{@render children?.()}
</div>
</header>
@@ -23,8 +47,35 @@
padding: 0 1.5rem;
z-index: 90;
transition: left 0.2s;
gap: 0.75rem;
}
.topnav-left { display: flex; align-items: center; gap: 0.5rem; }
.topnav-announcement { flex: 1; text-align: center; }
.announcement-text { font-size: 0.85rem; color: #6b7280; }
.topnav-actions { display: flex; align-items: center; gap: 0.5rem; }
.role-pill {
font-size: 0.7rem;
padding: 0.15rem 0.5rem;
border-radius: 999px;
text-transform: uppercase;
font-weight: 700;
letter-spacing: 0.03em;
}
.role-pill.parent { background: #fef3c7; color: #b45309; }
.role-pill.child { background: #dbeafe; color: #1d4ed8; }
.season-pill {
font-size: 0.75rem;
padding: 0.2rem 0.6rem;
border-radius: 999px;
border: 1.5px solid var(--season-color, #d1d5db);
background: transparent;
color: #374151;
font-weight: 500;
transition: all 0.15s;
}
.season-pill.active {
background: var(--season-color, #6366f1);
color: #fff;
border-color: var(--season-color, #6366f1);
}
</style>
+9
View File
@@ -65,6 +65,9 @@ export const hono = {
async claimReward(event: RequestEvent, famId: string, rewardId: string) {
return request('POST', `/api/admin/${famId}/rewards/${rewardId}/claim`, undefined, sessionHeaders(event));
},
async issueAllRewards(event: RequestEvent, famId: string, memberId: string, message?: string) {
return request('POST', `/api/admin/${famId}/rewards/issue-all`, { memberId, message }, sessionHeaders(event));
},
async bonusConfigs(event: RequestEvent, famId: string) {
return request('GET', `/api/admin/${famId}/bonus-configs`, undefined, sessionHeaders(event));
},
@@ -107,5 +110,11 @@ export const hono = {
async updateProfile(event: RequestEvent, famId: string, data: Record<string, unknown>) {
return request('PATCH', `/api/admin/${famId}/profile`, data, sessionHeaders(event));
},
async updateFam(event: RequestEvent, famId: string, data: Record<string, unknown>) {
return request('PATCH', `/api/admin/${famId}/fam`, data, sessionHeaders(event));
},
async request(event: RequestEvent, method: string, path: string, body?: unknown) {
return request(method, path, body, sessionHeaders(event));
},
},
};
+12
View File
@@ -42,4 +42,16 @@ export const pbAdmin = {
if (!res.ok) throw new Error(`PB get ${collection}/${id}: ${JSON.stringify(data)}`);
return data;
},
async update(collection: string, id: string, data: Record<string, unknown>) {
const t = await ensureToken();
const res = await fetch(`${PB_ENDPOINT}/api/collections/${collection}/records/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${t}` },
body: JSON.stringify(data),
});
const result = await res.json();
if (!res.ok) throw new Error(`PB update ${collection}/${id}: ${JSON.stringify(result)}`);
return result;
},
};
+8 -3
View File
@@ -1,10 +1,10 @@
import { pb } from '$lib/pocketbase';
import type {
Member, ChoreTemplate, AssignedChore, Completion,
WeeklyHistory, Reward, BonusConfig, Fam,
WeeklyHistory, Reward, BonusConfig, Fam, Season,
} from '$lib/types';
type CollectionName = 'members' | 'chore_templates' | 'assigned_chores' | 'completions' | 'bonus_configs' | 'rewards';
type CollectionName = 'members' | 'chore_templates' | 'assigned_chores' | 'completions' | 'bonus_configs' | 'rewards' | 'seasons';
class FamStore {
fam = $state<Fam | null>(null)
@@ -15,6 +15,7 @@ class FamStore {
history = $state<WeeklyHistory[]>([])
rewards = $state<Reward[]>([])
bonusConfigs = $state<BonusConfig[]>([])
seasons = $state<Season[]>([])
initialized = $state(false)
famId = $state('')
@@ -62,7 +63,7 @@ class FamStore {
this.initPromise = (async () => {
try {
const [famRes, membersRes, templatesRes, assignedRes, completionsRes, bonusConfigsRes, rewardsRes] =
const [famRes, membersRes, templatesRes, assignedRes, completionsRes, bonusConfigsRes, rewardsRes, seasonsRes] =
await Promise.all([
pb.collection('fams').getOne(famId) as Promise<Fam>,
pb.collection('members').getFullList({ filter: `famId = '${famId}'` }) as Promise<Member[]>,
@@ -71,6 +72,7 @@ class FamStore {
pb.collection('completions').getFullList({ filter: `famId = '${famId}'` }) as Promise<Completion[]>,
pb.collection('bonus_configs').getFullList({ filter: `famId = '${famId}'` }) as Promise<BonusConfig[]>,
pb.collection('rewards').getFullList({ filter: `famId = '${famId}'` }) as Promise<Reward[]>,
pb.collection('seasons').getFullList({ filter: `famId = '${famId}'` }) as Promise<Season[]>,
])
this.fam = famRes
this.members = membersRes
@@ -79,6 +81,7 @@ class FamStore {
this.completions = completionsRes
this.bonusConfigs = bonusConfigsRes
this.rewards = rewardsRes
this.seasons = seasonsRes
this.initialized = true
} catch (e) {
console.error('FamStore.init failed:', e)
@@ -101,6 +104,7 @@ class FamStore {
{ collection: 'completions', filter: this.famId },
{ collection: 'bonus_configs', filter: this.famId },
{ collection: 'rewards', filter: this.famId },
{ collection: 'seasons', filter: this.famId },
]
const promises = subs.map(({ collection, filter }) => {
@@ -136,6 +140,7 @@ class FamStore {
case 'completions': this.completions = apply(this.completions); break
case 'bonus_configs': this.bonusConfigs = apply(this.bonusConfigs); break
case 'rewards': this.rewards = apply(this.rewards); break
case 'seasons': this.seasons = apply(this.seasons); break
}
}
+15 -1
View File
@@ -8,6 +8,18 @@ export type BonusPeriod = 'weekly' | 'monthly'
export type BonusStatus = 'active' | 'archived'
export type BonusState = 'pending' | 'unclaimed' | 'claimed'
export interface Season {
id: string
famId: string
name: string
color: string
active: boolean
autoDisable?: string
autoStart?: string
created: string
updated: string
}
export interface Fam {
id: string
name: string
@@ -51,6 +63,7 @@ export interface AssignedChore {
type: RewardType
value: number
customName?: string
seasonIds?: string[]
created: string
updated: string
}
@@ -102,8 +115,9 @@ export interface Reward {
label: string
value: number
rewardType: BonusRewardType
claimed: boolean
status: 'unclaimed' | 'requested' | 'claimed'
claimedAt?: string
requestedAt?: string
date: string
created: string
updated: string