add fixes to claims and seasons
This commit is contained in:
@@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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 },
|
||||
]);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user