gymmonk-schema 0.47.0 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -171,10 +171,10 @@ export declare const memberOnboardingFormSchema: z.ZodObject<{
171
171
  female: "female";
172
172
  other: "other";
173
173
  }>> & z.ZodType<{} | undefined, "male" | "female" | "other" | null, z.core.$ZodTypeInternals<{} | undefined, "male" | "female" | "other" | null>>;
174
- heightCm: z.ZodNullable<z.ZodNumber> & z.ZodType<{} | undefined, number | null, z.core.$ZodTypeInternals<{} | undefined, number | null>>;
175
- weightKg: z.ZodNullable<z.ZodNumber> & z.ZodType<{} | undefined, number | null, z.core.$ZodTypeInternals<{} | undefined, number | null>>;
176
- neckCm: z.ZodNullable<z.ZodNumber> & z.ZodType<{} | undefined, number | null, z.core.$ZodTypeInternals<{} | undefined, number | null>>;
177
- waistCm: z.ZodNullable<z.ZodNumber> & z.ZodType<{} | undefined, number | null, z.core.$ZodTypeInternals<{} | undefined, number | null>>;
174
+ heightCm: z.ZodNullable<z.ZodNumber>;
175
+ weightKg: z.ZodNullable<z.ZodNumber>;
176
+ neckCm: z.ZodNullable<z.ZodNumber>;
177
+ waistCm: z.ZodNullable<z.ZodNumber>;
178
178
  hipsCm: z.ZodNullable<z.ZodNumber>;
179
179
  phone: z.ZodString;
180
180
  avatarUrl: z.ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding-form.d.ts","sourceRoot":"","sources":["../src/onboarding-form.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"onboarding-form.d.ts","sourceRoot":"","sources":["../src/onboarding-form.ts"],"names":[],"mappings":"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"}
@@ -111,10 +111,21 @@ export const memberOnboardingFormSchema = z
111
111
  dobMonth: z.string().min(1, 'Enter the month'),
112
112
  dobYear: z.string().length(4, 'Enter a 4-digit year'),
113
113
  gender: requiredChoice(z.enum(['male', 'female', 'other']), 'Select your gender'),
114
- heightCm: requiredChoice(z.number(), 'Add your height'),
115
- weightKg: requiredChoice(z.number(), 'Add your weight'),
116
- neckCm: requiredChoice(z.number(), 'Add your neck'),
117
- waistCm: requiredChoice(z.number(), 'Add your waist'),
114
+ /**
115
+ * The five body measurements are OPTIONAL, all of them.
116
+ *
117
+ * They were `requiredChoice`, which put two screens of tape-measure work
118
+ * between somebody signing up at the desk and having an account. Neither
119
+ * number is needed to train, to be enrolled or to be charged; both are
120
+ * things a member is more likely to fill in accurately at home than with a
121
+ * queue behind them, and `/me/edit/body` already exists for exactly that.
122
+ *
123
+ * Optional does NOT mean half-filled: see the two refinements below.
124
+ */
125
+ heightCm: z.number().nullable(),
126
+ weightKg: z.number().nullable(),
127
+ neckCm: z.number().nullable(),
128
+ waistCm: z.number().nullable(),
118
129
  hipsCm: z.number().nullable(),
119
130
  /**
120
131
  * The member's OWN number, asked on step one beside their name.
@@ -131,11 +142,44 @@ export const memberOnboardingFormSchema = z
131
142
  })
132
143
  .superRefine(refineMemberDob)
133
144
  /**
134
- * The API requires the hips measurement for female members (needed for the
135
- * body-fat estimate), so it fails inline on the measurements step instead of
136
- * 422-ing at Finish. Own refine no early-return coupling with the DOB rule.
145
+ * Skippable, but not half-skippable: the BMI step.
146
+ *
147
+ * BMI needs both numbers or neither. Leaving one behind stores a lone
148
+ * measurement that computes nothing and that nobody will ever be prompted
149
+ * for again, so the moment either is touched the other is asked for. An
150
+ * untouched step raises nothing at all, which is what makes Continue work on
151
+ * an empty screen.
152
+ *
153
+ * Own refine, with no early-return coupling to the DOB rule above.
154
+ */
155
+ .superRefine((v, ctx) => {
156
+ if (v.heightCm == null && v.weightKg == null)
157
+ return;
158
+ if (v.heightCm == null) {
159
+ ctx.addIssue({ code: 'custom', path: ['heightCm'], message: 'Add your height' });
160
+ }
161
+ if (v.weightKg == null) {
162
+ ctx.addIssue({ code: 'custom', path: ['weightKg'], message: 'Add your weight' });
163
+ }
164
+ })
165
+ /**
166
+ * The same rule for the body-fat step, plus the one measurement whose
167
+ * absence is easy to miss.
168
+ *
169
+ * The U.S. Navy estimate takes neck and waist for everyone and hips as well
170
+ * for women. A woman who fills the first two and skips the third gets no
171
+ * number and no explanation, so hips is asked for once this step is under
172
+ * way — and never when it has been skipped outright.
137
173
  */
138
174
  .superRefine((v, ctx) => {
175
+ if (v.neckCm == null && v.waistCm == null && v.hipsCm == null)
176
+ return;
177
+ if (v.neckCm == null) {
178
+ ctx.addIssue({ code: 'custom', path: ['neckCm'], message: 'Add your neck' });
179
+ }
180
+ if (v.waistCm == null) {
181
+ ctx.addIssue({ code: 'custom', path: ['waistCm'], message: 'Add your waist' });
182
+ }
139
183
  if (v.gender === 'female' && v.hipsCm == null) {
140
184
  ctx.addIssue({
141
185
  code: 'custom',
@@ -166,16 +210,26 @@ export const MEMBER_ONBOARDING_STEPS = [
166
210
  helpText: "Enter your details, we'll need these to set things up.",
167
211
  fields: ['firstName', 'dobDay', 'dobMonth', 'dobYear', 'gender', 'phone'],
168
212
  },
213
+ /**
214
+ * Both of these were titled "Personal details", which is what step one is.
215
+ * Three screens deep with the same heading on the last two, a member had no
216
+ * way to tell where they were or what the screen was going to give them.
217
+ * Each is named after the number it produces.
218
+ *
219
+ * The help line says the step can be skipped. That is the only place a
220
+ * member is told, and without it an optional screen simply reads as a
221
+ * required one they have not finished.
222
+ */
169
223
  {
170
224
  key: 'body-metrics',
171
- title: 'Personal details',
172
- helpText: 'Your height and weight, to calculate your BMI.',
225
+ title: 'Your BMI',
226
+ helpText: 'Your height and weight, to calculate your BMI. You can skip this and add it later.',
173
227
  fields: ['heightCm', 'weightKg'],
174
228
  },
175
229
  {
176
230
  key: 'body-composition',
177
- title: 'Personal details',
178
- helpText: 'A few measurements, to estimate your body fat.',
231
+ title: 'Your body fat percentage',
232
+ helpText: 'A few measurements, to estimate your body fat. You can skip this and add it later.',
179
233
  fields: ['neckCm', 'waistCm', 'hipsCm'],
180
234
  },
181
235
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding-form.js","sourceRoot":"","sources":["../src/onboarding-form.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Onboarding & gym-registration FORM schemas\n * ===========================================================\n * The client-side validation contract for the member onboarding wizard and the\n * \"list your gym\" wizard.\n *\n * Why these live here and are distinct from the `*OnboardingBody` API schemas:\n * a half-filled form legitimately holds `null` for a choice the user has not\n * made yet, whereas the API body only ever sees completed values. So these are\n * the SAME rules expressed over the form's working shape — every message,\n * regex and bound is defined once, here, and both sides agree on it.\n *\n * Everything reuses the primitives in `shared.ts` / `common.ts`\n * (`INDIAN_STATES`, `CENTER_TYPES`, `WEEKDAYS`, phone/pincode rules) rather\n * than restating them.\n *\n * @module gymmonk-schema/onboarding-form\n */\n\nimport { z } from 'zod';\nimport { type CenterType, centerTypeSchema } from './center.js';\nimport { ageInYears, isRealDate, PHONE_REGEX } from './common.js';\nimport type { WEEKDAYS } from './shared.js';\nimport { type Address, addressSchema } from './shared.js';\n\n// ─── Field-level rules (shared by both wizards) ──────────────────────────────\n\n/** A required 10-digit Indian mobile, as typed into a form field. */\nconst formPhone = z.string().regex(PHONE_REGEX, 'Enter a valid 10-digit mobile number');\n\n/** An optional phone: blank is allowed, anything else must be valid. */\nconst optionalFormPhone = z\n .string()\n .refine((v) => v === '' || PHONE_REGEX.test(v), 'Enter a valid 10-digit mobile number');\n\n/** An optional email: blank is allowed, anything else must look like an email. */\nconst optionalFormEmail = z\n .string()\n .refine((v) => v === '' || /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(v), {\n message: 'Enter a valid email',\n });\n\n/** A choice the user has not made yet is `null` until they pick one. */\nconst requiredChoice = <T extends z.ZodTypeAny>(inner: T, message: string) =>\n inner.nullable().refine((v: unknown) => v !== null, { message });\n\n// ─── Address (form shape: one atomic value, null until picked) ────────────\n\n/**\n * An address inside an onboarding form is ONE cell, not four.\n *\n * It used to be `street` + `city` + `state` + `pincode`, each independently\n * fillable, which is why this module needed rules for a half-filled address.\n * Now it is picked through the location drawer, which either hands back a\n * complete `Address` — area chain, ids, names, ancestry, coordinates — or\n * nothing at all. So there is no partial state left to validate, and `null`\n * simply means \"not chosen yet\".\n */\nexport const onboardingAddressSchema = requiredChoice(addressSchema, 'Add your address');\n\n/** Working shape of an address inside an onboarding form. */\nexport type OnboardingAddress = Address | null;\n\n/** An address has been picked. */\nexport function isOnboardingAddressComplete(a: OnboardingAddress): boolean {\n return a !== null;\n}\n\n// ─── Social links (form shape: every handle is a plain string) ───────────────\n\nexport const onboardingSocialLinksSchema = z.object({\n jansathi: z.string(),\n youtube: z.string(),\n instagram: z.string(),\n facebook: z.string(),\n});\nexport type OnboardingSocialLinks = z.infer<typeof onboardingSocialLinksSchema>;\n\n// ─── Opening hours (form shape) ─────────────────────────────────────────────\n\nexport const onboardingDayHoursSchema = z.object({\n open: z.boolean(),\n from: z.string(),\n to: z.string(),\n});\nexport type OnboardingDayHours = z.infer<typeof onboardingDayHoursSchema>;\nexport type OnboardingWeeklyHours = Record<(typeof WEEKDAYS)[number], OnboardingDayHours>;\n\n// ─── Member onboarding form ─────────────────────────────────────────────────\n\n/** Age bounds a gym membership realistically spans. */\nexport const MEMBER_MIN_AGE_YEARS = 10;\nexport const MEMBER_MAX_AGE_YEARS = 100;\n\n/**\n * The three-cell date-of-birth rule, in ONE place.\n *\n * Each cell is bounded by its own input, but only the assembled date can catch\n * an impossible one (31 Feb) or an implausible age. The issue is attached to\n * `dobDay` so it renders under the field group rather than under one cell.\n *\n * Exported because the member's own profile editor asks for the SAME date with\n * the same three cells. When the rule lived inline in the wizard, that editor\n * had a free-text box parsed with `new Date()` instead, which accepted \"March\n * 18, 1994\", accepted nonsense just as happily, and silently dropped whatever\n * it could not read.\n */\nexport function refineMemberDob(\n v: { dobDay: string; dobMonth: string; dobYear: string },\n ctx: z.RefinementCtx,\n): void {\n if (!v.dobDay || !v.dobMonth || v.dobYear.length !== 4) return;\n\n const day = Number(v.dobDay);\n const month = Number(v.dobMonth);\n const year = Number(v.dobYear);\n\n if (!isRealDate(year, month, day)) {\n ctx.addIssue({ code: 'custom', path: ['dobDay'], message: 'That date does not exist' });\n return;\n }\n\n const age = ageInYears(year, month, day);\n if (age < MEMBER_MIN_AGE_YEARS) {\n ctx.addIssue({\n code: 'custom',\n path: ['dobDay'],\n message: `You must be at least ${MEMBER_MIN_AGE_YEARS} to join`,\n });\n } else if (age > MEMBER_MAX_AGE_YEARS) {\n ctx.addIssue({ code: 'custom', path: ['dobDay'], message: 'Check the year of birth' });\n }\n}\n\nexport const memberOnboardingFormSchema = z\n .object({\n firstName: z.string().trim().min(1, 'First name is required'),\n lastName: z.string().trim(),\n dobDay: z.string().min(1, 'Enter the day'),\n dobMonth: z.string().min(1, 'Enter the month'),\n dobYear: z.string().length(4, 'Enter a 4-digit year'),\n gender: requiredChoice(z.enum(['male', 'female', 'other']), 'Select your gender'),\n heightCm: requiredChoice(z.number(), 'Add your height'),\n weightKg: requiredChoice(z.number(), 'Add your weight'),\n neckCm: requiredChoice(z.number(), 'Add your neck'),\n waistCm: requiredChoice(z.number(), 'Add your waist'),\n hipsCm: z.number().nullable(),\n /**\n * The member's OWN number, asked on step one beside their name.\n *\n * It moved here when the emergency step was removed. Emergency contact and\n * blood group genuinely belong in the profile — nobody needs them to\n * finish signing up — but this one is how the gym reaches the member at\n * all, and it drives every chase action on the owner's roster card. Left\n * on the removed step it would simply have stopped being collected.\n */\n phone: formPhone,\n /** GCS URL of the uploaded photo. Optional — it can be added later. */\n avatarUrl: z.string(),\n })\n .superRefine(refineMemberDob)\n /**\n * The API requires the hips measurement for female members (needed for the\n * body-fat estimate), so it fails inline on the measurements step instead of\n * 422-ing at Finish. Own refine — no early-return coupling with the DOB rule.\n */\n .superRefine((v, ctx) => {\n if (v.gender === 'female' && v.hipsCm == null) {\n ctx.addIssue({\n code: 'custom',\n path: ['hipsCm'],\n message: 'Hips measurement is required',\n });\n }\n });\n\n/**\n * The form's working shape. Hand-written (not `z.infer`) so unset numeric /\n * choice fields can hold `null` while the schema's `requiredChoice` rules still\n * drive the field errors — the resolver is cast to this type at `useForm`.\n */\nexport type MemberOnboardingForm = {\n firstName: string;\n lastName: string;\n dobDay: string;\n dobMonth: string;\n dobYear: string;\n gender: 'male' | 'female' | 'other' | null;\n heightCm: number | null;\n weightKg: number | null;\n neckCm: number | null;\n waistCm: number | null;\n hipsCm: number | null;\n phone: string;\n avatarUrl: string;\n};\n\nexport const EMPTY_MEMBER_ONBOARDING_FORM: MemberOnboardingForm = {\n firstName: '',\n lastName: '',\n dobDay: '',\n dobMonth: '',\n dobYear: '',\n gender: null,\n heightCm: null,\n weightKg: null,\n neckCm: null,\n waistCm: null,\n hipsCm: null,\n phone: '',\n avatarUrl: '',\n};\n\n/**\n * One onboarding step, described ONCE.\n *\n * The wizard chrome (screen name, help line, progress denominator) and the\n * validation gate used to live in the page as a local `META` array beside a\n * separate field map — two lists that had to be kept the same length by hand.\n * They are one list here, so a step cannot exist without a title or be left\n * without a validation gate.\n */\nexport interface OnboardingStepConfig<TField extends string> {\n /** Stable identifier — safe for analytics and resume, unlike the index. */\n key: string;\n /** Screen name in the step header. */\n title: string;\n /** Help line under the screen name. */\n helpText: string;\n /**\n * Fields validated before this step may be left. Naming a parent path (e.g.\n * `address`) validates its nested fields too.\n */\n fields: readonly TField[];\n}\n\nexport const MEMBER_ONBOARDING_STEPS: readonly OnboardingStepConfig<\n keyof MemberOnboardingForm & string\n>[] = [\n {\n key: 'basics',\n title: 'Basic information',\n helpText: \"Enter your details, we'll need these to set things up.\",\n fields: ['firstName', 'dobDay', 'dobMonth', 'dobYear', 'gender', 'phone'],\n },\n {\n key: 'body-metrics',\n title: 'Personal details',\n helpText: 'Your height and weight, to calculate your BMI.',\n fields: ['heightCm', 'weightKg'],\n },\n {\n key: 'body-composition',\n title: 'Personal details',\n helpText: 'A few measurements, to estimate your body fat.',\n fields: ['neckCm', 'waistCm', 'hipsCm'],\n },\n] as const;\n\nexport const MEMBER_ONBOARDING_TOTAL_STEPS = MEMBER_ONBOARDING_STEPS.length;\n\n/**\n * Fields validated before advancing each member step.\n * DERIVED from `MEMBER_ONBOARDING_STEPS` so the two can never disagree.\n */\nexport const MEMBER_STEP_FIELDS: (keyof MemberOnboardingForm)[][] = MEMBER_ONBOARDING_STEPS.map(\n (s) => [...s.fields],\n);\n\n// ─── Gym registration form (\"list your gym\") ────────────────────────────────\n\n/**\n * The gym-registration wizard's working shape.\n *\n * THREE steps, not five. The two it used to open with — \"your details\" and\n * \"contact details\" — asked a signed-in member for their name, gender, date of\n * birth and phone, every one of which onboarding already captured. Registering\n * a business asks about the BUSINESS; the person is already known.\n */\n/**\n * The gym-registration form's FIELD SHAPE, exported separately from the schema.\n *\n * Separate because the schema carries a `superRefine` (see\n * `refineGymRegistrationForm`) and a refined object cannot be `.extend()`ed —\n * zod hands back a ZodEffects, and calling `.extend` on one throws at MODULE\n * LOAD, which no build catches and which takes the whole page down on import.\n * The web client adds the two pasted Maps links to this form, so it needs the\n * shape and the refinement as separate pieces it can recombine.\n */\nexport const gymRegistrationFormShape = {\n // 1 — organisation\n orgName: z.string().trim().min(1, 'Organisation name is required'),\n orgDescription: z.string(),\n hqAddress: onboardingAddressSchema,\n orgWebsite: z.string(),\n gstNumber: z.string(),\n cinNumber: z.string(),\n numberOfCenters: requiredChoice(z.number(), 'Add the number of centers'),\n orgSocials: onboardingSocialLinksSchema,\n // 2 — center\n centerName: z.string().trim().min(1, 'Center name is required'),\n centerDescription: z.string(),\n /**\n * The real enum, not `z.array(z.string())`.\n *\n * It was strings, and `toGymRegistrationBody` then CAST them to the enum on\n * the way to the API. A cast is not a check: retiring a centre type left the\n * wizard perfectly happy to submit the retired value and the server to\n * reject the whole registration, with the owner told only that something was\n * wrong. Now a value the API will not accept cannot leave the form.\n */\n centerTypes: z.array(centerTypeSchema).min(1, 'Pick at least one'),\n /**\n * \"Same as organisation location\".\n *\n * Most gyms register one branch at the address they just typed on the\n * previous screen, and asking for it twice invites a typo between two things\n * that are meant to be the same place — one of which decides whether a\n * member standing at the door is close enough to check in.\n *\n * It is the CHOICE that is stored, not a copy of the answer. The address is\n * resolved from the organisation at submit, so an owner who goes back and\n * corrects the HQ address does not leave a stale duplicate behind on the\n * centre.\n */\n centerSameAsOrg: z.boolean(),\n /**\n * Nullable here and required by `refineGymRegistrationForm` instead, because\n * \"required\" is now conditional — see that function.\n */\n centerAddress: addressSchema.nullable(),\n floorArea: z.number().nullable(),\n centerPhone1: formPhone,\n centerPhone2: optionalFormPhone,\n centerEmail: optionalFormEmail,\n hours: z.object({\n mon: onboardingDayHoursSchema,\n tue: onboardingDayHoursSchema,\n wed: onboardingDayHoursSchema,\n thu: onboardingDayHoursSchema,\n fri: onboardingDayHoursSchema,\n sat: onboardingDayHoursSchema,\n sun: onboardingDayHoursSchema,\n }),\n photos: z.array(z.string()),\n // 3 — plan\n planId: requiredChoice(z.string(), 'Choose a plan'),\n tenureId: z.string().nullable(),\n};\n\n/**\n * The centre needs an address unless it is inheriting the organisation's.\n *\n * A cross-field rule, so it cannot live on `centerAddress` itself. The issue is\n * raised AT `centerAddress` deliberately: the wizard validates each step by\n * calling `trigger` with that step's field names, and an error parked on the\n * object root would match none of them — Continue would refuse to advance with\n * nothing on screen explaining why. That exact shape of bug has cost this\n * codebase two silent forms already.\n *\n * Structurally typed rather than tied to `GymRegistrationForm` so the web\n * client can apply the same function to its own extended schema.\n */\nexport function refineGymRegistrationForm(\n v: { centerSameAsOrg: boolean; centerAddress: Address | null },\n ctx: z.RefinementCtx,\n): void {\n if (v.centerSameAsOrg) return;\n if (v.centerAddress === null) {\n ctx.addIssue({ code: 'custom', path: ['centerAddress'], message: 'Add your address' });\n }\n}\n\nexport const gymRegistrationFormSchema = z\n .object(gymRegistrationFormShape)\n .superRefine(refineGymRegistrationForm);\n\n/** Hand-written working shape (nullable \"unset\" fields); see the member form. */\nexport type GymRegistrationForm = {\n orgName: string;\n orgDescription: string;\n hqAddress: OnboardingAddress;\n orgWebsite: string;\n gstNumber: string;\n cinNumber: string;\n numberOfCenters: number | null;\n orgSocials: OnboardingSocialLinks;\n centerName: string;\n centerDescription: string;\n centerTypes: CenterType[];\n /** \"Same as organisation location\" — see `gymRegistrationFormShape`. */\n centerSameAsOrg: boolean;\n centerAddress: OnboardingAddress;\n floorArea: number | null;\n centerPhone1: string;\n centerPhone2: string;\n centerEmail: string;\n hours: OnboardingWeeklyHours;\n photos: string[];\n planId: string | null;\n tenureId: string | null;\n};\n\nconst EMPTY_ONBOARDING_SOCIALS: OnboardingSocialLinks = {\n jansathi: '',\n youtube: '',\n instagram: '',\n facebook: '',\n};\n\n/** The opening hours a new center starts with. */\nexport const DEFAULT_ONBOARDING_HOURS: OnboardingWeeklyHours = {\n mon: { open: true, from: '06:00', to: '22:00' },\n tue: { open: true, from: '06:00', to: '22:00' },\n wed: { open: true, from: '06:00', to: '22:00' },\n thu: { open: true, from: '06:00', to: '22:00' },\n fri: { open: true, from: '06:00', to: '22:00' },\n sat: { open: true, from: '07:00', to: '21:00' },\n sun: { open: false, from: '07:00', to: '13:00' },\n};\n\nexport const EMPTY_GYM_REGISTRATION_FORM: GymRegistrationForm = {\n orgName: '',\n orgDescription: '',\n hqAddress: null,\n orgWebsite: '',\n gstNumber: '',\n cinNumber: '',\n numberOfCenters: null,\n orgSocials: { ...EMPTY_ONBOARDING_SOCIALS },\n centerName: '',\n centerDescription: '',\n centerTypes: [],\n // Unticked. A pre-ticked box would put the organisation's address on the\n // centre without the owner ever looking at it — and that address is what a\n // member's check-in is measured against.\n centerSameAsOrg: false,\n centerAddress: null,\n floorArea: null,\n centerPhone1: '',\n centerPhone2: '',\n centerEmail: '',\n hours: structuredClone(DEFAULT_ONBOARDING_HOURS),\n photos: [],\n planId: null,\n tenureId: null,\n};\n\n/**\n * The gym-registration wizard, described once.\n *\n * The last step is the pricing screen, which renders its own header; its title\n * and help line are unused by the wizard chrome but kept so every step has one.\n */\nexport const GYM_REGISTRATION_STEPS: readonly OnboardingStepConfig<\n keyof GymRegistrationForm & string\n>[] = [\n {\n key: 'organisation',\n title: 'Your organisation',\n helpText: 'Tell us about the business that runs the gym.',\n fields: ['orgName', 'hqAddress', 'numberOfCenters'],\n },\n {\n key: 'center',\n title: 'Your first center',\n helpText: 'Set up your primary center. You can add more later.',\n fields: [\n 'centerName',\n 'centerTypes',\n 'centerAddress',\n 'centerPhone1',\n 'centerPhone2',\n 'centerEmail',\n ],\n },\n {\n key: 'plan',\n title: 'Choose your plan',\n helpText: 'Pick the plan that fits your gym.',\n fields: ['planId', 'tenureId'],\n },\n] as const;\n\nexport const GYM_REGISTRATION_TOTAL_STEPS = GYM_REGISTRATION_STEPS.length;\n\n/**\n * Fields validated (via `form.trigger`) before advancing each step.\n * DERIVED from `GYM_REGISTRATION_STEPS` so the two can never disagree.\n */\nexport const GYM_REGISTRATION_STEP_FIELDS: (keyof GymRegistrationForm)[][] =\n GYM_REGISTRATION_STEPS.map((s) => [...s.fields]);\n\n// ─── Retained: the owner onboarding wizard ───────────────────────────────────\n// The gym-registration flow above replaces this, but the owner console's repo\n// still ships the four-step owner wizard and cannot migrate in the same change\n// that publishes this package. Nothing above imports any of it.\n//\n// TODO(onboarding-migration): delete once the owner wizard moves to\n// `GYM_REGISTRATION_STEPS`.\n\n// ─── Owner onboarding form ──────────────────────────────────────────────────\n\n/** Age bounds for a gym owner (the DOB itself stays optional). */\nexport const OWNER_MIN_AGE_YEARS = 16;\nexport const OWNER_MAX_AGE_YEARS = 100;\n\nexport const ownerOnboardingFormSchema = z\n .object({\n // 1 — basics\n firstName: z.string().trim().min(1, 'First name is required'),\n lastName: z.string().trim(),\n dobDay: z.string(),\n dobMonth: z.string(),\n dobYear: z.string(),\n gender: requiredChoice(z.enum(['male', 'female', 'other']), 'Select your gender'),\n // 2 — contact\n phone: formPhone,\n altEmail: optionalFormEmail,\n website: z.string(),\n noWebsite: z.boolean(),\n address: onboardingAddressSchema,\n socials: onboardingSocialLinksSchema,\n // 3 — organisation\n orgName: z.string().trim().min(1, 'Organisation name is required'),\n orgDescription: z.string(),\n hqAddress: onboardingAddressSchema,\n gstNumber: z.string(),\n cinNumber: z.string(),\n numberOfCenters: requiredChoice(z.number(), 'Add the number of centers'),\n orgSocials: onboardingSocialLinksSchema,\n // 4 — center\n centerName: z.string().trim().min(1, 'Center name is required'),\n centerDescription: z.string(),\n centerTypes: z.array(z.string()).min(1, 'Pick at least one'),\n centerAddress: onboardingAddressSchema,\n floorArea: z.number().nullable(),\n centerPhone1: formPhone,\n centerPhone2: optionalFormPhone,\n centerEmail: optionalFormEmail,\n hours: z.object({\n mon: onboardingDayHoursSchema,\n tue: onboardingDayHoursSchema,\n wed: onboardingDayHoursSchema,\n thu: onboardingDayHoursSchema,\n fri: onboardingDayHoursSchema,\n sat: onboardingDayHoursSchema,\n sun: onboardingDayHoursSchema,\n }),\n photos: z.array(z.string()),\n /** GCS URL of the owner's uploaded photo. Optional. */\n avatarUrl: z.string(),\n // 5 — plan\n planId: requiredChoice(z.string(), 'Choose a plan'),\n tenureId: z.string().nullable(),\n })\n /**\n * The owner's DOB is optional, so this only engages once any cell is touched:\n * then all three are required and must form a real, plausible date.\n */\n .superRefine((v, ctx) => {\n const touched = Boolean(v.dobDay || v.dobMonth || v.dobYear);\n if (!touched) return;\n\n const fail = (message: string) => ctx.addIssue({ code: 'custom', path: ['dobDay'], message });\n\n if (!v.dobDay || !v.dobMonth || v.dobYear.length !== 4) {\n fail('Enter the full date of birth');\n return;\n }\n\n const day = Number(v.dobDay);\n const month = Number(v.dobMonth);\n const year = Number(v.dobYear);\n\n if (!isRealDate(year, month, day)) {\n fail('That date does not exist');\n return;\n }\n\n const age = ageInYears(year, month, day);\n if (age < OWNER_MIN_AGE_YEARS) fail(`You must be at least ${OWNER_MIN_AGE_YEARS}`);\n else if (age > OWNER_MAX_AGE_YEARS) fail('Check the year of birth');\n });\n\n/** Hand-written working shape (nullable \"unset\" fields); see the member form. */\nexport type OwnerOnboardingForm = {\n firstName: string;\n lastName: string;\n dobDay: string;\n dobMonth: string;\n dobYear: string;\n gender: 'male' | 'female' | 'other' | null;\n phone: string;\n altEmail: string;\n website: string;\n noWebsite: boolean;\n address: OnboardingAddress;\n socials: OnboardingSocialLinks;\n orgName: string;\n orgDescription: string;\n hqAddress: OnboardingAddress;\n gstNumber: string;\n cinNumber: string;\n numberOfCenters: number | null;\n orgSocials: OnboardingSocialLinks;\n centerName: string;\n centerDescription: string;\n centerTypes: string[];\n centerAddress: OnboardingAddress;\n floorArea: number | null;\n centerPhone1: string;\n centerPhone2: string;\n centerEmail: string;\n hours: OnboardingWeeklyHours;\n photos: string[];\n avatarUrl: string;\n planId: string | null;\n tenureId: string | null;\n};\n\nexport const EMPTY_OWNER_ONBOARDING_FORM: OwnerOnboardingForm = {\n firstName: '',\n lastName: '',\n dobDay: '',\n dobMonth: '',\n dobYear: '',\n gender: null,\n phone: '',\n altEmail: '',\n website: '',\n noWebsite: false,\n address: null,\n socials: { ...EMPTY_ONBOARDING_SOCIALS },\n orgName: '',\n orgDescription: '',\n hqAddress: null,\n gstNumber: '',\n cinNumber: '',\n numberOfCenters: null,\n orgSocials: { ...EMPTY_ONBOARDING_SOCIALS },\n centerName: '',\n centerDescription: '',\n centerTypes: [],\n centerAddress: null,\n floorArea: null,\n centerPhone1: '',\n centerPhone2: '',\n centerEmail: '',\n hours: structuredClone(DEFAULT_ONBOARDING_HOURS),\n photos: [],\n avatarUrl: '',\n planId: null,\n tenureId: null,\n};\n\n/**\n * The owner wizard, described once.\n *\n * The DOB cells belong in step 1's `fields` even though the date is optional:\n * the refine above still rejects a half-typed date, and a field missing from\n * this map would be unreachable twice over — Continue would not surface it, and\n * the final submit (which finds the offending STEP by searching these lists)\n * would find no step and leave the plan screen dead.\n *\n * The last step is the pricing screen, which renders its own header; its title\n * and help line are unused by the wizard chrome but kept so every step has one.\n */\nexport const OWNER_ONBOARDING_STEPS: readonly OnboardingStepConfig<\n keyof OwnerOnboardingForm & string\n>[] = [\n {\n key: 'basics',\n title: 'Your details',\n helpText: 'A few basics about you.',\n fields: ['firstName', 'gender', 'dobDay', 'dobMonth', 'dobYear'],\n },\n {\n key: 'contact',\n title: 'Contact details',\n helpText: 'How members and we can reach you.',\n fields: ['phone', 'altEmail', 'address'],\n },\n {\n key: 'organisation',\n title: 'Organisation',\n helpText: 'Tell us about your business.',\n fields: ['orgName', 'hqAddress', 'numberOfCenters'],\n },\n {\n key: 'center',\n title: 'Your first center',\n helpText: 'Set up your primary center. You can add more later.',\n fields: [\n 'centerName',\n 'centerTypes',\n 'centerAddress',\n 'centerPhone1',\n 'centerPhone2',\n 'centerEmail',\n ],\n },\n {\n key: 'plan',\n title: 'Choose your plan',\n helpText: 'Pick the plan that fits your gym.',\n fields: ['planId', 'tenureId'],\n },\n] as const;\n\nexport const OWNER_ONBOARDING_TOTAL_STEPS = OWNER_ONBOARDING_STEPS.length;\n\n/**\n * Fields validated (via `form.trigger`) before advancing each owner step.\n * DERIVED from `OWNER_ONBOARDING_STEPS` so the two can never disagree.\n */\nexport const OWNER_STEP_FIELDS: (keyof OwnerOnboardingForm)[][] = OWNER_ONBOARDING_STEPS.map(\n (s) => [...s.fields],\n);\n\n// ─── Gym selection (member step 0) ──────────────────────────────────────────\n\nexport const gymSelectionSchema = z.object({\n gymId: z.string().trim().min(1, 'Enter your gym id or pick a gym below'),\n});\nexport type GymSelection = z.infer<typeof gymSelectionSchema>;\n"]}
1
+ {"version":3,"file":"onboarding-form.js","sourceRoot":"","sources":["../src/onboarding-form.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Onboarding & gym-registration FORM schemas\n * ===========================================================\n * The client-side validation contract for the member onboarding wizard and the\n * \"list your gym\" wizard.\n *\n * Why these live here and are distinct from the `*OnboardingBody` API schemas:\n * a half-filled form legitimately holds `null` for a choice the user has not\n * made yet, whereas the API body only ever sees completed values. So these are\n * the SAME rules expressed over the form's working shape — every message,\n * regex and bound is defined once, here, and both sides agree on it.\n *\n * Everything reuses the primitives in `shared.ts` / `common.ts`\n * (`INDIAN_STATES`, `CENTER_TYPES`, `WEEKDAYS`, phone/pincode rules) rather\n * than restating them.\n *\n * @module gymmonk-schema/onboarding-form\n */\n\nimport { z } from 'zod';\nimport { type CenterType, centerTypeSchema } from './center.js';\nimport { ageInYears, isRealDate, PHONE_REGEX } from './common.js';\nimport type { WEEKDAYS } from './shared.js';\nimport { type Address, addressSchema } from './shared.js';\n\n// ─── Field-level rules (shared by both wizards) ──────────────────────────────\n\n/** A required 10-digit Indian mobile, as typed into a form field. */\nconst formPhone = z.string().regex(PHONE_REGEX, 'Enter a valid 10-digit mobile number');\n\n/** An optional phone: blank is allowed, anything else must be valid. */\nconst optionalFormPhone = z\n .string()\n .refine((v) => v === '' || PHONE_REGEX.test(v), 'Enter a valid 10-digit mobile number');\n\n/** An optional email: blank is allowed, anything else must look like an email. */\nconst optionalFormEmail = z\n .string()\n .refine((v) => v === '' || /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(v), {\n message: 'Enter a valid email',\n });\n\n/** A choice the user has not made yet is `null` until they pick one. */\nconst requiredChoice = <T extends z.ZodTypeAny>(inner: T, message: string) =>\n inner.nullable().refine((v: unknown) => v !== null, { message });\n\n// ─── Address (form shape: one atomic value, null until picked) ────────────\n\n/**\n * An address inside an onboarding form is ONE cell, not four.\n *\n * It used to be `street` + `city` + `state` + `pincode`, each independently\n * fillable, which is why this module needed rules for a half-filled address.\n * Now it is picked through the location drawer, which either hands back a\n * complete `Address` — area chain, ids, names, ancestry, coordinates — or\n * nothing at all. So there is no partial state left to validate, and `null`\n * simply means \"not chosen yet\".\n */\nexport const onboardingAddressSchema = requiredChoice(addressSchema, 'Add your address');\n\n/** Working shape of an address inside an onboarding form. */\nexport type OnboardingAddress = Address | null;\n\n/** An address has been picked. */\nexport function isOnboardingAddressComplete(a: OnboardingAddress): boolean {\n return a !== null;\n}\n\n// ─── Social links (form shape: every handle is a plain string) ───────────────\n\nexport const onboardingSocialLinksSchema = z.object({\n jansathi: z.string(),\n youtube: z.string(),\n instagram: z.string(),\n facebook: z.string(),\n});\nexport type OnboardingSocialLinks = z.infer<typeof onboardingSocialLinksSchema>;\n\n// ─── Opening hours (form shape) ─────────────────────────────────────────────\n\nexport const onboardingDayHoursSchema = z.object({\n open: z.boolean(),\n from: z.string(),\n to: z.string(),\n});\nexport type OnboardingDayHours = z.infer<typeof onboardingDayHoursSchema>;\nexport type OnboardingWeeklyHours = Record<(typeof WEEKDAYS)[number], OnboardingDayHours>;\n\n// ─── Member onboarding form ─────────────────────────────────────────────────\n\n/** Age bounds a gym membership realistically spans. */\nexport const MEMBER_MIN_AGE_YEARS = 10;\nexport const MEMBER_MAX_AGE_YEARS = 100;\n\n/**\n * The three-cell date-of-birth rule, in ONE place.\n *\n * Each cell is bounded by its own input, but only the assembled date can catch\n * an impossible one (31 Feb) or an implausible age. The issue is attached to\n * `dobDay` so it renders under the field group rather than under one cell.\n *\n * Exported because the member's own profile editor asks for the SAME date with\n * the same three cells. When the rule lived inline in the wizard, that editor\n * had a free-text box parsed with `new Date()` instead, which accepted \"March\n * 18, 1994\", accepted nonsense just as happily, and silently dropped whatever\n * it could not read.\n */\nexport function refineMemberDob(\n v: { dobDay: string; dobMonth: string; dobYear: string },\n ctx: z.RefinementCtx,\n): void {\n if (!v.dobDay || !v.dobMonth || v.dobYear.length !== 4) return;\n\n const day = Number(v.dobDay);\n const month = Number(v.dobMonth);\n const year = Number(v.dobYear);\n\n if (!isRealDate(year, month, day)) {\n ctx.addIssue({ code: 'custom', path: ['dobDay'], message: 'That date does not exist' });\n return;\n }\n\n const age = ageInYears(year, month, day);\n if (age < MEMBER_MIN_AGE_YEARS) {\n ctx.addIssue({\n code: 'custom',\n path: ['dobDay'],\n message: `You must be at least ${MEMBER_MIN_AGE_YEARS} to join`,\n });\n } else if (age > MEMBER_MAX_AGE_YEARS) {\n ctx.addIssue({ code: 'custom', path: ['dobDay'], message: 'Check the year of birth' });\n }\n}\n\nexport const memberOnboardingFormSchema = z\n .object({\n firstName: z.string().trim().min(1, 'First name is required'),\n lastName: z.string().trim(),\n dobDay: z.string().min(1, 'Enter the day'),\n dobMonth: z.string().min(1, 'Enter the month'),\n dobYear: z.string().length(4, 'Enter a 4-digit year'),\n gender: requiredChoice(z.enum(['male', 'female', 'other']), 'Select your gender'),\n /**\n * The five body measurements are OPTIONAL, all of them.\n *\n * They were `requiredChoice`, which put two screens of tape-measure work\n * between somebody signing up at the desk and having an account. Neither\n * number is needed to train, to be enrolled or to be charged; both are\n * things a member is more likely to fill in accurately at home than with a\n * queue behind them, and `/me/edit/body` already exists for exactly that.\n *\n * Optional does NOT mean half-filled: see the two refinements below.\n */\n heightCm: z.number().nullable(),\n weightKg: z.number().nullable(),\n neckCm: z.number().nullable(),\n waistCm: z.number().nullable(),\n hipsCm: z.number().nullable(),\n /**\n * The member's OWN number, asked on step one beside their name.\n *\n * It moved here when the emergency step was removed. Emergency contact and\n * blood group genuinely belong in the profile — nobody needs them to\n * finish signing up — but this one is how the gym reaches the member at\n * all, and it drives every chase action on the owner's roster card. Left\n * on the removed step it would simply have stopped being collected.\n */\n phone: formPhone,\n /** GCS URL of the uploaded photo. Optional — it can be added later. */\n avatarUrl: z.string(),\n })\n .superRefine(refineMemberDob)\n /**\n * Skippable, but not half-skippable: the BMI step.\n *\n * BMI needs both numbers or neither. Leaving one behind stores a lone\n * measurement that computes nothing and that nobody will ever be prompted\n * for again, so the moment either is touched the other is asked for. An\n * untouched step raises nothing at all, which is what makes Continue work on\n * an empty screen.\n *\n * Own refine, with no early-return coupling to the DOB rule above.\n */\n .superRefine((v, ctx) => {\n if (v.heightCm == null && v.weightKg == null) return;\n if (v.heightCm == null) {\n ctx.addIssue({ code: 'custom', path: ['heightCm'], message: 'Add your height' });\n }\n if (v.weightKg == null) {\n ctx.addIssue({ code: 'custom', path: ['weightKg'], message: 'Add your weight' });\n }\n })\n /**\n * The same rule for the body-fat step, plus the one measurement whose\n * absence is easy to miss.\n *\n * The U.S. Navy estimate takes neck and waist for everyone and hips as well\n * for women. A woman who fills the first two and skips the third gets no\n * number and no explanation, so hips is asked for once this step is under\n * way — and never when it has been skipped outright.\n */\n .superRefine((v, ctx) => {\n if (v.neckCm == null && v.waistCm == null && v.hipsCm == null) return;\n if (v.neckCm == null) {\n ctx.addIssue({ code: 'custom', path: ['neckCm'], message: 'Add your neck' });\n }\n if (v.waistCm == null) {\n ctx.addIssue({ code: 'custom', path: ['waistCm'], message: 'Add your waist' });\n }\n if (v.gender === 'female' && v.hipsCm == null) {\n ctx.addIssue({\n code: 'custom',\n path: ['hipsCm'],\n message: 'Hips measurement is required',\n });\n }\n });\n\n/**\n * The form's working shape. Hand-written (not `z.infer`) so unset numeric /\n * choice fields can hold `null` while the schema's `requiredChoice` rules still\n * drive the field errors — the resolver is cast to this type at `useForm`.\n */\nexport type MemberOnboardingForm = {\n firstName: string;\n lastName: string;\n dobDay: string;\n dobMonth: string;\n dobYear: string;\n gender: 'male' | 'female' | 'other' | null;\n heightCm: number | null;\n weightKg: number | null;\n neckCm: number | null;\n waistCm: number | null;\n hipsCm: number | null;\n phone: string;\n avatarUrl: string;\n};\n\nexport const EMPTY_MEMBER_ONBOARDING_FORM: MemberOnboardingForm = {\n firstName: '',\n lastName: '',\n dobDay: '',\n dobMonth: '',\n dobYear: '',\n gender: null,\n heightCm: null,\n weightKg: null,\n neckCm: null,\n waistCm: null,\n hipsCm: null,\n phone: '',\n avatarUrl: '',\n};\n\n/**\n * One onboarding step, described ONCE.\n *\n * The wizard chrome (screen name, help line, progress denominator) and the\n * validation gate used to live in the page as a local `META` array beside a\n * separate field map — two lists that had to be kept the same length by hand.\n * They are one list here, so a step cannot exist without a title or be left\n * without a validation gate.\n */\nexport interface OnboardingStepConfig<TField extends string> {\n /** Stable identifier — safe for analytics and resume, unlike the index. */\n key: string;\n /** Screen name in the step header. */\n title: string;\n /** Help line under the screen name. */\n helpText: string;\n /**\n * Fields validated before this step may be left. Naming a parent path (e.g.\n * `address`) validates its nested fields too.\n */\n fields: readonly TField[];\n}\n\nexport const MEMBER_ONBOARDING_STEPS: readonly OnboardingStepConfig<\n keyof MemberOnboardingForm & string\n>[] = [\n {\n key: 'basics',\n title: 'Basic information',\n helpText: \"Enter your details, we'll need these to set things up.\",\n fields: ['firstName', 'dobDay', 'dobMonth', 'dobYear', 'gender', 'phone'],\n },\n /**\n * Both of these were titled \"Personal details\", which is what step one is.\n * Three screens deep with the same heading on the last two, a member had no\n * way to tell where they were or what the screen was going to give them.\n * Each is named after the number it produces.\n *\n * The help line says the step can be skipped. That is the only place a\n * member is told, and without it an optional screen simply reads as a\n * required one they have not finished.\n */\n {\n key: 'body-metrics',\n title: 'Your BMI',\n helpText: 'Your height and weight, to calculate your BMI. You can skip this and add it later.',\n fields: ['heightCm', 'weightKg'],\n },\n {\n key: 'body-composition',\n title: 'Your body fat percentage',\n helpText: 'A few measurements, to estimate your body fat. You can skip this and add it later.',\n fields: ['neckCm', 'waistCm', 'hipsCm'],\n },\n] as const;\n\nexport const MEMBER_ONBOARDING_TOTAL_STEPS = MEMBER_ONBOARDING_STEPS.length;\n\n/**\n * Fields validated before advancing each member step.\n * DERIVED from `MEMBER_ONBOARDING_STEPS` so the two can never disagree.\n */\nexport const MEMBER_STEP_FIELDS: (keyof MemberOnboardingForm)[][] = MEMBER_ONBOARDING_STEPS.map(\n (s) => [...s.fields],\n);\n\n// ─── Gym registration form (\"list your gym\") ────────────────────────────────\n\n/**\n * The gym-registration wizard's working shape.\n *\n * THREE steps, not five. The two it used to open with — \"your details\" and\n * \"contact details\" — asked a signed-in member for their name, gender, date of\n * birth and phone, every one of which onboarding already captured. Registering\n * a business asks about the BUSINESS; the person is already known.\n */\n/**\n * The gym-registration form's FIELD SHAPE, exported separately from the schema.\n *\n * Separate because the schema carries a `superRefine` (see\n * `refineGymRegistrationForm`) and a refined object cannot be `.extend()`ed —\n * zod hands back a ZodEffects, and calling `.extend` on one throws at MODULE\n * LOAD, which no build catches and which takes the whole page down on import.\n * The web client adds the two pasted Maps links to this form, so it needs the\n * shape and the refinement as separate pieces it can recombine.\n */\nexport const gymRegistrationFormShape = {\n // 1 — organisation\n orgName: z.string().trim().min(1, 'Organisation name is required'),\n orgDescription: z.string(),\n hqAddress: onboardingAddressSchema,\n orgWebsite: z.string(),\n gstNumber: z.string(),\n cinNumber: z.string(),\n numberOfCenters: requiredChoice(z.number(), 'Add the number of centers'),\n orgSocials: onboardingSocialLinksSchema,\n // 2 — center\n centerName: z.string().trim().min(1, 'Center name is required'),\n centerDescription: z.string(),\n /**\n * The real enum, not `z.array(z.string())`.\n *\n * It was strings, and `toGymRegistrationBody` then CAST them to the enum on\n * the way to the API. A cast is not a check: retiring a centre type left the\n * wizard perfectly happy to submit the retired value and the server to\n * reject the whole registration, with the owner told only that something was\n * wrong. Now a value the API will not accept cannot leave the form.\n */\n centerTypes: z.array(centerTypeSchema).min(1, 'Pick at least one'),\n /**\n * \"Same as organisation location\".\n *\n * Most gyms register one branch at the address they just typed on the\n * previous screen, and asking for it twice invites a typo between two things\n * that are meant to be the same place — one of which decides whether a\n * member standing at the door is close enough to check in.\n *\n * It is the CHOICE that is stored, not a copy of the answer. The address is\n * resolved from the organisation at submit, so an owner who goes back and\n * corrects the HQ address does not leave a stale duplicate behind on the\n * centre.\n */\n centerSameAsOrg: z.boolean(),\n /**\n * Nullable here and required by `refineGymRegistrationForm` instead, because\n * \"required\" is now conditional — see that function.\n */\n centerAddress: addressSchema.nullable(),\n floorArea: z.number().nullable(),\n centerPhone1: formPhone,\n centerPhone2: optionalFormPhone,\n centerEmail: optionalFormEmail,\n hours: z.object({\n mon: onboardingDayHoursSchema,\n tue: onboardingDayHoursSchema,\n wed: onboardingDayHoursSchema,\n thu: onboardingDayHoursSchema,\n fri: onboardingDayHoursSchema,\n sat: onboardingDayHoursSchema,\n sun: onboardingDayHoursSchema,\n }),\n photos: z.array(z.string()),\n // 3 — plan\n planId: requiredChoice(z.string(), 'Choose a plan'),\n tenureId: z.string().nullable(),\n};\n\n/**\n * The centre needs an address unless it is inheriting the organisation's.\n *\n * A cross-field rule, so it cannot live on `centerAddress` itself. The issue is\n * raised AT `centerAddress` deliberately: the wizard validates each step by\n * calling `trigger` with that step's field names, and an error parked on the\n * object root would match none of them — Continue would refuse to advance with\n * nothing on screen explaining why. That exact shape of bug has cost this\n * codebase two silent forms already.\n *\n * Structurally typed rather than tied to `GymRegistrationForm` so the web\n * client can apply the same function to its own extended schema.\n */\nexport function refineGymRegistrationForm(\n v: { centerSameAsOrg: boolean; centerAddress: Address | null },\n ctx: z.RefinementCtx,\n): void {\n if (v.centerSameAsOrg) return;\n if (v.centerAddress === null) {\n ctx.addIssue({ code: 'custom', path: ['centerAddress'], message: 'Add your address' });\n }\n}\n\nexport const gymRegistrationFormSchema = z\n .object(gymRegistrationFormShape)\n .superRefine(refineGymRegistrationForm);\n\n/** Hand-written working shape (nullable \"unset\" fields); see the member form. */\nexport type GymRegistrationForm = {\n orgName: string;\n orgDescription: string;\n hqAddress: OnboardingAddress;\n orgWebsite: string;\n gstNumber: string;\n cinNumber: string;\n numberOfCenters: number | null;\n orgSocials: OnboardingSocialLinks;\n centerName: string;\n centerDescription: string;\n centerTypes: CenterType[];\n /** \"Same as organisation location\" — see `gymRegistrationFormShape`. */\n centerSameAsOrg: boolean;\n centerAddress: OnboardingAddress;\n floorArea: number | null;\n centerPhone1: string;\n centerPhone2: string;\n centerEmail: string;\n hours: OnboardingWeeklyHours;\n photos: string[];\n planId: string | null;\n tenureId: string | null;\n};\n\nconst EMPTY_ONBOARDING_SOCIALS: OnboardingSocialLinks = {\n jansathi: '',\n youtube: '',\n instagram: '',\n facebook: '',\n};\n\n/** The opening hours a new center starts with. */\nexport const DEFAULT_ONBOARDING_HOURS: OnboardingWeeklyHours = {\n mon: { open: true, from: '06:00', to: '22:00' },\n tue: { open: true, from: '06:00', to: '22:00' },\n wed: { open: true, from: '06:00', to: '22:00' },\n thu: { open: true, from: '06:00', to: '22:00' },\n fri: { open: true, from: '06:00', to: '22:00' },\n sat: { open: true, from: '07:00', to: '21:00' },\n sun: { open: false, from: '07:00', to: '13:00' },\n};\n\nexport const EMPTY_GYM_REGISTRATION_FORM: GymRegistrationForm = {\n orgName: '',\n orgDescription: '',\n hqAddress: null,\n orgWebsite: '',\n gstNumber: '',\n cinNumber: '',\n numberOfCenters: null,\n orgSocials: { ...EMPTY_ONBOARDING_SOCIALS },\n centerName: '',\n centerDescription: '',\n centerTypes: [],\n // Unticked. A pre-ticked box would put the organisation's address on the\n // centre without the owner ever looking at it — and that address is what a\n // member's check-in is measured against.\n centerSameAsOrg: false,\n centerAddress: null,\n floorArea: null,\n centerPhone1: '',\n centerPhone2: '',\n centerEmail: '',\n hours: structuredClone(DEFAULT_ONBOARDING_HOURS),\n photos: [],\n planId: null,\n tenureId: null,\n};\n\n/**\n * The gym-registration wizard, described once.\n *\n * The last step is the pricing screen, which renders its own header; its title\n * and help line are unused by the wizard chrome but kept so every step has one.\n */\nexport const GYM_REGISTRATION_STEPS: readonly OnboardingStepConfig<\n keyof GymRegistrationForm & string\n>[] = [\n {\n key: 'organisation',\n title: 'Your organisation',\n helpText: 'Tell us about the business that runs the gym.',\n fields: ['orgName', 'hqAddress', 'numberOfCenters'],\n },\n {\n key: 'center',\n title: 'Your first center',\n helpText: 'Set up your primary center. You can add more later.',\n fields: [\n 'centerName',\n 'centerTypes',\n 'centerAddress',\n 'centerPhone1',\n 'centerPhone2',\n 'centerEmail',\n ],\n },\n {\n key: 'plan',\n title: 'Choose your plan',\n helpText: 'Pick the plan that fits your gym.',\n fields: ['planId', 'tenureId'],\n },\n] as const;\n\nexport const GYM_REGISTRATION_TOTAL_STEPS = GYM_REGISTRATION_STEPS.length;\n\n/**\n * Fields validated (via `form.trigger`) before advancing each step.\n * DERIVED from `GYM_REGISTRATION_STEPS` so the two can never disagree.\n */\nexport const GYM_REGISTRATION_STEP_FIELDS: (keyof GymRegistrationForm)[][] =\n GYM_REGISTRATION_STEPS.map((s) => [...s.fields]);\n\n// ─── Retained: the owner onboarding wizard ───────────────────────────────────\n// The gym-registration flow above replaces this, but the owner console's repo\n// still ships the four-step owner wizard and cannot migrate in the same change\n// that publishes this package. Nothing above imports any of it.\n//\n// TODO(onboarding-migration): delete once the owner wizard moves to\n// `GYM_REGISTRATION_STEPS`.\n\n// ─── Owner onboarding form ──────────────────────────────────────────────────\n\n/** Age bounds for a gym owner (the DOB itself stays optional). */\nexport const OWNER_MIN_AGE_YEARS = 16;\nexport const OWNER_MAX_AGE_YEARS = 100;\n\nexport const ownerOnboardingFormSchema = z\n .object({\n // 1 — basics\n firstName: z.string().trim().min(1, 'First name is required'),\n lastName: z.string().trim(),\n dobDay: z.string(),\n dobMonth: z.string(),\n dobYear: z.string(),\n gender: requiredChoice(z.enum(['male', 'female', 'other']), 'Select your gender'),\n // 2 — contact\n phone: formPhone,\n altEmail: optionalFormEmail,\n website: z.string(),\n noWebsite: z.boolean(),\n address: onboardingAddressSchema,\n socials: onboardingSocialLinksSchema,\n // 3 — organisation\n orgName: z.string().trim().min(1, 'Organisation name is required'),\n orgDescription: z.string(),\n hqAddress: onboardingAddressSchema,\n gstNumber: z.string(),\n cinNumber: z.string(),\n numberOfCenters: requiredChoice(z.number(), 'Add the number of centers'),\n orgSocials: onboardingSocialLinksSchema,\n // 4 — center\n centerName: z.string().trim().min(1, 'Center name is required'),\n centerDescription: z.string(),\n centerTypes: z.array(z.string()).min(1, 'Pick at least one'),\n centerAddress: onboardingAddressSchema,\n floorArea: z.number().nullable(),\n centerPhone1: formPhone,\n centerPhone2: optionalFormPhone,\n centerEmail: optionalFormEmail,\n hours: z.object({\n mon: onboardingDayHoursSchema,\n tue: onboardingDayHoursSchema,\n wed: onboardingDayHoursSchema,\n thu: onboardingDayHoursSchema,\n fri: onboardingDayHoursSchema,\n sat: onboardingDayHoursSchema,\n sun: onboardingDayHoursSchema,\n }),\n photos: z.array(z.string()),\n /** GCS URL of the owner's uploaded photo. Optional. */\n avatarUrl: z.string(),\n // 5 — plan\n planId: requiredChoice(z.string(), 'Choose a plan'),\n tenureId: z.string().nullable(),\n })\n /**\n * The owner's DOB is optional, so this only engages once any cell is touched:\n * then all three are required and must form a real, plausible date.\n */\n .superRefine((v, ctx) => {\n const touched = Boolean(v.dobDay || v.dobMonth || v.dobYear);\n if (!touched) return;\n\n const fail = (message: string) => ctx.addIssue({ code: 'custom', path: ['dobDay'], message });\n\n if (!v.dobDay || !v.dobMonth || v.dobYear.length !== 4) {\n fail('Enter the full date of birth');\n return;\n }\n\n const day = Number(v.dobDay);\n const month = Number(v.dobMonth);\n const year = Number(v.dobYear);\n\n if (!isRealDate(year, month, day)) {\n fail('That date does not exist');\n return;\n }\n\n const age = ageInYears(year, month, day);\n if (age < OWNER_MIN_AGE_YEARS) fail(`You must be at least ${OWNER_MIN_AGE_YEARS}`);\n else if (age > OWNER_MAX_AGE_YEARS) fail('Check the year of birth');\n });\n\n/** Hand-written working shape (nullable \"unset\" fields); see the member form. */\nexport type OwnerOnboardingForm = {\n firstName: string;\n lastName: string;\n dobDay: string;\n dobMonth: string;\n dobYear: string;\n gender: 'male' | 'female' | 'other' | null;\n phone: string;\n altEmail: string;\n website: string;\n noWebsite: boolean;\n address: OnboardingAddress;\n socials: OnboardingSocialLinks;\n orgName: string;\n orgDescription: string;\n hqAddress: OnboardingAddress;\n gstNumber: string;\n cinNumber: string;\n numberOfCenters: number | null;\n orgSocials: OnboardingSocialLinks;\n centerName: string;\n centerDescription: string;\n centerTypes: string[];\n centerAddress: OnboardingAddress;\n floorArea: number | null;\n centerPhone1: string;\n centerPhone2: string;\n centerEmail: string;\n hours: OnboardingWeeklyHours;\n photos: string[];\n avatarUrl: string;\n planId: string | null;\n tenureId: string | null;\n};\n\nexport const EMPTY_OWNER_ONBOARDING_FORM: OwnerOnboardingForm = {\n firstName: '',\n lastName: '',\n dobDay: '',\n dobMonth: '',\n dobYear: '',\n gender: null,\n phone: '',\n altEmail: '',\n website: '',\n noWebsite: false,\n address: null,\n socials: { ...EMPTY_ONBOARDING_SOCIALS },\n orgName: '',\n orgDescription: '',\n hqAddress: null,\n gstNumber: '',\n cinNumber: '',\n numberOfCenters: null,\n orgSocials: { ...EMPTY_ONBOARDING_SOCIALS },\n centerName: '',\n centerDescription: '',\n centerTypes: [],\n centerAddress: null,\n floorArea: null,\n centerPhone1: '',\n centerPhone2: '',\n centerEmail: '',\n hours: structuredClone(DEFAULT_ONBOARDING_HOURS),\n photos: [],\n avatarUrl: '',\n planId: null,\n tenureId: null,\n};\n\n/**\n * The owner wizard, described once.\n *\n * The DOB cells belong in step 1's `fields` even though the date is optional:\n * the refine above still rejects a half-typed date, and a field missing from\n * this map would be unreachable twice over — Continue would not surface it, and\n * the final submit (which finds the offending STEP by searching these lists)\n * would find no step and leave the plan screen dead.\n *\n * The last step is the pricing screen, which renders its own header; its title\n * and help line are unused by the wizard chrome but kept so every step has one.\n */\nexport const OWNER_ONBOARDING_STEPS: readonly OnboardingStepConfig<\n keyof OwnerOnboardingForm & string\n>[] = [\n {\n key: 'basics',\n title: 'Your details',\n helpText: 'A few basics about you.',\n fields: ['firstName', 'gender', 'dobDay', 'dobMonth', 'dobYear'],\n },\n {\n key: 'contact',\n title: 'Contact details',\n helpText: 'How members and we can reach you.',\n fields: ['phone', 'altEmail', 'address'],\n },\n {\n key: 'organisation',\n title: 'Organisation',\n helpText: 'Tell us about your business.',\n fields: ['orgName', 'hqAddress', 'numberOfCenters'],\n },\n {\n key: 'center',\n title: 'Your first center',\n helpText: 'Set up your primary center. You can add more later.',\n fields: [\n 'centerName',\n 'centerTypes',\n 'centerAddress',\n 'centerPhone1',\n 'centerPhone2',\n 'centerEmail',\n ],\n },\n {\n key: 'plan',\n title: 'Choose your plan',\n helpText: 'Pick the plan that fits your gym.',\n fields: ['planId', 'tenureId'],\n },\n] as const;\n\nexport const OWNER_ONBOARDING_TOTAL_STEPS = OWNER_ONBOARDING_STEPS.length;\n\n/**\n * Fields validated (via `form.trigger`) before advancing each owner step.\n * DERIVED from `OWNER_ONBOARDING_STEPS` so the two can never disagree.\n */\nexport const OWNER_STEP_FIELDS: (keyof OwnerOnboardingForm)[][] = OWNER_ONBOARDING_STEPS.map(\n (s) => [...s.fields],\n);\n\n// ─── Gym selection (member step 0) ──────────────────────────────────────────\n\nexport const gymSelectionSchema = z.object({\n gymId: z.string().trim().min(1, 'Enter your gym id or pick a gym below'),\n});\nexport type GymSelection = z.infer<typeof gymSelectionSchema>;\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiBxB,eAAO,MAAM,oBAAoB;;;EAA8B,CAAC;AAChE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8CnC,CAAC;AACL,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,wCAAoC,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;iBAQnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,sFAAsF;AACtF,eAAO,MAAM,8BAA8B;;;;;;;;;;iBAS/B,CAAC;AACb,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAEtF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;iBAUjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAatE,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAa5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,mEAAmE;AACnE,eAAO,MAAM,6BAA6B;;;iBAGxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAKpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
1
+ {"version":3,"file":"onboarding.d.ts","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiBxB,eAAO,MAAM,oBAAoB;;;EAA8B,CAAC;AAChE,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiFnC,CAAC;AACL,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAI9E;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,wCAAoC,CAAC;AACvE,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;iBAQnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,sFAAsF;AACtF,eAAO,MAAM,8BAA8B;;;;;;;;;;iBAS/B,CAAC;AACb,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAEtF;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;iBAUjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAatE,2EAA2E;AAC3E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAa5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,mEAAmE;AACnE,eAAO,MAAM,6BAA6B;;;iBAGxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAKpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
@@ -64,17 +64,50 @@ export const memberOnboardingBodySchema = z
64
64
  /** Uploaded profile photo → stored on the shared `User.avatar`. */
65
65
  avatarUrl: optionalTrimmedString(2000),
66
66
  })
67
- .refine((v) => v.body.heightCm != null && v.body.weightKg != null, {
68
- message: 'Height and weight are required',
69
- path: ['body'],
70
- })
71
- .refine((v) => v.body.neckCm != null && v.body.waistCm != null, {
72
- message: 'Neck and waist are required',
73
- path: ['body'],
74
- })
75
- .refine((v) => v.gender !== 'female' || v.body.hipsCm != null, {
76
- message: 'Hips measurement is required',
77
- path: ['body', 'hipsCm'],
67
+ /**
68
+ * Body measurements are OPTIONAL, and this is where that had to be agreed.
69
+ *
70
+ * These were three flat `.refine`s demanding height, weight, neck and waist
71
+ * on every submit, so a member who skipped the two measurement screens got a
72
+ * 422 at Finish after the wizard had already told them the steps could be
73
+ * skipped. The form schema and this one describe the same rules over
74
+ * different shapes, and they have to say the same thing.
75
+ *
76
+ * What survives is the coherence rule, not the presence one: a body that
77
+ * arrives half-filled is rejected, because a lone height computes nothing
78
+ * and a woman's neck and waist without hips yields no Navy estimate. Absent
79
+ * is fine; incomplete is not.
80
+ *
81
+ * A strict LOOSENING of the previous contract, so the two-role flow still
82
+ * living in the owner console's repo keeps validating exactly as it did.
83
+ */
84
+ .superRefine((v, ctx) => {
85
+ const b = v.body;
86
+ if (b.heightCm != null || b.weightKg != null) {
87
+ if (b.heightCm == null || b.weightKg == null) {
88
+ ctx.addIssue({
89
+ code: 'custom',
90
+ path: ['body'],
91
+ message: 'Height and weight are needed together',
92
+ });
93
+ }
94
+ }
95
+ if (b.neckCm == null && b.waistCm == null && b.hipsCm == null)
96
+ return;
97
+ if (b.neckCm == null || b.waistCm == null) {
98
+ ctx.addIssue({
99
+ code: 'custom',
100
+ path: ['body'],
101
+ message: 'Neck and waist are needed together',
102
+ });
103
+ }
104
+ if (v.gender === 'female' && b.hipsCm == null) {
105
+ ctx.addIssue({
106
+ code: 'custom',
107
+ path: ['body', 'hipsCm'],
108
+ message: 'Hips measurement is required',
109
+ });
110
+ }
78
111
  });
79
112
  // ─── Onboarding progress & resume ─────────────────────────────────────────────
80
113
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"onboarding.js","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Onboarding submit payload\n * ===========================================\n * The end-of-flow POST body, plus the resume cursor.\n *\n * ONE FLOW, ONE ROLE. Everybody who signs up onboards as a MEMBER — there is no\n * \"are you an owner or a member?\" fork any more. Running a gym is something a\n * member goes on to do (`gym-registration.ts`), not a different kind of account,\n * which is why an owner can also be an ordinary member somewhere else and why\n * nobody has to guess their role before they have seen the product.\n *\n * Onboarding also no longer picks a gym. A new member finishes with no gym\n * attached and finds one from their Me tab, so choosing a gym is a repeatable\n * action (leave, get declined, move city) rather than a one-shot step buried in\n * a flow they can never re-enter.\n *\n * @module gymmonk-schema/onboarding\n */\n\nimport { z } from 'zod';\nimport { createCenterBodySchema } from './center.js';\nimport { centerCodeSchema } from './codes.js';\nimport {\n isoDateSchema,\n optionalEmailSchema,\n optionalTrimmedString,\n phoneSchema,\n} from './common.js';\nimport { bodyMetricsSchema, emergencyContactSchema } from './member-profile.js';\nimport { createOrganisationBodySchema } from './organisation.js';\nimport { addressSchema, bloodGroupSchema, genderSchema, socialLinksSchema } from './shared.js';\n\n// ─── Retained: role (hoisted) ────────────────────────────────────────────────\n// Declared here, not with the rest of the retained block at the foot of the\n// file, because the progress cursor below references it — and a `const` used\n// before its initialiser is a module-load ReferenceError that no build catches.\nexport const onboardingRoleSchema = z.enum(['owner', 'member']);\nexport type OnboardingRole = z.infer<typeof onboardingRoleSchema>;\n\n// ─── Member onboarding ───────────────────────────────────────────────────────\n\nexport const memberOnboardingBodySchema = z\n .object({\n /**\n * The gym code picked at the \"find your gym\" step.\n *\n * RETAINED, and optional: the one-flow refactor finishes onboarding with\n * no gym attached, but the two-role flow still in the owner console's\n * repo sends it. Optional so neither side has to change.\n */\n gymCode: centerCodeSchema.optional(),\n firstName: z.string().trim().min(1, 'First name is required').max(80),\n lastName: optionalTrimmedString(80),\n gender: genderSchema,\n dob: isoDateSchema,\n phone: phoneSchema,\n body: bodyMetricsSchema,\n /**\n * OPTIONAL, both of them, because onboarding no longer asks.\n *\n * They were collected on a final \"Emergency info\" step that stood between\n * a new member and a finished account for information nobody needs to\n * create one. The member fills them in from their own profile, where the\n * Legal & Emergency editor already writes exactly these two facts.\n *\n * Blood group being optional is the SAFETY half of this. The client used\n * to send `bloodGroup ?? 'O+'`, so a member who skipped the question was\n * recorded as O-positive — a real blood group, indistinguishable from an\n * answer, sitting on the screen a gym would read in an emergency. Absent\n * has to stay absent.\n */\n emergencyContact: emergencyContactSchema.optional(),\n bloodGroup: bloodGroupSchema.optional(),\n /** Uploaded profile photo → stored on the shared `User.avatar`. */\n avatarUrl: optionalTrimmedString(2000),\n })\n .refine((v) => v.body.heightCm != null && v.body.weightKg != null, {\n message: 'Height and weight are required',\n path: ['body'],\n })\n .refine((v) => v.body.neckCm != null && v.body.waistCm != null, {\n message: 'Neck and waist are required',\n path: ['body'],\n })\n .refine((v) => v.gender !== 'female' || v.body.hipsCm != null, {\n message: 'Hips measurement is required',\n path: ['body', 'hipsCm'],\n });\nexport type MemberOnboardingBody = z.infer<typeof memberOnboardingBodySchema>;\n\n// ─── Onboarding progress & resume ─────────────────────────────────────────────\n\n/**\n * In-progress onboarding draft: the UI-shaped, partially-filled form the client\n * persists so a returning user resumes with their typed data intact. Opaque to\n * the backend by design — the client owns the form shape, and the data is\n * re-validated strictly by the member body schema at finalize. Keys are form\n * field names; the backend caps the stored size.\n */\nexport const onboardingDraftSchema = z.record(z.string(), z.unknown());\nexport type OnboardingDraft = z.infer<typeof onboardingDraftSchema>;\n\n/**\n * The server-persisted onboarding cursor: how far the user reached (`step`, the\n * 1-indexed sub-step of the member form) and their draft. Read on app entry to\n * resume to the exact step. `welcomed` records that the one-time welcome\n * carousel has been seen.\n */\nexport const onboardingProgressSchema = z.object({\n welcomed: z.boolean(),\n step: z.number().int().min(0).max(20),\n memberDraft: onboardingDraftSchema.optional(),\n // ── Retained for the two-role flow (see the note at the foot of this file) ──\n role: onboardingRoleSchema.nullish(),\n gymCode: centerCodeSchema.optional(),\n ownerDraft: onboardingDraftSchema.optional(),\n});\nexport type OnboardingProgress = z.infer<typeof onboardingProgressSchema>;\n\n/** `PUT /onboarding/progress` body — any subset of the cursor, merged server-side. */\nexport const onboardingProgressUpdateSchema = z\n .object({\n welcomed: z.boolean(),\n step: z.number().int().min(0).max(20),\n memberDraft: onboardingDraftSchema,\n role: onboardingRoleSchema.nullable(),\n gymCode: centerCodeSchema,\n ownerDraft: onboardingDraftSchema,\n })\n .partial();\nexport type OnboardingProgressUpdate = z.infer<typeof onboardingProgressUpdateSchema>;\n\n/**\n * `GET /onboarding/status` response: completion truth (derived from the user's\n * member profile) plus the resume cursor.\n *\n * `organisationId` is here rather than a `role` field. Onboarding no longer has\n * a role to report — everyone finishes it as a member — so what the app shells\n * actually need to know is whether this person also runs a gym, which is a fact\n * about their organisation, not about their onboarding.\n */\nexport const onboardingStatusSchema = z.object({\n onboarded: z.boolean(),\n /** The gym business they own, if any. Null for a plain member. */\n organisationId: z.string().nullable(),\n welcomed: z.boolean(),\n step: z.number().int().min(0),\n memberDraft: onboardingDraftSchema.optional(),\n role: onboardingRoleSchema.nullish(),\n gymCode: z.string().optional(),\n ownerDraft: onboardingDraftSchema.optional(),\n});\nexport type OnboardingStatus = z.infer<typeof onboardingStatusSchema>;\n\n// ─── Retained: the two-role onboarding flow ──────────────────────────────────\n//\n// The one-flow refactor above removed these. They are kept because the owner\n// console's repo still implements the two-role wizard (role fork, owner basics,\n// organisation + first centre, SaaS plan) and cannot migrate in the same change\n// that publishes this package. Nothing in the one-flow path imports them, so\n// they cost its consumers nothing.\n//\n// TODO(onboarding-migration): delete this block once the owner wizard moves to\n// `gym-registration.ts`.\n\n/** The owner's personal basics captured before the organisation/center. */\nexport const ownerBasicsSchema = z.object({\n firstName: z.string().trim().min(1, 'First name is required').max(80),\n lastName: optionalTrimmedString(80),\n gender: genderSchema,\n dob: isoDateSchema.optional(),\n phone: phoneSchema,\n altEmail: optionalEmailSchema,\n website: optionalTrimmedString(200),\n socials: socialLinksSchema,\n /** The owner's personal contact address (step-2 of owner onboarding). */\n address: addressSchema.optional(),\n /** Uploaded profile photo → stored on the shared `User.avatar`. */\n avatarUrl: optionalTrimmedString(2000),\n});\nexport type OwnerBasics = z.infer<typeof ownerBasicsSchema>;\n\n/** The SaaS plan an owner selects at the final onboarding step. */\nexport const onboardingPlanSelectionSchema = z.object({\n planId: z.string().trim().min(1),\n tenureId: z.string().trim().min(1).nullable(),\n});\nexport type OnboardingPlanSelection = z.infer<typeof onboardingPlanSelectionSchema>;\n\nexport const ownerOnboardingBodySchema = z.object({\n owner: ownerBasicsSchema,\n organisation: createOrganisationBodySchema,\n center: createCenterBodySchema,\n subscription: onboardingPlanSelectionSchema,\n});\nexport type OwnerOnboardingBody = z.infer<typeof ownerOnboardingBodySchema>;\n"]}
1
+ {"version":3,"file":"onboarding.js","sourceRoot":"","sources":["../src/onboarding.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Onboarding submit payload\n * ===========================================\n * The end-of-flow POST body, plus the resume cursor.\n *\n * ONE FLOW, ONE ROLE. Everybody who signs up onboards as a MEMBER — there is no\n * \"are you an owner or a member?\" fork any more. Running a gym is something a\n * member goes on to do (`gym-registration.ts`), not a different kind of account,\n * which is why an owner can also be an ordinary member somewhere else and why\n * nobody has to guess their role before they have seen the product.\n *\n * Onboarding also no longer picks a gym. A new member finishes with no gym\n * attached and finds one from their Me tab, so choosing a gym is a repeatable\n * action (leave, get declined, move city) rather than a one-shot step buried in\n * a flow they can never re-enter.\n *\n * @module gymmonk-schema/onboarding\n */\n\nimport { z } from 'zod';\nimport { createCenterBodySchema } from './center.js';\nimport { centerCodeSchema } from './codes.js';\nimport {\n isoDateSchema,\n optionalEmailSchema,\n optionalTrimmedString,\n phoneSchema,\n} from './common.js';\nimport { bodyMetricsSchema, emergencyContactSchema } from './member-profile.js';\nimport { createOrganisationBodySchema } from './organisation.js';\nimport { addressSchema, bloodGroupSchema, genderSchema, socialLinksSchema } from './shared.js';\n\n// ─── Retained: role (hoisted) ────────────────────────────────────────────────\n// Declared here, not with the rest of the retained block at the foot of the\n// file, because the progress cursor below references it — and a `const` used\n// before its initialiser is a module-load ReferenceError that no build catches.\nexport const onboardingRoleSchema = z.enum(['owner', 'member']);\nexport type OnboardingRole = z.infer<typeof onboardingRoleSchema>;\n\n// ─── Member onboarding ───────────────────────────────────────────────────────\n\nexport const memberOnboardingBodySchema = z\n .object({\n /**\n * The gym code picked at the \"find your gym\" step.\n *\n * RETAINED, and optional: the one-flow refactor finishes onboarding with\n * no gym attached, but the two-role flow still in the owner console's\n * repo sends it. Optional so neither side has to change.\n */\n gymCode: centerCodeSchema.optional(),\n firstName: z.string().trim().min(1, 'First name is required').max(80),\n lastName: optionalTrimmedString(80),\n gender: genderSchema,\n dob: isoDateSchema,\n phone: phoneSchema,\n body: bodyMetricsSchema,\n /**\n * OPTIONAL, both of them, because onboarding no longer asks.\n *\n * They were collected on a final \"Emergency info\" step that stood between\n * a new member and a finished account for information nobody needs to\n * create one. The member fills them in from their own profile, where the\n * Legal & Emergency editor already writes exactly these two facts.\n *\n * Blood group being optional is the SAFETY half of this. The client used\n * to send `bloodGroup ?? 'O+'`, so a member who skipped the question was\n * recorded as O-positive — a real blood group, indistinguishable from an\n * answer, sitting on the screen a gym would read in an emergency. Absent\n * has to stay absent.\n */\n emergencyContact: emergencyContactSchema.optional(),\n bloodGroup: bloodGroupSchema.optional(),\n /** Uploaded profile photo → stored on the shared `User.avatar`. */\n avatarUrl: optionalTrimmedString(2000),\n })\n /**\n * Body measurements are OPTIONAL, and this is where that had to be agreed.\n *\n * These were three flat `.refine`s demanding height, weight, neck and waist\n * on every submit, so a member who skipped the two measurement screens got a\n * 422 at Finish — after the wizard had already told them the steps could be\n * skipped. The form schema and this one describe the same rules over\n * different shapes, and they have to say the same thing.\n *\n * What survives is the coherence rule, not the presence one: a body that\n * arrives half-filled is rejected, because a lone height computes nothing\n * and a woman's neck and waist without hips yields no Navy estimate. Absent\n * is fine; incomplete is not.\n *\n * A strict LOOSENING of the previous contract, so the two-role flow still\n * living in the owner console's repo keeps validating exactly as it did.\n */\n .superRefine((v, ctx) => {\n const b = v.body;\n\n if (b.heightCm != null || b.weightKg != null) {\n if (b.heightCm == null || b.weightKg == null) {\n ctx.addIssue({\n code: 'custom',\n path: ['body'],\n message: 'Height and weight are needed together',\n });\n }\n }\n\n if (b.neckCm == null && b.waistCm == null && b.hipsCm == null) return;\n\n if (b.neckCm == null || b.waistCm == null) {\n ctx.addIssue({\n code: 'custom',\n path: ['body'],\n message: 'Neck and waist are needed together',\n });\n }\n if (v.gender === 'female' && b.hipsCm == null) {\n ctx.addIssue({\n code: 'custom',\n path: ['body', 'hipsCm'],\n message: 'Hips measurement is required',\n });\n }\n });\nexport type MemberOnboardingBody = z.infer<typeof memberOnboardingBodySchema>;\n\n// ─── Onboarding progress & resume ─────────────────────────────────────────────\n\n/**\n * In-progress onboarding draft: the UI-shaped, partially-filled form the client\n * persists so a returning user resumes with their typed data intact. Opaque to\n * the backend by design — the client owns the form shape, and the data is\n * re-validated strictly by the member body schema at finalize. Keys are form\n * field names; the backend caps the stored size.\n */\nexport const onboardingDraftSchema = z.record(z.string(), z.unknown());\nexport type OnboardingDraft = z.infer<typeof onboardingDraftSchema>;\n\n/**\n * The server-persisted onboarding cursor: how far the user reached (`step`, the\n * 1-indexed sub-step of the member form) and their draft. Read on app entry to\n * resume to the exact step. `welcomed` records that the one-time welcome\n * carousel has been seen.\n */\nexport const onboardingProgressSchema = z.object({\n welcomed: z.boolean(),\n step: z.number().int().min(0).max(20),\n memberDraft: onboardingDraftSchema.optional(),\n // ── Retained for the two-role flow (see the note at the foot of this file) ──\n role: onboardingRoleSchema.nullish(),\n gymCode: centerCodeSchema.optional(),\n ownerDraft: onboardingDraftSchema.optional(),\n});\nexport type OnboardingProgress = z.infer<typeof onboardingProgressSchema>;\n\n/** `PUT /onboarding/progress` body — any subset of the cursor, merged server-side. */\nexport const onboardingProgressUpdateSchema = z\n .object({\n welcomed: z.boolean(),\n step: z.number().int().min(0).max(20),\n memberDraft: onboardingDraftSchema,\n role: onboardingRoleSchema.nullable(),\n gymCode: centerCodeSchema,\n ownerDraft: onboardingDraftSchema,\n })\n .partial();\nexport type OnboardingProgressUpdate = z.infer<typeof onboardingProgressUpdateSchema>;\n\n/**\n * `GET /onboarding/status` response: completion truth (derived from the user's\n * member profile) plus the resume cursor.\n *\n * `organisationId` is here rather than a `role` field. Onboarding no longer has\n * a role to report — everyone finishes it as a member — so what the app shells\n * actually need to know is whether this person also runs a gym, which is a fact\n * about their organisation, not about their onboarding.\n */\nexport const onboardingStatusSchema = z.object({\n onboarded: z.boolean(),\n /** The gym business they own, if any. Null for a plain member. */\n organisationId: z.string().nullable(),\n welcomed: z.boolean(),\n step: z.number().int().min(0),\n memberDraft: onboardingDraftSchema.optional(),\n role: onboardingRoleSchema.nullish(),\n gymCode: z.string().optional(),\n ownerDraft: onboardingDraftSchema.optional(),\n});\nexport type OnboardingStatus = z.infer<typeof onboardingStatusSchema>;\n\n// ─── Retained: the two-role onboarding flow ──────────────────────────────────\n//\n// The one-flow refactor above removed these. They are kept because the owner\n// console's repo still implements the two-role wizard (role fork, owner basics,\n// organisation + first centre, SaaS plan) and cannot migrate in the same change\n// that publishes this package. Nothing in the one-flow path imports them, so\n// they cost its consumers nothing.\n//\n// TODO(onboarding-migration): delete this block once the owner wizard moves to\n// `gym-registration.ts`.\n\n/** The owner's personal basics captured before the organisation/center. */\nexport const ownerBasicsSchema = z.object({\n firstName: z.string().trim().min(1, 'First name is required').max(80),\n lastName: optionalTrimmedString(80),\n gender: genderSchema,\n dob: isoDateSchema.optional(),\n phone: phoneSchema,\n altEmail: optionalEmailSchema,\n website: optionalTrimmedString(200),\n socials: socialLinksSchema,\n /** The owner's personal contact address (step-2 of owner onboarding). */\n address: addressSchema.optional(),\n /** Uploaded profile photo → stored on the shared `User.avatar`. */\n avatarUrl: optionalTrimmedString(2000),\n});\nexport type OwnerBasics = z.infer<typeof ownerBasicsSchema>;\n\n/** The SaaS plan an owner selects at the final onboarding step. */\nexport const onboardingPlanSelectionSchema = z.object({\n planId: z.string().trim().min(1),\n tenureId: z.string().trim().min(1).nullable(),\n});\nexport type OnboardingPlanSelection = z.infer<typeof onboardingPlanSelectionSchema>;\n\nexport const ownerOnboardingBodySchema = z.object({\n owner: ownerBasicsSchema,\n organisation: createOrganisationBodySchema,\n center: createCenterBodySchema,\n subscription: onboardingPlanSelectionSchema,\n});\nexport type OwnerOnboardingBody = z.infer<typeof ownerOnboardingBodySchema>;\n"]}
package/dist/session.d.ts CHANGED
@@ -177,6 +177,7 @@ export declare const attendanceEntrySchema: z.ZodObject<{
177
177
  }>>;
178
178
  checkInBy: z.ZodNullable<z.ZodString>;
179
179
  checkOutBy: z.ZodNullable<z.ZodString>;
180
+ atGym: z.ZodNullable<z.ZodBoolean>;
180
181
  }, z.core.$strip>;
181
182
  export type AttendanceEntry = z.infer<typeof attendanceEntrySchema>;
182
183
  export declare const attendanceQuerySchema: z.ZodObject<{
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,eAAO,MAAM,mBAAmB;;;;EAA0C,CAAC;AAC3E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAIhE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,sBAAsB;;;;iBAMjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,+CAA+C;AAC/C,eAAO,MAAM,cAAc,EAAE,gBAI5B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAO/F;AAID,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAY/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB;;;;;iBAOQ,CAAC;AACvC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;iBAE7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,0BAA0B;;;iBAGrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB;;;;;;iBAEpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E,eAAO,MAAM,sBAAsB;;;;EAAwC,CAAC;AAC5E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB;;;;EAAqC,CAAC;AACzE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;iBAkBhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,qBAAqB;;;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,sEAAsE;AACtE,eAAO,MAAM,wBAAwB;;;;;;;;;;;iBAOnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,eAAO,MAAM,mBAAmB;;;;EAA0C,CAAC;AAC3E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAIhE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,sBAAsB;;;;iBAMjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,+CAA+C;AAC/C,eAAO,MAAM,cAAc,EAAE,gBAI5B,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,gBAAgB,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAO/F;AAID,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAY/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB;;;;;iBAOQ,CAAC;AACvC,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;iBAE7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,eAAO,MAAM,0BAA0B;;;iBAGrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB;;;;;;iBAEpC,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAI5E,eAAO,MAAM,sBAAsB;;;;EAAwC,CAAC;AAC5E,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB;;;;EAAqC,CAAC;AACzE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;iBA0ChC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,eAAO,MAAM,qBAAqB;;;;;;iBAIhC,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE,sEAAsE;AACtE,eAAO,MAAM,wBAAwB;;;;;;;;;;;iBAOnC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
package/dist/session.js CHANGED
@@ -160,6 +160,29 @@ export const attendanceEntrySchema = z.object({
160
160
  */
161
161
  checkInBy: z.string().nullable(),
162
162
  checkOutBy: z.string().nullable(),
163
+ /**
164
+ * Was the member at the gym when they scanned? THREE answers, never two.
165
+ *
166
+ * `true` we measured, and they were within the gym's radius.
167
+ * `false` we measured, and they were not.
168
+ * `null` we could not measure — nobody pinned the gym, or the phone would
169
+ * not give a fix.
170
+ *
171
+ * Nullable is the whole point. A gym that has never been pinned and a member
172
+ * standing at the front desk must not read the same, and collapsing
173
+ * "unknown" into either answer invents a fact: into `true` it launders a
174
+ * guess into a verification, into `false` it accuses somebody over a
175
+ * measurement nobody took.
176
+ *
177
+ * ── This never refuses a check-in ───────────────────────────────────────────
178
+ * It is a REPORT, not a gate, and the ONLY thing location does in this
179
+ * product. Scanning always succeeds. A phone indoors, behind a concrete wall
180
+ * and a floor of equipment, routinely reports a fix 100-150 m out — so
181
+ * refusing on this locked members out of the gym they pay for while catching
182
+ * nobody, since spoofing a coordinate is cheaper than the walk it was meant
183
+ * to enforce. The owner sees the flag and decides.
184
+ */
185
+ atGym: z.boolean().nullable(),
163
186
  });
164
187
  export const attendanceQuerySchema = paginationSchema.extend({
165
188
  userId: objectIdSchema.optional(),
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Workout sessions, check-in & attendance\n * ========================================================\n * A member checks in to start a live session, marks exercises done, and checks\n * out. Sessions roll up into attendance records the owner can review, and can\n * also be recorded directly by an owner (manual attendance marking).\n *\n * @module gymmonk-schema/session\n */\n\nimport { z } from 'zod';\nimport { checkInCodeSchema } from './check-in.js';\nimport {\n isoDateSchema,\n isoDateTimeSchema,\n objectIdSchema,\n optionalTrimmedString,\n paginationSchema,\n} from './common.js';\nimport { checkInGeoInputSchema } from './location.js';\nimport { weekdaySchema } from './shared.js';\n\n// ─── Live session widget status ──────────────────────────────────────────────\n\nexport const sessionStatusSchema = z.enum(['idle', 'active', 'completed']);\nexport type SessionStatus = z.infer<typeof sessionStatusSchema>;\n\n// ─── The set stopwatch ───────────────────────────────────────────────────────\n\n/**\n * The member's own stopwatch, which is NOT the session clock.\n *\n * Home shows two running times and they measure different things. The session\n * clock is \"how long have you been in the gym\" — it runs from `checkInAt`,\n * cannot be paused, and is derived rather than stored. This one is \"how long\n * has this set taken\", and the member starts, pauses and resets it freely.\n *\n * Stored as ACCUMULATED + STARTED-AT rather than as a running total, because a\n * total would need the server to tick. Elapsed is\n * `accumulatedSec + (running ? now - startedAt : 0)`, so a paused stopwatch is\n * a plain number, a running one survives a refresh or a dead battery, and no\n * job has to write to the database once a second.\n *\n * `startedAt` is null exactly when `running` is false; the pair is a small\n * state machine, and the backend is the only writer.\n */\nexport const sessionStopwatchSchema = z.object({\n running: z.boolean(),\n /** Seconds banked by previous runs, excluding any run in progress. */\n accumulatedSec: z.number().int().nonnegative(),\n /** When the current run began. Null while paused. */\n startedAt: isoDateTimeSchema.nullable(),\n});\nexport type SessionStopwatch = z.infer<typeof sessionStopwatchSchema>;\n\n/** A stopwatch that has never been started. */\nexport const IDLE_STOPWATCH: SessionStopwatch = {\n running: false,\n accumulatedSec: 0,\n startedAt: null,\n};\n\n/**\n * Resolve a stopwatch to whole seconds elapsed.\n *\n * Shared rather than reimplemented on each side: the backend needs it to bank\n * time on pause, and the client needs it to render every tick. Two copies of\n * this arithmetic would drift the moment one of them forgot the running run.\n */\nexport function stopwatchElapsedSec(stopwatch: SessionStopwatch, now: Date = new Date()): number {\n if (!stopwatch.running || !stopwatch.startedAt) return stopwatch.accumulatedSec;\n const started = Date.parse(stopwatch.startedAt);\n if (Number.isNaN(started)) return stopwatch.accumulatedSec;\n // Floored at the banked value: a client clock behind the server's would\n // otherwise make the number run backwards.\n return stopwatch.accumulatedSec + Math.max(0, Math.floor((now.getTime() - started) / 1000));\n}\n\n// ─── Session entity ──────────────────────────────────────────────────────────\n\nexport const workoutSessionSchema = z.object({\n id: objectIdSchema,\n userId: objectIdSchema,\n centerId: objectIdSchema,\n status: sessionStatusSchema,\n checkInAt: isoDateTimeSchema,\n checkOutAt: isoDateTimeSchema.nullable(),\n /** The plan weekday being trained, if the member is on a plan. */\n planWeekday: weekdaySchema.nullable(),\n completedExerciseIds: z.array(objectIdSchema),\n totalExercises: z.number().int().nonnegative(),\n stopwatch: sessionStopwatchSchema,\n});\nexport type WorkoutSession = z.infer<typeof workoutSessionSchema>;\n\n// ─── Session mutations ───────────────────────────────────────────────────────\n\n/**\n * Check in by scanning the gym's QR poster.\n *\n * `code` is REQUIRED and is the whole point: the center is resolved FROM the\n * scanned code, never from a client-supplied `centerId`. An earlier version of\n * this body took an optional `centerId`, which meant any authenticated member\n * could mark themselves present from their sofa. Presence has to be proven by\n * being close enough to the poster to photograph it.\n *\n * Staff marking someone present by hand is a different, authorised path —\n * `markAttendanceBodySchema` below.\n */\nexport const checkInBodySchema = z\n .object({\n code: checkInCodeSchema,\n })\n // Where the scanning device thought it was. All three optional — see\n // `checkInGeoInputSchema`. The server measures this against the gym's pin and\n // treats a missing fix as unverifiable rather than as a failure.\n .extend(checkInGeoInputSchema.shape);\nexport type CheckInBody = z.infer<typeof checkInBodySchema>;\n\n/**\n * Check out — the same poster, scanned again. Verified against the session's\n * OWN center, so a member cannot close a session by scanning a different gym.\n */\nexport const checkOutBodySchema = z.object({\n code: checkInCodeSchema,\n});\nexport type CheckOutBody = z.infer<typeof checkOutBodySchema>;\n\nexport const markExerciseDoneBodySchema = z.object({\n exerciseId: objectIdSchema,\n done: z.boolean(),\n});\nexport type MarkExerciseDoneBody = z.infer<typeof markExerciseDoneBodySchema>;\n\n/**\n * Drive the set stopwatch.\n *\n * An ACTION, not a state patch. The client says what the member pressed and the\n * server computes the resulting `{ running, accumulatedSec, startedAt }` — which\n * keeps the banking arithmetic in one place and means a client cannot post an\n * arbitrary elapsed time to inflate a workout.\n */\nexport const stopwatchActionBodySchema = z.object({\n action: z.enum(['start', 'pause', 'reset']),\n});\nexport type StopwatchActionBody = z.infer<typeof stopwatchActionBodySchema>;\n\n// ─── Attendance ──────────────────────────────────────────────────────────────\n\nexport const attendanceStatusSchema = z.enum(['present', 'absent', 'rest']);\nexport type AttendanceStatus = z.infer<typeof attendanceStatusSchema>;\n\n/**\n * How an attendance mark came to be recorded.\n *\n * `scan` — the member scanned the gym's QR poster themselves.\n * `manual` — a person put it there: an owner, manager or front desk.\n * `auto` — nobody did. The member never scanned out and the session hit its\n * cap, so the system closed it (see `AUTO_CHECK_OUT_HOURS`).\n *\n * Recorded per SIDE, because the two halves of a visit routinely differ: the\n * common case is a member who scans in and is closed out automatically. An\n * attendance report that cannot tell a scanned departure from an assumed one is\n * reporting a guess as a measurement.\n */\nexport const attendanceSourceSchema = z.enum(['scan', 'manual', 'auto']);\nexport type AttendanceSource = z.infer<typeof attendanceSourceSchema>;\n\n/**\n * How long a session may stay open before it is closed for the member.\n *\n * Shared with the client so the member's screen can say when their session will\n * end, rather than having the number live only in the server that enforces it.\n */\nexport const AUTO_CHECK_OUT_HOURS = 3;\n\nexport const attendanceEntrySchema = z.object({\n date: isoDateSchema,\n checkInAt: isoDateTimeSchema.nullable(),\n checkOutAt: isoDateTimeSchema.nullable(),\n status: attendanceStatusSchema,\n /** How each half was recorded. Null where that half has not happened. */\n checkInSource: attendanceSourceSchema.nullable(),\n checkOutSource: attendanceSourceSchema.nullable(),\n /**\n * WHO marked it, when a person did — a name, snapshotted at the time.\n *\n * Null for `scan` and `auto`, which have no author. A snapshot rather than a\n * live lookup because this is a record of what happened: a manager who later\n * leaves, or changes their name, must not silently rewrite last month's\n * attendance sheet.\n */\n checkInBy: z.string().nullable(),\n checkOutBy: z.string().nullable(),\n});\nexport type AttendanceEntry = z.infer<typeof attendanceEntrySchema>;\n\nexport const attendanceQuerySchema = paginationSchema.extend({\n userId: objectIdSchema.optional(),\n from: isoDateSchema.optional(),\n to: isoDateSchema.optional(),\n});\nexport type AttendanceQuery = z.infer<typeof attendanceQuerySchema>;\n\n/** Owner marking attendance for a member or staff member manually. */\nexport const markAttendanceBodySchema = z.object({\n userId: objectIdSchema,\n date: isoDateSchema.optional(),\n status: attendanceStatusSchema.default('present'),\n checkInAt: isoDateTimeSchema.optional(),\n checkOutAt: isoDateTimeSchema.optional(),\n note: optionalTrimmedString(160),\n});\nexport type MarkAttendanceBody = z.infer<typeof markAttendanceBodySchema>;\n"]}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Workout sessions, check-in & attendance\n * ========================================================\n * A member checks in to start a live session, marks exercises done, and checks\n * out. Sessions roll up into attendance records the owner can review, and can\n * also be recorded directly by an owner (manual attendance marking).\n *\n * @module gymmonk-schema/session\n */\n\nimport { z } from 'zod';\nimport { checkInCodeSchema } from './check-in.js';\nimport {\n isoDateSchema,\n isoDateTimeSchema,\n objectIdSchema,\n optionalTrimmedString,\n paginationSchema,\n} from './common.js';\nimport { checkInGeoInputSchema } from './location.js';\nimport { weekdaySchema } from './shared.js';\n\n// ─── Live session widget status ──────────────────────────────────────────────\n\nexport const sessionStatusSchema = z.enum(['idle', 'active', 'completed']);\nexport type SessionStatus = z.infer<typeof sessionStatusSchema>;\n\n// ─── The set stopwatch ───────────────────────────────────────────────────────\n\n/**\n * The member's own stopwatch, which is NOT the session clock.\n *\n * Home shows two running times and they measure different things. The session\n * clock is \"how long have you been in the gym\" — it runs from `checkInAt`,\n * cannot be paused, and is derived rather than stored. This one is \"how long\n * has this set taken\", and the member starts, pauses and resets it freely.\n *\n * Stored as ACCUMULATED + STARTED-AT rather than as a running total, because a\n * total would need the server to tick. Elapsed is\n * `accumulatedSec + (running ? now - startedAt : 0)`, so a paused stopwatch is\n * a plain number, a running one survives a refresh or a dead battery, and no\n * job has to write to the database once a second.\n *\n * `startedAt` is null exactly when `running` is false; the pair is a small\n * state machine, and the backend is the only writer.\n */\nexport const sessionStopwatchSchema = z.object({\n running: z.boolean(),\n /** Seconds banked by previous runs, excluding any run in progress. */\n accumulatedSec: z.number().int().nonnegative(),\n /** When the current run began. Null while paused. */\n startedAt: isoDateTimeSchema.nullable(),\n});\nexport type SessionStopwatch = z.infer<typeof sessionStopwatchSchema>;\n\n/** A stopwatch that has never been started. */\nexport const IDLE_STOPWATCH: SessionStopwatch = {\n running: false,\n accumulatedSec: 0,\n startedAt: null,\n};\n\n/**\n * Resolve a stopwatch to whole seconds elapsed.\n *\n * Shared rather than reimplemented on each side: the backend needs it to bank\n * time on pause, and the client needs it to render every tick. Two copies of\n * this arithmetic would drift the moment one of them forgot the running run.\n */\nexport function stopwatchElapsedSec(stopwatch: SessionStopwatch, now: Date = new Date()): number {\n if (!stopwatch.running || !stopwatch.startedAt) return stopwatch.accumulatedSec;\n const started = Date.parse(stopwatch.startedAt);\n if (Number.isNaN(started)) return stopwatch.accumulatedSec;\n // Floored at the banked value: a client clock behind the server's would\n // otherwise make the number run backwards.\n return stopwatch.accumulatedSec + Math.max(0, Math.floor((now.getTime() - started) / 1000));\n}\n\n// ─── Session entity ──────────────────────────────────────────────────────────\n\nexport const workoutSessionSchema = z.object({\n id: objectIdSchema,\n userId: objectIdSchema,\n centerId: objectIdSchema,\n status: sessionStatusSchema,\n checkInAt: isoDateTimeSchema,\n checkOutAt: isoDateTimeSchema.nullable(),\n /** The plan weekday being trained, if the member is on a plan. */\n planWeekday: weekdaySchema.nullable(),\n completedExerciseIds: z.array(objectIdSchema),\n totalExercises: z.number().int().nonnegative(),\n stopwatch: sessionStopwatchSchema,\n});\nexport type WorkoutSession = z.infer<typeof workoutSessionSchema>;\n\n// ─── Session mutations ───────────────────────────────────────────────────────\n\n/**\n * Check in by scanning the gym's QR poster.\n *\n * `code` is REQUIRED and is the whole point: the center is resolved FROM the\n * scanned code, never from a client-supplied `centerId`. An earlier version of\n * this body took an optional `centerId`, which meant any authenticated member\n * could mark themselves present from their sofa. Presence has to be proven by\n * being close enough to the poster to photograph it.\n *\n * Staff marking someone present by hand is a different, authorised path —\n * `markAttendanceBodySchema` below.\n */\nexport const checkInBodySchema = z\n .object({\n code: checkInCodeSchema,\n })\n // Where the scanning device thought it was. All three optional — see\n // `checkInGeoInputSchema`. The server measures this against the gym's pin and\n // treats a missing fix as unverifiable rather than as a failure.\n .extend(checkInGeoInputSchema.shape);\nexport type CheckInBody = z.infer<typeof checkInBodySchema>;\n\n/**\n * Check out — the same poster, scanned again. Verified against the session's\n * OWN center, so a member cannot close a session by scanning a different gym.\n */\nexport const checkOutBodySchema = z.object({\n code: checkInCodeSchema,\n});\nexport type CheckOutBody = z.infer<typeof checkOutBodySchema>;\n\nexport const markExerciseDoneBodySchema = z.object({\n exerciseId: objectIdSchema,\n done: z.boolean(),\n});\nexport type MarkExerciseDoneBody = z.infer<typeof markExerciseDoneBodySchema>;\n\n/**\n * Drive the set stopwatch.\n *\n * An ACTION, not a state patch. The client says what the member pressed and the\n * server computes the resulting `{ running, accumulatedSec, startedAt }` — which\n * keeps the banking arithmetic in one place and means a client cannot post an\n * arbitrary elapsed time to inflate a workout.\n */\nexport const stopwatchActionBodySchema = z.object({\n action: z.enum(['start', 'pause', 'reset']),\n});\nexport type StopwatchActionBody = z.infer<typeof stopwatchActionBodySchema>;\n\n// ─── Attendance ──────────────────────────────────────────────────────────────\n\nexport const attendanceStatusSchema = z.enum(['present', 'absent', 'rest']);\nexport type AttendanceStatus = z.infer<typeof attendanceStatusSchema>;\n\n/**\n * How an attendance mark came to be recorded.\n *\n * `scan` — the member scanned the gym's QR poster themselves.\n * `manual` — a person put it there: an owner, manager or front desk.\n * `auto` — nobody did. The member never scanned out and the session hit its\n * cap, so the system closed it (see `AUTO_CHECK_OUT_HOURS`).\n *\n * Recorded per SIDE, because the two halves of a visit routinely differ: the\n * common case is a member who scans in and is closed out automatically. An\n * attendance report that cannot tell a scanned departure from an assumed one is\n * reporting a guess as a measurement.\n */\nexport const attendanceSourceSchema = z.enum(['scan', 'manual', 'auto']);\nexport type AttendanceSource = z.infer<typeof attendanceSourceSchema>;\n\n/**\n * How long a session may stay open before it is closed for the member.\n *\n * Shared with the client so the member's screen can say when their session will\n * end, rather than having the number live only in the server that enforces it.\n */\nexport const AUTO_CHECK_OUT_HOURS = 3;\n\nexport const attendanceEntrySchema = z.object({\n date: isoDateSchema,\n checkInAt: isoDateTimeSchema.nullable(),\n checkOutAt: isoDateTimeSchema.nullable(),\n status: attendanceStatusSchema,\n /** How each half was recorded. Null where that half has not happened. */\n checkInSource: attendanceSourceSchema.nullable(),\n checkOutSource: attendanceSourceSchema.nullable(),\n /**\n * WHO marked it, when a person did — a name, snapshotted at the time.\n *\n * Null for `scan` and `auto`, which have no author. A snapshot rather than a\n * live lookup because this is a record of what happened: a manager who later\n * leaves, or changes their name, must not silently rewrite last month's\n * attendance sheet.\n */\n checkInBy: z.string().nullable(),\n checkOutBy: z.string().nullable(),\n\n /**\n * Was the member at the gym when they scanned? THREE answers, never two.\n *\n * `true` we measured, and they were within the gym's radius.\n * `false` we measured, and they were not.\n * `null` we could not measure — nobody pinned the gym, or the phone would\n * not give a fix.\n *\n * Nullable is the whole point. A gym that has never been pinned and a member\n * standing at the front desk must not read the same, and collapsing\n * \"unknown\" into either answer invents a fact: into `true` it launders a\n * guess into a verification, into `false` it accuses somebody over a\n * measurement nobody took.\n *\n * ── This never refuses a check-in ───────────────────────────────────────────\n * It is a REPORT, not a gate, and the ONLY thing location does in this\n * product. Scanning always succeeds. A phone indoors, behind a concrete wall\n * and a floor of equipment, routinely reports a fix 100-150 m out — so\n * refusing on this locked members out of the gym they pay for while catching\n * nobody, since spoofing a coordinate is cheaper than the walk it was meant\n * to enforce. The owner sees the flag and decides.\n */\n atGym: z.boolean().nullable(),\n});\nexport type AttendanceEntry = z.infer<typeof attendanceEntrySchema>;\n\nexport const attendanceQuerySchema = paginationSchema.extend({\n userId: objectIdSchema.optional(),\n from: isoDateSchema.optional(),\n to: isoDateSchema.optional(),\n});\nexport type AttendanceQuery = z.infer<typeof attendanceQuerySchema>;\n\n/** Owner marking attendance for a member or staff member manually. */\nexport const markAttendanceBodySchema = z.object({\n userId: objectIdSchema,\n date: isoDateSchema.optional(),\n status: attendanceStatusSchema.default('present'),\n checkInAt: isoDateTimeSchema.optional(),\n checkOutAt: isoDateTimeSchema.optional(),\n note: optionalTrimmedString(160),\n});\nexport type MarkAttendanceBody = z.infer<typeof markAttendanceBodySchema>;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gymmonk-schema",
3
- "version": "0.47.0",
3
+ "version": "0.49.0",
4
4
  "description": "Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS) — single source of truth (SSOT) consumed by gymmonk-backend and gymmonk-web-client.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",