gymmonk-schema 0.41.0 → 0.43.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/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/membership-plan.d.ts +40 -6
- package/dist/membership-plan.d.ts.map +1 -1
- package/dist/membership-plan.js +43 -7
- package/dist/membership-plan.js.map +1 -1
- package/dist/messages.d.ts +17 -0
- package/dist/messages.d.ts.map +1 -1
- package/dist/messages.js +17 -0
- package/dist/messages.js.map +1 -1
- package/dist/plan-abilities.d.ts +102 -0
- package/dist/plan-abilities.d.ts.map +1 -0
- package/dist/plan-abilities.js +169 -0
- package/dist/plan-abilities.js.map +1 -0
- package/dist/plan-features-data.d.ts +50 -0
- package/dist/plan-features-data.d.ts.map +1 -0
- package/dist/plan-features-data.js +314 -0
- package/dist/plan-features-data.js.map +1 -0
- package/dist/plan-limits.d.ts +277 -0
- package/dist/plan-limits.d.ts.map +1 -0
- package/dist/plan-limits.js +352 -0
- package/dist/plan-limits.js.map +1 -0
- package/dist/saas-catalog.d.ts +210 -0
- package/dist/saas-catalog.d.ts.map +1 -0
- package/dist/saas-catalog.js +243 -0
- package/dist/saas-catalog.js.map +1 -0
- package/dist/subscription.d.ts +12 -10
- package/dist/subscription.d.ts.map +1 -1
- package/dist/subscription.js +14 -4
- package/dist/subscription.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -36,9 +36,13 @@ export * from './onboarding.js';
|
|
|
36
36
|
export * from './onboarding-form.js';
|
|
37
37
|
export * from './organisation.js';
|
|
38
38
|
export * from './owner-profile.js';
|
|
39
|
+
export * from './plan-abilities.js';
|
|
40
|
+
export * from './plan-features-data.js';
|
|
41
|
+
export * from './plan-limits.js';
|
|
39
42
|
export * from './platform.js';
|
|
40
43
|
export * from './pricing.js';
|
|
41
44
|
export * from './profile-edit-form.js';
|
|
45
|
+
export * from './saas-catalog.js';
|
|
42
46
|
export * from './session.js';
|
|
43
47
|
export * from './shared.js';
|
|
44
48
|
export * from './social.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAElC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAE5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAE1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AAExC,cAAc,eAAe,CAAC;AAE9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAE7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAElC,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAE5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAE1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AAExC,cAAc,eAAe,CAAC;AAE9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAE7B,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -51,10 +51,14 @@ export * from './onboarding-form.js';
|
|
|
51
51
|
export * from './organisation.js';
|
|
52
52
|
export * from './owner-profile.js';
|
|
53
53
|
// ─── Platform console (people + gyms directories, role keys) ─────────────────
|
|
54
|
+
export * from './plan-abilities.js';
|
|
55
|
+
export * from './plan-features-data.js';
|
|
56
|
+
export * from './plan-limits.js';
|
|
54
57
|
export * from './platform.js';
|
|
55
58
|
export * from './pricing.js';
|
|
56
59
|
// ─── Member "edit my profile" FORM validation ────────────────────────────────
|
|
57
60
|
export * from './profile-edit-form.js';
|
|
61
|
+
export * from './saas-catalog.js';
|
|
58
62
|
export * from './session.js';
|
|
59
63
|
export * from './shared.js';
|
|
60
64
|
export * from './social.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,6EAA6E;AAC7E,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,gFAAgF;AAChF,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,gFAAgF;AAChF,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC","sourcesContent":["/**\n * gymmonk-schema — Public API\n * ===========================\n * Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS).\n * Single source of truth (SSOT) for data shapes, TypeScript types and\n * validation, consumed by both gymmonk-backend and gymmonk-web-client.\n *\n * Every domain module is re-exported through this barrel. Import names are\n * globally unique across modules, so `export *` composes without collisions.\n */\n\n// ─── Owner desk-entry FORM validation (client-side working shapes) ───────────\nexport * from './add-person-form.js';\nexport * from './announcement.js';\n// ─── Location master data (the address picker's lookup API) ───────────────\nexport * from './area.js';\nexport * from './center.js';\n// ─── Check-in QR (poster payload shared by owner, member and backend) ────────\nexport * from './chat.js';\nexport * from './check-in.js';\n// ─── Foundation ──────────────────────────────────────────────────────────────\nexport * from './codes.js';\nexport * from './common.js';\nexport * from './enrolment.js';\nexport * from './equipment.js';\n// ─── Training ────────────────────────────────────────────────────────────────\nexport * from './exercise.js';\nexport * from './feedback.js';\n// ─── Uploads ─────────────────────────────────────────────────────────────────\nexport * from './file.js';\n// ─── \"List your gym\" — a member registers the business they run ──────────────\nexport * from './gym-registration.js';\nexport * from './home.js';\nexport * from './insights.js';\n// ─── Where a place physically is (Maps link, pin, check-in position) ─────────\nexport * from './location.js';\n// ─── People ──────────────────────────────────────────────────────────────────\nexport * from './member.js';\nexport * from './member-profile.js';\nexport * from './membership.js';\n// ─── Membership ──────────────────────────────────────────────────────────────\nexport * from './membership-plan.js';\nexport * from './membership-request.js';\n// ─── API result messages + error codes ───────────────────────────────────────\nexport * from './messages.js';\n// ─── Engagement & analytics ──────────────────────────────────────────────────\nexport * from './notification.js';\nexport * from './onboarding.js';\n// ─── Onboarding wizard FORM validation (client-side working shapes) ──────────\nexport * from './onboarding-form.js';\n// ─── Gym structure & onboarding ──────────────────────────────────────────────\nexport * from './organisation.js';\nexport * from './owner-profile.js';\n// ─── Platform console (people + gyms directories, role keys) ─────────────────\nexport * from './platform.js';\nexport * from './pricing.js';\n// ─── Member \"edit my profile\" FORM validation ────────────────────────────────\nexport * from './profile-edit-form.js';\nexport * from './session.js';\nexport * from './shared.js';\nexport * from './social.js';\nexport * from './staff.js';\nexport * from './subscription.js';\nexport * from './user.js';\nexport * from './workout-plan.js';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,6EAA6E;AAC7E,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,gFAAgF;AAChF,cAAc,uBAAuB,CAAC;AACtC,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,gFAAgF;AAChF,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,gFAAgF;AAChF,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC","sourcesContent":["/**\n * gymmonk-schema — Public API\n * ===========================\n * Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS).\n * Single source of truth (SSOT) for data shapes, TypeScript types and\n * validation, consumed by both gymmonk-backend and gymmonk-web-client.\n *\n * Every domain module is re-exported through this barrel. Import names are\n * globally unique across modules, so `export *` composes without collisions.\n */\n\n// ─── Owner desk-entry FORM validation (client-side working shapes) ───────────\nexport * from './add-person-form.js';\nexport * from './announcement.js';\n// ─── Location master data (the address picker's lookup API) ───────────────\nexport * from './area.js';\nexport * from './center.js';\n// ─── Check-in QR (poster payload shared by owner, member and backend) ────────\nexport * from './chat.js';\nexport * from './check-in.js';\n// ─── Foundation ──────────────────────────────────────────────────────────────\nexport * from './codes.js';\nexport * from './common.js';\nexport * from './enrolment.js';\nexport * from './equipment.js';\n// ─── Training ────────────────────────────────────────────────────────────────\nexport * from './exercise.js';\nexport * from './feedback.js';\n// ─── Uploads ─────────────────────────────────────────────────────────────────\nexport * from './file.js';\n// ─── \"List your gym\" — a member registers the business they run ──────────────\nexport * from './gym-registration.js';\nexport * from './home.js';\nexport * from './insights.js';\n// ─── Where a place physically is (Maps link, pin, check-in position) ─────────\nexport * from './location.js';\n// ─── People ──────────────────────────────────────────────────────────────────\nexport * from './member.js';\nexport * from './member-profile.js';\nexport * from './membership.js';\n// ─── Membership ──────────────────────────────────────────────────────────────\nexport * from './membership-plan.js';\nexport * from './membership-request.js';\n// ─── API result messages + error codes ───────────────────────────────────────\nexport * from './messages.js';\n// ─── Engagement & analytics ──────────────────────────────────────────────────\nexport * from './notification.js';\nexport * from './onboarding.js';\n// ─── Onboarding wizard FORM validation (client-side working shapes) ──────────\nexport * from './onboarding-form.js';\n// ─── Gym structure & onboarding ──────────────────────────────────────────────\nexport * from './organisation.js';\nexport * from './owner-profile.js';\n// ─── Platform console (people + gyms directories, role keys) ─────────────────\nexport * from './plan-abilities.js';\nexport * from './plan-features-data.js';\nexport * from './plan-limits.js';\nexport * from './platform.js';\nexport * from './pricing.js';\n// ─── Member \"edit my profile\" FORM validation ────────────────────────────────\nexport * from './profile-edit-form.js';\nexport * from './saas-catalog.js';\nexport * from './session.js';\nexport * from './shared.js';\nexport * from './social.js';\nexport * from './staff.js';\nexport * from './subscription.js';\nexport * from './user.js';\nexport * from './workout-plan.js';\n"]}
|
|
@@ -53,14 +53,48 @@ export declare const planVariantSchema: z.ZodObject<{
|
|
|
53
53
|
month: "month";
|
|
54
54
|
}>;
|
|
55
55
|
originalPriceInr: z.ZodNumber;
|
|
56
|
-
|
|
56
|
+
finalPriceInr: z.ZodNumber;
|
|
57
57
|
popular: z.ZodOptional<z.ZodBoolean>;
|
|
58
58
|
}, z.core.$strip>;
|
|
59
59
|
export type PlanVariant = z.infer<typeof planVariantSchema>;
|
|
60
|
+
/**
|
|
61
|
+
* The same variant, with the one rule the two prices have between them.
|
|
62
|
+
*
|
|
63
|
+
* Separate from `planVariantSchema` because a `.refine()` turns a schema into
|
|
64
|
+
* a ZodEffects, and `.partial()` or `.omit()` on one of those throws at MODULE
|
|
65
|
+
* LOAD — invisible to a build, and the page simply dies on import. Keeping
|
|
66
|
+
* the plain object available means the plan bodies can go on composing it
|
|
67
|
+
* while the API still validates the pair.
|
|
68
|
+
*/
|
|
69
|
+
export declare const checkedPlanVariantSchema: z.ZodObject<{
|
|
70
|
+
id: z.ZodString;
|
|
71
|
+
durationCount: z.ZodNumber;
|
|
72
|
+
durationUnit: z.ZodEnum<{
|
|
73
|
+
year: "year";
|
|
74
|
+
day: "day";
|
|
75
|
+
week: "week";
|
|
76
|
+
month: "month";
|
|
77
|
+
}>;
|
|
78
|
+
originalPriceInr: z.ZodNumber;
|
|
79
|
+
finalPriceInr: z.ZodNumber;
|
|
80
|
+
popular: z.ZodOptional<z.ZodBoolean>;
|
|
81
|
+
}, z.core.$strip>;
|
|
60
82
|
/** What the member actually pays, in whole rupees. */
|
|
61
|
-
export declare function variantFinalPrice(variant: Pick<PlanVariant, '
|
|
83
|
+
export declare function variantFinalPrice(variant: Pick<PlanVariant, 'finalPriceInr'>): number;
|
|
62
84
|
/** Whole rupees saved, or 0 on an undiscounted variant. */
|
|
63
|
-
export declare function variantSavings(variant: Pick<PlanVariant, 'originalPriceInr' | '
|
|
85
|
+
export declare function variantSavings(variant: Pick<PlanVariant, 'originalPriceInr' | 'finalPriceInr'>): number;
|
|
86
|
+
/**
|
|
87
|
+
* The discount as a percentage, for the "25% off" ribbon.
|
|
88
|
+
*
|
|
89
|
+
* DERIVED, and rounded only here where rounding is harmless: this number is
|
|
90
|
+
* shown, never stored and never used to compute a price. That is the whole
|
|
91
|
+
* point of the change — the rounding used to happen before the value was
|
|
92
|
+
* saved, so it moved the money.
|
|
93
|
+
*
|
|
94
|
+
* Zero when there is no discount, or when the original price is zero and the
|
|
95
|
+
* question has no answer.
|
|
96
|
+
*/
|
|
97
|
+
export declare function variantDiscountPercent(variant: Pick<PlanVariant, 'originalPriceInr' | 'finalPriceInr'>): number;
|
|
64
98
|
/** `{ 3, 'month' }` → `"3 Months"`. One spelling of a duration, everywhere. */
|
|
65
99
|
export declare function variantLabel(variant: Pick<PlanVariant, 'durationCount' | 'durationUnit'>): string;
|
|
66
100
|
/**
|
|
@@ -589,7 +623,7 @@ export declare const createMembershipPlanBodySchema: z.ZodObject<{
|
|
|
589
623
|
month: "month";
|
|
590
624
|
}>;
|
|
591
625
|
originalPriceInr: z.ZodNumber;
|
|
592
|
-
|
|
626
|
+
finalPriceInr: z.ZodNumber;
|
|
593
627
|
popular: z.ZodOptional<z.ZodBoolean>;
|
|
594
628
|
}, z.core.$strip>>;
|
|
595
629
|
}, z.core.$strip>;
|
|
@@ -686,7 +720,7 @@ export declare const updateMembershipPlanBodySchema: z.ZodObject<{
|
|
|
686
720
|
month: "month";
|
|
687
721
|
}>;
|
|
688
722
|
originalPriceInr: z.ZodNumber;
|
|
689
|
-
|
|
723
|
+
finalPriceInr: z.ZodNumber;
|
|
690
724
|
popular: z.ZodOptional<z.ZodBoolean>;
|
|
691
725
|
}, z.core.$strip>>>;
|
|
692
726
|
isActive: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -786,7 +820,7 @@ export declare const membershipPlanSchema: z.ZodObject<{
|
|
|
786
820
|
month: "month";
|
|
787
821
|
}>;
|
|
788
822
|
originalPriceInr: z.ZodNumber;
|
|
789
|
-
|
|
823
|
+
finalPriceInr: z.ZodNumber;
|
|
790
824
|
popular: z.ZodOptional<z.ZodBoolean>;
|
|
791
825
|
}, z.core.$strip>>;
|
|
792
826
|
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;
|
|
1
|
+
{"version":3,"file":"membership-plan.d.ts","sourceRoot":"","sources":["../src/membership-plan.ts"],"names":[],"mappings":"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"}
|
package/dist/membership-plan.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { isoDateTimeSchema, objectIdSchema, optionalTrimmedString, priceInrSchema, } from './common.js';
|
|
11
|
+
import { ERROR_MESSAGE } from './messages.js';
|
|
11
12
|
// ─── Accent ──────────────────────────────────────────────────────────────────
|
|
12
13
|
/** UI accent colour for the plan card. */
|
|
13
14
|
export const planAccentSchema = z.enum(['neutral', 'teal', 'purple', 'gold']);
|
|
@@ -47,19 +48,54 @@ export const planVariantSchema = z.object({
|
|
|
47
48
|
durationUnit: durationUnitSchema,
|
|
48
49
|
/** The list price, before any discount. Struck through on the card. */
|
|
49
50
|
originalPriceInr: priceInrSchema,
|
|
50
|
-
/**
|
|
51
|
-
|
|
51
|
+
/**
|
|
52
|
+
* What the member actually pays. THE STORED TRUTH, not a derivation.
|
|
53
|
+
*
|
|
54
|
+
* It used to be a whole-number `discountPercent` with the price computed
|
|
55
|
+
* from it, which cannot represent most prices a gym would name. A gym
|
|
56
|
+
* pricing ${'₹'}1000 down to ${'₹'}999 is offering 0.1% off; rounded to the nearest
|
|
57
|
+
* whole percent that is 0, so the price came back as ${'₹'}1000 and the ${'₹'}999
|
|
58
|
+
* they typed was quietly gone. Owners think in the price on the poster, not
|
|
59
|
+
* in percentages, so the price is what is kept and the percentage is worked
|
|
60
|
+
* out for display.
|
|
61
|
+
*/
|
|
62
|
+
finalPriceInr: priceInrSchema,
|
|
52
63
|
/** Ribbon on the variant tile. Editorial, like the plan badge. */
|
|
53
64
|
popular: z.boolean().optional(),
|
|
54
65
|
});
|
|
66
|
+
/**
|
|
67
|
+
* The same variant, with the one rule the two prices have between them.
|
|
68
|
+
*
|
|
69
|
+
* Separate from `planVariantSchema` because a `.refine()` turns a schema into
|
|
70
|
+
* a ZodEffects, and `.partial()` or `.omit()` on one of those throws at MODULE
|
|
71
|
+
* LOAD — invisible to a build, and the page simply dies on import. Keeping
|
|
72
|
+
* the plain object available means the plan bodies can go on composing it
|
|
73
|
+
* while the API still validates the pair.
|
|
74
|
+
*/
|
|
75
|
+
export const checkedPlanVariantSchema = planVariantSchema.refine((v) => v.finalPriceInr <= v.originalPriceInr, { message: ERROR_MESSAGE.planFinalPriceAboveOriginal, path: ['finalPriceInr'] });
|
|
55
76
|
/** What the member actually pays, in whole rupees. */
|
|
56
77
|
export function variantFinalPrice(variant) {
|
|
57
|
-
|
|
58
|
-
return Math.max(0, variant.originalPriceInr - off);
|
|
78
|
+
return variant.finalPriceInr;
|
|
59
79
|
}
|
|
60
80
|
/** Whole rupees saved, or 0 on an undiscounted variant. */
|
|
61
81
|
export function variantSavings(variant) {
|
|
62
|
-
return variant.originalPriceInr -
|
|
82
|
+
return Math.max(0, variant.originalPriceInr - variant.finalPriceInr);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The discount as a percentage, for the "25% off" ribbon.
|
|
86
|
+
*
|
|
87
|
+
* DERIVED, and rounded only here where rounding is harmless: this number is
|
|
88
|
+
* shown, never stored and never used to compute a price. That is the whole
|
|
89
|
+
* point of the change — the rounding used to happen before the value was
|
|
90
|
+
* saved, so it moved the money.
|
|
91
|
+
*
|
|
92
|
+
* Zero when there is no discount, or when the original price is zero and the
|
|
93
|
+
* question has no answer.
|
|
94
|
+
*/
|
|
95
|
+
export function variantDiscountPercent(variant) {
|
|
96
|
+
if (variant.originalPriceInr <= 0)
|
|
97
|
+
return 0;
|
|
98
|
+
return Math.round((variantSavings(variant) / variant.originalPriceInr) * 100);
|
|
63
99
|
}
|
|
64
100
|
/** `{ 3, 'month' }` → `"3 Months"`. One spelling of a duration, everywhere. */
|
|
65
101
|
export function variantLabel(variant) {
|
|
@@ -214,7 +250,7 @@ export const createMembershipPlanBodySchema = z.object({
|
|
|
214
250
|
* equally is pushing none of them. */
|
|
215
251
|
badge: planBadgeSchema.nullish(),
|
|
216
252
|
features: z.array(planFeatureSchema).max(40).default([]),
|
|
217
|
-
variants: z.array(
|
|
253
|
+
variants: z.array(checkedPlanVariantSchema).min(1, 'Add at least one variant'),
|
|
218
254
|
});
|
|
219
255
|
export const updateMembershipPlanBodySchema = createMembershipPlanBodySchema
|
|
220
256
|
.omit({ centerId: true })
|
|
@@ -229,7 +265,7 @@ export const membershipPlanSchema = z.object({
|
|
|
229
265
|
accent: planAccentSchema,
|
|
230
266
|
badge: planBadgeSchema.nullable(),
|
|
231
267
|
features: z.array(planFeatureSchema),
|
|
232
|
-
variants: z.array(
|
|
268
|
+
variants: z.array(checkedPlanVariantSchema),
|
|
233
269
|
isActive: z.boolean(),
|
|
234
270
|
sortOrder: z.number().int(),
|
|
235
271
|
/** Denormalised count of active members on this plan (owner list view). */
|
|
@@ -1 +1 @@
|
|
|
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 + variant.\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// ─── Accent ──────────────────────────────────────────────────────────────────\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// ─── Variants (duration + price) ─────────────────────────────────────────────\n\n/**\n * The unit a plan's duration is counted in.\n *\n * `day` earns its place: a gym selling a one-day trial pass or a ten-day\n * tourist package cannot express either in months, and the fixed\n * 1m/3m/6m/1y list it replaces forced them to.\n */\nexport const durationUnitSchema = z.enum(['day', 'week', 'month', 'year']);\nexport type DurationUnit = z.infer<typeof durationUnitSchema>;\n\n/** Singular / plural label for each unit, so no caller writes `unit + 's'`. */\nexport const DURATION_UNIT_LABEL: Record<DurationUnit, { one: string; many: string }> = {\n day: { one: 'Day', many: 'Days' },\n week: { one: 'Week', many: 'Weeks' },\n month: { one: 'Month', many: 'Months' },\n year: { one: 'Year', many: 'Years' },\n};\n\n/**\n * One buyable duration of a plan: how long it runs, what it lists at, and what\n * comes off.\n *\n * The gym enters the ORIGINAL price and a DISCOUNT; the selling price is\n * derived (`variantFinalPrice`) rather than stored. Storing all three invites\n * the arithmetic to drift — a gym edits the discount, forgets the total, and\n * the card advertises a saving the checkout does not honour.\n */\nexport const planVariantSchema = z.object({\n /**\n * Stable per plan, minted by the client when a variant is added, because a\n * live `membership` points back at it. Reusing an array index instead would\n * silently re-point every membership when a variant is deleted.\n */\n id: z.string().trim().min(1).max(40),\n durationCount: z.number().int().min(1).max(365),\n durationUnit: durationUnitSchema,\n /** The list price, before any discount. Struck through on the card. */\n originalPriceInr: priceInrSchema,\n /** Whole percent off. 0 means the plan sells at its list price. */\n discountPercent: z.number().int().min(0).max(100).default(0),\n /** Ribbon on the variant tile. Editorial, like the plan badge. */\n popular: z.boolean().optional(),\n});\nexport type PlanVariant = z.infer<typeof planVariantSchema>;\n\n/** What the member actually pays, in whole rupees. */\nexport function variantFinalPrice(\n variant: Pick<PlanVariant, 'originalPriceInr' | 'discountPercent'>,\n): number {\n const off = Math.round((variant.originalPriceInr * variant.discountPercent) / 100);\n return Math.max(0, variant.originalPriceInr - off);\n}\n\n/** Whole rupees saved, or 0 on an undiscounted variant. */\nexport function variantSavings(\n variant: Pick<PlanVariant, 'originalPriceInr' | 'discountPercent'>,\n): number {\n return variant.originalPriceInr - variantFinalPrice(variant);\n}\n\n/** `{ 3, 'month' }` → `\"3 Months\"`. One spelling of a duration, everywhere. */\nexport function variantLabel(variant: Pick<PlanVariant, 'durationCount' | 'durationUnit'>): string {\n const label = DURATION_UNIT_LABEL[variant.durationUnit];\n return `${variant.durationCount} ${variant.durationCount === 1 ? label.one : label.many}`;\n}\n\n// ─── Badge ───────────────────────────────────────────────────────────────────\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};\n\n// ─── Amenities ───────────────────────────────────────────────────────────────\n\n/**\n * What a gym can advertise on a plan, grouped the way the picker shows them.\n *\n * Deliberately EXHAUSTIVE. The old sixteen-key list made an owner describe a\n * real gym in someone else's vocabulary: no drinking water, no washroom, no\n * air conditioning, no parking — the things members actually ask about before\n * they join — so plans all read the same and the chips stopped meaning\n * anything.\n *\n * Keys are stable ids, never shown. The LABEL is the default caption and a gym\n * may override it per plan (see `planFeatureSchema.label`), which is how a\n * studio calls `classes` \"Group Sessions\" without needing a new key.\n */\nexport const AMENITY_CATEGORIES = [\n 'training',\n 'classes',\n 'facilities',\n 'wellness',\n 'services',\n 'extras',\n] as const;\nexport type AmenityCategory = (typeof AMENITY_CATEGORIES)[number];\n\nexport const AMENITY_CATEGORY_LABEL: Record<AmenityCategory, string> = {\n training: 'Training',\n classes: 'Classes',\n facilities: 'Facilities',\n wellness: 'Wellness & Recovery',\n services: 'Services',\n extras: 'Extras',\n};\n\n/**\n * The full catalogue: key → default caption + which group it sits in.\n *\n * Labels live HERE rather than in the client. They used to be duplicated in\n * two frontend maps that had drifted apart — the same key drew a different\n * icon on the owner's screen and the member's, and three keys drew nothing at\n * all on one of them. One source, and the client maps keys to icons only.\n */\nexport const AMENITY_CATALOG = {\n // ── Training floor ─────────────────────────────────────────────────────────\n cardio: { label: 'Cardio Machines', category: 'training' },\n strength: { label: 'Strength Training', category: 'training' },\n freeWeights: { label: 'Free Weights', category: 'training' },\n machines: { label: 'Weight Machines', category: 'training' },\n functional: { label: 'Functional Area', category: 'training' },\n crossfit: { label: 'CrossFit', category: 'training' },\n calisthenics: { label: 'Calisthenics', category: 'training' },\n powerlifting: { label: 'Powerlifting', category: 'training' },\n boxing: { label: 'Boxing', category: 'training' },\n martialArts: { label: 'Martial Arts', category: 'training' },\n treadmill: { label: 'Treadmills', category: 'training' },\n cycling: { label: 'Cycling', category: 'training' },\n rowing: { label: 'Rowing', category: 'training' },\n stretchZone: { label: 'Stretching Zone', category: 'training' },\n turfArea: { label: 'Turf Area', category: 'training' },\n\n // ── Classes ────────────────────────────────────────────────────────────────\n classes: { label: 'Group Classes', category: 'classes' },\n yoga: { label: 'Yoga', category: 'classes' },\n yogaArea: { label: 'Yoga Area', category: 'classes' },\n zumba: { label: 'Zumba', category: 'classes' },\n pilates: { label: 'Pilates', category: 'classes' },\n aerobics: { label: 'Aerobics', category: 'classes' },\n dance: { label: 'Dance', category: 'classes' },\n spinning: { label: 'Spinning', category: 'classes' },\n bootcamp: { label: 'Bootcamp', category: 'classes' },\n meditation: { label: 'Meditation', category: 'classes' },\n\n // ── Facilities ─────────────────────────────────────────────────────────────\n ac: { label: 'Air Conditioned', category: 'facilities' },\n drinkingWater: { label: 'Drinking Water', category: 'facilities' },\n washroom: { label: 'Washroom', category: 'facilities' },\n restroom: { label: 'Restroom', category: 'facilities' },\n shower: { label: 'Showers', category: 'facilities' },\n changingRoom: { label: 'Changing Room', category: 'facilities' },\n locker: { label: 'Lockers', category: 'facilities' },\n parking: { label: 'Parking', category: 'facilities' },\n wifi: { label: 'Free WiFi', category: 'facilities' },\n musicSystem: { label: 'Music System', category: 'facilities' },\n powerBackup: { label: 'Power Backup', category: 'facilities' },\n lift: { label: 'Lift Access', category: 'facilities' },\n cctv: { label: 'CCTV Security', category: 'facilities' },\n wheelchairAccess: { label: 'Wheelchair Access', category: 'facilities' },\n\n // ── Wellness & recovery ────────────────────────────────────────────────────\n steam: { label: 'Steam Room', category: 'wellness' },\n sauna: { label: 'Sauna', category: 'wellness' },\n spa: { label: 'Spa', category: 'wellness' },\n massage: { label: 'Massage', category: 'wellness' },\n iceBath: { label: 'Ice Bath', category: 'wellness' },\n pool: { label: 'Swimming Pool', category: 'wellness' },\n physiotherapy: { label: 'Physiotherapy', category: 'wellness' },\n\n // ── Services ───────────────────────────────────────────────────────────────\n trainer: { label: 'Trainer Support', category: 'services' },\n personalTraining: { label: 'Personal Training', category: 'services' },\n diet: { label: 'Diet Plan', category: 'services' },\n nutritionist: { label: 'Nutritionist', category: 'services' },\n health: { label: 'Health Stats', category: 'services' },\n bodyComposition: { label: 'Body Composition', category: 'services' },\n doctorConsult: { label: 'Doctor Consultation', category: 'services' },\n priority: { label: 'Priority Support', category: 'services' },\n\n // ── Extras ─────────────────────────────────────────────────────────────────\n cafe: { label: 'Cafe', category: 'extras' },\n coffeeMachine: { label: 'Coffee Machine', category: 'extras' },\n proteinBar: { label: 'Protein Bar', category: 'extras' },\n supplementStore: { label: 'Supplement Store', category: 'extras' },\n childArea: { label: 'Kids Play Area', category: 'extras' },\n towelService: { label: 'Towel Service', category: 'extras' },\n laundry: { label: 'Laundry', category: 'extras' },\n guestPass: { label: 'Guest Pass', category: 'extras' },\n unlimited: { label: 'Unlimited Access', category: 'extras' },\n multiCenter: { label: 'All Branches', category: 'extras' },\n} as const satisfies Record<string, { label: string; category: AmenityCategory }>;\n\n/** Every amenity key, in catalogue order. */\nexport const FEATURE_ICONS = Object.keys(AMENITY_CATALOG) as [FeatureIcon, ...FeatureIcon[]];\nexport type FeatureIcon = keyof typeof AMENITY_CATALOG;\n\nexport const featureIconSchema = z.enum(\n Object.keys(AMENITY_CATALOG) as [FeatureIcon, ...FeatureIcon[]],\n);\n\n/** The amenity keys in one category, for a grouped picker. */\nexport function amenitiesInCategory(category: AmenityCategory): FeatureIcon[] {\n return (Object.keys(AMENITY_CATALOG) as FeatureIcon[]).filter(\n (key) => AMENITY_CATALOG[key].category === category,\n );\n}\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 name: z.string().trim().min(1, 'Plan name is required').max(80),\n /**\n * What the gym wants to say about the plan in its own words.\n *\n * Replaces a 60-character `note` that the card rendered but no screen could\n * ever set — a caption nobody could write. One optional field, roomy enough\n * for a sentence or two.\n */\n description: optionalTrimmedString(300),\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(40).default([]),\n variants: z.array(planVariantSchema).min(1, 'Add at least one variant'),\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 name: z.string(),\n description: z.string().nullable(),\n accent: planAccentSchema,\n badge: planBadgeSchema.nullable(),\n features: z.array(planFeatureSchema),\n variants: z.array(planVariantSchema),\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"]}
|
|
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 + variant.\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';\nimport { ERROR_MESSAGE } from './messages.js';\n\n// ─── Accent ──────────────────────────────────────────────────────────────────\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// ─── Variants (duration + price) ─────────────────────────────────────────────\n\n/**\n * The unit a plan's duration is counted in.\n *\n * `day` earns its place: a gym selling a one-day trial pass or a ten-day\n * tourist package cannot express either in months, and the fixed\n * 1m/3m/6m/1y list it replaces forced them to.\n */\nexport const durationUnitSchema = z.enum(['day', 'week', 'month', 'year']);\nexport type DurationUnit = z.infer<typeof durationUnitSchema>;\n\n/** Singular / plural label for each unit, so no caller writes `unit + 's'`. */\nexport const DURATION_UNIT_LABEL: Record<DurationUnit, { one: string; many: string }> = {\n day: { one: 'Day', many: 'Days' },\n week: { one: 'Week', many: 'Weeks' },\n month: { one: 'Month', many: 'Months' },\n year: { one: 'Year', many: 'Years' },\n};\n\n/**\n * One buyable duration of a plan: how long it runs, what it lists at, and what\n * comes off.\n *\n * The gym enters the ORIGINAL price and a DISCOUNT; the selling price is\n * derived (`variantFinalPrice`) rather than stored. Storing all three invites\n * the arithmetic to drift — a gym edits the discount, forgets the total, and\n * the card advertises a saving the checkout does not honour.\n */\nexport const planVariantSchema = z.object({\n /**\n * Stable per plan, minted by the client when a variant is added, because a\n * live `membership` points back at it. Reusing an array index instead would\n * silently re-point every membership when a variant is deleted.\n */\n id: z.string().trim().min(1).max(40),\n durationCount: z.number().int().min(1).max(365),\n durationUnit: durationUnitSchema,\n /** The list price, before any discount. Struck through on the card. */\n originalPriceInr: priceInrSchema,\n /**\n * What the member actually pays. THE STORED TRUTH, not a derivation.\n *\n * It used to be a whole-number `discountPercent` with the price computed\n * from it, which cannot represent most prices a gym would name. A gym\n * pricing ${'₹'}1000 down to ${'₹'}999 is offering 0.1% off; rounded to the nearest\n * whole percent that is 0, so the price came back as ${'₹'}1000 and the ${'₹'}999\n * they typed was quietly gone. Owners think in the price on the poster, not\n * in percentages, so the price is what is kept and the percentage is worked\n * out for display.\n */\n finalPriceInr: priceInrSchema,\n /** Ribbon on the variant tile. Editorial, like the plan badge. */\n popular: z.boolean().optional(),\n});\nexport type PlanVariant = z.infer<typeof planVariantSchema>;\n\n/**\n * The same variant, with the one rule the two prices have between them.\n *\n * Separate from `planVariantSchema` because a `.refine()` turns a schema into\n * a ZodEffects, and `.partial()` or `.omit()` on one of those throws at MODULE\n * LOAD — invisible to a build, and the page simply dies on import. Keeping\n * the plain object available means the plan bodies can go on composing it\n * while the API still validates the pair.\n */\nexport const checkedPlanVariantSchema = planVariantSchema.refine(\n (v) => v.finalPriceInr <= v.originalPriceInr,\n { message: ERROR_MESSAGE.planFinalPriceAboveOriginal, path: ['finalPriceInr'] },\n);\n\n/** What the member actually pays, in whole rupees. */\nexport function variantFinalPrice(variant: Pick<PlanVariant, 'finalPriceInr'>): number {\n return variant.finalPriceInr;\n}\n\n/** Whole rupees saved, or 0 on an undiscounted variant. */\nexport function variantSavings(\n variant: Pick<PlanVariant, 'originalPriceInr' | 'finalPriceInr'>,\n): number {\n return Math.max(0, variant.originalPriceInr - variant.finalPriceInr);\n}\n\n/**\n * The discount as a percentage, for the \"25% off\" ribbon.\n *\n * DERIVED, and rounded only here where rounding is harmless: this number is\n * shown, never stored and never used to compute a price. That is the whole\n * point of the change — the rounding used to happen before the value was\n * saved, so it moved the money.\n *\n * Zero when there is no discount, or when the original price is zero and the\n * question has no answer.\n */\nexport function variantDiscountPercent(\n variant: Pick<PlanVariant, 'originalPriceInr' | 'finalPriceInr'>,\n): number {\n if (variant.originalPriceInr <= 0) return 0;\n return Math.round((variantSavings(variant) / variant.originalPriceInr) * 100);\n}\n\n/** `{ 3, 'month' }` → `\"3 Months\"`. One spelling of a duration, everywhere. */\nexport function variantLabel(variant: Pick<PlanVariant, 'durationCount' | 'durationUnit'>): string {\n const label = DURATION_UNIT_LABEL[variant.durationUnit];\n return `${variant.durationCount} ${variant.durationCount === 1 ? label.one : label.many}`;\n}\n\n// ─── Badge ───────────────────────────────────────────────────────────────────\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};\n\n// ─── Amenities ───────────────────────────────────────────────────────────────\n\n/**\n * What a gym can advertise on a plan, grouped the way the picker shows them.\n *\n * Deliberately EXHAUSTIVE. The old sixteen-key list made an owner describe a\n * real gym in someone else's vocabulary: no drinking water, no washroom, no\n * air conditioning, no parking — the things members actually ask about before\n * they join — so plans all read the same and the chips stopped meaning\n * anything.\n *\n * Keys are stable ids, never shown. The LABEL is the default caption and a gym\n * may override it per plan (see `planFeatureSchema.label`), which is how a\n * studio calls `classes` \"Group Sessions\" without needing a new key.\n */\nexport const AMENITY_CATEGORIES = [\n 'training',\n 'classes',\n 'facilities',\n 'wellness',\n 'services',\n 'extras',\n] as const;\nexport type AmenityCategory = (typeof AMENITY_CATEGORIES)[number];\n\nexport const AMENITY_CATEGORY_LABEL: Record<AmenityCategory, string> = {\n training: 'Training',\n classes: 'Classes',\n facilities: 'Facilities',\n wellness: 'Wellness & Recovery',\n services: 'Services',\n extras: 'Extras',\n};\n\n/**\n * The full catalogue: key → default caption + which group it sits in.\n *\n * Labels live HERE rather than in the client. They used to be duplicated in\n * two frontend maps that had drifted apart — the same key drew a different\n * icon on the owner's screen and the member's, and three keys drew nothing at\n * all on one of them. One source, and the client maps keys to icons only.\n */\nexport const AMENITY_CATALOG = {\n // ── Training floor ─────────────────────────────────────────────────────────\n cardio: { label: 'Cardio Machines', category: 'training' },\n strength: { label: 'Strength Training', category: 'training' },\n freeWeights: { label: 'Free Weights', category: 'training' },\n machines: { label: 'Weight Machines', category: 'training' },\n functional: { label: 'Functional Area', category: 'training' },\n crossfit: { label: 'CrossFit', category: 'training' },\n calisthenics: { label: 'Calisthenics', category: 'training' },\n powerlifting: { label: 'Powerlifting', category: 'training' },\n boxing: { label: 'Boxing', category: 'training' },\n martialArts: { label: 'Martial Arts', category: 'training' },\n treadmill: { label: 'Treadmills', category: 'training' },\n cycling: { label: 'Cycling', category: 'training' },\n rowing: { label: 'Rowing', category: 'training' },\n stretchZone: { label: 'Stretching Zone', category: 'training' },\n turfArea: { label: 'Turf Area', category: 'training' },\n\n // ── Classes ────────────────────────────────────────────────────────────────\n classes: { label: 'Group Classes', category: 'classes' },\n yoga: { label: 'Yoga', category: 'classes' },\n yogaArea: { label: 'Yoga Area', category: 'classes' },\n zumba: { label: 'Zumba', category: 'classes' },\n pilates: { label: 'Pilates', category: 'classes' },\n aerobics: { label: 'Aerobics', category: 'classes' },\n dance: { label: 'Dance', category: 'classes' },\n spinning: { label: 'Spinning', category: 'classes' },\n bootcamp: { label: 'Bootcamp', category: 'classes' },\n meditation: { label: 'Meditation', category: 'classes' },\n\n // ── Facilities ─────────────────────────────────────────────────────────────\n ac: { label: 'Air Conditioned', category: 'facilities' },\n drinkingWater: { label: 'Drinking Water', category: 'facilities' },\n washroom: { label: 'Washroom', category: 'facilities' },\n restroom: { label: 'Restroom', category: 'facilities' },\n shower: { label: 'Showers', category: 'facilities' },\n changingRoom: { label: 'Changing Room', category: 'facilities' },\n locker: { label: 'Lockers', category: 'facilities' },\n parking: { label: 'Parking', category: 'facilities' },\n wifi: { label: 'Free WiFi', category: 'facilities' },\n musicSystem: { label: 'Music System', category: 'facilities' },\n powerBackup: { label: 'Power Backup', category: 'facilities' },\n lift: { label: 'Lift Access', category: 'facilities' },\n cctv: { label: 'CCTV Security', category: 'facilities' },\n wheelchairAccess: { label: 'Wheelchair Access', category: 'facilities' },\n\n // ── Wellness & recovery ────────────────────────────────────────────────────\n steam: { label: 'Steam Room', category: 'wellness' },\n sauna: { label: 'Sauna', category: 'wellness' },\n spa: { label: 'Spa', category: 'wellness' },\n massage: { label: 'Massage', category: 'wellness' },\n iceBath: { label: 'Ice Bath', category: 'wellness' },\n pool: { label: 'Swimming Pool', category: 'wellness' },\n physiotherapy: { label: 'Physiotherapy', category: 'wellness' },\n\n // ── Services ───────────────────────────────────────────────────────────────\n trainer: { label: 'Trainer Support', category: 'services' },\n personalTraining: { label: 'Personal Training', category: 'services' },\n diet: { label: 'Diet Plan', category: 'services' },\n nutritionist: { label: 'Nutritionist', category: 'services' },\n health: { label: 'Health Stats', category: 'services' },\n bodyComposition: { label: 'Body Composition', category: 'services' },\n doctorConsult: { label: 'Doctor Consultation', category: 'services' },\n priority: { label: 'Priority Support', category: 'services' },\n\n // ── Extras ─────────────────────────────────────────────────────────────────\n cafe: { label: 'Cafe', category: 'extras' },\n coffeeMachine: { label: 'Coffee Machine', category: 'extras' },\n proteinBar: { label: 'Protein Bar', category: 'extras' },\n supplementStore: { label: 'Supplement Store', category: 'extras' },\n childArea: { label: 'Kids Play Area', category: 'extras' },\n towelService: { label: 'Towel Service', category: 'extras' },\n laundry: { label: 'Laundry', category: 'extras' },\n guestPass: { label: 'Guest Pass', category: 'extras' },\n unlimited: { label: 'Unlimited Access', category: 'extras' },\n multiCenter: { label: 'All Branches', category: 'extras' },\n} as const satisfies Record<string, { label: string; category: AmenityCategory }>;\n\n/** Every amenity key, in catalogue order. */\nexport const FEATURE_ICONS = Object.keys(AMENITY_CATALOG) as [FeatureIcon, ...FeatureIcon[]];\nexport type FeatureIcon = keyof typeof AMENITY_CATALOG;\n\nexport const featureIconSchema = z.enum(\n Object.keys(AMENITY_CATALOG) as [FeatureIcon, ...FeatureIcon[]],\n);\n\n/** The amenity keys in one category, for a grouped picker. */\nexport function amenitiesInCategory(category: AmenityCategory): FeatureIcon[] {\n return (Object.keys(AMENITY_CATALOG) as FeatureIcon[]).filter(\n (key) => AMENITY_CATALOG[key].category === category,\n );\n}\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 name: z.string().trim().min(1, 'Plan name is required').max(80),\n /**\n * What the gym wants to say about the plan in its own words.\n *\n * Replaces a 60-character `note` that the card rendered but no screen could\n * ever set — a caption nobody could write. One optional field, roomy enough\n * for a sentence or two.\n */\n description: optionalTrimmedString(300),\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(40).default([]),\n variants: z.array(checkedPlanVariantSchema).min(1, 'Add at least one variant'),\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 name: z.string(),\n description: z.string().nullable(),\n accent: planAccentSchema,\n badge: planBadgeSchema.nullable(),\n features: z.array(planFeatureSchema),\n variants: z.array(checkedPlanVariantSchema),\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
CHANGED
|
@@ -35,6 +35,21 @@ export declare const ERROR_CODE: {
|
|
|
35
35
|
readonly INVALID_ROLE: "INVALID_ROLE";
|
|
36
36
|
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
37
37
|
readonly RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED";
|
|
38
|
+
/**
|
|
39
|
+
* The caller MAY do this — their subscription simply does not stretch to it.
|
|
40
|
+
*
|
|
41
|
+
* Deliberately NOT `FORBIDDEN`, and the distinction is load-bearing on the
|
|
42
|
+
* client. A role denial means "this is not yours"; the answer is to go away.
|
|
43
|
+
* A plan denial means "this costs money"; the answer is an upgrade prompt with
|
|
44
|
+
* the price on it. A client that cannot tell them apart either shows an
|
|
45
|
+
* upgrade sheet to a trainer who will never be allowed to buy one, or shows a
|
|
46
|
+
* bare 403 to an owner who would happily have paid.
|
|
47
|
+
*
|
|
48
|
+
* Always accompanied by `details`: `{ feature, currentPlan, requiredPlan,
|
|
49
|
+
* current?, limit? }` — enough for the prompt to name the feature, the tier
|
|
50
|
+
* that unlocks it, and how full the current allowance is.
|
|
51
|
+
*/
|
|
52
|
+
readonly PLAN_LIMIT_EXCEEDED: "PLAN_LIMIT_EXCEEDED";
|
|
38
53
|
readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
|
|
39
54
|
};
|
|
40
55
|
export type ErrorCode = (typeof ERROR_CODE)[keyof typeof ERROR_CODE];
|
|
@@ -354,6 +369,8 @@ export declare const ERROR_MESSAGE: {
|
|
|
354
369
|
readonly planNameTaken: "This center already has a plan with that name. Pick another name.";
|
|
355
370
|
readonly planVariantDurationRequired: "Enter how long this variant runs for";
|
|
356
371
|
readonly planVariantPriceRequired: "Enter the price for this variant";
|
|
372
|
+
/** A "discount" that raises the price is a typo, every time. */
|
|
373
|
+
readonly planFinalPriceAboveOriginal: "The final price cannot be more than the original price";
|
|
357
374
|
readonly centerAddressRequired: "Add the center address before creating it.";
|
|
358
375
|
readonly noCenterForPlans: "No gym center found. Add a center before creating plans.";
|
|
359
376
|
readonly noCenterForWorkouts: "No gym center found. Add a center before creating workouts.";
|
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
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;IAsBrB;;;;;;;;;;;;;OAaG;;;CAKK,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;;;;;;;;;IAU1B,sEAAsE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA8CtE;;;;OAIG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkH6C,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;uCAGtB,MAAM,KAAG,aAAa;uCAItB,MAAM,KAAG,aAAa;CAIxC,CAAC;AAIX;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;IAiBxB;;;;;OAKG;;IAGH;;;;;;;OAOG;;IAGH;;;;;;OAMG;;IAGH,4EAA4E;;IAE5E,+EAA+E;;IAE/E,gFAAgF;;IAGhF,iDAAiD;;;IAKjD,kDAAkD;;IAElD;;;;OAIG;;IAEH;;;;OAIG;;IAEH,qEAAqE;;IAErE,2EAA2E;;IAE3E,wEAAwE;;;;;;;IASxE,gEAAgE;;;;;;;;;;;IAehE,2EAA2E;;;IAI3E;;;OAGG;;;IAIH;;;OAGG;;IAGH,mEAAmE;;;;;IAKnE,8EAA8E;;;;;;;;;;;;;IAgB9E;;;OAGG;;;;IAMH,wEAAwE;;IAExE,uEAAuE;;IAEvE,yEAAyE;;IAEzE,8EAA8E;;IAE9E,0DAA0D;;;IAG1D;;;;OAIG;;IAIH,4EAA4E;;IAG5E,6DAA6D;;IAE7D,kDAAkD;;IAElD,+DAA+D;;IAE/D,sEAAsE;;;IAItE,kEAAkE;;;;IAIlE,uEAAuE;;IAEvE,8EAA8E;;;;;;;;;;;;;;;;;CAyBrC,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
CHANGED
|
@@ -40,6 +40,21 @@ export const ERROR_CODE = {
|
|
|
40
40
|
// Rate limiting
|
|
41
41
|
RATE_LIMITED: 'RATE_LIMITED',
|
|
42
42
|
RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
|
|
43
|
+
/**
|
|
44
|
+
* The caller MAY do this — their subscription simply does not stretch to it.
|
|
45
|
+
*
|
|
46
|
+
* Deliberately NOT `FORBIDDEN`, and the distinction is load-bearing on the
|
|
47
|
+
* client. A role denial means "this is not yours"; the answer is to go away.
|
|
48
|
+
* A plan denial means "this costs money"; the answer is an upgrade prompt with
|
|
49
|
+
* the price on it. A client that cannot tell them apart either shows an
|
|
50
|
+
* upgrade sheet to a trainer who will never be allowed to buy one, or shows a
|
|
51
|
+
* bare 403 to an owner who would happily have paid.
|
|
52
|
+
*
|
|
53
|
+
* Always accompanied by `details`: `{ feature, currentPlan, requiredPlan,
|
|
54
|
+
* current?, limit? }` — enough for the prompt to name the feature, the tier
|
|
55
|
+
* that unlocks it, and how full the current allowance is.
|
|
56
|
+
*/
|
|
57
|
+
PLAN_LIMIT_EXCEEDED: 'PLAN_LIMIT_EXCEEDED',
|
|
43
58
|
// Upstream / dependency unavailable
|
|
44
59
|
SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
|
|
45
60
|
};
|
|
@@ -315,6 +330,8 @@ export const ERROR_MESSAGE = {
|
|
|
315
330
|
planNameTaken: 'This center already has a plan with that name. Pick another name.',
|
|
316
331
|
planVariantDurationRequired: 'Enter how long this variant runs for',
|
|
317
332
|
planVariantPriceRequired: 'Enter the price for this variant',
|
|
333
|
+
/** A "discount" that raises the price is a typo, every time. */
|
|
334
|
+
planFinalPriceAboveOriginal: 'The final price cannot be more than the original price',
|
|
318
335
|
centerAddressRequired: 'Add the center address before creating it.',
|
|
319
336
|
noCenterForPlans: 'No gym center found. Add a center before creating plans.',
|
|
320
337
|
noCenterForWorkouts: 'No gym center found. Add a center before creating workouts.',
|
package/dist/messages.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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: {\n title: \"You're all set\",\n description: 'Welcome to GymMonk.',\n },\n ownerOnboardingComplete: {\n title: 'Your gym is ready',\n description: 'Welcome to GymMonk.',\n },\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 membershipPlanUpdated: { title: 'Membership plan updated' },\n equipmentSaved: { title: 'Equipment updated' },\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 workoutPlanUpdated: { title: 'Workout plan saved' },\n workoutPlanDeleted: {\n title: 'Workout plan deleted',\n description: 'Members on this plan no longer have one.',\n },\n workoutCreated: { title: 'Workout created' },\n workoutUpdated: { title: 'Workout saved' },\n workoutDeleted: { title: 'Workout removed' },\n workoutPlanAssigned: { title: 'Workout plan assigned' },\n\n // Roster (owner console)\n attendanceMarked: { title: 'Attendance saved' },\n memberMarkedInactive: {\n title: 'Member marked inactive',\n description: 'They can no longer check in.',\n },\n memberMarkedActive: { title: 'Member reactivated' },\n memberDeleted: { title: 'Member removed' },\n /**\n * Says what actually happened. Assigning a plan no longer starts it —\n * nothing does until the payment is marked done — so a bare \"Plan\n * updated\" would send the owner away believing the member could train.\n */\n memberPlanAssigned: {\n title: 'Plan assigned',\n description: 'It starts once you mark the payment done.',\n },\n memberMembershipPaused: { title: 'Membership paused' },\n memberMembershipCancelled: { title: 'Membership cancelled' },\n paymentCollected: { title: 'Payment recorded' },\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 // Membership payment confirmation\n membershipRequested: {\n title: 'Plan requested',\n description: 'It starts once your gym confirms the payment.',\n },\n membershipRequestWithdrawn: {\n title: 'Request withdrawn',\n description: 'Pick another plan whenever you are ready.',\n },\n membershipPaymentConfirmed: {\n title: 'Payment confirmed',\n description: 'The plan is now active for this member.',\n },\n membershipPaymentRejected: {\n title: 'Payment turned down',\n description: 'The member has been told and can send it again.',\n },\n\n // Gym registration + platform review\n gymRegistered: {\n title: 'Gym submitted',\n description: 'Our team will review it and let you know.',\n },\n gymApproved: {\n title: 'Gym approved',\n description: 'It is now listed for members to find.',\n },\n gymRejected: {\n title: 'Gym rejected',\n description: 'The owner has been told.',\n },\n\n // Engagement\n feedbackSubmitted: {\n title: 'Feedback sent',\n description: 'Thank you for helping us improve.',\n },\n complaintSubmitted: {\n title: 'Complaint submitted',\n description: 'Your gym will look into it.',\n },\n announcementSent: { title: 'Announcement sent' },\n announcementScheduled: { title: 'Announcement scheduled' },\n announcementDraftSaved: { title: 'Draft saved' },\n announcementDeleted: { title: 'Announcement deleted' },\n announcementCancelled: {\n title: 'Announcement cancelled',\n description: 'It will not be sent.',\n },\n\n // Notification feed\n notificationHidden: { title: 'Notification hidden' },\n notificationRestored: { title: 'Notification restored' },\n allNotificationsRead: { title: 'All caught up' },\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\n // ── Platform console (superadmin / platform manager) ────────────────────────\n platformRolesUpdated: { title: 'Platform roles updated' },\n platformUserSuspended: {\n title: 'Account suspended',\n description: 'They can no longer sign in.',\n },\n platformUserReinstated: { title: 'Account reinstated' },\n platformUserDeleted: {\n title: 'Account deleted',\n description: 'It is hidden everywhere but can still be restored.',\n },\n platformUserRestored: { title: 'Account restored' },\n platformGymDeleted: {\n title: 'Gym deleted',\n description: 'It is hidden everywhere but can still be restored.',\n },\n platformGymRestored: {\n title: 'Gym restored',\n description: 'It is back in the directory and its owner has the console again.',\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 gymApprovedFor: (gymName: string): ResultMessage => ({\n title: 'Gym approved',\n description: `${gymName} is now live on GymMonk.`,\n }),\n gymRejectedFor: (gymName: string): ResultMessage => ({\n title: 'Gym rejected',\n description: `The owner of ${gymName} has been told why.`,\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 ownerOnboardingFailed: 'We could not create your gym. Please try again.',\n memberOnboardingFailed: 'We could not save your details. Please try again.',\n\n // ── Gym registration (\"list your gym\") ─────────────────────────────────────\n gymRegistrationFailed: 'We could not register your gym. Please try again.',\n /**\n * The wizard could not read what the owner had already saved.\n *\n * It refuses to open on a blank form rather than risk the next Continue\n * overwriting a real draft, so this is the whole screen, not a toast.\n */\n gymRegistrationDraftLoadFailed:\n 'We could not open your saved gym details. Check your connection and try again.',\n /**\n * The step the wizard tried to save is implausibly large.\n *\n * A guard against a client sending something absurd rather than a limit the\n * real form approaches, but it is raised by the SERVER and shown verbatim in\n * the step-save toast — so the wording lives here with the rest of the copy,\n * not as a hand-written string at the throw site.\n */\n gymRegistrationDraftTooLarge:\n 'Those gym details are too large to save. Please shorten the descriptions and try again.',\n /**\n * \"Near me\" found nothing inside the radius.\n *\n * Deliberately not the same as `gymSearchNoResults`: this member searched\n * nowhere in particular, so telling them to refine their search would be\n * advice they cannot act on. What helps is a different place or a gym id.\n */\n gymSearchNoneNearby:\n 'No gyms within 50 km of you yet. Try a different location, or enter a gym id if you have one.',\n /** One organisation per account — registering a second is not supported. */\n gymAlreadyRegistered: 'You have already registered a gym on this account.',\n /** Registering a gym is something a MEMBER does, so the profile must exist. */\n gymRegistrationNeedsProfile: 'Finish setting up your profile before registering a gym.',\n /** Every owner action other than editing their own details, before approval. */\n gymAwaitingApproval:\n 'Your gym is waiting for approval. You can manage it once our team has reviewed it.',\n /** The reviewer turned the registration down. */\n gymRegistrationRejected: 'Your gym registration was not approved.',\n organisationNotFound: 'That gym could not be found.',\n\n // ── Platform console ────────────────────────────────────────────────────────\n /** The account is gone, or was already erased. */\n platformUserNotFound: 'That account could not be found.',\n /**\n * An admin acting on their OWN row. Suspending or deleting yourself takes away\n * the access needed to undo it, so the console refuses rather than letting one\n * mis-tap lock the platform's last superadmin out of it.\n */\n platformCannotActOnSelf: 'You cannot do that to your own account.',\n /**\n * Removing the last superadmin. `manage GymApproval` is held by nobody else,\n * so the gym review queue would become unreachable with no way back in short\n * of a database edit.\n */\n platformLastSuperadmin: 'The platform must keep at least one superadmin.',\n /** The typed confirmation did not match the record being deleted. */\n platformConfirmationMismatch: 'That did not match. Nothing was changed.',\n /** Two reviewers acting on the same registration; the second one loses. */\n organisationAlreadyDecided: 'Someone has already reviewed this gym.',\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 planVariantRequired: 'Add at least one pricing variant before saving the plan.',\n planNameTaken: 'This center already has a plan with that name. Pick another name.',\n planVariantDurationRequired: 'Enter how long this variant runs for',\n planVariantPriceRequired: 'Enter the price for this variant',\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 announcementAlreadySent: 'That announcement has already gone out, so it cannot be cancelled.',\n\n // Notification feed\n notificationHideFailed: 'Could not hide that notification.',\n notificationRestoreFailed: 'Could not restore that notification.',\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 * Not an error the member can act on by trying harder — it is the whole\n * point of the flow, so it reads as a status rather than a refusal.\n */\n membershipAwaitingPayment:\n 'Your gym is checking your payment. The plan starts as soon as they confirm it.',\n membershipNoPendingRequest: 'There is no plan waiting for confirmation.',\n /**\n * The owner reached for the roster toggle on a plan that has not been paid\n * for. Points at the action that WOULD start it rather than just refusing.\n */\n membershipPendingUseConfirm:\n 'This plan is waiting for payment confirmation. Mark the payment done to start it.',\n /** Two managers opening the same request; the second one loses. */\n membershipAlreadyDecided: 'Someone has already answered this payment.',\n membershipConfirmFailed: 'Could not confirm that payment. Please try again.',\n membershipRejectFailed: 'Could not turn down that payment. Please try again.',\n membershipWithdrawFailed: 'Could not withdraw that request. Please try again.',\n /** The URL did not come from our own storage — a broken or hostile client. */\n paymentProofInvalid: 'That payment screenshot could not be attached. Upload it again.',\n\n // Training\n workoutPlanRequired: 'Choose a plan to assign',\n // Roster (owner console)\n attendanceMarkFailed: 'Could not save that attendance. Please try again.',\n memberStatusFailed: \"Could not change that member's status. Please try again.\",\n memberDeleteFailed: 'Could not remove that member. Please try again.',\n memberPlanAssignFailed: 'Could not update that plan. Please try again.',\n memberPauseFailed: 'Could not pause that membership. Please try again.',\n memberCancelFailed: 'Could not cancel that membership. Please try again.',\n paymentCollectFailed: 'Could not record that payment. Please try again.',\n paymentAmountRequired: 'Enter the amount collected',\n pauseDatesRequired: 'Pick both pause dates',\n pauseDatesOrder: 'The end date has to be after the start date',\n /**\n * Attendance, workout plans and payments are all keyed on the central-auth\n * user, which an invited record does not have until the person claims it.\n */\n memberNotClaimed: 'This member has not claimed their invite yet.',\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 * Ticking an exercise off records it against a live session, so there has to\n * be one. Shown when a member opens an exercise outside a workout and taps\n * Mark As Done.\n */\n noSessionToTrack: 'Check in at your gym to track this workout.',\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 /** v1 memberships are single-center — leave the current gym first. */\n membershipRequestAlreadyAtAnotherGym:\n 'You already belong to a gym. Cancel that membership before joining another.',\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 /** Actions that only make sense once a gym has taken the member on. */\n noGymJoined: 'Join a gym first. You can find one from your Me tab.',\n /** Searching by name needs somewhere to look — see `gymSearchQuerySchema`. */\n gymSearchStateRequired: 'Pick a state to search gyms by name.',\n gymSearchNoResults: 'No gyms matched. Try a different area, or enter the gym id.',\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 /**\n * The caller MAY do this — their subscription simply does not stretch to it.\n *\n * Deliberately NOT `FORBIDDEN`, and the distinction is load-bearing on the\n * client. A role denial means \"this is not yours\"; the answer is to go away.\n * A plan denial means \"this costs money\"; the answer is an upgrade prompt with\n * the price on it. A client that cannot tell them apart either shows an\n * upgrade sheet to a trainer who will never be allowed to buy one, or shows a\n * bare 403 to an owner who would happily have paid.\n *\n * Always accompanied by `details`: `{ feature, currentPlan, requiredPlan,\n * current?, limit? }` — enough for the prompt to name the feature, the tier\n * that unlocks it, and how full the current allowance is.\n */\n PLAN_LIMIT_EXCEEDED: 'PLAN_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: {\n title: \"You're all set\",\n description: 'Welcome to GymMonk.',\n },\n ownerOnboardingComplete: {\n title: 'Your gym is ready',\n description: 'Welcome to GymMonk.',\n },\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 membershipPlanUpdated: { title: 'Membership plan updated' },\n equipmentSaved: { title: 'Equipment updated' },\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 workoutPlanUpdated: { title: 'Workout plan saved' },\n workoutPlanDeleted: {\n title: 'Workout plan deleted',\n description: 'Members on this plan no longer have one.',\n },\n workoutCreated: { title: 'Workout created' },\n workoutUpdated: { title: 'Workout saved' },\n workoutDeleted: { title: 'Workout removed' },\n workoutPlanAssigned: { title: 'Workout plan assigned' },\n\n // Roster (owner console)\n attendanceMarked: { title: 'Attendance saved' },\n memberMarkedInactive: {\n title: 'Member marked inactive',\n description: 'They can no longer check in.',\n },\n memberMarkedActive: { title: 'Member reactivated' },\n memberDeleted: { title: 'Member removed' },\n /**\n * Says what actually happened. Assigning a plan no longer starts it —\n * nothing does until the payment is marked done — so a bare \"Plan\n * updated\" would send the owner away believing the member could train.\n */\n memberPlanAssigned: {\n title: 'Plan assigned',\n description: 'It starts once you mark the payment done.',\n },\n memberMembershipPaused: { title: 'Membership paused' },\n memberMembershipCancelled: { title: 'Membership cancelled' },\n paymentCollected: { title: 'Payment recorded' },\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 // Membership payment confirmation\n membershipRequested: {\n title: 'Plan requested',\n description: 'It starts once your gym confirms the payment.',\n },\n membershipRequestWithdrawn: {\n title: 'Request withdrawn',\n description: 'Pick another plan whenever you are ready.',\n },\n membershipPaymentConfirmed: {\n title: 'Payment confirmed',\n description: 'The plan is now active for this member.',\n },\n membershipPaymentRejected: {\n title: 'Payment turned down',\n description: 'The member has been told and can send it again.',\n },\n\n // Gym registration + platform review\n gymRegistered: {\n title: 'Gym submitted',\n description: 'Our team will review it and let you know.',\n },\n gymApproved: {\n title: 'Gym approved',\n description: 'It is now listed for members to find.',\n },\n gymRejected: {\n title: 'Gym rejected',\n description: 'The owner has been told.',\n },\n\n // Engagement\n feedbackSubmitted: {\n title: 'Feedback sent',\n description: 'Thank you for helping us improve.',\n },\n complaintSubmitted: {\n title: 'Complaint submitted',\n description: 'Your gym will look into it.',\n },\n announcementSent: { title: 'Announcement sent' },\n announcementScheduled: { title: 'Announcement scheduled' },\n announcementDraftSaved: { title: 'Draft saved' },\n announcementDeleted: { title: 'Announcement deleted' },\n announcementCancelled: {\n title: 'Announcement cancelled',\n description: 'It will not be sent.',\n },\n\n // Notification feed\n notificationHidden: { title: 'Notification hidden' },\n notificationRestored: { title: 'Notification restored' },\n allNotificationsRead: { title: 'All caught up' },\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\n // ── Platform console (superadmin / platform manager) ────────────────────────\n platformRolesUpdated: { title: 'Platform roles updated' },\n platformUserSuspended: {\n title: 'Account suspended',\n description: 'They can no longer sign in.',\n },\n platformUserReinstated: { title: 'Account reinstated' },\n platformUserDeleted: {\n title: 'Account deleted',\n description: 'It is hidden everywhere but can still be restored.',\n },\n platformUserRestored: { title: 'Account restored' },\n platformGymDeleted: {\n title: 'Gym deleted',\n description: 'It is hidden everywhere but can still be restored.',\n },\n platformGymRestored: {\n title: 'Gym restored',\n description: 'It is back in the directory and its owner has the console again.',\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 gymApprovedFor: (gymName: string): ResultMessage => ({\n title: 'Gym approved',\n description: `${gymName} is now live on GymMonk.`,\n }),\n gymRejectedFor: (gymName: string): ResultMessage => ({\n title: 'Gym rejected',\n description: `The owner of ${gymName} has been told why.`,\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 ownerOnboardingFailed: 'We could not create your gym. Please try again.',\n memberOnboardingFailed: 'We could not save your details. Please try again.',\n\n // ── Gym registration (\"list your gym\") ─────────────────────────────────────\n gymRegistrationFailed: 'We could not register your gym. Please try again.',\n /**\n * The wizard could not read what the owner had already saved.\n *\n * It refuses to open on a blank form rather than risk the next Continue\n * overwriting a real draft, so this is the whole screen, not a toast.\n */\n gymRegistrationDraftLoadFailed:\n 'We could not open your saved gym details. Check your connection and try again.',\n /**\n * The step the wizard tried to save is implausibly large.\n *\n * A guard against a client sending something absurd rather than a limit the\n * real form approaches, but it is raised by the SERVER and shown verbatim in\n * the step-save toast — so the wording lives here with the rest of the copy,\n * not as a hand-written string at the throw site.\n */\n gymRegistrationDraftTooLarge:\n 'Those gym details are too large to save. Please shorten the descriptions and try again.',\n /**\n * \"Near me\" found nothing inside the radius.\n *\n * Deliberately not the same as `gymSearchNoResults`: this member searched\n * nowhere in particular, so telling them to refine their search would be\n * advice they cannot act on. What helps is a different place or a gym id.\n */\n gymSearchNoneNearby:\n 'No gyms within 50 km of you yet. Try a different location, or enter a gym id if you have one.',\n /** One organisation per account — registering a second is not supported. */\n gymAlreadyRegistered: 'You have already registered a gym on this account.',\n /** Registering a gym is something a MEMBER does, so the profile must exist. */\n gymRegistrationNeedsProfile: 'Finish setting up your profile before registering a gym.',\n /** Every owner action other than editing their own details, before approval. */\n gymAwaitingApproval:\n 'Your gym is waiting for approval. You can manage it once our team has reviewed it.',\n /** The reviewer turned the registration down. */\n gymRegistrationRejected: 'Your gym registration was not approved.',\n organisationNotFound: 'That gym could not be found.',\n\n // ── Platform console ────────────────────────────────────────────────────────\n /** The account is gone, or was already erased. */\n platformUserNotFound: 'That account could not be found.',\n /**\n * An admin acting on their OWN row. Suspending or deleting yourself takes away\n * the access needed to undo it, so the console refuses rather than letting one\n * mis-tap lock the platform's last superadmin out of it.\n */\n platformCannotActOnSelf: 'You cannot do that to your own account.',\n /**\n * Removing the last superadmin. `manage GymApproval` is held by nobody else,\n * so the gym review queue would become unreachable with no way back in short\n * of a database edit.\n */\n platformLastSuperadmin: 'The platform must keep at least one superadmin.',\n /** The typed confirmation did not match the record being deleted. */\n platformConfirmationMismatch: 'That did not match. Nothing was changed.',\n /** Two reviewers acting on the same registration; the second one loses. */\n organisationAlreadyDecided: 'Someone has already reviewed this gym.',\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 planVariantRequired: 'Add at least one pricing variant before saving the plan.',\n planNameTaken: 'This center already has a plan with that name. Pick another name.',\n planVariantDurationRequired: 'Enter how long this variant runs for',\n planVariantPriceRequired: 'Enter the price for this variant',\n /** A \"discount\" that raises the price is a typo, every time. */\n planFinalPriceAboveOriginal: 'The final price cannot be more than the original price',\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 announcementAlreadySent: 'That announcement has already gone out, so it cannot be cancelled.',\n\n // Notification feed\n notificationHideFailed: 'Could not hide that notification.',\n notificationRestoreFailed: 'Could not restore that notification.',\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 * Not an error the member can act on by trying harder — it is the whole\n * point of the flow, so it reads as a status rather than a refusal.\n */\n membershipAwaitingPayment:\n 'Your gym is checking your payment. The plan starts as soon as they confirm it.',\n membershipNoPendingRequest: 'There is no plan waiting for confirmation.',\n /**\n * The owner reached for the roster toggle on a plan that has not been paid\n * for. Points at the action that WOULD start it rather than just refusing.\n */\n membershipPendingUseConfirm:\n 'This plan is waiting for payment confirmation. Mark the payment done to start it.',\n /** Two managers opening the same request; the second one loses. */\n membershipAlreadyDecided: 'Someone has already answered this payment.',\n membershipConfirmFailed: 'Could not confirm that payment. Please try again.',\n membershipRejectFailed: 'Could not turn down that payment. Please try again.',\n membershipWithdrawFailed: 'Could not withdraw that request. Please try again.',\n /** The URL did not come from our own storage — a broken or hostile client. */\n paymentProofInvalid: 'That payment screenshot could not be attached. Upload it again.',\n\n // Training\n workoutPlanRequired: 'Choose a plan to assign',\n // Roster (owner console)\n attendanceMarkFailed: 'Could not save that attendance. Please try again.',\n memberStatusFailed: \"Could not change that member's status. Please try again.\",\n memberDeleteFailed: 'Could not remove that member. Please try again.',\n memberPlanAssignFailed: 'Could not update that plan. Please try again.',\n memberPauseFailed: 'Could not pause that membership. Please try again.',\n memberCancelFailed: 'Could not cancel that membership. Please try again.',\n paymentCollectFailed: 'Could not record that payment. Please try again.',\n paymentAmountRequired: 'Enter the amount collected',\n pauseDatesRequired: 'Pick both pause dates',\n pauseDatesOrder: 'The end date has to be after the start date',\n /**\n * Attendance, workout plans and payments are all keyed on the central-auth\n * user, which an invited record does not have until the person claims it.\n */\n memberNotClaimed: 'This member has not claimed their invite yet.',\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 * Ticking an exercise off records it against a live session, so there has to\n * be one. Shown when a member opens an exercise outside a workout and taps\n * Mark As Done.\n */\n noSessionToTrack: 'Check in at your gym to track this workout.',\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 /** v1 memberships are single-center — leave the current gym first. */\n membershipRequestAlreadyAtAnotherGym:\n 'You already belong to a gym. Cancel that membership before joining another.',\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 /** Actions that only make sense once a gym has taken the member on. */\n noGymJoined: 'Join a gym first. You can find one from your Me tab.',\n /** Searching by name needs somewhere to look — see `gymSearchQuerySchema`. */\n gymSearchStateRequired: 'Pick a state to search gyms by name.',\n gymSearchNoResults: 'No gyms matched. Try a different area, or enter the gym id.',\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"]}
|