mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
The setup flow capped a display name at 50 characters while the profile took 80, and the profile checked its minimum before trimming, so a name of only spaces was stored as an empty string. Every surface now reads one bound (80), the profile trims before validating, and only spaces means no name on both paths: the profile clears it to null, the setup flow leaves it unset.
57 lines
3.0 KiB
TypeScript
57 lines
3.0 KiB
TypeScript
/**
|
|
* Request schema for the completion write.
|
|
*
|
|
* It lives outside the route file for the same reason
|
|
* `src/lib/validations/about-me.ts` does: a route module may only export
|
|
* handlers plus the Next.js route config, so the OpenAPI registry cannot
|
|
* import a schema declared inside one. Keeping the shape here lets the
|
|
* published contract and the runtime parser be the same object rather than
|
|
* two hand-kept copies.
|
|
*
|
|
* Prisma-free by construction, so the generator script can pull it in
|
|
* without dragging the server graph along.
|
|
*/
|
|
import { optionalDisplayNameSchema } from "@/lib/validations/display-name";
|
|
import { z } from "zod/v4";
|
|
|
|
/**
|
|
* `POST /api/onboarding/complete` — the completion stamp.
|
|
*
|
|
* Every field is optional: the endpoint's job is the completion stamp, and
|
|
* the profile fields are whatever the caller happened to collect. A field
|
|
* that parses but is falsy (an empty `displayName`, `heightCm: 0`) is not
|
|
* written — the route only assigns truthy values. The web flow sends an
|
|
* empty body and writes its profile through `PUT /api/auth/profile`.
|
|
*/
|
|
export const onboardingCompleteSchema = z.object({
|
|
/**
|
|
* v1.39 — the managed record the answers were given FOR. Sent by the
|
|
* confirm screen after "someone I look after" created the profile: the
|
|
* module map and the dashboard order derived from the caller's answers are
|
|
* applied to that record, and the caller's own record is stamped complete
|
|
* without deriving. The caller must be a guardian of it, and the answer to
|
|
* Q1 must be "someone-else"; anything else is refused rather than applied
|
|
* to the wrong record.
|
|
*/
|
|
managedRecordId: z
|
|
.string()
|
|
.trim()
|
|
.min(1)
|
|
.max(64)
|
|
.optional()
|
|
.describe(
|
|
"The managed record the setup answers were given FOR — the profile \"someone I look after\" created on the confirm screen. When present, the module map and the dashboard order derived from the caller's answers are applied to THAT record and its own setup row is written as finished; the caller's own record is stamped complete and its derivation latched without any module being touched. Honoured only for a managed profile the caller actively guards (otherwise 404 `onboarding.complete.recordNotFound`) and only when the answer to Q1 was `someone-else` (otherwise 422 `onboarding.complete.recordTargetMismatch`); nothing is stamped on a refusal. Absent with Q1 = `someone-else`, the flow completes without deriving anything anywhere.",
|
|
),
|
|
// Only spaces is no name: trimmed to "", which the route leaves unset.
|
|
displayName: optionalDisplayNameSchema.optional(),
|
|
heightCm: z.number().min(50).max(300).optional(),
|
|
dateOfBirth: z.string().optional(),
|
|
// The same three values the profile schema stores. A narrower enum here
|
|
// rejected the whole onboarding submission over a field the account is
|
|
// allowed to hold, and left the value unrecordable until the person found
|
|
// the setting again afterwards.
|
|
gender: z.enum(["MALE", "FEMALE", "OTHER"]).optional(),
|
|
});
|
|
|
|
export type OnboardingCompleteInput = z.infer<typeof onboardingCompleteSchema>;
|