gymmonk-schema 0.48.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.
- package/dist/onboarding-form.d.ts +4 -4
- package/dist/onboarding-form.d.ts.map +1 -1
- package/dist/onboarding-form.js +65 -11
- package/dist/onboarding-form.js.map +1 -1
- package/dist/onboarding.d.ts.map +1 -1
- package/dist/onboarding.js +44 -11
- package/dist/onboarding.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
175
|
-
weightKg: z.ZodNullable<z.ZodNumber
|
|
176
|
-
neckCm: z.ZodNullable<z.ZodNumber
|
|
177
|
-
waistCm: z.ZodNullable<z.ZodNumber
|
|
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":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,aAAa,CAAC;AAEhE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,KAAK,OAAO,EAAiB,MAAM,aAAa,CAAC;AAyB1D;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAAoD,CAAC;AAEzF,6DAA6D;AAC7D,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,IAAI,CAAC;AAE/C,kCAAkC;AAClC,wBAAgB,2BAA2B,CAAC,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAEzE;AAID,eAAO,MAAM,2BAA2B;;;;;iBAKtC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAIhF,eAAO,MAAM,wBAAwB;;;;iBAInC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC1E,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC;AAI1F,uDAAuD;AACvD,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,oBAAoB,MAAM,CAAC;AAExC;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EACxD,GAAG,EAAE,CAAC,CAAC,aAAa,GACnB,IAAI,CAsBN;AAED,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"onboarding-form.d.ts","sourceRoot":"","sources":["../src/onboarding-form.ts"],"names":[],"mappings":"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"}
|
package/dist/onboarding-form.js
CHANGED
|
@@ -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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
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: '
|
|
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: '
|
|
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"]}
|
package/dist/onboarding.d.ts.map
CHANGED
|
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
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"}
|
package/dist/onboarding.js
CHANGED
|
@@ -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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
/**
|
package/dist/onboarding.js.map
CHANGED
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gymmonk-schema",
|
|
3
|
-
"version": "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",
|