mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
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:
1 parent
9fe5673251
commit
5b75184649
32 files changed
+2648
-423
No files matched your search
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* v1.16.0 — admin invite table: status-derivation precedence.
|
||||
*
|
||||
* revoked > exhausted > expired > active. Revocation must win even on
|
||||
* a row that is also expired/exhausted, because the badge documents
|
||||
* the admin's action; exhaustion beats expiry because "it was fully
|
||||
* used" is the more informative fact.
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { deriveInviteStatus } from "../invite-tokens-section";
|
||||
|
||||
const NOW = new Date("2026-06-10T12:00:00Z");
|
||||
const FUTURE = new Date(NOW.getTime() + 86_400_000).toISOString();
|
||||
const PAST = new Date(NOW.getTime() - 86_400_000).toISOString();
|
||||
|
||||
describe("deriveInviteStatus", () => {
|
||||
it("revoked wins over every other state", () => {
|
||||
expect(
|
||||
deriveInviteStatus(
|
||||
{ uses: 5, maxUses: 1, expiresAt: PAST, revokedAt: PAST },
|
||||
NOW,
|
||||
),
|
||||
).toBe("revoked");
|
||||
});
|
||||
|
||||
it("exhausted beats expired", () => {
|
||||
expect(
|
||||
deriveInviteStatus(
|
||||
{ uses: 1, maxUses: 1, expiresAt: PAST, revokedAt: null },
|
||||
NOW,
|
||||
),
|
||||
).toBe("exhausted");
|
||||
});
|
||||
|
||||
it("expired when past expiry with uses left", () => {
|
||||
expect(
|
||||
deriveInviteStatus(
|
||||
{ uses: 0, maxUses: 1, expiresAt: PAST, revokedAt: null },
|
||||
NOW,
|
||||
),
|
||||
).toBe("expired");
|
||||
});
|
||||
|
||||
it("active otherwise", () => {
|
||||
expect(
|
||||
deriveInviteStatus(
|
||||
{ uses: 0, maxUses: 1, expiresAt: FUTURE, revokedAt: null },
|
||||
NOW,
|
||||
),
|
||||
).toBe("active");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user