add platform demo fam

This commit is contained in:
JCEEE
2026-09-10 18:54:38 +01:00
parent 4730ab3eb9
commit 41033914b4
10 changed files with 114 additions and 30 deletions
+7 -3
View File
@@ -3,6 +3,7 @@ import { createPbClient } from '$lib/server/pocketbase';
import { createServices, type ChatActor } from '$lib/server/services';
import { ensureFamAccess } from '$lib/server/access';
import { seedDemoCompletions } from '$lib/server/migrate';
import { getPlatformFlags } from '$lib/server/platform';
async function paydayCheck(famId: string, pbToken: string) {
try {
@@ -47,8 +48,8 @@ async function resolveChatIdentity(
}
export async function load(event) {
// Demo family: rotate 3-week completion data on every visit (idempotent).
if (event.params.fam === 'showboaters') {
// Demo family: rotate 3-week completion data on every visit when demo mode is on (idempotent).
if (event.params.fam === 'showboaters' && (await getPlatformFlags()).demo) {
seedDemoCompletions().catch(() => {});
}
@@ -94,6 +95,8 @@ export async function load(event) {
}
}
const demoMode = (await getPlatformFlags()).demo;
return {
// Canonical fam slug — from the URL param ([fam] routes). Client code
// reads page.data.famSlug; never copy it into local $state.
@@ -117,6 +120,7 @@ export async function load(event) {
chat,
pbToken,
fam,
famAccess
famAccess,
demoMode
};
}
+2 -1
View File
@@ -32,6 +32,7 @@
let session = $state<Session | null>(data.session);
let isParent = $state(data.isParent);
let role = $state(data.role);
let isDemo = $state(data.demoMode);
let famName = $derived(
famStore.initialized ? (famStore.fam as any)?.name || page.params.fam : page.params.fam
);
@@ -198,7 +199,7 @@
class="app-shell {bgPattern ? `pattern-${bgPattern}` : ''}"
style={`${bgSizeVw ? `--s:calc(${bgSizeVw} * 1vw);` : ''}--c1:${bgShades.c1};--c2:${bgShades.c2};--c3:${bgShades.c3};--c4:${bgShades.c4}`}
>
<Sidebar {famName} session={data.session} {isParent} {role} />
<Sidebar {famName} session={data.session} {isParent} {role} {isDemo} />
<TopNav
role={data.session ? role : ''}
seasons={famStore.seasons.filter((s) => s.active !== false)}
@@ -1,19 +1,27 @@
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') {
const famId = session.famId;
const famSlug = event.params.fam;
const username = event.params.username;
if (session.name && session.username && session.username !== username) {
throw redirect(303, `/${famSlug}/${session.username}`);
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 [
@@ -27,15 +35,15 @@ export async function load(event) {
completions,
settings
] = await Promise.all([
s.crud.list('members', famId),
s.crud.list('chore-templates', famId),
s.crud.list('assigned-chores', famId),
s.fam.weeklySummary(famId),
s.fam.get(famId),
s.crud.list('rewards', famId),
s.crud.list('bonus-configs', famId),
s.crud.list('completions', famId),
s.settings.get(famId)
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',
@@ -52,18 +60,23 @@ export async function load(event) {
};
}
// Child (users auth, role=child) → kanban
const famSlug = event.params.fam;
const username = event.params.username;
// 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 (session?.username && session.username !== username) {
throw redirect(303, `/${famSlug}/${session.username}`);
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 || '';
const childId = session?.id || '';
// 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,
@@ -1,7 +1,16 @@
import { redirect } from '@sveltejs/kit';
import { servicesFor } from '$lib/server/servicesFor';
import { getPlatformFlags } from '$lib/server/platform';
async function isDemoMode(): Promise<boolean> {
return (await getPlatformFlags()).demo;
}
export async function load(event) {
if (await isDemoMode()) {
return { verified: false, role: '', token: '', memberId: '', famId: '', memberName: '', memberColor: '', pattern: '', themeSize: '', themeOpacity: '', email: '', demoMode: true };
}
const session = event.locals.user;
const famSlug = event.params.fam;
const username = event.params.username;
@@ -7,6 +7,7 @@ import { issueAccess, createChild, inviteParent } from '$lib/server/member-otp';
import { sendParentInviteEmail } from '$lib/server/email';
import { slugify, handle, famUsername } from '@shared/slugify';
import { applyAccessCode } from '$lib/server/access';
import { getPlatformFlags } from '$lib/server/platform';
import {
createBillingPortalSession,
cancelSubscriptionAtPeriodEnd,
@@ -18,7 +19,15 @@ function famIdOf(event: RequestEvent): string {
return event.locals.user.famId;
}
// In demo mode, settings is disabled for all users.
async function isDemoMode(): Promise<boolean> {
return (await getPlatformFlags()).demo;
}
export async function load(event: RequestEvent) {
if (await isDemoMode()) {
return { members: [], fam: null, seasons: [], accessCode: null, subStatus: null, demoMode: true };
}
const famId = famIdOf(event);
const pb = pbUser(event);
const [members, fam, seasons] = await Promise.all([