update ui and other frontend updates
This commit is contained in:
@@ -18,11 +18,15 @@
|
||||
|
||||
## Auth
|
||||
|
||||
| Role | Auth | Session |
|
||||
| ---------------- | -------------------------- | --------------------------- |
|
||||
| Super admin (me) | PB email+pass | 24hr JWT |
|
||||
| Fam admin | PB email+pass | 24hr JWT, scoped to own fam |
|
||||
| Member | Invite code + device token | localStorage, no expiry |
|
||||
| Role | Auth | Session | Record in |
|
||||
| ---------------- | -------------------------- | --------------------------- | -------------------- |
|
||||
| Admin (parent) | PB email+pass | 24hr JWT | `fam_admins` |
|
||||
| Member (child) | Invite code + device token | `device_token` cookie only | `members` |
|
||||
|
||||
- **Admins** (parents) have a PB auth record + `fam_admins` record. They authenticate via email/password login, get a session cookie (`session`) with `{ famId, userId, famSlug, memberName, role: "parent" }`.
|
||||
- **Members** (children) exist only in the `members` collection. They authenticate via invite code + device token (SHA-256 hashed). The `device_token` cookie is set on join; no session cookie.
|
||||
- **Platform superuser** (`_superusers`) used only server-side by `pb-admin.ts` for cross-family queries (e.g. `/admin` stats dashboard). Not an app role.
|
||||
- The layout (`[fam]/+layout.server.ts`) derives `isParent` and `role` centrally from the session cookie — child pages use `page.data.isParent` or `page.data.role` from `$app/state`.
|
||||
|
||||
## PB Collections (all scoped by `famId`)
|
||||
|
||||
@@ -42,9 +46,15 @@
|
||||
/ Landing (SaaS marketing)
|
||||
/admin Admin panel - statistic dashboard, and any donations made
|
||||
/join/:code Member invite code
|
||||
/join/:code/:member Member invite with pre-selected member
|
||||
/{fam} Fam dashboard
|
||||
/{fam}/admin Admin panel
|
||||
/{fam}/:username Member kanban (?token= for auth)
|
||||
/{fam}/:username Member kanban & admin dashboard (role determined by session)
|
||||
/{fam}/:username/preferences User preferences (admin→fam_admins, member→members)
|
||||
/{fam}/:username/settings Family admin settings (session required)
|
||||
/{fam}/:username/chores Chore templates & assignment grid
|
||||
/{fam}/:username/rewards Rewards overview
|
||||
/{fam}/:username/bonuses Bonus configs & evaluation
|
||||
/api/* Hono proxy (webhooks, CRON)
|
||||
```
|
||||
|
||||
@@ -58,11 +68,82 @@
|
||||
- **WhatsApp:** Deferred — Hono CRON handler has pluggable notification interface
|
||||
- **UI reactivity:** Svelte `$state` / `$derived` / `$effect` — no PB SDK `.subscribe()` / SSE
|
||||
|
||||
## Env Vars (SvelteKit 3.0.0-next.4)
|
||||
|
||||
Env vars must be declared in `frontend/src/env.ts` using `defineEnvVars` from `@sveltejs/kit/hooks`:
|
||||
|
||||
```ts
|
||||
import { defineEnvVars } from '@sveltejs/kit/hooks';
|
||||
export const variables = defineEnvVars({
|
||||
DEBUG_RECORD_ID: {},
|
||||
SERVER_IP: {public: true},
|
||||
PB_PORT: {public: true}
|
||||
});
|
||||
```
|
||||
|
||||
Only vars with `{public: true}` are exposed to client-side code via `$app/env/public`. If you need a new client-side env var (e.g., `PUBLIC_PB_URL`), you must:
|
||||
1. Add it to `.env` with `PUBLIC_` prefix
|
||||
2. Add it to `frontend/src/env.ts` with `{public: true}`
|
||||
|
||||
The actual values come from `.env` (symlinked from project root at `frontend/.env -> ../.env`). Server-only env vars (no `{public}` flag) are available via `$app/env/private` but only in server modules.
|
||||
|
||||
## ⚠️ CRITICAL: Reactivity Pattern (NEVER use `$effect` to sync from famStore)
|
||||
|
||||
**Do NOT do this:**
|
||||
```svelte
|
||||
<!-- ❌ WRONG: $effect syncing local state from famStore fights Svelte reactivity -->
|
||||
let items = $state(data.items);
|
||||
$effect(() => {
|
||||
if (data.items?.length) items = data.items;
|
||||
if (famStore.initialized && famStore.items.length) items = famStore.items;
|
||||
});
|
||||
```
|
||||
|
||||
**Do this instead:**
|
||||
```svelte
|
||||
<!-- ✅ RIGHT: Initialize from famStore, mutate via applyRecord in callbacks -->
|
||||
let items = $state(famStore.initialized ? famStore.items : (data.items || []));
|
||||
```
|
||||
|
||||
**Form action callbacks must call `famStore.applyRecord()`:**
|
||||
```ts
|
||||
// In use:enhance callback:
|
||||
famStore.applyRecord('collection_name', record, 'create' | 'update' | 'delete');
|
||||
```
|
||||
|
||||
The `famStore` has reactive `$state` properties and an `applyRecord()` method designed for instant UI feedback from form actions. Calling `applyRecord()` mutates the store directly — the UI updates immediately without waiting for PB SSE. The SSE subscription is a backup for multi-user sync only. Do NOT add `$effect` watchers to bridge the gap between form actions and reactive state.
|
||||
|
||||
## UI Component Conventions
|
||||
|
||||
All admin and member pages use the following pattern:
|
||||
|
||||
```svelte
|
||||
<ViewHeader title="..." subtitle="..." tabs={...} weeknav={...} sort={...} />
|
||||
<CardGrid>
|
||||
<Card {cols} title="..." accent="...">
|
||||
<!-- card content → micro-layout per page -->
|
||||
</Card>
|
||||
</CardGrid>
|
||||
```
|
||||
|
||||
- **Layout shell** lives in `[fam]/+layout.svelte` — Sidebar, TopNav, Footer. All fam-scoped routes inherit it.
|
||||
- **Sidebar** role-aware: shows admin CTAs on `/admin/*`, member CTAs on `/[username]/*`.
|
||||
- **TopNav** has `announcement` (center slot) and `actions` (right slot).
|
||||
- **`Card` `cols` prop**: `1 | 2 | 3` — spans that many columns in the 3-column `CardGrid`.
|
||||
- **`Button`** for all CTAs: `<Button variant="primary|secondary|ghost|danger" size="sm|md|lg">`.
|
||||
- **`Accordion`** for expand/collapse sections (settings, logs).
|
||||
- **Icons**: defined as SVG strings in `lib/components/icons.ts`. No icon library dependency.
|
||||
- **Components** live in `frontend/src/lib/components/` and are re-exported from `index.ts`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Every collection query includes `famId = @request.auth.famId` filter
|
||||
- Super admin bypasses famId filter (access via PB admin API)
|
||||
- `deviceToken` stored as SHA-256 hash; never log raw tokens
|
||||
- **Admin → Proxy**: `hono.admin.*` in `$lib/server/hono.ts` — uses `sessionHeaders(event)` (server-side only, requires `RequestEvent`)
|
||||
- **Member → Proxy (server)**: `memberApi.*` in `$lib/client/api.ts` — use inside `+page.server.ts` load/actions; `BASE_URL` resolves to Hono port on server
|
||||
- **Member → Proxy (browser)**: `memberApi.*` in `$lib/client/api.ts` — use inside `+page.svelte`; `BASE_URL` is empty, Vite proxies `/api/*` to Hono
|
||||
- **`$page`**: import `{ page }` from `$app/state` (NOT `$app/stores` — that's the old Svelte 4 API). Reference as `page.params.fam`, `page.url.pathname` etc. without `$` prefix
|
||||
- `config.ts` at root for dev/build-time shared config (e.g. `PROXY_PORT`); runtime config via env vars
|
||||
- `.env` at root tracks port values (`PROXY_PORT`, `PORT`); `.env.example` committed as template
|
||||
- Docker: `docker/Dockerfile` (prod, multi-stage + nginx) + `docker/Dockerfile.dev` (PocketBase)
|
||||
|
||||
Reference in New Issue
Block a user