gymmonk-schema 0.4.0 → 0.6.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/common.d.ts +6 -0
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +8 -2
- package/dist/common.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/messages.d.ts +190 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +166 -0
- package/dist/messages.js.map +1 -0
- package/dist/onboarding-form.d.ts +291 -0
- package/dist/onboarding-form.d.ts.map +1 -0
- package/dist/onboarding-form.js +384 -0
- package/dist/onboarding-form.js.map +1 -0
- package/package.json +1 -1
package/dist/common.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export declare const paginationSchema: z.ZodObject<{
|
|
|
24
24
|
limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
25
25
|
}, z.core.$strip>;
|
|
26
26
|
export type Pagination = z.infer<typeof paginationSchema>;
|
|
27
|
+
/**
|
|
28
|
+
* The raw patterns, exported so the onboarding FORM schemas validate a typed
|
|
29
|
+
* value exactly the way the API schemas below do — one rule, both sides.
|
|
30
|
+
*/
|
|
31
|
+
export declare const PHONE_REGEX: RegExp;
|
|
32
|
+
export declare const PINCODE_REGEX: RegExp;
|
|
27
33
|
/**
|
|
28
34
|
* A bare 10-digit Indian mobile number. Stored RAW (no `+91`) app-wide — the
|
|
29
35
|
* country code is display-only. Mirrors the jansathi phone-number standard.
|
package/dist/common.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,OAAO,KAAG,OAGvD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,GAAI,kBAAe,gDAC8B,CAAC;AAIpF,kDAAkD;AAClD,eAAO,MAAM,cAAc,aAGgB,CAAC;AAC5C,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAI5D,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB;;;iBAG3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D;;;GAGG;AACH,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,OAAO,OAAO,KAAG,OAGvD,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,GAAI,kBAAe,gDAC8B,CAAC;AAIpF,kDAAkD;AAClD,eAAO,MAAM,cAAc,aAGgB,CAAC;AAC5C,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAI5D,4DAA4D;AAC5D,eAAO,MAAM,gBAAgB;;;iBAG3B,CAAC;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D;;;GAGG;AACH,eAAO,MAAM,WAAW,QAAiB,CAAC;AAC1C,eAAO,MAAM,aAAa,QAAY,CAAC;AAEvC;;;GAGG;AACH,eAAO,MAAM,WAAW,aAGqC,CAAC;AAC9D,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEhD,6DAA6D;AAC7D,eAAO,MAAM,mBAAmB,6CAA+D,CAAC;AAEhG,wCAAwC;AACxC,eAAO,MAAM,aAAa,aAG8B,CAAC;AACzD,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,4CAG/B,CAAC;AAIF,4DAA4D;AAC5D,eAAO,MAAM,aAAa,aAGoC,CAAC;AAC/D,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,kBAAmC,CAAC;AAClE,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,sCAAsC;AACtC,eAAO,MAAM,eAAe,aAGmC,CAAC;AAChE,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAIxD;;;GAGG;AACH,eAAO,MAAM,cAAc,aAA0B,CAAC;AACtD,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
|
package/dist/common.js
CHANGED
|
@@ -33,6 +33,12 @@ export const paginationSchema = z.object({
|
|
|
33
33
|
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
34
34
|
});
|
|
35
35
|
// ─── Contact primitives (India) ──────────────────────────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* The raw patterns, exported so the onboarding FORM schemas validate a typed
|
|
38
|
+
* value exactly the way the API schemas below do — one rule, both sides.
|
|
39
|
+
*/
|
|
40
|
+
export const PHONE_REGEX = /^[6-9]\d{9}$/;
|
|
41
|
+
export const PINCODE_REGEX = /^\d{6}$/;
|
|
36
42
|
/**
|
|
37
43
|
* A bare 10-digit Indian mobile number. Stored RAW (no `+91`) app-wide — the
|
|
38
44
|
* country code is display-only. Mirrors the jansathi phone-number standard.
|
|
@@ -40,14 +46,14 @@ export const paginationSchema = z.object({
|
|
|
40
46
|
export const phoneSchema = z
|
|
41
47
|
.string()
|
|
42
48
|
.trim()
|
|
43
|
-
.regex(
|
|
49
|
+
.regex(PHONE_REGEX, 'Enter a valid 10-digit mobile number');
|
|
44
50
|
/** Optional phone — empty/whitespace becomes `undefined`. */
|
|
45
51
|
export const optionalPhoneSchema = z.preprocess(emptyStringToUndefined, phoneSchema.optional());
|
|
46
52
|
/** A 6-digit Indian postal PIN code. */
|
|
47
53
|
export const pincodeSchema = z
|
|
48
54
|
.string()
|
|
49
55
|
.trim()
|
|
50
|
-
.regex(
|
|
56
|
+
.regex(PINCODE_REGEX, 'Enter a valid 6-digit pincode');
|
|
51
57
|
/** Optional email — empty/whitespace becomes `undefined`, else validated. */
|
|
52
58
|
export const optionalEmailSchema = z.preprocess(emptyStringToUndefined, z.email('Enter a valid email').optional());
|
|
53
59
|
// ─── Date & time primitives ──────────────────────────────────────────────────
|
package/dist/common.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAc,EAAW,EAAE;IAChE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IACvE,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,yEAAyE;AACzE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,SAAS,GAAG,GAAG,EAAE,EAAE,CACvD,CAAC,CAAC,UAAU,CAAC,sBAAsB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;AAEpF,gFAAgF;AAEhF,kDAAkD;AAClD,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC;KAC5B,MAAM,EAAE;KACR,IAAI,EAAE;KACN,KAAK,CAAC,mBAAmB,EAAE,YAAY,CAAC,CAAC;AAG5C,gFAAgF;AAEhF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CAC3D,CAAC,CAAC;AAGH,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC;KACzB,MAAM,EAAE;KACR,IAAI,EAAE;KACN,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../src/common.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * gymmonk-schema — Shared helpers and primitives\n * ==============================================\n * The stable building blocks reused across every GymMonk domain schema.\n * Mirrors the `dating-schema` / `near-me-schema` idiom: pure Zod, const-object\n * enums fed to `z.enum(...)`, NodeNext `.js` import specifiers.\n *\n * @module gymmonk-schema/common\n */\n\nimport { z } from 'zod';\n\n// ─── String helpers ──────────────────────────────────────────────────────────\n\n/**\n * Converts empty / whitespace-only strings to `undefined`\n * so Zod `.optional()` treats them as \"not provided\".\n */\nexport const emptyStringToUndefined = (value: unknown): unknown => {\n if (typeof value === 'string' && value.trim() === '') return undefined;\n return value;\n};\n\n/** Optional trimmed string. Empty strings are coerced to `undefined`. */\nexport const optionalTrimmedString = (maxLength = 100) =>\n z.preprocess(emptyStringToUndefined, z.string().trim().max(maxLength).optional());\n\n// ─── Identifiers ─────────────────────────────────────────────────────────────\n\n/** A 24-character hex MongoDB ObjectId string. */\nexport const objectIdSchema = z\n .string()\n .trim()\n .regex(/^[0-9a-fA-F]{24}$/, 'Invalid id');\nexport type ObjectIdString = z.infer<typeof objectIdSchema>;\n\n// ─── Pagination ──────────────────────────────────────────────────────────────\n\n/** Pagination query params shared across list endpoints. */\nexport const paginationSchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n limit: z.coerce.number().int().min(1).max(100).default(20),\n});\nexport type Pagination = z.infer<typeof paginationSchema>;\n\n// ─── Contact primitives (India) ──────────────────────────────────────────────\n\n/**\n * The raw patterns, exported so the onboarding FORM schemas validate a typed\n * value exactly the way the API schemas below do — one rule, both sides.\n */\nexport const PHONE_REGEX = /^[6-9]\\d{9}$/;\nexport const PINCODE_REGEX = /^\\d{6}$/;\n\n/**\n * A bare 10-digit Indian mobile number. Stored RAW (no `+91`) app-wide — the\n * country code is display-only. Mirrors the jansathi phone-number standard.\n */\nexport const phoneSchema = z\n .string()\n .trim()\n .regex(PHONE_REGEX, 'Enter a valid 10-digit mobile number');\nexport type Phone = z.infer<typeof phoneSchema>;\n\n/** Optional phone — empty/whitespace becomes `undefined`. */\nexport const optionalPhoneSchema = z.preprocess(emptyStringToUndefined, phoneSchema.optional());\n\n/** A 6-digit Indian postal PIN code. */\nexport const pincodeSchema = z\n .string()\n .trim()\n .regex(PINCODE_REGEX, 'Enter a valid 6-digit pincode');\nexport type Pincode = z.infer<typeof pincodeSchema>;\n\n/** Optional email — empty/whitespace becomes `undefined`, else validated. */\nexport const optionalEmailSchema = z.preprocess(\n emptyStringToUndefined,\n z.email('Enter a valid email').optional(),\n);\n\n// ─── Date & time primitives ──────────────────────────────────────────────────\n\n/** A calendar date with no time component, `YYYY-MM-DD`. */\nexport const isoDateSchema = z\n .string()\n .trim()\n .regex(/^\\d{4}-\\d{2}-\\d{2}$/, 'Expected an YYYY-MM-DD date');\nexport type IsoDate = z.infer<typeof isoDateSchema>;\n\n/** A full ISO-8601 timestamp, e.g. `2026-08-06T09:30:00.000Z`. */\nexport const isoDateTimeSchema = z.iso.datetime({ offset: true });\nexport type IsoDateTime = z.infer<typeof isoDateTimeSchema>;\n\n/** Time of day in 24-hour `HH:MM`. */\nexport const timeOfDaySchema = z\n .string()\n .trim()\n .regex(/^([01]\\d|2[0-3]):[0-5]\\d$/, 'Expected an HH:MM time');\nexport type TimeOfDay = z.infer<typeof timeOfDaySchema>;\n\n// ─── Money ───────────────────────────────────────────────────────────────────\n\n/**\n * A whole-rupee (INR) amount. GymMonk prices are whole rupees; format with a\n * `₹` and grouping at the edge, never store the formatted string.\n */\nexport const priceInrSchema = z.number().int().min(0);\nexport type PriceInr = z.infer<typeof priceInrSchema>;\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -21,8 +21,10 @@ export * from './member.js';
|
|
|
21
21
|
export * from './member-profile.js';
|
|
22
22
|
export * from './membership.js';
|
|
23
23
|
export * from './membership-plan.js';
|
|
24
|
+
export * from './messages.js';
|
|
24
25
|
export * from './notification.js';
|
|
25
26
|
export * from './onboarding.js';
|
|
27
|
+
export * from './onboarding-form.js';
|
|
26
28
|
export * from './organisation.js';
|
|
27
29
|
export * from './owner-profile.js';
|
|
28
30
|
export * from './pricing.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;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAE5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,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;AAEH,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAE5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,eAAe,CAAC;AAE9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -26,9 +26,13 @@ export * from './member-profile.js';
|
|
|
26
26
|
export * from './membership.js';
|
|
27
27
|
// ─── Membership ──────────────────────────────────────────────────────────────
|
|
28
28
|
export * from './membership-plan.js';
|
|
29
|
+
// ─── API result messages + error codes ───────────────────────────────────────
|
|
30
|
+
export * from './messages.js';
|
|
29
31
|
// ─── Engagement & analytics ──────────────────────────────────────────────────
|
|
30
32
|
export * from './notification.js';
|
|
31
33
|
export * from './onboarding.js';
|
|
34
|
+
// ─── Onboarding wizard FORM validation (client-side working shapes) ──────────
|
|
35
|
+
export * from './onboarding-form.js';
|
|
32
36
|
// ─── Gym structure & onboarding ──────────────────────────────────────────────
|
|
33
37
|
export * from './organisation.js';
|
|
34
38
|
export * from './owner-profile.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,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,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\nexport * from './announcement.js';\nexport * from './center.js';\n// ─── Foundation ──────────────────────────────────────────────────────────────\nexport * from './common.js';\nexport * from './equipment.js';\n// ─── Training ────────────────────────────────────────────────────────────────\nexport * from './exercise.js';\nexport * from './feedback.js';\n// ─── Uploads ─────────────────────────────────────────────────────────────────\nexport * from './file.js';\nexport * from './home.js';\nexport * from './insights.js';\n// ─── People ──────────────────────────────────────────────────────────────────\nexport * from './member.js';\nexport * from './member-profile.js';\nexport * from './membership.js';\n// ─── Membership ──────────────────────────────────────────────────────────────\nexport * from './membership-plan.js';\n// ─── Engagement & analytics ──────────────────────────────────────────────────\nexport * from './notification.js';\nexport * from './onboarding.js';\n// ─── Gym structure & onboarding ──────────────────────────────────────────────\nexport * from './organisation.js';\nexport * from './owner-profile.js';\nexport * from './pricing.js';\nexport * from './session.js';\nexport * from './shared.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,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,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,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,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\nexport * from './announcement.js';\nexport * from './center.js';\n// ─── Foundation ──────────────────────────────────────────────────────────────\nexport * from './common.js';\nexport * from './equipment.js';\n// ─── Training ────────────────────────────────────────────────────────────────\nexport * from './exercise.js';\nexport * from './feedback.js';\n// ─── Uploads ─────────────────────────────────────────────────────────────────\nexport * from './file.js';\nexport * from './home.js';\nexport * from './insights.js';\n// ─── People ──────────────────────────────────────────────────────────────────\nexport * from './member.js';\nexport * from './member-profile.js';\nexport * from './membership.js';\n// ─── Membership ──────────────────────────────────────────────────────────────\nexport * from './membership-plan.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';\nexport * from './pricing.js';\nexport * from './session.js';\nexport * from './shared.js';\nexport * from './staff.js';\nexport * from './subscription.js';\nexport * from './user.js';\nexport * from './workout-plan.js';\n"]}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gymmonk-schema — API result messages
|
|
3
|
+
* ====================================
|
|
4
|
+
* Single source of truth for every user-facing SUCCESS and ERROR message the
|
|
5
|
+
* product shows, plus the API error codes both sides key off.
|
|
6
|
+
*
|
|
7
|
+
* Why it lives here: the schema package is already the SSOT for data shapes,
|
|
8
|
+
* types and validation. Result copy is part of that contract — the backend
|
|
9
|
+
* raises a message, the client shows it, and neither should invent its own
|
|
10
|
+
* wording. Both repos import from here, so copy changes land in one place and
|
|
11
|
+
* can never drift between server and client.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* backend → `throw new BadRequestError(ERROR_MESSAGE.centerAddressRequired)`
|
|
15
|
+
* frontend → `showSuccess(...toast(SUCCESS_MESSAGE.profileUpdated))`
|
|
16
|
+
*
|
|
17
|
+
* @module gymmonk-schema/messages
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Machine-readable error codes returned in the API envelope
|
|
21
|
+
* (`{ success: false, error: { code, message } }`). The client can branch on a
|
|
22
|
+
* code; the message is what a human reads.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ERROR_CODE: {
|
|
25
|
+
readonly INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
26
|
+
readonly VALIDATION_ERROR: "VALIDATION_ERROR";
|
|
27
|
+
readonly NOT_FOUND: "NOT_FOUND";
|
|
28
|
+
readonly INVALID_TOKEN: "INVALID_TOKEN";
|
|
29
|
+
readonly TOKEN_EXPIRED: "TOKEN_EXPIRED";
|
|
30
|
+
readonly UNAUTHORIZED: "UNAUTHORIZED";
|
|
31
|
+
readonly FORBIDDEN: "FORBIDDEN";
|
|
32
|
+
readonly SESSION_INVALIDATED: "SESSION_INVALIDATED";
|
|
33
|
+
readonly USER_NOT_FOUND: "USER_NOT_FOUND";
|
|
34
|
+
readonly USER_ALREADY_EXISTS: "USER_ALREADY_EXISTS";
|
|
35
|
+
readonly INVALID_ROLE: "INVALID_ROLE";
|
|
36
|
+
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
37
|
+
readonly RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED";
|
|
38
|
+
readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
|
|
39
|
+
};
|
|
40
|
+
export type ErrorCode = (typeof ERROR_CODE)[keyof typeof ERROR_CODE];
|
|
41
|
+
/** A result message: a headline and an optional quieter second line. */
|
|
42
|
+
export interface ResultMessage {
|
|
43
|
+
title: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Confirmation shown after a write succeeds, keyed by action. Interpolating
|
|
48
|
+
* entries are functions so the caller supplies the value without re-wording.
|
|
49
|
+
*/
|
|
50
|
+
export declare const SUCCESS_MESSAGE: {
|
|
51
|
+
readonly memberOnboardingComplete: {
|
|
52
|
+
readonly title: "You're all set";
|
|
53
|
+
readonly description: "Welcome to GymMonk.";
|
|
54
|
+
};
|
|
55
|
+
readonly ownerOnboardingComplete: {
|
|
56
|
+
readonly title: "Your gym is ready";
|
|
57
|
+
readonly description: "Welcome to GymMonk.";
|
|
58
|
+
};
|
|
59
|
+
/** Each wizard step is persisted on its own, so each one confirms. */
|
|
60
|
+
readonly onboardingStepSaved: {
|
|
61
|
+
readonly title: "Saved";
|
|
62
|
+
};
|
|
63
|
+
readonly profileUpdated: {
|
|
64
|
+
readonly title: "Profile updated";
|
|
65
|
+
};
|
|
66
|
+
readonly organisationUpdated: {
|
|
67
|
+
readonly title: "Organisation updated";
|
|
68
|
+
};
|
|
69
|
+
readonly centerAdded: {
|
|
70
|
+
readonly title: "Center added";
|
|
71
|
+
};
|
|
72
|
+
readonly centerUpdated: {
|
|
73
|
+
readonly title: "Center updated";
|
|
74
|
+
};
|
|
75
|
+
readonly membershipPlanCreated: {
|
|
76
|
+
readonly title: "Membership plan created";
|
|
77
|
+
};
|
|
78
|
+
readonly membershipUpdated: {
|
|
79
|
+
readonly title: "Membership updated";
|
|
80
|
+
};
|
|
81
|
+
readonly membershipRenewed: {
|
|
82
|
+
readonly title: "Membership renewed";
|
|
83
|
+
};
|
|
84
|
+
readonly membershipCancelled: {
|
|
85
|
+
readonly title: "Membership cancelled";
|
|
86
|
+
};
|
|
87
|
+
readonly membershipPauseRequested: {
|
|
88
|
+
readonly title: "Pause request sent";
|
|
89
|
+
readonly description: "Your gym will confirm the pause.";
|
|
90
|
+
};
|
|
91
|
+
readonly subscriptionUpdated: {
|
|
92
|
+
readonly title: "Subscription updated";
|
|
93
|
+
};
|
|
94
|
+
readonly workoutPlanCreated: {
|
|
95
|
+
readonly title: "Workout plan created";
|
|
96
|
+
};
|
|
97
|
+
readonly workoutCreated: {
|
|
98
|
+
readonly title: "Workout created";
|
|
99
|
+
};
|
|
100
|
+
readonly workoutPlanAssigned: {
|
|
101
|
+
readonly title: "Workout plan assigned";
|
|
102
|
+
};
|
|
103
|
+
readonly checkedIn: {
|
|
104
|
+
readonly title: "Checked in";
|
|
105
|
+
readonly description: "Your session has started.";
|
|
106
|
+
};
|
|
107
|
+
readonly checkedOut: {
|
|
108
|
+
readonly title: "Checked out";
|
|
109
|
+
readonly description: "Session saved.";
|
|
110
|
+
};
|
|
111
|
+
readonly feedbackSubmitted: {
|
|
112
|
+
readonly title: "Feedback sent";
|
|
113
|
+
readonly description: "Thank you for helping us improve.";
|
|
114
|
+
};
|
|
115
|
+
readonly complaintSubmitted: {
|
|
116
|
+
readonly title: "Complaint submitted";
|
|
117
|
+
readonly description: "Your gym will look into it.";
|
|
118
|
+
};
|
|
119
|
+
readonly announcementSent: {
|
|
120
|
+
readonly title: "Announcement sent";
|
|
121
|
+
};
|
|
122
|
+
readonly announcementScheduled: {
|
|
123
|
+
readonly title: "Announcement scheduled";
|
|
124
|
+
};
|
|
125
|
+
readonly announcementDraftSaved: {
|
|
126
|
+
readonly title: "Draft saved";
|
|
127
|
+
};
|
|
128
|
+
readonly mpinSet: {
|
|
129
|
+
readonly title: "PIN set";
|
|
130
|
+
};
|
|
131
|
+
readonly mpinReset: {
|
|
132
|
+
readonly title: "Your PIN has been reset.";
|
|
133
|
+
};
|
|
134
|
+
readonly mpinResetLinkSent: {
|
|
135
|
+
readonly title: "We've emailed you a reset link";
|
|
136
|
+
readonly description: "Check your inbox.";
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
export type SuccessMessageKey = keyof typeof SUCCESS_MESSAGE;
|
|
140
|
+
/** Success messages that name the thing they acted on. */
|
|
141
|
+
export declare const SUCCESS_MESSAGE_FOR: {
|
|
142
|
+
readonly memberAdded: (name: string) => ResultMessage;
|
|
143
|
+
readonly staffAdded: (name: string) => ResultMessage;
|
|
144
|
+
readonly inviteClaimed: (gymName: string) => ResultMessage;
|
|
145
|
+
readonly roleSwitched: (roleLabel: string) => ResultMessage;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Failure copy. Transport entries describe a request that never reached the
|
|
149
|
+
* API; the rest are domain rules the backend enforces and the client can also
|
|
150
|
+
* check up-front — same wording either way.
|
|
151
|
+
*/
|
|
152
|
+
export declare const ERROR_MESSAGE: {
|
|
153
|
+
readonly generic: "Something went wrong. Please try again.";
|
|
154
|
+
readonly network: "Network error. Check your connection and try again.";
|
|
155
|
+
readonly timeout: "That took too long. Please try again.";
|
|
156
|
+
readonly sessionExpired: "Your session expired. Please sign in again.";
|
|
157
|
+
readonly formInvalid: "Please check the highlighted fields.";
|
|
158
|
+
readonly roleRequired: "Choose a role to continue";
|
|
159
|
+
readonly memberOnboardingFailed: "We could not save your details. Please try again.";
|
|
160
|
+
readonly ownerOnboardingFailed: "We could not create your gym. Please try again.";
|
|
161
|
+
/** A step failed to persist — the user stays put so nothing is lost. */
|
|
162
|
+
readonly onboardingStepSaveFailed: "We could not save this step. Please try again.";
|
|
163
|
+
readonly centerRequiredForPlan: "Pick the center this plan belongs to.";
|
|
164
|
+
readonly organisationAddressRequired: "Add your organisation address before creating a center.";
|
|
165
|
+
readonly noCenterForPlans: "No gym center found. Add a center before creating plans.";
|
|
166
|
+
readonly noCenterForWorkouts: "No gym center found. Add a center before creating workouts.";
|
|
167
|
+
readonly noCenterForAnnouncements: "No gym center found. Add a center before sending announcements.";
|
|
168
|
+
readonly planUnavailable: "That plan is not available right now.";
|
|
169
|
+
readonly workoutPlanRequired: "Choose a plan to assign";
|
|
170
|
+
readonly checkInFailed: "Could not check you in. Please try again.";
|
|
171
|
+
readonly checkOutFailed: "Could not check you out. Please try again.";
|
|
172
|
+
readonly feedbackRatingRequired: "Choose a rating to submit your feedback";
|
|
173
|
+
readonly complaintIssueRequired: "Choose the issue you are reporting";
|
|
174
|
+
readonly mpinIncorrect: "Incorrect PIN.";
|
|
175
|
+
readonly mpinVerifyFailed: "Couldn't verify. Try again.";
|
|
176
|
+
readonly mpinSetFailed: "Couldn't set your PIN. Try again.";
|
|
177
|
+
readonly mpinMismatch: "Those didn't match. Try again.";
|
|
178
|
+
readonly mpinResetLinkFailed: "Couldn't send the reset link. Try again.";
|
|
179
|
+
readonly mpinResetFailed: "Couldn't reset your PIN. Try again.";
|
|
180
|
+
readonly imageUploadFailed: "Could not upload that image. Please try again.";
|
|
181
|
+
readonly imageProcessFailed: "Could not process that image. Please try again.";
|
|
182
|
+
};
|
|
183
|
+
export type ErrorMessageKey = keyof typeof ERROR_MESSAGE;
|
|
184
|
+
/** Error messages that name a limit or value. */
|
|
185
|
+
export declare const ERROR_MESSAGE_FOR: {
|
|
186
|
+
readonly maxPhotos: (max: number) => string;
|
|
187
|
+
/** Role-switch PIN lockout, in whole minutes remaining. */
|
|
188
|
+
readonly mpinLockedOut: (minutes: number) => string;
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH;;;;GAIG;AACH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;CAwBb,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC;AAIrE,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;;;;;IAI1B,sEAAsE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+CtB,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,eAAe,CAAC;AAE7D,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB;iCACV,MAAM,KAAG,aAAa;gCAIvB,MAAM,KAAG,aAAa;sCAIhB,MAAM,KAAG,aAAa;uCAIrB,MAAM,KAAG,aAAa;CAGxC,CAAC;AAIX;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;;;;IAcxB,wEAAwE;;;;;;;;;;;;;;;;;;;;;CAmC/B,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
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gymmonk-schema — API result messages
|
|
3
|
+
* ====================================
|
|
4
|
+
* Single source of truth for every user-facing SUCCESS and ERROR message the
|
|
5
|
+
* product shows, plus the API error codes both sides key off.
|
|
6
|
+
*
|
|
7
|
+
* Why it lives here: the schema package is already the SSOT for data shapes,
|
|
8
|
+
* types and validation. Result copy is part of that contract — the backend
|
|
9
|
+
* raises a message, the client shows it, and neither should invent its own
|
|
10
|
+
* wording. Both repos import from here, so copy changes land in one place and
|
|
11
|
+
* can never drift between server and client.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* backend → `throw new BadRequestError(ERROR_MESSAGE.centerAddressRequired)`
|
|
15
|
+
* frontend → `showSuccess(...toast(SUCCESS_MESSAGE.profileUpdated))`
|
|
16
|
+
*
|
|
17
|
+
* @module gymmonk-schema/messages
|
|
18
|
+
*/
|
|
19
|
+
// ─── API error codes ─────────────────────────────────────────────────────────
|
|
20
|
+
/**
|
|
21
|
+
* Machine-readable error codes returned in the API envelope
|
|
22
|
+
* (`{ success: false, error: { code, message } }`). The client can branch on a
|
|
23
|
+
* code; the message is what a human reads.
|
|
24
|
+
*/
|
|
25
|
+
export const ERROR_CODE = {
|
|
26
|
+
// General
|
|
27
|
+
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
28
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
29
|
+
NOT_FOUND: 'NOT_FOUND',
|
|
30
|
+
// Auth
|
|
31
|
+
INVALID_TOKEN: 'INVALID_TOKEN',
|
|
32
|
+
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
|
|
33
|
+
UNAUTHORIZED: 'UNAUTHORIZED',
|
|
34
|
+
FORBIDDEN: 'FORBIDDEN',
|
|
35
|
+
SESSION_INVALIDATED: 'SESSION_INVALIDATED',
|
|
36
|
+
// User
|
|
37
|
+
USER_NOT_FOUND: 'USER_NOT_FOUND',
|
|
38
|
+
USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS',
|
|
39
|
+
INVALID_ROLE: 'INVALID_ROLE',
|
|
40
|
+
// Rate limiting
|
|
41
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
42
|
+
RATE_LIMIT_EXCEEDED: 'RATE_LIMIT_EXCEEDED',
|
|
43
|
+
// Upstream / dependency unavailable
|
|
44
|
+
SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
|
|
45
|
+
};
|
|
46
|
+
// ─── Success messages ────────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Confirmation shown after a write succeeds, keyed by action. Interpolating
|
|
49
|
+
* entries are functions so the caller supplies the value without re-wording.
|
|
50
|
+
*/
|
|
51
|
+
export const SUCCESS_MESSAGE = {
|
|
52
|
+
// Onboarding
|
|
53
|
+
memberOnboardingComplete: { title: "You're all set", description: 'Welcome to GymMonk.' },
|
|
54
|
+
ownerOnboardingComplete: { title: 'Your gym is ready', description: 'Welcome to GymMonk.' },
|
|
55
|
+
/** Each wizard step is persisted on its own, so each one confirms. */
|
|
56
|
+
onboardingStepSaved: { title: 'Saved' },
|
|
57
|
+
// Identity & profile
|
|
58
|
+
profileUpdated: { title: 'Profile updated' },
|
|
59
|
+
organisationUpdated: { title: 'Organisation updated' },
|
|
60
|
+
// Gym structure
|
|
61
|
+
centerAdded: { title: 'Center added' },
|
|
62
|
+
centerUpdated: { title: 'Center updated' },
|
|
63
|
+
membershipPlanCreated: { title: 'Membership plan created' },
|
|
64
|
+
// Membership (member-side)
|
|
65
|
+
membershipUpdated: { title: 'Membership updated' },
|
|
66
|
+
membershipRenewed: { title: 'Membership renewed' },
|
|
67
|
+
membershipCancelled: { title: 'Membership cancelled' },
|
|
68
|
+
membershipPauseRequested: {
|
|
69
|
+
title: 'Pause request sent',
|
|
70
|
+
description: 'Your gym will confirm the pause.',
|
|
71
|
+
},
|
|
72
|
+
// Owner SaaS subscription
|
|
73
|
+
subscriptionUpdated: { title: 'Subscription updated' },
|
|
74
|
+
// Training
|
|
75
|
+
workoutPlanCreated: { title: 'Workout plan created' },
|
|
76
|
+
workoutCreated: { title: 'Workout created' },
|
|
77
|
+
workoutPlanAssigned: { title: 'Workout plan assigned' },
|
|
78
|
+
// Sessions
|
|
79
|
+
checkedIn: { title: 'Checked in', description: 'Your session has started.' },
|
|
80
|
+
checkedOut: { title: 'Checked out', description: 'Session saved.' },
|
|
81
|
+
// Engagement
|
|
82
|
+
feedbackSubmitted: { title: 'Feedback sent', description: 'Thank you for helping us improve.' },
|
|
83
|
+
complaintSubmitted: { title: 'Complaint submitted', description: 'Your gym will look into it.' },
|
|
84
|
+
announcementSent: { title: 'Announcement sent' },
|
|
85
|
+
announcementScheduled: { title: 'Announcement scheduled' },
|
|
86
|
+
announcementDraftSaved: { title: 'Draft saved' },
|
|
87
|
+
// Security (role-switch PIN)
|
|
88
|
+
mpinSet: { title: 'PIN set' },
|
|
89
|
+
mpinReset: { title: 'Your PIN has been reset.' },
|
|
90
|
+
mpinResetLinkSent: {
|
|
91
|
+
title: "We've emailed you a reset link",
|
|
92
|
+
description: 'Check your inbox.',
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
/** Success messages that name the thing they acted on. */
|
|
96
|
+
export const SUCCESS_MESSAGE_FOR = {
|
|
97
|
+
memberAdded: (name) => ({
|
|
98
|
+
title: 'Member added',
|
|
99
|
+
description: `Share the invite with ${name}.`,
|
|
100
|
+
}),
|
|
101
|
+
staffAdded: (name) => ({
|
|
102
|
+
title: 'Staff added',
|
|
103
|
+
description: `Share the invite with ${name}.`,
|
|
104
|
+
}),
|
|
105
|
+
inviteClaimed: (gymName) => ({
|
|
106
|
+
title: "You're all set",
|
|
107
|
+
description: `Your account is linked to ${gymName}.`,
|
|
108
|
+
}),
|
|
109
|
+
roleSwitched: (roleLabel) => ({
|
|
110
|
+
title: `Switched to ${roleLabel}`,
|
|
111
|
+
}),
|
|
112
|
+
};
|
|
113
|
+
// ─── Error messages ──────────────────────────────────────────────────────────
|
|
114
|
+
/**
|
|
115
|
+
* Failure copy. Transport entries describe a request that never reached the
|
|
116
|
+
* API; the rest are domain rules the backend enforces and the client can also
|
|
117
|
+
* check up-front — same wording either way.
|
|
118
|
+
*/
|
|
119
|
+
export const ERROR_MESSAGE = {
|
|
120
|
+
// Transport / generic
|
|
121
|
+
generic: 'Something went wrong. Please try again.',
|
|
122
|
+
network: 'Network error. Check your connection and try again.',
|
|
123
|
+
timeout: 'That took too long. Please try again.',
|
|
124
|
+
sessionExpired: 'Your session expired. Please sign in again.',
|
|
125
|
+
// Client-side validation
|
|
126
|
+
formInvalid: 'Please check the highlighted fields.',
|
|
127
|
+
// Onboarding
|
|
128
|
+
roleRequired: 'Choose a role to continue',
|
|
129
|
+
memberOnboardingFailed: 'We could not save your details. Please try again.',
|
|
130
|
+
ownerOnboardingFailed: 'We could not create your gym. Please try again.',
|
|
131
|
+
/** A step failed to persist — the user stays put so nothing is lost. */
|
|
132
|
+
onboardingStepSaveFailed: 'We could not save this step. Please try again.',
|
|
133
|
+
// Gym structure prerequisites
|
|
134
|
+
centerRequiredForPlan: 'Pick the center this plan belongs to.',
|
|
135
|
+
organisationAddressRequired: 'Add your organisation address before creating a center.',
|
|
136
|
+
noCenterForPlans: 'No gym center found. Add a center before creating plans.',
|
|
137
|
+
noCenterForWorkouts: 'No gym center found. Add a center before creating workouts.',
|
|
138
|
+
noCenterForAnnouncements: 'No gym center found. Add a center before sending announcements.',
|
|
139
|
+
// Membership
|
|
140
|
+
planUnavailable: 'That plan is not available right now.',
|
|
141
|
+
// Training
|
|
142
|
+
workoutPlanRequired: 'Choose a plan to assign',
|
|
143
|
+
// Sessions
|
|
144
|
+
checkInFailed: 'Could not check you in. Please try again.',
|
|
145
|
+
checkOutFailed: 'Could not check you out. Please try again.',
|
|
146
|
+
// Engagement
|
|
147
|
+
feedbackRatingRequired: 'Choose a rating to submit your feedback',
|
|
148
|
+
complaintIssueRequired: 'Choose the issue you are reporting',
|
|
149
|
+
// Security (role-switch PIN)
|
|
150
|
+
mpinIncorrect: 'Incorrect PIN.',
|
|
151
|
+
mpinVerifyFailed: "Couldn't verify. Try again.",
|
|
152
|
+
mpinSetFailed: "Couldn't set your PIN. Try again.",
|
|
153
|
+
mpinMismatch: "Those didn't match. Try again.",
|
|
154
|
+
mpinResetLinkFailed: "Couldn't send the reset link. Try again.",
|
|
155
|
+
mpinResetFailed: "Couldn't reset your PIN. Try again.",
|
|
156
|
+
// Uploads
|
|
157
|
+
imageUploadFailed: 'Could not upload that image. Please try again.',
|
|
158
|
+
imageProcessFailed: 'Could not process that image. Please try again.',
|
|
159
|
+
};
|
|
160
|
+
/** Error messages that name a limit or value. */
|
|
161
|
+
export const ERROR_MESSAGE_FOR = {
|
|
162
|
+
maxPhotos: (max) => `You can add up to ${max} photos`,
|
|
163
|
+
/** Role-switch PIN lockout, in whole minutes remaining. */
|
|
164
|
+
mpinLockedOut: (minutes) => `Too many attempts. Try again in ${minutes} minute${minutes > 1 ? 's' : ''}.`,
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +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: { title: \"You're all set\", description: 'Welcome to GymMonk.' },\n ownerOnboardingComplete: { title: 'Your gym is ready', description: 'Welcome to GymMonk.' },\n /** Each wizard step is persisted on its own, so each one confirms. */\n onboardingStepSaved: { title: 'Saved' },\n\n // Identity & profile\n profileUpdated: { title: 'Profile updated' },\n organisationUpdated: { title: 'Organisation updated' },\n\n // Gym structure\n centerAdded: { title: 'Center added' },\n centerUpdated: { title: 'Center updated' },\n membershipPlanCreated: { title: 'Membership plan created' },\n\n // Membership (member-side)\n membershipUpdated: { title: 'Membership updated' },\n membershipRenewed: { title: 'Membership renewed' },\n membershipCancelled: { title: 'Membership cancelled' },\n membershipPauseRequested: {\n title: 'Pause request sent',\n description: 'Your gym will confirm the pause.',\n },\n\n // Owner SaaS subscription\n subscriptionUpdated: { title: 'Subscription updated' },\n\n // Training\n workoutPlanCreated: { title: 'Workout plan created' },\n workoutCreated: { title: 'Workout created' },\n workoutPlanAssigned: { title: 'Workout plan assigned' },\n\n // Sessions\n checkedIn: { title: 'Checked in', description: 'Your session has started.' },\n checkedOut: { title: 'Checked out', description: 'Session saved.' },\n\n // Engagement\n feedbackSubmitted: { title: 'Feedback sent', description: 'Thank you for helping us improve.' },\n complaintSubmitted: { title: 'Complaint submitted', description: 'Your gym will look into it.' },\n announcementSent: { title: 'Announcement sent' },\n announcementScheduled: { title: 'Announcement scheduled' },\n announcementDraftSaved: { title: 'Draft saved' },\n\n // Security (role-switch PIN)\n mpinSet: { title: 'PIN set' },\n mpinReset: { title: 'Your PIN has been reset.' },\n mpinResetLinkSent: {\n title: \"We've emailed you a reset link\",\n description: 'Check your inbox.',\n },\n} as const satisfies Record<string, ResultMessage>;\n\nexport type SuccessMessageKey = keyof typeof SUCCESS_MESSAGE;\n\n/** Success messages that name the thing they acted on. */\nexport const SUCCESS_MESSAGE_FOR = {\n memberAdded: (name: string): ResultMessage => ({\n title: 'Member added',\n description: `Share the invite with ${name}.`,\n }),\n staffAdded: (name: string): ResultMessage => ({\n title: 'Staff added',\n description: `Share the invite with ${name}.`,\n }),\n inviteClaimed: (gymName: string): ResultMessage => ({\n title: \"You're all set\",\n description: `Your account is linked to ${gymName}.`,\n }),\n roleSwitched: (roleLabel: string): ResultMessage => ({\n title: `Switched to ${roleLabel}`,\n }),\n} as const;\n\n// ─── Error messages ──────────────────────────────────────────────────────────\n\n/**\n * Failure copy. Transport entries describe a request that never reached the\n * API; the rest are domain rules the backend enforces and the client can also\n * check up-front — same wording either way.\n */\nexport const ERROR_MESSAGE = {\n // Transport / generic\n generic: 'Something went wrong. Please try again.',\n network: 'Network error. Check your connection and try again.',\n timeout: 'That took too long. Please try again.',\n sessionExpired: 'Your session expired. Please sign in again.',\n\n // Client-side validation\n formInvalid: 'Please check the highlighted fields.',\n\n // Onboarding\n roleRequired: 'Choose a role to continue',\n memberOnboardingFailed: 'We could not save your details. Please try again.',\n ownerOnboardingFailed: 'We could not create your gym. Please try again.',\n /** A step failed to persist — the user stays put so nothing is lost. */\n onboardingStepSaveFailed: 'We could not save this step. Please try again.',\n\n // Gym structure prerequisites\n centerRequiredForPlan: 'Pick the center this plan belongs to.',\n organisationAddressRequired: 'Add your organisation address before creating a center.',\n noCenterForPlans: 'No gym center found. Add a center before creating plans.',\n noCenterForWorkouts: 'No gym center found. Add a center before creating workouts.',\n noCenterForAnnouncements: 'No gym center found. Add a center before sending announcements.',\n\n // Membership\n planUnavailable: 'That plan is not available right now.',\n\n // Training\n workoutPlanRequired: 'Choose a plan to assign',\n\n // Sessions\n checkInFailed: 'Could not check you in. Please try again.',\n checkOutFailed: 'Could not check you out. Please try again.',\n\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"]}
|