gymmonk-schema 0.9.0 → 0.11.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/member.d.ts +8 -0
- package/dist/member.d.ts.map +1 -1
- package/dist/member.js +35 -0
- package/dist/member.js.map +1 -1
- package/dist/membership-plan.d.ts +31 -0
- package/dist/membership-plan.d.ts.map +1 -1
- package/dist/membership-plan.js +32 -1
- package/dist/membership-plan.js.map +1 -1
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js.map +1 -1
- package/package.json +1 -1
- package/README.md +0 -43
package/dist/member.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export declare const memberRosterItemSchema: z.ZodObject<{
|
|
|
27
27
|
name: z.ZodString;
|
|
28
28
|
avatarUrl: z.ZodNullable<z.ZodString>;
|
|
29
29
|
verified: z.ZodBoolean;
|
|
30
|
+
phone: z.ZodNullable<z.ZodString>;
|
|
31
|
+
email: z.ZodNullable<z.ZodEmail>;
|
|
30
32
|
planName: z.ZodNullable<z.ZodString>;
|
|
31
33
|
tier: z.ZodNullable<z.ZodEnum<{
|
|
32
34
|
free: "free";
|
|
@@ -50,7 +52,13 @@ export declare const memberRosterItemSchema: z.ZodObject<{
|
|
|
50
52
|
new: "new";
|
|
51
53
|
"at-risk": "at-risk";
|
|
52
54
|
}>>;
|
|
55
|
+
joinedAt: z.ZodISODateTime;
|
|
56
|
+
visits14d: z.ZodNumber;
|
|
57
|
+
trainerId: z.ZodNullable<z.ZodString>;
|
|
58
|
+
trainerName: z.ZodNullable<z.ZodString>;
|
|
53
59
|
}, z.core.$strip>;
|
|
60
|
+
/** Trailing window the roster's attendance filters and `visits14d` are measured over. */
|
|
61
|
+
export declare const ATTENDANCE_WINDOW_DAYS = 14;
|
|
54
62
|
export type MemberRosterItem = z.infer<typeof memberRosterItemSchema>;
|
|
55
63
|
/** Header counts for the roster. */
|
|
56
64
|
export declare const memberListSummarySchema: z.ZodObject<{
|
package/dist/member.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"member.d.ts","sourceRoot":"","sources":["../src/member.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqBxB,kDAAkD;AAClD,eAAO,MAAM,mBAAmB;;;;EAAyC,CAAC;AAC1E,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAIhE,eAAO,MAAM,sBAAsB
|
|
1
|
+
{"version":3,"file":"member.d.ts","sourceRoot":"","sources":["../src/member.ts"],"names":[],"mappings":"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"}
|
package/dist/member.js
CHANGED
|
@@ -28,6 +28,21 @@ export const memberRosterItemSchema = z.object({
|
|
|
28
28
|
name: z.string(),
|
|
29
29
|
avatarUrl: z.string().nullable(),
|
|
30
30
|
verified: z.boolean(),
|
|
31
|
+
/**
|
|
32
|
+
* Contact details, on the ROSTER row rather than only the detail record.
|
|
33
|
+
*
|
|
34
|
+
* The card's whole job when someone is overdue is to let the owner chase it
|
|
35
|
+
* without leaving the list — call, WhatsApp, email. Those are `tel:` /
|
|
36
|
+
* `wa.me` / `mailto:` links, so the row cannot build them without the raw
|
|
37
|
+
* values, and fetching the full member just to dial them would be one
|
|
38
|
+
* request per card.
|
|
39
|
+
*
|
|
40
|
+
* Nullable because an invited record may have only one of the two: the gym
|
|
41
|
+
* creates it with whatever it has, and the member fills in the rest on claim.
|
|
42
|
+
* Phone is RAW 10 digits — `+91` is display-only, never stored.
|
|
43
|
+
*/
|
|
44
|
+
phone: z.string().nullable(),
|
|
45
|
+
email: z.email().nullable(),
|
|
31
46
|
planName: z.string().nullable(),
|
|
32
47
|
tier: planTierSchema.nullable(),
|
|
33
48
|
status: membershipStatusSchema.nullable(),
|
|
@@ -36,7 +51,27 @@ export const memberRosterItemSchema = z.object({
|
|
|
36
51
|
balanceDueInr: priceInrSchema,
|
|
37
52
|
overdueDays: z.number().int().nullable(),
|
|
38
53
|
segments: z.array(memberSegmentSchema),
|
|
54
|
+
/**
|
|
55
|
+
* When the gym record was created — NOT when the person claimed it. The
|
|
56
|
+
* roster's "Joined Date" filter groups by how long someone has been a member
|
|
57
|
+
* of the gym, which starts at the desk, not at the login.
|
|
58
|
+
*/
|
|
59
|
+
joinedAt: isoDateTimeSchema,
|
|
60
|
+
/**
|
|
61
|
+
* Check-ins in the trailing 14 days, used by the roster's attendance filters
|
|
62
|
+
* to separate a no-show from an irregular from a regular.
|
|
63
|
+
*
|
|
64
|
+
* Counted rather than derived from `lastVisitAt`, because the two answer
|
|
65
|
+
* different questions: someone who came once yesterday and someone who comes
|
|
66
|
+
* daily share a last visit but are not the same member to chase.
|
|
67
|
+
*/
|
|
68
|
+
visits14d: z.number().int().nonnegative(),
|
|
69
|
+
/** Assigned trainer, so the roster can be filtered down to one trainer's people. */
|
|
70
|
+
trainerId: objectIdSchema.nullable(),
|
|
71
|
+
trainerName: z.string().nullable(),
|
|
39
72
|
});
|
|
73
|
+
/** Trailing window the roster's attendance filters and `visits14d` are measured over. */
|
|
74
|
+
export const ATTENDANCE_WINDOW_DAYS = 14;
|
|
40
75
|
/** Header counts for the roster. */
|
|
41
76
|
export const memberListSummarySchema = z.object({
|
|
42
77
|
total: z.number().int().nonnegative(),
|
package/dist/member.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"member.js","sourceRoot":"","sources":["../src/member.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Member management (owner console)\n * ==================================================\n * Owner-facing roster + detail projections (User × MemberProfile × Membership)\n * and the \"add member\" provisioning payload. Adding a member provisions a real\n * central-auth account, so the result carries the login email + temp password.\n *\n * @module gymmonk-schema/member\n */\n\nimport { z } from 'zod';\nimport {\n isoDateSchema,\n isoDateTimeSchema,\n objectIdSchema,\n optionalTrimmedString,\n paginationSchema,\n phoneSchema,\n priceInrSchema,\n} from './common.js';\nimport { emergencyContactSchema, memberRecordStatusSchema } from './member-profile.js';\nimport {\n assignMembershipBodySchema,\n membershipSchema,\n membershipStatusSchema,\n} from './membership.js';\nimport { planTierSchema } from './membership-plan.js';\nimport { addressSchema, bloodGroupSchema, genderSchema } from './shared.js';\n\n// ─── Segments ────────────────────────────────────────────────────────────────\n\n/** Attention buckets surfaced on roster cards. */\nexport const memberSegmentSchema = z.enum(['expiring', 'new', 'at-risk']);\nexport type MemberSegment = z.infer<typeof memberSegmentSchema>;\n\n// ─── Roster item (list projection) ───────────────────────────────────────────\n\nexport const memberRosterItemSchema = z.object({\n /** The gym record id — always present, even before the member claims. */\n memberProfileId: objectIdSchema,\n /** Null until claimed; the roster shows an \"Invited\" pill in that state. */\n userId: objectIdSchema.nullable(),\n recordStatus: memberRecordStatusSchema,\n membershipId: objectIdSchema.nullable(),\n memberCode: z.string(),\n name: z.string(),\n avatarUrl: z.string().nullable(),\n verified: z.boolean(),\n planName: z.string().nullable(),\n tier: planTierSchema.nullable(),\n status: membershipStatusSchema.nullable(),\n endDate: isoDateSchema.nullable(),\n lastVisitAt: isoDateTimeSchema.nullable(),\n balanceDueInr: priceInrSchema,\n overdueDays: z.number().int().nullable(),\n segments: z.array(memberSegmentSchema),\n});\nexport type MemberRosterItem = z.infer<typeof memberRosterItemSchema>;\n\n/** Header counts for the roster. */\nexport const memberListSummarySchema = z.object({\n total: z.number().int().nonnegative(),\n active: z.number().int().nonnegative(),\n expiring: z.number().int().nonnegative(),\n inactive: z.number().int().nonnegative(),\n});\nexport type MemberListSummary = z.infer<typeof memberListSummarySchema>;\n\n// ─── List query ──────────────────────────────────────────────────────────────\n\nexport const memberFilterSchema = z.enum(['all', 'active', 'inactive', 'verified']);\nexport type MemberFilter = z.infer<typeof memberFilterSchema>;\n\nexport const memberSortSchema = z.enum(['recent', 'az', 'expiry', 'dues']);\nexport type MemberSort = z.infer<typeof memberSortSchema>;\n\nexport const memberListQuerySchema = paginationSchema.extend({\n centerId: objectIdSchema.optional(),\n filter: memberFilterSchema.default('all'),\n sort: memberSortSchema.default('recent'),\n tier: planTierSchema.optional(),\n q: optionalTrimmedString(120),\n});\nexport type MemberListQuery = z.infer<typeof memberListQuerySchema>;\n\n// ─── Detail projection ───────────────────────────────────────────────────────\n\n/** The identity slice a projection pulls from the auth `User`. */\nexport const memberUserRefSchema = z.object({\n /** Null while the record is still `invited` (no central-auth account linked). */\n id: objectIdSchema.nullable(),\n name: z.string(),\n email: z.string(),\n phone: z.string().nullable(),\n avatarUrl: z.string().nullable(),\n verified: z.boolean(),\n});\nexport type MemberUserRef = z.infer<typeof memberUserRefSchema>;\n\nexport const memberDetailSchema = z.object({\n memberProfileId: objectIdSchema,\n recordStatus: memberRecordStatusSchema,\n /**\n * Identity from central auth once claimed. Before that the gym still knows the\n * person (name / contact captured at the desk), so this carries the invited\n * details with `id: null`.\n */\n user: memberUserRefSchema,\n memberCode: z.string(),\n gender: genderSchema.nullable(),\n dob: isoDateSchema.nullable(),\n bloodGroup: bloodGroupSchema.nullable(),\n address: addressSchema.nullable(),\n emergencyContacts: z.array(emergencyContactSchema),\n joinedAt: isoDateTimeSchema,\n lastVisitAt: isoDateTimeSchema.nullable(),\n assignedWorkoutPlanId: objectIdSchema.nullable(),\n assignedTrainerId: objectIdSchema.nullable(),\n membershipId: objectIdSchema.nullable(),\n});\nexport type MemberDetail = z.infer<typeof memberDetailSchema>;\n\n// ─── Add member (provisioning) ───────────────────────────────────────────────\n\nexport const createMemberBodySchema = z.object({\n firstName: z.string().trim().min(1, 'First name is required').max(80),\n lastName: optionalTrimmedString(80),\n email: z.email('A valid email is required'),\n phone: phoneSchema,\n gender: genderSchema,\n dob: isoDateSchema.optional(),\n bloodGroup: bloodGroupSchema.optional(),\n address: addressSchema.optional(),\n emergencyContacts: z.array(emergencyContactSchema).max(5).default([]),\n idProofUrl: optionalTrimmedString(2000),\n avatarUrl: optionalTrimmedString(2000),\n assignedTrainerId: objectIdSchema.optional(),\n /** Optionally assign a plan in the same step. */\n initialPlan: assignMembershipBodySchema.optional(),\n});\nexport type CreateMemberBody = z.infer<typeof createMemberBodySchema>;\n\n// ─── Invite & claim ──────────────────────────────────────────────────────────\n\n/**\n * The invite a gym hands to a new member.\n *\n * GymMonk never mints credentials for anyone: central auth is the SSOT for\n * identity, and an account there is shared across products. So adding a member\n * creates a gym record only, plus an unguessable invite code. The member\n * registers/logs in themselves through central auth (exactly as in jansathi) and\n * then CLAIMS the record. The owner shares `url` over WhatsApp or shows the QR.\n */\nexport const memberInviteSchema = z.object({\n /** Single-use, unguessable. Regenerating an invite invalidates the old one. */\n code: z.string(),\n /** Absolute claim URL the owner shares (also encoded into the QR). */\n url: z.string(),\n /** The address the member MUST register with for the claim to succeed. */\n email: z.string(),\n /** WhatsApp number to share on, when the gym recorded one. */\n phone: z.string().nullable(),\n expiresAt: isoDateTimeSchema,\n});\nexport type MemberInvite = z.infer<typeof memberInviteSchema>;\n\n/** Result of adding a member: the gym record + the invite to hand over. */\nexport const provisionResultSchema = z.object({\n /** The new member record (NOT a central-auth user — none is created). */\n memberProfileId: objectIdSchema,\n code: z.string(),\n invite: memberInviteSchema,\n /**\n * The membership created from `initialPlan`, or `null`.\n *\n * Null means one of two DIFFERENT things, and the caller can tell them apart\n * because it knows whether it sent `initialPlan`: no plan was chosen, or the\n * chosen plan could not be assigned. Adding a member never fails over the\n * second case — the person is on the roster either way — so this is how the\n * owner finds out the plan still needs assigning rather than assuming it took.\n */\n membership: membershipSchema.nullable(),\n});\nexport type ProvisionResult = z.infer<typeof provisionResultSchema>;\n\n/**\n * What the claim screen shows BEFORE the member authenticates, so they know\n * which address to register with. Public (the code is the bearer secret), and\n * deliberately minimal — gym name, member code, and the expected email.\n */\nexport const invitePreviewSchema = z.object({\n gymName: z.string(),\n memberCode: z.string(),\n /** Shown verbatim: the member must know exactly which address to use. */\n email: z.string(),\n expiresAt: isoDateTimeSchema,\n /** True once claimed / expired, so the screen can explain why it failed. */\n claimed: z.boolean(),\n expired: z.boolean(),\n});\nexport type InvitePreview = z.infer<typeof invitePreviewSchema>;\n\n/**\n * Claim an invite as the authenticated caller. The server links the record only\n * when the code is valid AND the caller's central-auth email matches the address\n * the invite was issued to.\n */\nexport const claimInviteBodySchema = z.object({\n code: z.string().trim().min(1),\n});\nexport type ClaimInviteBody = z.infer<typeof claimInviteBodySchema>;\n\n// ─── Mutations ───────────────────────────────────────────────────────────────\n\nexport const setMemberStatusBodySchema = z.object({ active: z.boolean() });\nexport type SetMemberStatusBody = z.infer<typeof setMemberStatusBodySchema>;\n\nexport const assignTrainerBodySchema = z.object({\n trainerId: objectIdSchema.nullable(),\n});\nexport type AssignTrainerBody = z.infer<typeof assignTrainerBodySchema>;\n\nexport const assignWorkoutPlanBodySchema = z.object({\n planId: objectIdSchema.nullable(),\n});\nexport type AssignWorkoutPlanBody = z.infer<typeof assignWorkoutPlanBodySchema>;\n\n/** Remove a member from the gym — echoes the member code to confirm. */\nexport const deleteMemberBodySchema = z.object({\n confirmationCode: z.string().trim().min(1),\n});\nexport type DeleteMemberBody = z.infer<typeof deleteMemberBodySchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"member.js","sourceRoot":"","sources":["../src/member.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,WAAW,EACX,cAAc,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5E,gFAAgF;AAEhF,kDAAkD;AAClD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;AAG1E,gFAAgF;AAEhF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,yEAAyE;IACzE,eAAe,EAAE,cAAc;IAC/B,4EAA4E;IAC5E,MAAM,EAAE,cAAc,CAAC,QAAQ,EAAE;IACjC,YAAY,EAAE,wBAAwB;IACtC,YAAY,EAAE,cAAc,CAAC,QAAQ,EAAE;IACvC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB;;;;;;;;;;;;OAYG;IACH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;IAC3B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,IAAI,EAAE,cAAc,CAAC,QAAQ,EAAE;IAC/B,MAAM,EAAE,sBAAsB,CAAC,QAAQ,EAAE;IACzC,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE;IACjC,WAAW,EAAE,iBAAiB,CAAC,QAAQ,EAAE;IACzC,aAAa,EAAE,cAAc;IAC7B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACxC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC;IAEtC;;;;OAIG;IACH,QAAQ,EAAE,iBAAiB;IAE3B;;;;;;;OAOG;IACH,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAEzC,oFAAoF;IACpF,SAAS,EAAE,cAAc,CAAC,QAAQ,EAAE;IACpC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACnC,CAAC,CAAC;AAEH,yFAAyF;AACzF,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAGzC,oCAAoC;AACpC,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACrC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACtC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACxC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;CACzC,CAAC,CAAC;AAGH,gFAAgF;AAEhF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;AAGpF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAG3E,MAAM,CAAC,MAAM,qBAAqB,GAAG,gBAAgB,CAAC,MAAM,CAAC;IAC3D,QAAQ,EAAE,cAAc,CAAC,QAAQ,EAAE;IACnC,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC;IACzC,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC,QAAQ,CAAC;IACxC,IAAI,EAAE,cAAc,CAAC,QAAQ,EAAE;IAC/B,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC;CAC9B,CAAC,CAAC;AAGH,gFAAgF;AAEhF,kEAAkE;AAClE,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,iFAAiF;IACjF,EAAE,EAAE,cAAc,CAAC,QAAQ,EAAE;IAC7B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;CACtB,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,eAAe,EAAE,cAAc;IAC/B,YAAY,EAAE,wBAAwB;IACtC;;;;OAIG;IACH,IAAI,EAAE,mBAAmB;IACzB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,MAAM,EAAE,YAAY,CAAC,QAAQ,EAAE;IAC/B,GAAG,EAAE,aAAa,CAAC,QAAQ,EAAE;IAC7B,UAAU,EAAE,gBAAgB,CAAC,QAAQ,EAAE;IACvC,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE;IACjC,iBAAiB,EAAE,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC;IAClD,QAAQ,EAAE,iBAAiB;IAC3B,WAAW,EAAE,iBAAiB,CAAC,QAAQ,EAAE;IACzC,qBAAqB,EAAE,cAAc,CAAC,QAAQ,EAAE;IAChD,iBAAiB,EAAE,cAAc,CAAC,QAAQ,EAAE;IAC5C,YAAY,EAAE,cAAc,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAGH,gFAAgF;AAEhF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,wBAAwB,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;IACrE,QAAQ,EAAE,qBAAqB,CAAC,EAAE,CAAC;IACnC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,2BAA2B,CAAC;IAC3C,KAAK,EAAE,WAAW;IAClB,MAAM,EAAE,YAAY;IACpB,GAAG,EAAE,aAAa,CAAC,QAAQ,EAAE;IAC7B,UAAU,EAAE,gBAAgB,CAAC,QAAQ,EAAE;IACvC,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE;IACjC,iBAAiB,EAAE,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrE,UAAU,EAAE,qBAAqB,CAAC,IAAI,CAAC;IACvC,SAAS,EAAE,qBAAqB,CAAC,IAAI,CAAC;IACtC,iBAAiB,EAAE,cAAc,CAAC,QAAQ,EAAE;IAC5C,iDAAiD;IACjD,WAAW,EAAE,0BAA0B,CAAC,QAAQ,EAAE;CACnD,CAAC,CAAC;AAGH,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,+EAA+E;IAC/E,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,sEAAsE;IACtE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,0EAA0E;IAC1E,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,8DAA8D;IAC9D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,SAAS,EAAE,iBAAiB;CAC7B,CAAC,CAAC;AAGH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,yEAAyE;IACzE,eAAe,EAAE,cAAc;IAC/B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,MAAM,EAAE,kBAAkB;IAC1B;;;;;;;;OAQG;IACH,UAAU,EAAE,gBAAgB,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAGH;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,yEAAyE;IACzE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,SAAS,EAAE,iBAAiB;IAC5B,4EAA4E;IAC5E,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;CACrB,CAAC,CAAC;AAGH;;;;GAIG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC/B,CAAC,CAAC;AAGH,gFAAgF;AAEhF,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAG3E,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,SAAS,EAAE,cAAc,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,MAAM,CAAC;IAClD,MAAM,EAAE,cAAc,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAGH,wEAAwE;AACxE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC3C,CAAC,CAAC","sourcesContent":["/**\n * gymmonk-schema — Member management (owner console)\n * ==================================================\n * Owner-facing roster + detail projections (User × MemberProfile × Membership)\n * and the \"add member\" provisioning payload. Adding a member provisions a real\n * central-auth account, so the result carries the login email + temp password.\n *\n * @module gymmonk-schema/member\n */\n\nimport { z } from 'zod';\nimport {\n isoDateSchema,\n isoDateTimeSchema,\n objectIdSchema,\n optionalTrimmedString,\n paginationSchema,\n phoneSchema,\n priceInrSchema,\n} from './common.js';\nimport { emergencyContactSchema, memberRecordStatusSchema } from './member-profile.js';\nimport {\n assignMembershipBodySchema,\n membershipSchema,\n membershipStatusSchema,\n} from './membership.js';\nimport { planTierSchema } from './membership-plan.js';\nimport { addressSchema, bloodGroupSchema, genderSchema } from './shared.js';\n\n// ─── Segments ────────────────────────────────────────────────────────────────\n\n/** Attention buckets surfaced on roster cards. */\nexport const memberSegmentSchema = z.enum(['expiring', 'new', 'at-risk']);\nexport type MemberSegment = z.infer<typeof memberSegmentSchema>;\n\n// ─── Roster item (list projection) ───────────────────────────────────────────\n\nexport const memberRosterItemSchema = z.object({\n /** The gym record id — always present, even before the member claims. */\n memberProfileId: objectIdSchema,\n /** Null until claimed; the roster shows an \"Invited\" pill in that state. */\n userId: objectIdSchema.nullable(),\n recordStatus: memberRecordStatusSchema,\n membershipId: objectIdSchema.nullable(),\n memberCode: z.string(),\n name: z.string(),\n avatarUrl: z.string().nullable(),\n verified: z.boolean(),\n /**\n * Contact details, on the ROSTER row rather than only the detail record.\n *\n * The card's whole job when someone is overdue is to let the owner chase it\n * without leaving the list — call, WhatsApp, email. Those are `tel:` /\n * `wa.me` / `mailto:` links, so the row cannot build them without the raw\n * values, and fetching the full member just to dial them would be one\n * request per card.\n *\n * Nullable because an invited record may have only one of the two: the gym\n * creates it with whatever it has, and the member fills in the rest on claim.\n * Phone is RAW 10 digits — `+91` is display-only, never stored.\n */\n phone: z.string().nullable(),\n email: z.email().nullable(),\n planName: z.string().nullable(),\n tier: planTierSchema.nullable(),\n status: membershipStatusSchema.nullable(),\n endDate: isoDateSchema.nullable(),\n lastVisitAt: isoDateTimeSchema.nullable(),\n balanceDueInr: priceInrSchema,\n overdueDays: z.number().int().nullable(),\n segments: z.array(memberSegmentSchema),\n\n /**\n * When the gym record was created — NOT when the person claimed it. The\n * roster's \"Joined Date\" filter groups by how long someone has been a member\n * of the gym, which starts at the desk, not at the login.\n */\n joinedAt: isoDateTimeSchema,\n\n /**\n * Check-ins in the trailing 14 days, used by the roster's attendance filters\n * to separate a no-show from an irregular from a regular.\n *\n * Counted rather than derived from `lastVisitAt`, because the two answer\n * different questions: someone who came once yesterday and someone who comes\n * daily share a last visit but are not the same member to chase.\n */\n visits14d: z.number().int().nonnegative(),\n\n /** Assigned trainer, so the roster can be filtered down to one trainer's people. */\n trainerId: objectIdSchema.nullable(),\n trainerName: z.string().nullable(),\n});\n\n/** Trailing window the roster's attendance filters and `visits14d` are measured over. */\nexport const ATTENDANCE_WINDOW_DAYS = 14;\nexport type MemberRosterItem = z.infer<typeof memberRosterItemSchema>;\n\n/** Header counts for the roster. */\nexport const memberListSummarySchema = z.object({\n total: z.number().int().nonnegative(),\n active: z.number().int().nonnegative(),\n expiring: z.number().int().nonnegative(),\n inactive: z.number().int().nonnegative(),\n});\nexport type MemberListSummary = z.infer<typeof memberListSummarySchema>;\n\n// ─── List query ──────────────────────────────────────────────────────────────\n\nexport const memberFilterSchema = z.enum(['all', 'active', 'inactive', 'verified']);\nexport type MemberFilter = z.infer<typeof memberFilterSchema>;\n\nexport const memberSortSchema = z.enum(['recent', 'az', 'expiry', 'dues']);\nexport type MemberSort = z.infer<typeof memberSortSchema>;\n\nexport const memberListQuerySchema = paginationSchema.extend({\n centerId: objectIdSchema.optional(),\n filter: memberFilterSchema.default('all'),\n sort: memberSortSchema.default('recent'),\n tier: planTierSchema.optional(),\n q: optionalTrimmedString(120),\n});\nexport type MemberListQuery = z.infer<typeof memberListQuerySchema>;\n\n// ─── Detail projection ───────────────────────────────────────────────────────\n\n/** The identity slice a projection pulls from the auth `User`. */\nexport const memberUserRefSchema = z.object({\n /** Null while the record is still `invited` (no central-auth account linked). */\n id: objectIdSchema.nullable(),\n name: z.string(),\n email: z.string(),\n phone: z.string().nullable(),\n avatarUrl: z.string().nullable(),\n verified: z.boolean(),\n});\nexport type MemberUserRef = z.infer<typeof memberUserRefSchema>;\n\nexport const memberDetailSchema = z.object({\n memberProfileId: objectIdSchema,\n recordStatus: memberRecordStatusSchema,\n /**\n * Identity from central auth once claimed. Before that the gym still knows the\n * person (name / contact captured at the desk), so this carries the invited\n * details with `id: null`.\n */\n user: memberUserRefSchema,\n memberCode: z.string(),\n gender: genderSchema.nullable(),\n dob: isoDateSchema.nullable(),\n bloodGroup: bloodGroupSchema.nullable(),\n address: addressSchema.nullable(),\n emergencyContacts: z.array(emergencyContactSchema),\n joinedAt: isoDateTimeSchema,\n lastVisitAt: isoDateTimeSchema.nullable(),\n assignedWorkoutPlanId: objectIdSchema.nullable(),\n assignedTrainerId: objectIdSchema.nullable(),\n membershipId: objectIdSchema.nullable(),\n});\nexport type MemberDetail = z.infer<typeof memberDetailSchema>;\n\n// ─── Add member (provisioning) ───────────────────────────────────────────────\n\nexport const createMemberBodySchema = z.object({\n firstName: z.string().trim().min(1, 'First name is required').max(80),\n lastName: optionalTrimmedString(80),\n email: z.email('A valid email is required'),\n phone: phoneSchema,\n gender: genderSchema,\n dob: isoDateSchema.optional(),\n bloodGroup: bloodGroupSchema.optional(),\n address: addressSchema.optional(),\n emergencyContacts: z.array(emergencyContactSchema).max(5).default([]),\n idProofUrl: optionalTrimmedString(2000),\n avatarUrl: optionalTrimmedString(2000),\n assignedTrainerId: objectIdSchema.optional(),\n /** Optionally assign a plan in the same step. */\n initialPlan: assignMembershipBodySchema.optional(),\n});\nexport type CreateMemberBody = z.infer<typeof createMemberBodySchema>;\n\n// ─── Invite & claim ──────────────────────────────────────────────────────────\n\n/**\n * The invite a gym hands to a new member.\n *\n * GymMonk never mints credentials for anyone: central auth is the SSOT for\n * identity, and an account there is shared across products. So adding a member\n * creates a gym record only, plus an unguessable invite code. The member\n * registers/logs in themselves through central auth (exactly as in jansathi) and\n * then CLAIMS the record. The owner shares `url` over WhatsApp or shows the QR.\n */\nexport const memberInviteSchema = z.object({\n /** Single-use, unguessable. Regenerating an invite invalidates the old one. */\n code: z.string(),\n /** Absolute claim URL the owner shares (also encoded into the QR). */\n url: z.string(),\n /** The address the member MUST register with for the claim to succeed. */\n email: z.string(),\n /** WhatsApp number to share on, when the gym recorded one. */\n phone: z.string().nullable(),\n expiresAt: isoDateTimeSchema,\n});\nexport type MemberInvite = z.infer<typeof memberInviteSchema>;\n\n/** Result of adding a member: the gym record + the invite to hand over. */\nexport const provisionResultSchema = z.object({\n /** The new member record (NOT a central-auth user — none is created). */\n memberProfileId: objectIdSchema,\n code: z.string(),\n invite: memberInviteSchema,\n /**\n * The membership created from `initialPlan`, or `null`.\n *\n * Null means one of two DIFFERENT things, and the caller can tell them apart\n * because it knows whether it sent `initialPlan`: no plan was chosen, or the\n * chosen plan could not be assigned. Adding a member never fails over the\n * second case — the person is on the roster either way — so this is how the\n * owner finds out the plan still needs assigning rather than assuming it took.\n */\n membership: membershipSchema.nullable(),\n});\nexport type ProvisionResult = z.infer<typeof provisionResultSchema>;\n\n/**\n * What the claim screen shows BEFORE the member authenticates, so they know\n * which address to register with. Public (the code is the bearer secret), and\n * deliberately minimal — gym name, member code, and the expected email.\n */\nexport const invitePreviewSchema = z.object({\n gymName: z.string(),\n memberCode: z.string(),\n /** Shown verbatim: the member must know exactly which address to use. */\n email: z.string(),\n expiresAt: isoDateTimeSchema,\n /** True once claimed / expired, so the screen can explain why it failed. */\n claimed: z.boolean(),\n expired: z.boolean(),\n});\nexport type InvitePreview = z.infer<typeof invitePreviewSchema>;\n\n/**\n * Claim an invite as the authenticated caller. The server links the record only\n * when the code is valid AND the caller's central-auth email matches the address\n * the invite was issued to.\n */\nexport const claimInviteBodySchema = z.object({\n code: z.string().trim().min(1),\n});\nexport type ClaimInviteBody = z.infer<typeof claimInviteBodySchema>;\n\n// ─── Mutations ───────────────────────────────────────────────────────────────\n\nexport const setMemberStatusBodySchema = z.object({ active: z.boolean() });\nexport type SetMemberStatusBody = z.infer<typeof setMemberStatusBodySchema>;\n\nexport const assignTrainerBodySchema = z.object({\n trainerId: objectIdSchema.nullable(),\n});\nexport type AssignTrainerBody = z.infer<typeof assignTrainerBodySchema>;\n\nexport const assignWorkoutPlanBodySchema = z.object({\n planId: objectIdSchema.nullable(),\n});\nexport type AssignWorkoutPlanBody = z.infer<typeof assignWorkoutPlanBodySchema>;\n\n/** Remove a member from the gym — echoes the member code to confirm. */\nexport const deleteMemberBodySchema = z.object({\n confirmationCode: z.string().trim().min(1),\n});\nexport type DeleteMemberBody = z.infer<typeof deleteMemberBodySchema>;\n"]}
|
|
@@ -39,8 +39,24 @@ export declare const planTenureSchema: z.ZodObject<{
|
|
|
39
39
|
}>;
|
|
40
40
|
label: z.ZodString;
|
|
41
41
|
price: z.ZodNumber;
|
|
42
|
+
mrp: z.ZodOptional<z.ZodNumber>;
|
|
42
43
|
popular: z.ZodOptional<z.ZodBoolean>;
|
|
43
44
|
}, z.core.$strip>;
|
|
45
|
+
/** Whole rupees saved on a tenure, or 0 when it carries no `mrp`. */
|
|
46
|
+
export declare function tenureSavings(tenure: {
|
|
47
|
+
price: number;
|
|
48
|
+
mrp?: number;
|
|
49
|
+
}): number;
|
|
50
|
+
/**
|
|
51
|
+
* The ribbon above a plan card. Editorial, not derived: which plan a gym wants
|
|
52
|
+
* to push is a commercial decision, not something to infer from price.
|
|
53
|
+
*/
|
|
54
|
+
export declare const planBadgeSchema: z.ZodEnum<{
|
|
55
|
+
popular: "popular";
|
|
56
|
+
"best-value": "best-value";
|
|
57
|
+
}>;
|
|
58
|
+
export type PlanBadge = z.infer<typeof planBadgeSchema>;
|
|
59
|
+
export declare const PLAN_BADGE_LABEL: Record<PlanBadge, string>;
|
|
44
60
|
export type PlanTenure = z.infer<typeof planTenureSchema>;
|
|
45
61
|
/** The full vocabulary of membership-plan perk icons (superset of member perks). */
|
|
46
62
|
export declare const FEATURE_ICONS: readonly ["cardio", "locker", "classes", "health", "steam", "strength", "trainer", "diet", "yoga", "spa", "unlimited", "massage", "priority", "crossfit", "sauna", "pool"];
|
|
@@ -101,6 +117,10 @@ export declare const createMembershipPlanBodySchema: z.ZodObject<{
|
|
|
101
117
|
purple: "purple";
|
|
102
118
|
gold: "gold";
|
|
103
119
|
}>;
|
|
120
|
+
badge: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
121
|
+
popular: "popular";
|
|
122
|
+
"best-value": "best-value";
|
|
123
|
+
}>>>;
|
|
104
124
|
features: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
105
125
|
icon: z.ZodEnum<{
|
|
106
126
|
cardio: "cardio";
|
|
@@ -131,6 +151,7 @@ export declare const createMembershipPlanBodySchema: z.ZodObject<{
|
|
|
131
151
|
}>;
|
|
132
152
|
label: z.ZodString;
|
|
133
153
|
price: z.ZodNumber;
|
|
154
|
+
mrp: z.ZodOptional<z.ZodNumber>;
|
|
134
155
|
popular: z.ZodOptional<z.ZodBoolean>;
|
|
135
156
|
}, z.core.$strip>>;
|
|
136
157
|
}, z.core.$strip>;
|
|
@@ -150,6 +171,10 @@ export declare const updateMembershipPlanBodySchema: z.ZodObject<{
|
|
|
150
171
|
gold: "gold";
|
|
151
172
|
}>>;
|
|
152
173
|
note: z.ZodOptional<z.ZodPreprocess<z.ZodOptional<z.ZodString>>>;
|
|
174
|
+
badge: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
175
|
+
popular: "popular";
|
|
176
|
+
"best-value": "best-value";
|
|
177
|
+
}>>>>;
|
|
153
178
|
features: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
154
179
|
icon: z.ZodEnum<{
|
|
155
180
|
cardio: "cardio";
|
|
@@ -180,6 +205,7 @@ export declare const updateMembershipPlanBodySchema: z.ZodObject<{
|
|
|
180
205
|
}>;
|
|
181
206
|
label: z.ZodString;
|
|
182
207
|
price: z.ZodNumber;
|
|
208
|
+
mrp: z.ZodOptional<z.ZodNumber>;
|
|
183
209
|
popular: z.ZodOptional<z.ZodBoolean>;
|
|
184
210
|
}, z.core.$strip>>>;
|
|
185
211
|
isActive: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -202,6 +228,10 @@ export declare const membershipPlanSchema: z.ZodObject<{
|
|
|
202
228
|
purple: "purple";
|
|
203
229
|
gold: "gold";
|
|
204
230
|
}>;
|
|
231
|
+
badge: z.ZodNullable<z.ZodEnum<{
|
|
232
|
+
popular: "popular";
|
|
233
|
+
"best-value": "best-value";
|
|
234
|
+
}>>;
|
|
205
235
|
features: z.ZodArray<z.ZodObject<{
|
|
206
236
|
icon: z.ZodEnum<{
|
|
207
237
|
cardio: "cardio";
|
|
@@ -232,6 +262,7 @@ export declare const membershipPlanSchema: z.ZodObject<{
|
|
|
232
262
|
}>;
|
|
233
263
|
label: z.ZodString;
|
|
234
264
|
price: z.ZodNumber;
|
|
265
|
+
mrp: z.ZodOptional<z.ZodNumber>;
|
|
235
266
|
popular: z.ZodOptional<z.ZodBoolean>;
|
|
236
267
|
}, z.core.$strip>>;
|
|
237
268
|
isActive: z.ZodBoolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership-plan.d.ts","sourceRoot":"","sources":["../src/membership-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,eAAO,MAAM,cAAc;;;;;EAAmD,CAAC;AAC/E,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,0CAA0C;AAC1C,eAAO,MAAM,gBAAgB;;;;;EAAgD,CAAC;AAC9E,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,sDAAsD;AACtD,eAAO,MAAM,cAAc;;;;;EAAmC,CAAC;AAC/D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,eAAO,MAAM,gBAAgB
|
|
1
|
+
{"version":3,"file":"membership-plan.d.ts","sourceRoot":"","sources":["../src/membership-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAUxB,eAAO,MAAM,cAAc;;;;;EAAmD,CAAC;AAC/E,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,0CAA0C;AAC1C,eAAO,MAAM,gBAAgB;;;;;EAAgD,CAAC;AAC9E,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,sDAAsD;AACtD,eAAO,MAAM,cAAc;;;;;EAAmC,CAAC;AAC/D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAEtD,eAAO,MAAM,gBAAgB;;;;;;;;;;;iBAmBzB,CAAC;AAEL,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAE7E;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe;;;EAAoC,CAAC;AACjE,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAGtD,CAAC;AACF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,oFAAoF;AACpF,eAAO,MAAM,aAAa,4KAiBhB,CAAC;AACX,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;EAAwB,CAAC;AACvD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;iBAG5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAI5D,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAWzC,CAAC;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAEtF,eAAO,MAAM,8BAA8B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAGI,CAAC;AAChD,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAItF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgB/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
|
package/dist/membership-plan.js
CHANGED
|
@@ -15,12 +15,39 @@ export const planAccentSchema = z.enum(['neutral', 'teal', 'purple', 'gold']);
|
|
|
15
15
|
// ─── Tenure ──────────────────────────────────────────────────────────────────
|
|
16
16
|
/** Billing durations offered on a membership plan. */
|
|
17
17
|
export const tenureIdSchema = z.enum(['1m', '3m', '6m', '1y']);
|
|
18
|
-
export const planTenureSchema = z
|
|
18
|
+
export const planTenureSchema = z
|
|
19
|
+
.object({
|
|
19
20
|
id: tenureIdSchema,
|
|
20
21
|
label: z.string().trim().min(1),
|
|
21
22
|
price: priceInrSchema,
|
|
23
|
+
/**
|
|
24
|
+
* The pre-discount price, shown struck through beside `price` so a longer
|
|
25
|
+
* tenure visibly saves money. Optional: a plan sold at one price has no
|
|
26
|
+
* "before", and inventing one would be a dark pattern the gym's own
|
|
27
|
+
* members would notice.
|
|
28
|
+
*/
|
|
29
|
+
mrp: priceInrSchema.optional(),
|
|
22
30
|
popular: z.boolean().optional(),
|
|
31
|
+
})
|
|
32
|
+
// A "discount" below the real price is a data-entry slip, and it would render
|
|
33
|
+
// as a negative saving on the card.
|
|
34
|
+
.refine((t) => t.mrp === undefined || t.mrp >= t.price, {
|
|
35
|
+
message: 'The original price cannot be lower than the selling price',
|
|
36
|
+
path: ['mrp'],
|
|
23
37
|
});
|
|
38
|
+
/** Whole rupees saved on a tenure, or 0 when it carries no `mrp`. */
|
|
39
|
+
export function tenureSavings(tenure) {
|
|
40
|
+
return tenure.mrp === undefined ? 0 : Math.max(0, tenure.mrp - tenure.price);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The ribbon above a plan card. Editorial, not derived: which plan a gym wants
|
|
44
|
+
* to push is a commercial decision, not something to infer from price.
|
|
45
|
+
*/
|
|
46
|
+
export const planBadgeSchema = z.enum(['popular', 'best-value']);
|
|
47
|
+
export const PLAN_BADGE_LABEL = {
|
|
48
|
+
popular: 'Most Popular',
|
|
49
|
+
'best-value': 'Best Value',
|
|
50
|
+
};
|
|
24
51
|
// ─── Feature chips ───────────────────────────────────────────────────────────
|
|
25
52
|
/** The full vocabulary of membership-plan perk icons (superset of member perks). */
|
|
26
53
|
export const FEATURE_ICONS = [
|
|
@@ -53,6 +80,9 @@ export const createMembershipPlanBodySchema = z.object({
|
|
|
53
80
|
name: z.string().trim().min(1, 'Plan name is required').max(80),
|
|
54
81
|
note: optionalTrimmedString(60),
|
|
55
82
|
accent: planAccentSchema,
|
|
83
|
+
/** Ribbon above the card. Null on most plans — a gym pushing every plan
|
|
84
|
+
* equally is pushing none of them. */
|
|
85
|
+
badge: planBadgeSchema.nullish(),
|
|
56
86
|
features: z.array(planFeatureSchema).max(20).default([]),
|
|
57
87
|
tenures: z.array(planTenureSchema).min(1, 'Add at least one tenure'),
|
|
58
88
|
});
|
|
@@ -68,6 +98,7 @@ export const membershipPlanSchema = z.object({
|
|
|
68
98
|
name: z.string(),
|
|
69
99
|
note: z.string().nullable(),
|
|
70
100
|
accent: planAccentSchema,
|
|
101
|
+
badge: planBadgeSchema.nullable(),
|
|
71
102
|
features: z.array(planFeatureSchema),
|
|
72
103
|
tenures: z.array(planTenureSchema),
|
|
73
104
|
isActive: z.boolean(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership-plan.js","sourceRoot":"","sources":["../src/membership-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,gFAAgF;AAEhF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;AAG/E,0CAA0C;AAC1C,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAG9E,gFAAgF;AAEhF,sDAAsD;AACtD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAG/D,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"membership-plan.js","sourceRoot":"","sources":["../src/membership-plan.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Membership plan (owner-defined catalog)\n * ========================================================\n * The plans a gym owner defines and sells to members, scoped to a center. A\n * member's active `membership` references one of these by id + tenure.\n *\n * @module gymmonk-schema/membership-plan\n */\n\nimport { z } from 'zod';\nimport {\n isoDateTimeSchema,\n objectIdSchema,\n optionalTrimmedString,\n priceInrSchema,\n} from './common.js';\n\n// ─── Tier & accent ───────────────────────────────────────────────────────────\n\nexport const planTierSchema = z.enum(['free', 'basic', 'standard', 'premium']);\nexport type PlanTier = z.infer<typeof planTierSchema>;\n\n/** UI accent colour for the plan card. */\nexport const planAccentSchema = z.enum(['neutral', 'teal', 'purple', 'gold']);\nexport type PlanAccent = z.infer<typeof planAccentSchema>;\n\n// ─── Tenure ──────────────────────────────────────────────────────────────────\n\n/** Billing durations offered on a membership plan. */\nexport const tenureIdSchema = z.enum(['1m', '3m', '6m', '1y']);\nexport type TenureId = z.infer<typeof tenureIdSchema>;\n\nexport const planTenureSchema = z\n .object({\n id: tenureIdSchema,\n label: z.string().trim().min(1),\n price: priceInrSchema,\n /**\n * The pre-discount price, shown struck through beside `price` so a longer\n * tenure visibly saves money. Optional: a plan sold at one price has no\n * \"before\", and inventing one would be a dark pattern the gym's own\n * members would notice.\n */\n mrp: priceInrSchema.optional(),\n popular: z.boolean().optional(),\n })\n // A \"discount\" below the real price is a data-entry slip, and it would render\n // as a negative saving on the card.\n .refine((t) => t.mrp === undefined || t.mrp >= t.price, {\n message: 'The original price cannot be lower than the selling price',\n path: ['mrp'],\n });\n\n/** Whole rupees saved on a tenure, or 0 when it carries no `mrp`. */\nexport function tenureSavings(tenure: { price: number; mrp?: number }): number {\n return tenure.mrp === undefined ? 0 : Math.max(0, tenure.mrp - tenure.price);\n}\n\n/**\n * The ribbon above a plan card. Editorial, not derived: which plan a gym wants\n * to push is a commercial decision, not something to infer from price.\n */\nexport const planBadgeSchema = z.enum(['popular', 'best-value']);\nexport type PlanBadge = z.infer<typeof planBadgeSchema>;\n\nexport const PLAN_BADGE_LABEL: Record<PlanBadge, string> = {\n popular: 'Most Popular',\n 'best-value': 'Best Value',\n};\nexport type PlanTenure = z.infer<typeof planTenureSchema>;\n\n// ─── Feature chips ───────────────────────────────────────────────────────────\n\n/** The full vocabulary of membership-plan perk icons (superset of member perks). */\nexport const FEATURE_ICONS = [\n 'cardio',\n 'locker',\n 'classes',\n 'health',\n 'steam',\n 'strength',\n 'trainer',\n 'diet',\n 'yoga',\n 'spa',\n 'unlimited',\n 'massage',\n 'priority',\n 'crossfit',\n 'sauna',\n 'pool',\n] as const;\nexport const featureIconSchema = z.enum(FEATURE_ICONS);\nexport type FeatureIcon = z.infer<typeof featureIconSchema>;\n\nexport const planFeatureSchema = z.object({\n icon: featureIconSchema,\n label: z.string().trim().min(1).max(80),\n});\nexport type PlanFeature = z.infer<typeof planFeatureSchema>;\n\n// ─── Create / update body ────────────────────────────────────────────────────\n\nexport const createMembershipPlanBodySchema = z.object({\n centerId: objectIdSchema,\n tier: planTierSchema,\n name: z.string().trim().min(1, 'Plan name is required').max(80),\n note: optionalTrimmedString(60),\n accent: planAccentSchema,\n /** Ribbon above the card. Null on most plans — a gym pushing every plan\n * equally is pushing none of them. */\n badge: planBadgeSchema.nullish(),\n features: z.array(planFeatureSchema).max(20).default([]),\n tenures: z.array(planTenureSchema).min(1, 'Add at least one tenure'),\n});\nexport type CreateMembershipPlanBody = z.infer<typeof createMembershipPlanBodySchema>;\n\nexport const updateMembershipPlanBodySchema = createMembershipPlanBodySchema\n .omit({ centerId: true })\n .partial()\n .extend({ isActive: z.boolean().optional() });\nexport type UpdateMembershipPlanBody = z.infer<typeof updateMembershipPlanBodySchema>;\n\n// ─── Entity (API response) ───────────────────────────────────────────────────\n\nexport const membershipPlanSchema = z.object({\n id: objectIdSchema,\n centerId: objectIdSchema,\n tier: planTierSchema,\n name: z.string(),\n note: z.string().nullable(),\n accent: planAccentSchema,\n badge: planBadgeSchema.nullable(),\n features: z.array(planFeatureSchema),\n tenures: z.array(planTenureSchema),\n isActive: z.boolean(),\n sortOrder: z.number().int(),\n /** Denormalised count of active members on this plan (owner list view). */\n memberCount: z.number().int().nonnegative(),\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type MembershipPlan = z.infer<typeof membershipPlanSchema>;\n"]}
|
package/dist/messages.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;CAwBb,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC;AAIrE,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;;;;;IAI1B,sEAAsE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiEtB,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,eAAe,CAAC;AAE7D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB;iCACV,MAAM,KAAG,aAAa;gCAIvB,MAAM,KAAG,aAAa;sCAIhB,MAAM,KAAG,aAAa;uCAIrB,MAAM,KAAG,aAAa;CAGxC,CAAC;AAIX;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;;;;IAcxB,wEAAwE;;;;;;;;;IAaxE,2EAA2E;;;;;;IAW3E,wEAAwE;;IAExE,uEAAuE;;IAEvE,yEAAyE;;IAEzE,8EAA8E;;IAE9E,0DAA0D;;;IAK1D,4EAA4E;;IAG5E,6DAA6D;;
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;CAwBb,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC;AAIrE,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;;;;;IAI1B,sEAAsE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiEtB,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,eAAe,CAAC;AAE7D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB;iCACV,MAAM,KAAG,aAAa;gCAIvB,MAAM,KAAG,aAAa;sCAIhB,MAAM,KAAG,aAAa;uCAIrB,MAAM,KAAG,aAAa;CAGxC,CAAC;AAIX;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;;;;IAcxB,wEAAwE;;;;;;;;;IAaxE,2EAA2E;;;;;;IAW3E,wEAAwE;;IAExE,uEAAuE;;IAEvE,yEAAyE;;IAEzE,8EAA8E;;IAE9E,0DAA0D;;;IAK1D,4EAA4E;;IAG5E,6DAA6D;;IAE7D,kDAAkD;;IAElD,+DAA+D;;;IAG/D,kEAAkE;;;;;;;;;;;;;;;;;;CA0BzB,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,aAAa,CAAC;AAEzD,iDAAiD;AACjD,eAAO,MAAM,iBAAiB;8BACX,MAAM,KAAG,MAAM;IAChC,2DAA2D;sCAClC,MAAM,KAAG,MAAM;CAEhC,CAAC"}
|
package/dist/messages.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,gFAAgF;AAEhF;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,UAAU;IACV,cAAc,EAAE,gBAAgB;IAChC,gBAAgB,EAAE,kBAAkB;IACpC,SAAS,EAAE,WAAW;IAEtB,OAAO;IACP,aAAa,EAAE,eAAe;IAC9B,aAAa,EAAE,eAAe;IAC9B,YAAY,EAAE,cAAc;IAC5B,SAAS,EAAE,WAAW;IACtB,mBAAmB,EAAE,qBAAqB;IAE1C,OAAO;IACP,cAAc,EAAE,gBAAgB;IAChC,mBAAmB,EAAE,qBAAqB;IAC1C,YAAY,EAAE,cAAc;IAE5B,gBAAgB;IAChB,YAAY,EAAE,cAAc;IAC5B,mBAAmB,EAAE,qBAAqB;IAE1C,oCAAoC;IACpC,mBAAmB,EAAE,qBAAqB;CAClC,CAAC;AAYX,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,aAAa;IACb,wBAAwB,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,qBAAqB,EAAE;IACzF,uBAAuB,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,WAAW,EAAE,qBAAqB,EAAE;IAC3F,sEAAsE;IACtE,mBAAmB,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE;IAEvC,qBAAqB;IACrB,cAAc,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE;IAC5C,mBAAmB,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE;IAEtD,gBAAgB;IAChB,WAAW,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE;IACtC,aAAa,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE;IAC1C,qBAAqB,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE;IAE3D,2BAA2B;IAC3B,iBAAiB,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE;IAClD,iBAAiB,EAAE,EAAE,KAAK,EAAE,oBAAoB,EAAE;IAClD,mBAAmB,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE;IACtD,wBAAwB,EAAE;QACxB,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EAAE,kCAAkC;KAChD;IAED,0BAA0B;IAC1B,mBAAmB,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE;IAEtD,WAAW;IACX,kBAAkB,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE;IACrD,cAAc,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE;IAC5C,mBAAmB,EAAE,EAAE,KAAK,EAAE,uBAAuB,EAAE;IAEvD,WAAW;IACX,SAAS,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,2BAA2B,EAAE;IAC5E,UAAU,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE;IACnE,gBAAgB,EAAE;QAChB,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE,sCAAsC;KACpD;IAED,2BAA2B;IAC3B,qBAAqB,EAAE;QACrB,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,iCAAiC;KAC/C;IACD,yBAAyB,EAAE;QACzB,KAAK,EAAE,iBAAiB;QACxB,WAAW,EAAE,oCAAoC;KAClD;IACD,yBAAyB,EAAE;QACzB,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE,2BAA2B;KACzC;IAED,aAAa;IACb,iBAAiB,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,mCAAmC,EAAE;IAC/F,kBAAkB,EAAE,EAAE,KAAK,EAAE,qBAAqB,EAAE,WAAW,EAAE,6BAA6B,EAAE;IAChG,gBAAgB,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE;IAChD,qBAAqB,EAAE,EAAE,KAAK,EAAE,wBAAwB,EAAE;IAC1D,sBAAsB,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE;IAEhD,6BAA6B;IAC7B,OAAO,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE;IAC7B,SAAS,EAAE,EAAE,KAAK,EAAE,0BAA0B,EAAE;IAChD,iBAAiB,EAAE;QACjB,KAAK,EAAE,gCAAgC;QACvC,WAAW,EAAE,mBAAmB;KACjC;CAC+C,CAAC;AAInD,0DAA0D;AAC1D,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,WAAW,EAAE,CAAC,IAAY,EAAiB,EAAE,CAAC,CAAC;QAC7C,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,yBAAyB,IAAI,GAAG;KAC9C,CAAC;IACF,UAAU,EAAE,CAAC,IAAY,EAAiB,EAAE,CAAC,CAAC;QAC5C,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,yBAAyB,IAAI,GAAG;KAC9C,CAAC;IACF,aAAa,EAAE,CAAC,OAAe,EAAiB,EAAE,CAAC,CAAC;QAClD,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,6BAA6B,OAAO,GAAG;KACrD,CAAC;IACF,YAAY,EAAE,CAAC,SAAiB,EAAiB,EAAE,CAAC,CAAC;QACnD,KAAK,EAAE,eAAe,SAAS,EAAE;KAClC,CAAC;CACM,CAAC;AAEX,gFAAgF;AAEhF;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,sBAAsB;IACtB,OAAO,EAAE,yCAAyC;IAClD,OAAO,EAAE,qDAAqD;IAC9D,OAAO,EAAE,uCAAuC;IAChD,cAAc,EAAE,6CAA6C;IAE7D,yBAAyB;IACzB,WAAW,EAAE,sCAAsC;IAEnD,aAAa;IACb,YAAY,EAAE,2BAA2B;IACzC,sBAAsB,EAAE,mDAAmD;IAC3E,qBAAqB,EAAE,iDAAiD;IACxE,wEAAwE;IACxE,wBAAwB,EAAE,gDAAgD;IAE1E,8BAA8B;IAC9B,qBAAqB,EAAE,uCAAuC;IAC9D,qBAAqB,EAAE,4CAA4C;IACnE,gBAAgB,EAAE,0DAA0D;IAC5E,mBAAmB,EAAE,6DAA6D;IAClF,wBAAwB,EAAE,iEAAiE;IAE3F,aAAa;IACb,eAAe,EAAE,uCAAuC;IACxD,sBAAsB,EAAE,sCAAsC;IAC9D,2EAA2E;IAC3E,sBAAsB,EACpB,iFAAiF;IACnF,eAAe,EAAE,yEAAyE;IAE1F,WAAW;IACX,mBAAmB,EAAE,yBAAyB;IAE9C,WAAW;IACX,aAAa,EAAE,2CAA2C;IAC1D,cAAc,EAAE,4CAA4C;IAC5D,wEAAwE;IACxE,eAAe,EAAE,gEAAgE;IACjF,uEAAuE;IACvE,UAAU,EAAE,qCAAqC;IACjD,yEAAyE;IACzE,SAAS,EAAE,iEAAiE;IAC5E,8EAA8E;IAC9E,qBAAqB,EAAE,8CAA8C;IACrE,0DAA0D;IAC1D,kBAAkB,EAAE,mDAAmD;IACvE,qBAAqB,EAAE,gDAAgD;IAEvE,8EAA8E;IAC9E,4EAA4E;IAC5E,qBAAqB,EACnB,iFAAiF;IACnF,6DAA6D;IAC7D,mBAAmB,EACjB,0EAA0E;IAC5E,kDAAkD;IAClD,wBAAwB,EAAE,mDAAmD;IAC7E,+DAA+D;IAC/D,8BAA8B,EAAE,uCAAuC;IACvE,yBAAyB,EAAE,qCAAqC;IAChE,kEAAkE;IAClE,+BAA+B,EAAE,4CAA4C;IAC7E,uBAAuB,EAAE,gDAAgD;IACzE,cAAc,EAAE,oDAAoD;IAEpE,uBAAuB;IACvB,YAAY,EAAE,sEAAsE;IACpF,iBAAiB,EAAE,sEAAsE;IACzF,cAAc,EAAE,6CAA6C;IAC7D,YAAY,EAAE,kEAAkE;IAEhF,aAAa;IACb,sBAAsB,EAAE,yCAAyC;IACjE,sBAAsB,EAAE,oCAAoC;IAE5D,6BAA6B;IAC7B,aAAa,EAAE,gBAAgB;IAC/B,gBAAgB,EAAE,6BAA6B;IAC/C,aAAa,EAAE,mCAAmC;IAClD,YAAY,EAAE,gCAAgC;IAC9C,mBAAmB,EAAE,0CAA0C;IAC/D,eAAe,EAAE,qCAAqC;IAEtD,UAAU;IACV,iBAAiB,EAAE,gDAAgD;IACnE,kBAAkB,EAAE,iDAAiD;CAC5B,CAAC;AAI5C,iDAAiD;AACjD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,SAAS,EAAE,CAAC,GAAW,EAAU,EAAE,CAAC,qBAAqB,GAAG,SAAS;IACrE,2DAA2D;IAC3D,aAAa,EAAE,CAAC,OAAe,EAAU,EAAE,CACzC,mCAAmC,OAAO,UAAU,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG;CACvE,CAAC","sourcesContent":["/**\n * gymmonk-schema — API result messages\n * ====================================\n * Single source of truth for every user-facing SUCCESS and ERROR message the\n * product shows, plus the API error codes both sides key off.\n *\n * Why it lives here: the schema package is already the SSOT for data shapes,\n * types and validation. Result copy is part of that contract — the backend\n * raises a message, the client shows it, and neither should invent its own\n * wording. Both repos import from here, so copy changes land in one place and\n * can never drift between server and client.\n *\n * Usage:\n * backend → `throw new BadRequestError(ERROR_MESSAGE.centerAddressRequired)`\n * frontend → `showSuccess(...toast(SUCCESS_MESSAGE.profileUpdated))`\n *\n * @module gymmonk-schema/messages\n */\n\n// ─── API error codes ─────────────────────────────────────────────────────────\n\n/**\n * Machine-readable error codes returned in the API envelope\n * (`{ success: false, error: { code, message } }`). The client can branch on a\n * code; the message is what a human reads.\n */\nexport const ERROR_CODE = {\n // General\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n NOT_FOUND: 'NOT_FOUND',\n\n // Auth\n INVALID_TOKEN: 'INVALID_TOKEN',\n TOKEN_EXPIRED: 'TOKEN_EXPIRED',\n UNAUTHORIZED: 'UNAUTHORIZED',\n FORBIDDEN: 'FORBIDDEN',\n SESSION_INVALIDATED: 'SESSION_INVALIDATED',\n\n // User\n USER_NOT_FOUND: 'USER_NOT_FOUND',\n USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS',\n INVALID_ROLE: 'INVALID_ROLE',\n\n // Rate limiting\n RATE_LIMITED: 'RATE_LIMITED',\n RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',\n\n // Upstream / dependency unavailable\n SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODE)[keyof typeof ERROR_CODE];\n\n// ─── Message shape ───────────────────────────────────────────────────────────\n\n/** A result message: a headline and an optional quieter second line. */\nexport interface ResultMessage {\n title: string;\n description?: string;\n}\n\n// ─── Success messages ────────────────────────────────────────────────────────\n\n/**\n * Confirmation shown after a write succeeds, keyed by action. Interpolating\n * entries are functions so the caller supplies the value without re-wording.\n */\nexport const SUCCESS_MESSAGE = {\n // Onboarding\n memberOnboardingComplete: { title: \"You're all set\", description: 'Welcome to GymMonk.' },\n ownerOnboardingComplete: { title: 'Your gym is ready', description: 'Welcome to GymMonk.' },\n /** Each wizard step is persisted on its own, so each one confirms. */\n onboardingStepSaved: { title: 'Saved' },\n\n // Identity & profile\n profileUpdated: { title: 'Profile updated' },\n organisationUpdated: { title: 'Organisation updated' },\n\n // Gym structure\n centerAdded: { title: 'Center added' },\n centerUpdated: { title: 'Center updated' },\n membershipPlanCreated: { title: 'Membership plan created' },\n\n // Membership (member-side)\n membershipUpdated: { title: 'Membership updated' },\n membershipRenewed: { title: 'Membership renewed' },\n membershipCancelled: { title: 'Membership cancelled' },\n membershipPauseRequested: {\n title: 'Pause request sent',\n description: 'Your gym will confirm the pause.',\n },\n\n // Owner SaaS subscription\n subscriptionUpdated: { title: 'Subscription updated' },\n\n // Training\n workoutPlanCreated: { title: 'Workout plan created' },\n workoutCreated: { title: 'Workout created' },\n workoutPlanAssigned: { title: 'Workout plan assigned' },\n\n // Sessions\n checkedIn: { title: 'Checked in', description: 'Your session has started.' },\n checkedOut: { title: 'Checked out', description: 'Session saved.' },\n checkInQrRotated: {\n title: 'New QR generated',\n description: 'Print it and replace the old poster.',\n },\n\n // Membership join requests\n membershipRequestSent: {\n title: 'Request sent',\n description: 'The gym will review it shortly.',\n },\n membershipRequestApproved: {\n title: 'Member approved',\n description: 'They can now check in at your gym.',\n },\n membershipRequestRejected: {\n title: 'Request declined',\n description: 'The member has been told.',\n },\n\n // Engagement\n feedbackSubmitted: { title: 'Feedback sent', description: 'Thank you for helping us improve.' },\n complaintSubmitted: { title: 'Complaint submitted', description: 'Your gym will look into it.' },\n announcementSent: { title: 'Announcement sent' },\n announcementScheduled: { title: 'Announcement scheduled' },\n announcementDraftSaved: { title: 'Draft saved' },\n\n // Security (role-switch PIN)\n mpinSet: { title: 'PIN set' },\n mpinReset: { title: 'Your PIN has been reset.' },\n mpinResetLinkSent: {\n title: \"We've emailed you a reset link\",\n description: 'Check your inbox.',\n },\n} as const satisfies Record<string, ResultMessage>;\n\nexport type SuccessMessageKey = keyof typeof SUCCESS_MESSAGE;\n\n/** Success messages that name the thing they acted on. */\nexport const SUCCESS_MESSAGE_FOR = {\n memberAdded: (name: string): ResultMessage => ({\n title: 'Member added',\n description: `Share the invite with ${name}.`,\n }),\n staffAdded: (name: string): ResultMessage => ({\n title: 'Staff added',\n description: `Share the invite with ${name}.`,\n }),\n inviteClaimed: (gymName: string): ResultMessage => ({\n title: \"You're all set\",\n description: `Your account is linked to ${gymName}.`,\n }),\n roleSwitched: (roleLabel: string): ResultMessage => ({\n title: `Switched to ${roleLabel}`,\n }),\n} as const;\n\n// ─── Error messages ──────────────────────────────────────────────────────────\n\n/**\n * Failure copy. Transport entries describe a request that never reached the\n * API; the rest are domain rules the backend enforces and the client can also\n * check up-front — same wording either way.\n */\nexport const ERROR_MESSAGE = {\n // Transport / generic\n generic: 'Something went wrong. Please try again.',\n network: 'Network error. Check your connection and try again.',\n timeout: 'That took too long. Please try again.',\n sessionExpired: 'Your session expired. Please sign in again.',\n\n // Client-side validation\n formInvalid: 'Please check the highlighted fields.',\n\n // Onboarding\n roleRequired: 'Choose a role to continue',\n memberOnboardingFailed: 'We could not save your details. Please try again.',\n ownerOnboardingFailed: 'We could not create your gym. Please try again.',\n /** A step failed to persist — the user stays put so nothing is lost. */\n onboardingStepSaveFailed: 'We could not save this step. Please try again.',\n\n // Gym structure prerequisites\n centerRequiredForPlan: 'Pick the center this plan belongs to.',\n centerAddressRequired: 'Add the center address before creating it.',\n noCenterForPlans: 'No gym center found. Add a center before creating plans.',\n noCenterForWorkouts: 'No gym center found. Add a center before creating workouts.',\n noCenterForAnnouncements: 'No gym center found. Add a center before sending announcements.',\n\n // Membership\n planUnavailable: 'That plan is not available right now.',\n membershipPlanRequired: 'Choose a membership plan to continue',\n /** The member was created, but the plan the owner picked did not stick. */\n initialPlanNotAssigned:\n 'Member added, but the plan could not be assigned. Assign it from their profile.',\n noPlansToAssign: 'No membership plans yet. Create one to assign it while adding a member.',\n\n // Training\n workoutPlanRequired: 'Choose a plan to assign',\n\n // Sessions\n checkInFailed: 'Could not check you in. Please try again.',\n checkOutFailed: 'Could not check you out. Please try again.',\n /** The camera decoded a QR, but it is not a gymmonk check-in poster. */\n qrNotRecognised: 'That is not a gym check-in QR. Point at the poster at the gym.',\n /** A valid gymmonk QR, but for a gym the member does not belong to. */\n qrWrongGym: 'That QR belongs to a different gym.',\n /** The scanned code matches no center — a retired or mistyped poster. */\n qrUnknown: 'This QR is no longer valid. Ask the gym for the current poster.',\n /** Checking out with a poster from somewhere other than where you started. */\n qrWrongGymForCheckOut: 'Scan the QR at the gym where you checked in.',\n /** Manual entry only accepts a full, well-formed code. */\n checkInCodeInvalid: 'Enter the 16-character code printed under the QR.',\n checkInQrRotateFailed: 'Could not generate a new QR. Please try again.',\n\n // ── Membership join requests ───────────────────────────────────────────────\n /** Scanned a valid poster, but the gym has not accepted this member yet. */\n membershipNotApproved:\n 'Your gym has not accepted your join request yet. You can check in once they do.',\n /** Accepted, but with no live plan — check-in needs both. */\n membershipNotActive:\n 'Your membership plan is not active. Renew it to start checking in again.',\n /** A second request while one is already open. */\n membershipRequestPending: 'You already have a request waiting with this gym.',\n /** Requesting a gym they are already an accepted member of. */\n membershipRequestAlreadyMember: 'You are already a member of this gym.',\n membershipRequestNotFound: 'That join request no longer exists.',\n /** Two staff acting on the same request; the second one loses. */\n membershipRequestAlreadyDecided: 'Someone has already answered this request.',\n membershipRequestFailed: 'Could not send your request. Please try again.',\n gymCodeUnknown: 'No gym found with that id. Check it with your gym.',\n\n // Camera (QR scanning)\n cameraDenied: 'Camera access is blocked. Allow it in your browser settings to scan.',\n cameraUnavailable: 'No camera found on this device. Enter the code printed under the QR.',\n cameraInsecure: 'Scanning needs a secure (https) connection.',\n cameraFailed: 'Could not start the camera. Enter the code printed under the QR.',\n\n // Engagement\n feedbackRatingRequired: 'Choose a rating to submit your feedback',\n complaintIssueRequired: 'Choose the issue you are reporting',\n\n // Security (role-switch PIN)\n mpinIncorrect: 'Incorrect PIN.',\n mpinVerifyFailed: \"Couldn't verify. Try again.\",\n mpinSetFailed: \"Couldn't set your PIN. Try again.\",\n mpinMismatch: \"Those didn't match. Try again.\",\n mpinResetLinkFailed: \"Couldn't send the reset link. Try again.\",\n mpinResetFailed: \"Couldn't reset your PIN. Try again.\",\n\n // Uploads\n imageUploadFailed: 'Could not upload that image. Please try again.',\n imageProcessFailed: 'Could not process that image. Please try again.',\n} as const satisfies Record<string, string>;\n\nexport type ErrorMessageKey = keyof typeof ERROR_MESSAGE;\n\n/** Error messages that name a limit or value. */\nexport const ERROR_MESSAGE_FOR = {\n maxPhotos: (max: number): string => `You can add up to ${max} photos`,\n /** Role-switch PIN lockout, in whole minutes remaining. */\n mpinLockedOut: (minutes: number): string =>\n `Too many attempts. Try again in ${minutes} minute${minutes > 1 ? 's' : ''}.`,\n} as const;\n"]}
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — API result messages\n * ====================================\n * Single source of truth for every user-facing SUCCESS and ERROR message the\n * product shows, plus the API error codes both sides key off.\n *\n * Why it lives here: the schema package is already the SSOT for data shapes,\n * types and validation. Result copy is part of that contract — the backend\n * raises a message, the client shows it, and neither should invent its own\n * wording. Both repos import from here, so copy changes land in one place and\n * can never drift between server and client.\n *\n * Usage:\n * backend → `throw new BadRequestError(ERROR_MESSAGE.centerAddressRequired)`\n * frontend → `showSuccess(...toast(SUCCESS_MESSAGE.profileUpdated))`\n *\n * @module gymmonk-schema/messages\n */\n\n// ─── API error codes ─────────────────────────────────────────────────────────\n\n/**\n * Machine-readable error codes returned in the API envelope\n * (`{ success: false, error: { code, message } }`). The client can branch on a\n * code; the message is what a human reads.\n */\nexport const ERROR_CODE = {\n // General\n INTERNAL_ERROR: 'INTERNAL_ERROR',\n VALIDATION_ERROR: 'VALIDATION_ERROR',\n NOT_FOUND: 'NOT_FOUND',\n\n // Auth\n INVALID_TOKEN: 'INVALID_TOKEN',\n TOKEN_EXPIRED: 'TOKEN_EXPIRED',\n UNAUTHORIZED: 'UNAUTHORIZED',\n FORBIDDEN: 'FORBIDDEN',\n SESSION_INVALIDATED: 'SESSION_INVALIDATED',\n\n // User\n USER_NOT_FOUND: 'USER_NOT_FOUND',\n USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS',\n INVALID_ROLE: 'INVALID_ROLE',\n\n // Rate limiting\n RATE_LIMITED: 'RATE_LIMITED',\n RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',\n\n // Upstream / dependency unavailable\n SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',\n} as const;\n\nexport type ErrorCode = (typeof ERROR_CODE)[keyof typeof ERROR_CODE];\n\n// ─── Message shape ───────────────────────────────────────────────────────────\n\n/** A result message: a headline and an optional quieter second line. */\nexport interface ResultMessage {\n title: string;\n description?: string;\n}\n\n// ─── Success messages ────────────────────────────────────────────────────────\n\n/**\n * Confirmation shown after a write succeeds, keyed by action. Interpolating\n * entries are functions so the caller supplies the value without re-wording.\n */\nexport const SUCCESS_MESSAGE = {\n // Onboarding\n memberOnboardingComplete: { title: \"You're all set\", description: 'Welcome to GymMonk.' },\n ownerOnboardingComplete: { title: 'Your gym is ready', description: 'Welcome to GymMonk.' },\n /** Each wizard step is persisted on its own, so each one confirms. */\n onboardingStepSaved: { title: 'Saved' },\n\n // Identity & profile\n profileUpdated: { title: 'Profile updated' },\n organisationUpdated: { title: 'Organisation updated' },\n\n // Gym structure\n centerAdded: { title: 'Center added' },\n centerUpdated: { title: 'Center updated' },\n membershipPlanCreated: { title: 'Membership plan created' },\n\n // Membership (member-side)\n membershipUpdated: { title: 'Membership updated' },\n membershipRenewed: { title: 'Membership renewed' },\n membershipCancelled: { title: 'Membership cancelled' },\n membershipPauseRequested: {\n title: 'Pause request sent',\n description: 'Your gym will confirm the pause.',\n },\n\n // Owner SaaS subscription\n subscriptionUpdated: { title: 'Subscription updated' },\n\n // Training\n workoutPlanCreated: { title: 'Workout plan created' },\n workoutCreated: { title: 'Workout created' },\n workoutPlanAssigned: { title: 'Workout plan assigned' },\n\n // Sessions\n checkedIn: { title: 'Checked in', description: 'Your session has started.' },\n checkedOut: { title: 'Checked out', description: 'Session saved.' },\n checkInQrRotated: {\n title: 'New QR generated',\n description: 'Print it and replace the old poster.',\n },\n\n // Membership join requests\n membershipRequestSent: {\n title: 'Request sent',\n description: 'The gym will review it shortly.',\n },\n membershipRequestApproved: {\n title: 'Member approved',\n description: 'They can now check in at your gym.',\n },\n membershipRequestRejected: {\n title: 'Request declined',\n description: 'The member has been told.',\n },\n\n // Engagement\n feedbackSubmitted: { title: 'Feedback sent', description: 'Thank you for helping us improve.' },\n complaintSubmitted: { title: 'Complaint submitted', description: 'Your gym will look into it.' },\n announcementSent: { title: 'Announcement sent' },\n announcementScheduled: { title: 'Announcement scheduled' },\n announcementDraftSaved: { title: 'Draft saved' },\n\n // Security (role-switch PIN)\n mpinSet: { title: 'PIN set' },\n mpinReset: { title: 'Your PIN has been reset.' },\n mpinResetLinkSent: {\n title: \"We've emailed you a reset link\",\n description: 'Check your inbox.',\n },\n} as const satisfies Record<string, ResultMessage>;\n\nexport type SuccessMessageKey = keyof typeof SUCCESS_MESSAGE;\n\n/** Success messages that name the thing they acted on. */\nexport const SUCCESS_MESSAGE_FOR = {\n memberAdded: (name: string): ResultMessage => ({\n title: 'Member added',\n description: `Share the invite with ${name}.`,\n }),\n staffAdded: (name: string): ResultMessage => ({\n title: 'Staff added',\n description: `Share the invite with ${name}.`,\n }),\n inviteClaimed: (gymName: string): ResultMessage => ({\n title: \"You're all set\",\n description: `Your account is linked to ${gymName}.`,\n }),\n roleSwitched: (roleLabel: string): ResultMessage => ({\n title: `Switched to ${roleLabel}`,\n }),\n} as const;\n\n// ─── Error messages ──────────────────────────────────────────────────────────\n\n/**\n * Failure copy. Transport entries describe a request that never reached the\n * API; the rest are domain rules the backend enforces and the client can also\n * check up-front — same wording either way.\n */\nexport const ERROR_MESSAGE = {\n // Transport / generic\n generic: 'Something went wrong. Please try again.',\n network: 'Network error. Check your connection and try again.',\n timeout: 'That took too long. Please try again.',\n sessionExpired: 'Your session expired. Please sign in again.',\n\n // Client-side validation\n formInvalid: 'Please check the highlighted fields.',\n\n // Onboarding\n roleRequired: 'Choose a role to continue',\n memberOnboardingFailed: 'We could not save your details. Please try again.',\n ownerOnboardingFailed: 'We could not create your gym. Please try again.',\n /** A step failed to persist — the user stays put so nothing is lost. */\n onboardingStepSaveFailed: 'We could not save this step. Please try again.',\n\n // Gym structure prerequisites\n centerRequiredForPlan: 'Pick the center this plan belongs to.',\n centerAddressRequired: 'Add the center address before creating it.',\n noCenterForPlans: 'No gym center found. Add a center before creating plans.',\n noCenterForWorkouts: 'No gym center found. Add a center before creating workouts.',\n noCenterForAnnouncements: 'No gym center found. Add a center before sending announcements.',\n\n // Membership\n planUnavailable: 'That plan is not available right now.',\n membershipPlanRequired: 'Choose a membership plan to continue',\n /** The member was created, but the plan the owner picked did not stick. */\n initialPlanNotAssigned:\n 'Member added, but the plan could not be assigned. Assign it from their profile.',\n noPlansToAssign: 'No membership plans yet. Create one to assign it while adding a member.',\n\n // Training\n workoutPlanRequired: 'Choose a plan to assign',\n\n // Sessions\n checkInFailed: 'Could not check you in. Please try again.',\n checkOutFailed: 'Could not check you out. Please try again.',\n /** The camera decoded a QR, but it is not a gymmonk check-in poster. */\n qrNotRecognised: 'That is not a gym check-in QR. Point at the poster at the gym.',\n /** A valid gymmonk QR, but for a gym the member does not belong to. */\n qrWrongGym: 'That QR belongs to a different gym.',\n /** The scanned code matches no center — a retired or mistyped poster. */\n qrUnknown: 'This QR is no longer valid. Ask the gym for the current poster.',\n /** Checking out with a poster from somewhere other than where you started. */\n qrWrongGymForCheckOut: 'Scan the QR at the gym where you checked in.',\n /** Manual entry only accepts a full, well-formed code. */\n checkInCodeInvalid: 'Enter the 16-character code printed under the QR.',\n checkInQrRotateFailed: 'Could not generate a new QR. Please try again.',\n\n // ── Membership join requests ───────────────────────────────────────────────\n /** Scanned a valid poster, but the gym has not accepted this member yet. */\n membershipNotApproved:\n 'Your gym has not accepted your join request yet. You can check in once they do.',\n /** Accepted, but with no live plan — check-in needs both. */\n membershipNotActive: 'Your membership plan is not active. Renew it to start checking in again.',\n /** A second request while one is already open. */\n membershipRequestPending: 'You already have a request waiting with this gym.',\n /** Requesting a gym they are already an accepted member of. */\n membershipRequestAlreadyMember: 'You are already a member of this gym.',\n membershipRequestNotFound: 'That join request no longer exists.',\n /** Two staff acting on the same request; the second one loses. */\n membershipRequestAlreadyDecided: 'Someone has already answered this request.',\n membershipRequestFailed: 'Could not send your request. Please try again.',\n gymCodeUnknown: 'No gym found with that id. Check it with your gym.',\n\n // Camera (QR scanning)\n cameraDenied: 'Camera access is blocked. Allow it in your browser settings to scan.',\n cameraUnavailable: 'No camera found on this device. Enter the code printed under the QR.',\n cameraInsecure: 'Scanning needs a secure (https) connection.',\n cameraFailed: 'Could not start the camera. Enter the code printed under the QR.',\n\n // Engagement\n feedbackRatingRequired: 'Choose a rating to submit your feedback',\n complaintIssueRequired: 'Choose the issue you are reporting',\n\n // Security (role-switch PIN)\n mpinIncorrect: 'Incorrect PIN.',\n mpinVerifyFailed: \"Couldn't verify. Try again.\",\n mpinSetFailed: \"Couldn't set your PIN. Try again.\",\n mpinMismatch: \"Those didn't match. Try again.\",\n mpinResetLinkFailed: \"Couldn't send the reset link. Try again.\",\n mpinResetFailed: \"Couldn't reset your PIN. Try again.\",\n\n // Uploads\n imageUploadFailed: 'Could not upload that image. Please try again.',\n imageProcessFailed: 'Could not process that image. Please try again.',\n} as const satisfies Record<string, string>;\n\nexport type ErrorMessageKey = keyof typeof ERROR_MESSAGE;\n\n/** Error messages that name a limit or value. */\nexport const ERROR_MESSAGE_FOR = {\n maxPhotos: (max: number): string => `You can add up to ${max} photos`,\n /** Role-switch PIN lockout, in whole minutes remaining. */\n mpinLockedOut: (minutes: number): string =>\n `Too many attempts. Try again in ${minutes} minute${minutes > 1 ? 's' : ''}.`,\n} as const;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gymmonk-schema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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",
|
package/README.md
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# gymmonk-schema
|
|
2
|
-
|
|
3
|
-
Shared **Zod** schemas, enums and domain types for **GymMonk** (fitness SaaS).
|
|
4
|
-
Single source of truth (SSOT) consumed by both `gymmonk-backend` and
|
|
5
|
-
`gymmonk-web-client`, so validation, wire types and enums never drift between
|
|
6
|
-
the API and the app.
|
|
7
|
-
|
|
8
|
-
## What's inside
|
|
9
|
-
|
|
10
|
-
- **Common** (`common.ts`) — reusable primitives: `objectIdSchema`,
|
|
11
|
-
`paginationSchema`, `optionalTrimmedString`, `emptyStringToUndefined`.
|
|
12
|
-
- Domain modules (workouts, training library, membership, check-in, …) are
|
|
13
|
-
added here as they land, each re-exported through `src/index.ts`.
|
|
14
|
-
|
|
15
|
-
## Build & publish (npm-first)
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install
|
|
19
|
-
npm run build # tsc -> dist/
|
|
20
|
-
npm publish --access public
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
`prepublishOnly` runs `build` + `check` (type-check + biome) as the publish gate.
|
|
24
|
-
`--access public` is a CLI flag, not a manifest field.
|
|
25
|
-
|
|
26
|
-
## Consume
|
|
27
|
-
|
|
28
|
-
In `gymmonk-backend` and `gymmonk-web-client`:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npm install gymmonk-schema@<version>
|
|
32
|
-
# then delete any nested Zod dup so a single Zod instance is used:
|
|
33
|
-
rm -rf node_modules/gymmonk-schema/node_modules
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Bump the caret pin in each consumer's `package.json` on every publish (a `0.x`
|
|
37
|
-
caret locks to the same minor, so `npm install` won't auto-pick a new minor).
|
|
38
|
-
|
|
39
|
-
## Convention
|
|
40
|
-
|
|
41
|
-
Mirrors the jansathi schema family (`dating-schema`, `near-me-schema`) exactly:
|
|
42
|
-
pure ESM, `tsc`-only build, Zod is a **peer** dependency (`^4.4.0`), NodeNext
|
|
43
|
-
`.js` import specifiers, const-object enums fed to `z.enum(...)`.
|