feat(coach,admin): finish the self-context and invite surfaces end to end

Invites move into their own admin section with a real ledger: status
badges, expiry, uses, a redemption history of who joined when (new
redemption table, revocation is now soft so history survives), and an
honest one-time link/QR moment. The assistant self-context grows
structured fields for conditions, allergies and focus — encrypted like
the free text — merges age and gender from the profile into one fenced
prompt block, and asks up to three targeted follow-up questions that
surface as tappable chips above the coach composer. A fourth nudge
trigger gently suggests a check-up when the self-context is stale and
the coach is actually in use.
This commit is contained in:
Marc-André Bombeck committed 2026-06-10 16:18:48 +02:00
1 parent 9fe5673251
commit 5b75184649
32 files changed
+2648 -423

No files matched your search

+24
View File
@@ -1,5 +1,9 @@
/**
* v1.15.20 — request schema for `PUT /api/coach/about-me`.
* v1.16.0 — extended with three structured self-context fields
* (conditions, allergies, coach focus). Age/gender deliberately stay
* on the User profile and are merged into the prompt block at assembly
* time — the questionnaire never duplicates them.
*
* Lives outside the route file so the OpenAPI registry
* (`src/lib/openapi/routes.ts`) can import it without touching the
@@ -12,6 +16,20 @@ import { z } from "zod/v4";
/** Hard cap enforced BEFORE encryption. */
export const ABOUT_ME_MAX_CHARS = 4000;
/** Per-field cap for the structured answers, BEFORE encryption. */
export const ABOUT_ME_FIELD_MAX_CHARS = 500;
const structuredField = z
.string()
.max(
ABOUT_ME_FIELD_MAX_CHARS,
`Maximum ${ABOUT_ME_FIELD_MAX_CHARS} characters`,
)
// Optional so older clients that only send `aboutMe` keep working;
// an omitted field leaves the stored value untouched, an empty
// string clears it (mirrors the `aboutMe` semantics).
.optional();
export const aboutMePutSchema = z.object({
/**
* The user's free-text self-description. An empty / whitespace-only
@@ -20,6 +38,12 @@ export const aboutMePutSchema = z.object({
aboutMe: z
.string()
.max(ABOUT_ME_MAX_CHARS, `Maximum ${ABOUT_ME_MAX_CHARS} characters`),
/** Chronic conditions, short free text. */
conditions: structuredField,
/** Allergies / intolerances, short free text. */
allergies: structuredField,
/** What the Coach should pay attention to, short free text. */
coachFocus: structuredField,
});
export type AboutMePutInput = z.infer<typeof aboutMePutSchema>;