gymmonk-schema 0.65.0 → 0.66.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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/late-fee-reminder.d.ts +96 -0
- package/dist/late-fee-reminder.d.ts.map +1 -0
- package/dist/late-fee-reminder.js +61 -0
- package/dist/late-fee-reminder.js.map +1 -0
- package/dist/user.d.ts +14 -1
- package/dist/user.d.ts.map +1 -1
- package/dist/user.js +38 -1
- package/dist/user.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export * from './owner-profile.js';
|
|
|
44
44
|
export * from './payment.js';
|
|
45
45
|
export * from './plan-abilities.js';
|
|
46
46
|
export * from './plan-features-data.js';
|
|
47
|
+
export * from './late-fee-reminder.js';
|
|
47
48
|
export * from './plan-limits.js';
|
|
48
49
|
export * from './platform.js';
|
|
49
50
|
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;AAGH,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAElC,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAE5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAE1B,cAAc,WAAW,CAAC;AAC1B,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;AACxC,cAAc,wBAAwB,CAAC;AAEvC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAElC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,cAAc,CAAC;AAE7B,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;AAElC,cAAc,UAAU,CAAC;AACzB,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,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAE5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAE9B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAE/B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAE9B,cAAc,WAAW,CAAC;AAE1B,cAAc,WAAW,CAAC;AAC1B,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;AACxC,cAAc,wBAAwB,CAAC;AAEvC,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAElC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAEhC,cAAc,sBAAsB,CAAC;AAErC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,cAAc,CAAC;AAE7B,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,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;AAElC,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -60,6 +60,7 @@ export * from './payment.js';
|
|
|
60
60
|
// ─── Platform console (people + gyms directories, role keys) ─────────────────
|
|
61
61
|
export * from './plan-abilities.js';
|
|
62
62
|
export * from './plan-features-data.js';
|
|
63
|
+
export * from './late-fee-reminder.js';
|
|
63
64
|
export * from './plan-limits.js';
|
|
64
65
|
export * from './platform.js';
|
|
65
66
|
export * from './pricing.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,mBAAmB,CAAC;AAClC,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,iBAAiB,CAAC;AAChC,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,WAAW,CAAC;AAC1B,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,cAAc,wBAAwB,CAAC;AACvC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,gFAAgF;AAChF,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,gFAAgF;AAChF,cAAc,cAAc,CAAC;AAC7B,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,iFAAiF;AACjF,cAAc,UAAU,CAAC;AACzB,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 './body-metrics.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 './diet-chart.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 './food.js';\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';\nexport * from './membership-status.js';\n// ─── API result messages + error codes ───────────────────────────────────────\nexport * from './messages.js';\nexport * from './notification.js';\n// ─── Engagement & analytics ──────────────────────────────────────────────────\nexport * from './nutrition.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// ─── Razorpay checkout wire shapes ───────────────────────────────────────────\nexport * from './payment.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';\n// ─── Member fee payment by UPI QR (the gym's own VPA, no gateway) ─────────────\nexport * from './upi.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,mBAAmB,CAAC;AAClC,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,iBAAiB,CAAC;AAChC,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,WAAW,CAAC;AAC1B,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,cAAc,wBAAwB,CAAC;AACvC,gFAAgF;AAChF,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,gFAAgF;AAChF,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,gFAAgF;AAChF,cAAc,sBAAsB,CAAC;AACrC,gFAAgF;AAChF,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,gFAAgF;AAChF,cAAc,cAAc,CAAC;AAC7B,gFAAgF;AAChF,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,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,iFAAiF;AACjF,cAAc,UAAU,CAAC;AACzB,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 './body-metrics.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 './diet-chart.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 './food.js';\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';\nexport * from './membership-status.js';\n// ─── API result messages + error codes ───────────────────────────────────────\nexport * from './messages.js';\nexport * from './notification.js';\n// ─── Engagement & analytics ──────────────────────────────────────────────────\nexport * from './nutrition.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// ─── Razorpay checkout wire shapes ───────────────────────────────────────────\nexport * from './payment.js';\n// ─── Platform console (people + gyms directories, role keys) ─────────────────\nexport * from './plan-abilities.js';\nexport * from './plan-features-data.js';\nexport * from './late-fee-reminder.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';\n// ─── Member fee payment by UPI QR (the gym's own VPA, no gateway) ─────────────\nexport * from './upi.js';\nexport * from './user.js';\nexport * from './workout-plan.js';\n"]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Late-fee reminder contracts
|
|
3
|
+
* ===========================
|
|
4
|
+
* What the owner's WhatsApp reminder screen reads. One shape, defined once,
|
|
5
|
+
* because the backend builds it and the web client renders it and a drift
|
|
6
|
+
* between the two shows up as an empty card rather than as an error.
|
|
7
|
+
*
|
|
8
|
+
* ── ⚠️ THREE ZEROES THAT MEAN DIFFERENT THINGS ──────────────────────────────
|
|
9
|
+
* `configured: false` is the PLATFORM having no WhatsApp credentials — nobody
|
|
10
|
+
* on GymMonk is being messaged, and no gym can fix it.
|
|
11
|
+
* `cap: 0` is THIS GYM on a plan without the allowance. An upgrade fixes it.
|
|
12
|
+
* `used: 0` is a gym with the allowance and nobody overdue, which is healthy.
|
|
13
|
+
*
|
|
14
|
+
* They are separate fields rather than one number because a screen that renders
|
|
15
|
+
* "0 reminders" for all three tells an owner nothing about which one to act on,
|
|
16
|
+
* and two of the three are not problems at all.
|
|
17
|
+
*
|
|
18
|
+
* @module late-fee-reminder
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
/**
|
|
22
|
+
* Our lifecycle for one reminder, which is NOT Meta's.
|
|
23
|
+
*
|
|
24
|
+
* `sending` is a reservation written before the gateway is called, so a row can
|
|
25
|
+
* sit here briefly during a sweep. `undeliverable` is terminal and, importantly,
|
|
26
|
+
* does not consume the gym's allowance.
|
|
27
|
+
*/
|
|
28
|
+
export declare const lateFeeNoticeStatusSchema: z.ZodEnum<{
|
|
29
|
+
sent: "sent";
|
|
30
|
+
sending: "sending";
|
|
31
|
+
undeliverable: "undeliverable";
|
|
32
|
+
}>;
|
|
33
|
+
export type LateFeeNoticeStatus = z.infer<typeof lateFeeNoticeStatusSchema>;
|
|
34
|
+
/**
|
|
35
|
+
* META's outcome, which arrives on a webhook minutes after the send.
|
|
36
|
+
*
|
|
37
|
+
* ⚠️ `sent` here is not the status above. Ours means "Meta accepted it"; this
|
|
38
|
+
* one means Meta says it left. Null means no webhook has arrived yet, which is
|
|
39
|
+
* ordinary for a reminder sent seconds ago and suspicious for one sent a day
|
|
40
|
+
* ago — the difference is worth showing rather than flattening to "sent".
|
|
41
|
+
*/
|
|
42
|
+
export declare const lateFeeDeliveryStatusSchema: z.ZodEnum<{
|
|
43
|
+
failed: "failed";
|
|
44
|
+
sent: "sent";
|
|
45
|
+
read: "read";
|
|
46
|
+
delivered: "delivered";
|
|
47
|
+
}>;
|
|
48
|
+
export type LateFeeDeliveryStatus = z.infer<typeof lateFeeDeliveryStatusSchema>;
|
|
49
|
+
/**
|
|
50
|
+
* One row of the owner's history.
|
|
51
|
+
*
|
|
52
|
+
* `memberName` is nullable because the stored notice deliberately holds no
|
|
53
|
+
* personal data — the name is resolved at read time from the enrolment, and a
|
|
54
|
+
* member who has since been erased correctly resolves to nothing.
|
|
55
|
+
*/
|
|
56
|
+
export declare const lateFeeReminderEntrySchema: z.ZodObject<{
|
|
57
|
+
id: z.ZodString;
|
|
58
|
+
memberName: z.ZodNullable<z.ZodString>;
|
|
59
|
+
status: z.ZodEnum<{
|
|
60
|
+
sent: "sent";
|
|
61
|
+
sending: "sending";
|
|
62
|
+
undeliverable: "undeliverable";
|
|
63
|
+
}>;
|
|
64
|
+
deliveryStatus: z.ZodNullable<z.ZodEnum<{
|
|
65
|
+
failed: "failed";
|
|
66
|
+
sent: "sent";
|
|
67
|
+
read: "read";
|
|
68
|
+
delivered: "delivered";
|
|
69
|
+
}>>;
|
|
70
|
+
sentAt: z.ZodNullable<z.ZodString>;
|
|
71
|
+
}, z.core.$strip>;
|
|
72
|
+
export type LateFeeReminderEntry = z.infer<typeof lateFeeReminderEntrySchema>;
|
|
73
|
+
export declare const lateFeeReminderUsageSchema: z.ZodObject<{
|
|
74
|
+
configured: z.ZodBoolean;
|
|
75
|
+
cap: z.ZodNumber;
|
|
76
|
+
used: z.ZodNumber;
|
|
77
|
+
remaining: z.ZodNumber;
|
|
78
|
+
recent: z.ZodArray<z.ZodObject<{
|
|
79
|
+
id: z.ZodString;
|
|
80
|
+
memberName: z.ZodNullable<z.ZodString>;
|
|
81
|
+
status: z.ZodEnum<{
|
|
82
|
+
sent: "sent";
|
|
83
|
+
sending: "sending";
|
|
84
|
+
undeliverable: "undeliverable";
|
|
85
|
+
}>;
|
|
86
|
+
deliveryStatus: z.ZodNullable<z.ZodEnum<{
|
|
87
|
+
failed: "failed";
|
|
88
|
+
sent: "sent";
|
|
89
|
+
read: "read";
|
|
90
|
+
delivered: "delivered";
|
|
91
|
+
}>>;
|
|
92
|
+
sentAt: z.ZodNullable<z.ZodString>;
|
|
93
|
+
}, z.core.$strip>>;
|
|
94
|
+
}, z.core.$strip>;
|
|
95
|
+
export type LateFeeReminderUsage = z.infer<typeof lateFeeReminderUsageSchema>;
|
|
96
|
+
//# sourceMappingURL=late-fee-reminder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"late-fee-reminder.d.ts","sourceRoot":"","sources":["../src/late-fee-reminder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB;;;;EAA+C,CAAC;AACtF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE5E;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B;;;;;EAAkD,CAAC;AAC3F,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAC;AAEhF;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;iBAMrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAE9E,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;iBAQrC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Late-fee reminder contracts
|
|
3
|
+
* ===========================
|
|
4
|
+
* What the owner's WhatsApp reminder screen reads. One shape, defined once,
|
|
5
|
+
* because the backend builds it and the web client renders it and a drift
|
|
6
|
+
* between the two shows up as an empty card rather than as an error.
|
|
7
|
+
*
|
|
8
|
+
* ── ⚠️ THREE ZEROES THAT MEAN DIFFERENT THINGS ──────────────────────────────
|
|
9
|
+
* `configured: false` is the PLATFORM having no WhatsApp credentials — nobody
|
|
10
|
+
* on GymMonk is being messaged, and no gym can fix it.
|
|
11
|
+
* `cap: 0` is THIS GYM on a plan without the allowance. An upgrade fixes it.
|
|
12
|
+
* `used: 0` is a gym with the allowance and nobody overdue, which is healthy.
|
|
13
|
+
*
|
|
14
|
+
* They are separate fields rather than one number because a screen that renders
|
|
15
|
+
* "0 reminders" for all three tells an owner nothing about which one to act on,
|
|
16
|
+
* and two of the three are not problems at all.
|
|
17
|
+
*
|
|
18
|
+
* @module late-fee-reminder
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
/**
|
|
22
|
+
* Our lifecycle for one reminder, which is NOT Meta's.
|
|
23
|
+
*
|
|
24
|
+
* `sending` is a reservation written before the gateway is called, so a row can
|
|
25
|
+
* sit here briefly during a sweep. `undeliverable` is terminal and, importantly,
|
|
26
|
+
* does not consume the gym's allowance.
|
|
27
|
+
*/
|
|
28
|
+
export const lateFeeNoticeStatusSchema = z.enum(['sending', 'sent', 'undeliverable']);
|
|
29
|
+
/**
|
|
30
|
+
* META's outcome, which arrives on a webhook minutes after the send.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ `sent` here is not the status above. Ours means "Meta accepted it"; this
|
|
33
|
+
* one means Meta says it left. Null means no webhook has arrived yet, which is
|
|
34
|
+
* ordinary for a reminder sent seconds ago and suspicious for one sent a day
|
|
35
|
+
* ago — the difference is worth showing rather than flattening to "sent".
|
|
36
|
+
*/
|
|
37
|
+
export const lateFeeDeliveryStatusSchema = z.enum(['sent', 'delivered', 'read', 'failed']);
|
|
38
|
+
/**
|
|
39
|
+
* One row of the owner's history.
|
|
40
|
+
*
|
|
41
|
+
* `memberName` is nullable because the stored notice deliberately holds no
|
|
42
|
+
* personal data — the name is resolved at read time from the enrolment, and a
|
|
43
|
+
* member who has since been erased correctly resolves to nothing.
|
|
44
|
+
*/
|
|
45
|
+
export const lateFeeReminderEntrySchema = z.object({
|
|
46
|
+
id: z.string(),
|
|
47
|
+
memberName: z.string().nullable(),
|
|
48
|
+
status: lateFeeNoticeStatusSchema,
|
|
49
|
+
deliveryStatus: lateFeeDeliveryStatusSchema.nullable(),
|
|
50
|
+
sentAt: z.string().nullable(),
|
|
51
|
+
});
|
|
52
|
+
export const lateFeeReminderUsageSchema = z.object({
|
|
53
|
+
/** Platform-wide: are there WhatsApp credentials at all. */
|
|
54
|
+
configured: z.boolean(),
|
|
55
|
+
/** This gym's monthly allowance. 0 means the plan does not include it. */
|
|
56
|
+
cap: z.number(),
|
|
57
|
+
used: z.number(),
|
|
58
|
+
remaining: z.number(),
|
|
59
|
+
recent: z.array(lateFeeReminderEntrySchema),
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=late-fee-reminder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"late-fee-reminder.js","sourceRoot":"","sources":["../src/late-fee-reminder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;AAGtF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAG3F;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,MAAM,EAAE,yBAAyB;IACjC,cAAc,EAAE,2BAA2B,CAAC,QAAQ,EAAE;IACtD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IACjD,4DAA4D;IAC5D,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE;IACvB,0EAA0E;IAC1E,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,0BAA0B,CAAC;CAC5C,CAAC,CAAC","sourcesContent":["/**\n * Late-fee reminder contracts\n * ===========================\n * What the owner's WhatsApp reminder screen reads. One shape, defined once,\n * because the backend builds it and the web client renders it and a drift\n * between the two shows up as an empty card rather than as an error.\n *\n * ── ⚠️ THREE ZEROES THAT MEAN DIFFERENT THINGS ──────────────────────────────\n * `configured: false` is the PLATFORM having no WhatsApp credentials — nobody\n * on GymMonk is being messaged, and no gym can fix it.\n * `cap: 0` is THIS GYM on a plan without the allowance. An upgrade fixes it.\n * `used: 0` is a gym with the allowance and nobody overdue, which is healthy.\n *\n * They are separate fields rather than one number because a screen that renders\n * \"0 reminders\" for all three tells an owner nothing about which one to act on,\n * and two of the three are not problems at all.\n *\n * @module late-fee-reminder\n */\n\nimport { z } from 'zod';\n\n/**\n * Our lifecycle for one reminder, which is NOT Meta's.\n *\n * `sending` is a reservation written before the gateway is called, so a row can\n * sit here briefly during a sweep. `undeliverable` is terminal and, importantly,\n * does not consume the gym's allowance.\n */\nexport const lateFeeNoticeStatusSchema = z.enum(['sending', 'sent', 'undeliverable']);\nexport type LateFeeNoticeStatus = z.infer<typeof lateFeeNoticeStatusSchema>;\n\n/**\n * META's outcome, which arrives on a webhook minutes after the send.\n *\n * ⚠️ `sent` here is not the status above. Ours means \"Meta accepted it\"; this\n * one means Meta says it left. Null means no webhook has arrived yet, which is\n * ordinary for a reminder sent seconds ago and suspicious for one sent a day\n * ago — the difference is worth showing rather than flattening to \"sent\".\n */\nexport const lateFeeDeliveryStatusSchema = z.enum(['sent', 'delivered', 'read', 'failed']);\nexport type LateFeeDeliveryStatus = z.infer<typeof lateFeeDeliveryStatusSchema>;\n\n/**\n * One row of the owner's history.\n *\n * `memberName` is nullable because the stored notice deliberately holds no\n * personal data — the name is resolved at read time from the enrolment, and a\n * member who has since been erased correctly resolves to nothing.\n */\nexport const lateFeeReminderEntrySchema = z.object({\n id: z.string(),\n memberName: z.string().nullable(),\n status: lateFeeNoticeStatusSchema,\n deliveryStatus: lateFeeDeliveryStatusSchema.nullable(),\n sentAt: z.string().nullable(),\n});\nexport type LateFeeReminderEntry = z.infer<typeof lateFeeReminderEntrySchema>;\n\nexport const lateFeeReminderUsageSchema = z.object({\n /** Platform-wide: are there WhatsApp credentials at all. */\n configured: z.boolean(),\n /** This gym's monthly allowance. 0 means the plan does not include it. */\n cap: z.number(),\n used: z.number(),\n remaining: z.number(),\n recent: z.array(lateFeeReminderEntrySchema),\n});\nexport type LateFeeReminderUsage = z.infer<typeof lateFeeReminderUsageSchema>;\n"]}
|
package/dist/user.d.ts
CHANGED
|
@@ -85,9 +85,21 @@ export declare const notificationPreferencesSchema: z.ZodObject<{
|
|
|
85
85
|
dailyWorkout: z.ZodBoolean;
|
|
86
86
|
calorieBurn: z.ZodBoolean;
|
|
87
87
|
membershipRenewal: z.ZodBoolean;
|
|
88
|
+
whatsappLateFee: z.ZodBoolean;
|
|
88
89
|
}, z.core.$strip>;
|
|
89
90
|
export type NotificationPreferences = z.infer<typeof notificationPreferencesSchema>;
|
|
90
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Everything on, nothing paused — what a new account gets, and what an account
|
|
93
|
+
* with no stored block resolves to.
|
|
94
|
+
*
|
|
95
|
+
* ⚠️ `whatsappLateFee: true` is doing real work here, not padding the object.
|
|
96
|
+
* Every account created before this field existed has NO stored preferences at
|
|
97
|
+
* all, so they all read through to this default — which is the intended
|
|
98
|
+
* outcome under the opt-out ruling, but it means the value below is the actual
|
|
99
|
+
* consent position of the entire existing member base. Changing it to `false`
|
|
100
|
+
* silently switches the feature off for everyone who has never opened the
|
|
101
|
+
* preference screen.
|
|
102
|
+
*/
|
|
91
103
|
export declare const DEFAULT_NOTIFICATION_PREFERENCES: NotificationPreferences;
|
|
92
104
|
export declare const updateNotificationPreferencesBodySchema: z.ZodObject<{
|
|
93
105
|
pauseAll: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -95,6 +107,7 @@ export declare const updateNotificationPreferencesBodySchema: z.ZodObject<{
|
|
|
95
107
|
dailyWorkout: z.ZodOptional<z.ZodBoolean>;
|
|
96
108
|
calorieBurn: z.ZodOptional<z.ZodBoolean>;
|
|
97
109
|
membershipRenewal: z.ZodOptional<z.ZodBoolean>;
|
|
110
|
+
whatsappLateFee: z.ZodOptional<z.ZodBoolean>;
|
|
98
111
|
}, z.core.$strip>;
|
|
99
112
|
export type UpdateNotificationPreferencesBody = z.infer<typeof updateNotificationPreferencesBodySchema>;
|
|
100
113
|
/**
|
package/dist/user.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../src/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,kFAAkF;AAClF,eAAO,MAAM,aAAa;;;;;EAAkE,CAAC;AAC7F,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,eAAO,MAAM,gBAAgB;;;;;EAAyD,CAAC;AACvF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;iBAiCnB,CAAC;AACH,MAAM,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAI1C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB;;;;;iBAQ/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B
|
|
1
|
+
{"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../src/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,kFAAkF;AAClF,eAAO,MAAM,aAAa;;;;;EAAkE,CAAC;AAC7F,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,eAAO,MAAM,gBAAgB;;;;;EAAyD,CAAC;AACvF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI1D,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;iBAiCnB,CAAC;AACH,MAAM,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AAI1C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB;;;;;iBAQ/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAIlE;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B;;;;;;;iBAmCxC,CAAC;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAEpF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gCAAgC,EAAE,uBAO9C,CAAC;AAEF,eAAO,MAAM,uCAAuC;;;;;;;iBAA0C,CAAC;AAC/F,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CACrD,OAAO,uCAAuC,CAC/C,CAAC;AAIF;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B;;iBAEvC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC"}
|
package/dist/user.js
CHANGED
|
@@ -91,14 +91,51 @@ export const notificationPreferencesSchema = z.object({
|
|
|
91
91
|
calorieBurn: z.boolean(),
|
|
92
92
|
/** "Your membership expires in N days". */
|
|
93
93
|
membershipRenewal: z.boolean(),
|
|
94
|
+
/**
|
|
95
|
+
* The WhatsApp late-fee reminder, and the ONLY switch a member has for it.
|
|
96
|
+
*
|
|
97
|
+
* ── WHY IT IS ON BY DEFAULT, WHICH NONE OF THE OTHERS HAD TO JUSTIFY ──────
|
|
98
|
+
* Product ruling: enrolling at a gym is consent to be told over WhatsApp that
|
|
99
|
+
* a fee is overdue. So this is opt-OUT, unlike anything else here, and that
|
|
100
|
+
* asymmetry is deliberate rather than an oversight — a reminder nobody
|
|
101
|
+
* receives is a debt nobody is told about.
|
|
102
|
+
*
|
|
103
|
+
* ⚠️ IT IS ALSO THE ONLY PREFERENCE THAT COSTS MONEY TO IGNORE. Meta bills
|
|
104
|
+
* per conversation, so a send to somebody who has said no is a charge against
|
|
105
|
+
* the gym's allowance AND a mark against the quality rating of the one
|
|
106
|
+
* business number every gym on the platform shares.
|
|
107
|
+
*
|
|
108
|
+
* ⚠️ `pauseAll` SILENCES THIS TOO. That follows this object's own stated
|
|
109
|
+
* contract — a paused account must not be woken by a kind added later — and
|
|
110
|
+
* it is the safe direction: under-sending is recoverable, an unwanted paid
|
|
111
|
+
* message is not.
|
|
112
|
+
*
|
|
113
|
+
* ⚠️ This flag is NOT the whole answer, because it can only speak for people
|
|
114
|
+
* who have an account. A member the gym wrote down but who never claimed
|
|
115
|
+
* theirs still receives reminders and can still reply STOP, which is recorded
|
|
116
|
+
* against their number instead. Both are checked before a send.
|
|
117
|
+
*/
|
|
118
|
+
whatsappLateFee: z.boolean(),
|
|
94
119
|
});
|
|
95
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* Everything on, nothing paused — what a new account gets, and what an account
|
|
122
|
+
* with no stored block resolves to.
|
|
123
|
+
*
|
|
124
|
+
* ⚠️ `whatsappLateFee: true` is doing real work here, not padding the object.
|
|
125
|
+
* Every account created before this field existed has NO stored preferences at
|
|
126
|
+
* all, so they all read through to this default — which is the intended
|
|
127
|
+
* outcome under the opt-out ruling, but it means the value below is the actual
|
|
128
|
+
* consent position of the entire existing member base. Changing it to `false`
|
|
129
|
+
* silently switches the feature off for everyone who has never opened the
|
|
130
|
+
* preference screen.
|
|
131
|
+
*/
|
|
96
132
|
export const DEFAULT_NOTIFICATION_PREFERENCES = {
|
|
97
133
|
pauseAll: false,
|
|
98
134
|
gymTime: true,
|
|
99
135
|
dailyWorkout: true,
|
|
100
136
|
calorieBurn: true,
|
|
101
137
|
membershipRenewal: true,
|
|
138
|
+
whatsappLateFee: true,
|
|
102
139
|
};
|
|
103
140
|
export const updateNotificationPreferencesBodySchema = notificationPreferencesSchema.partial();
|
|
104
141
|
// ─── Account deletion ────────────────────────────────────────────────────────
|
package/dist/user.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.js","sourceRoot":"","sources":["../src/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEpG,gFAAgF;AAEhF,kFAAkF;AAClF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAG7F,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;AAGvF,gFAAgF;AAEhF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,EAAE,EAAE,cAAc;IAClB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE;IAC1B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC;IAChC,MAAM,EAAE,gBAAgB;IACxB,SAAS,EAAE,iBAAiB;IAC5B,SAAS,EAAE,iBAAiB;CAC7B,CAAC,CAAC;AAGH,gFAAgF;AAEhF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,SAAS,EAAE,qBAAqB,CAAC,EAAE,CAAC;IACpC,QAAQ,EAAE,qBAAqB,CAAC,EAAE,CAAC;IACnC,KAAK,EAAE,CAAC,CAAC,UAAU,CACjB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EACjE,WAAW,CAAC,QAAQ,EAAE,CACvB;IACD,MAAM,EAAE,qBAAqB,CAAC,IAAI,CAAC;CACpC,CAAC,CAAC;AAGH,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAAC;IACpD,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,yDAAyD;IACzD,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,uCAAuC;IACvC,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE;IACzB,sDAAsD;IACtD,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE;IACxB,2CAA2C;IAC3C,iBAAiB,EAAE,CAAC,CAAC,OAAO,EAAE;CAC/B,CAAC,CAAC;AAGH,+DAA+D;AAC/D,MAAM,CAAC,MAAM,gCAAgC,GAA4B;IACvE,QAAQ,EAAE,KAAK;IACf,OAAO,EAAE,IAAI;IACb,YAAY,EAAE,IAAI;IAClB,WAAW,EAAE,IAAI;IACjB,iBAAiB,EAAE,IAAI;CACxB,CAAC;AAEF,MAAM,CAAC,MAAM,uCAAuC,GAAG,6BAA6B,CAAC,OAAO,EAAE,CAAC;AAK/F,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC,MAAM,CAAC;IACnD,WAAW,EAAE,iBAAiB;CAC/B,CAAC,CAAC","sourcesContent":["/**\n * gymmonk-schema — Authenticated user identity (\"me\")\n * ===================================================\n * The identity the whole app boots from — the shape of `GET /users/me`, mirrored\n * from the auth-synced `User`. This is the SSOT for the signed-in user's name,\n * avatar, email, phone, platform roles and status; the member/owner profile\n * modules layer product-specific data on top of it.\n *\n * @module gymmonk-schema/user\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, optionalTrimmedString, phoneSchema } from './common.js';\n\n// ─── Platform roles & status ─────────────────────────────────────────────────\n\n/** Platform-wide roles (empty for most users). Distinct from gym owner/member. */\nexport const appRoleSchema = z.enum(['support_agent', 'moderator', 'manager', 'superadmin']);\nexport type AppRole = z.infer<typeof appRoleSchema>;\n\nexport const userStatusSchema = z.enum(['active', 'inactive', 'suspended', 'deleted']);\nexport type UserStatus = z.infer<typeof userStatusSchema>;\n\n// ─── The \"me\" entity ─────────────────────────────────────────────────────────\n\nexport const meSchema = z.object({\n id: objectIdSchema,\n email: z.string(),\n firstName: z.string(),\n lastName: z.string().nullable(),\n phone: z.string().nullable(),\n /**\n * Whether we have evidence this number reaches this person.\n *\n * True only when they opened a gym's WhatsApp invite link: the gym sent it to\n * that number, so whoever opened it holds that WhatsApp. A number the person\n * typed themselves is false, however true it happens to be — this records\n * what we have SEEN, not what we believe.\n *\n * It matters because the phone is the join key between an app account and a\n * gym's member record, and when two numbers disagree we have to prefer one.\n * The verified one wins.\n *\n * ⚠️ NOT a proof of ownership, and nothing security-bearing may treat it as\n * one. A WhatsApp message forwards, so this says the opener had the message,\n * not that they hold the SIM. It is an OTP's poor relation and is only ever\n * used to break a tie.\n *\n * ⚠️ Distinct from the roster's `verified`, which means \"this person has\n * claimed their record\". Two different facts, and a screen that shows both\n * must not let them read as one.\n */\n phoneVerified: z.boolean(),\n avatar: z.string().nullable(),\n appRoles: z.array(appRoleSchema),\n status: userStatusSchema,\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Me = z.infer<typeof meSchema>;\n\n// ─── Update the caller's own identity fields ─────────────────────────────────\n\n/**\n * Patch the product-owned identity fields (name/phone/avatar).\n * `email` is auth-owned and never updated here.\n *\n * A user has exactly TWO name fields and no denormalised third one. There used\n * to be a `displayName`, seeded once from the central-auth (Google) name and\n * never recomputed — and because every screen read it FIRST, the name a member\n * typed during onboarding was stored and never shown.\n *\n * The name is submitted as a UNIT: the profile screen edits it in one box and\n * splits on the first space, so a body carrying `firstName` is the complete\n * name and an absent `lastName` means the surname was deleted. The API applies\n * that rule (`shared/routes/user.route`), because `optionalTrimmedString` maps\n * an empty string to `undefined` and the wire cannot say \"clear it\" otherwise.\n */\nexport const updateUserBodySchema = z.object({\n firstName: optionalTrimmedString(80),\n lastName: optionalTrimmedString(80),\n phone: z.preprocess(\n (v) => (typeof v === 'string' && v.trim() === '' ? undefined : v),\n phoneSchema.optional(),\n ),\n avatar: optionalTrimmedString(2000),\n});\nexport type UpdateUserBody = z.infer<typeof updateUserBodySchema>;\n\n// ─── Notification preferences ────────────────────────────────────────────────\n\n/**\n * What a person wants to be told about.\n *\n * `pauseAll` is a MASTER switch, not a sixth category: it silences everything\n * without the user having to remember which four they had turned on, and\n * un-pausing restores exactly what they had. The dispatcher checks it first\n * (see `notification/gym.dispatch`), so a paused account cannot be woken by a\n * new notification kind added later that nobody thought to gate.\n */\nexport const notificationPreferencesSchema = z.object({\n pauseAll: z.boolean(),\n /** Reminders before a booked or habitual gym session. */\n gymTime: z.boolean(),\n /** The day's assigned workout plan. */\n dailyWorkout: z.boolean(),\n /** Estimated calorie-burn updates through the day. */\n calorieBurn: z.boolean(),\n /** \"Your membership expires in N days\". */\n membershipRenewal: z.boolean(),\n});\nexport type NotificationPreferences = z.infer<typeof notificationPreferencesSchema>;\n\n/** Everything on, nothing paused — what a new account gets. */\nexport const DEFAULT_NOTIFICATION_PREFERENCES: NotificationPreferences = {\n pauseAll: false,\n gymTime: true,\n dailyWorkout: true,\n calorieBurn: true,\n membershipRenewal: true,\n};\n\nexport const updateNotificationPreferencesBodySchema = notificationPreferencesSchema.partial();\nexport type UpdateNotificationPreferencesBody = z.infer<\n typeof updateNotificationPreferencesBodySchema\n>;\n\n// ─── Account deletion ────────────────────────────────────────────────────────\n\n/**\n * A request to erase the account.\n *\n * Recorded, not executed. Deleting a gym owner cascades into the organisation,\n * its centres, every member and staff record, the membership ledger and the\n * payment history — some of which the business is legally required to keep for\n * a period. So the account is marked, signed out and queued for an operator,\n * which is also what \"right to erasure with a verification step\" looks like.\n */\nexport const accountDeletionRequestSchema = z.object({\n requestedAt: isoDateTimeSchema,\n});\nexport type AccountDeletionRequest = z.infer<typeof accountDeletionRequestSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"user.js","sourceRoot":"","sources":["../src/user.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEpG,gFAAgF;AAEhF,kFAAkF;AAClF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAG7F,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;AAGvF,gFAAgF;AAEhF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,EAAE,EAAE,cAAc;IAClB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE;IAC1B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC;IAChC,MAAM,EAAE,gBAAgB;IACxB,SAAS,EAAE,iBAAiB;IAC5B,SAAS,EAAE,iBAAiB;CAC7B,CAAC,CAAC;AAGH,gFAAgF;AAEhF;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,SAAS,EAAE,qBAAqB,CAAC,EAAE,CAAC;IACpC,QAAQ,EAAE,qBAAqB,CAAC,EAAE,CAAC;IACnC,KAAK,EAAE,CAAC,CAAC,UAAU,CACjB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EACjE,WAAW,CAAC,QAAQ,EAAE,CACvB;IACD,MAAM,EAAE,qBAAqB,CAAC,IAAI,CAAC;CACpC,CAAC,CAAC;AAGH,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC,MAAM,CAAC;IACpD,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,yDAAyD;IACzD,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,uCAAuC;IACvC,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE;IACzB,sDAAsD;IACtD,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE;IACxB,2CAA2C;IAC3C,iBAAiB,EAAE,CAAC,CAAC,OAAO,EAAE;IAC9B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE;CAC7B,CAAC,CAAC;AAGH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAA4B;IACvE,QAAQ,EAAE,KAAK;IACf,OAAO,EAAE,IAAI;IACb,YAAY,EAAE,IAAI;IAClB,WAAW,EAAE,IAAI;IACjB,iBAAiB,EAAE,IAAI;IACvB,eAAe,EAAE,IAAI;CACtB,CAAC;AAEF,MAAM,CAAC,MAAM,uCAAuC,GAAG,6BAA6B,CAAC,OAAO,EAAE,CAAC;AAK/F,gFAAgF;AAEhF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC,MAAM,CAAC;IACnD,WAAW,EAAE,iBAAiB;CAC/B,CAAC,CAAC","sourcesContent":["/**\n * gymmonk-schema — Authenticated user identity (\"me\")\n * ===================================================\n * The identity the whole app boots from — the shape of `GET /users/me`, mirrored\n * from the auth-synced `User`. This is the SSOT for the signed-in user's name,\n * avatar, email, phone, platform roles and status; the member/owner profile\n * modules layer product-specific data on top of it.\n *\n * @module gymmonk-schema/user\n */\n\nimport { z } from 'zod';\nimport { isoDateTimeSchema, objectIdSchema, optionalTrimmedString, phoneSchema } from './common.js';\n\n// ─── Platform roles & status ─────────────────────────────────────────────────\n\n/** Platform-wide roles (empty for most users). Distinct from gym owner/member. */\nexport const appRoleSchema = z.enum(['support_agent', 'moderator', 'manager', 'superadmin']);\nexport type AppRole = z.infer<typeof appRoleSchema>;\n\nexport const userStatusSchema = z.enum(['active', 'inactive', 'suspended', 'deleted']);\nexport type UserStatus = z.infer<typeof userStatusSchema>;\n\n// ─── The \"me\" entity ─────────────────────────────────────────────────────────\n\nexport const meSchema = z.object({\n id: objectIdSchema,\n email: z.string(),\n firstName: z.string(),\n lastName: z.string().nullable(),\n phone: z.string().nullable(),\n /**\n * Whether we have evidence this number reaches this person.\n *\n * True only when they opened a gym's WhatsApp invite link: the gym sent it to\n * that number, so whoever opened it holds that WhatsApp. A number the person\n * typed themselves is false, however true it happens to be — this records\n * what we have SEEN, not what we believe.\n *\n * It matters because the phone is the join key between an app account and a\n * gym's member record, and when two numbers disagree we have to prefer one.\n * The verified one wins.\n *\n * ⚠️ NOT a proof of ownership, and nothing security-bearing may treat it as\n * one. A WhatsApp message forwards, so this says the opener had the message,\n * not that they hold the SIM. It is an OTP's poor relation and is only ever\n * used to break a tie.\n *\n * ⚠️ Distinct from the roster's `verified`, which means \"this person has\n * claimed their record\". Two different facts, and a screen that shows both\n * must not let them read as one.\n */\n phoneVerified: z.boolean(),\n avatar: z.string().nullable(),\n appRoles: z.array(appRoleSchema),\n status: userStatusSchema,\n createdAt: isoDateTimeSchema,\n updatedAt: isoDateTimeSchema,\n});\nexport type Me = z.infer<typeof meSchema>;\n\n// ─── Update the caller's own identity fields ─────────────────────────────────\n\n/**\n * Patch the product-owned identity fields (name/phone/avatar).\n * `email` is auth-owned and never updated here.\n *\n * A user has exactly TWO name fields and no denormalised third one. There used\n * to be a `displayName`, seeded once from the central-auth (Google) name and\n * never recomputed — and because every screen read it FIRST, the name a member\n * typed during onboarding was stored and never shown.\n *\n * The name is submitted as a UNIT: the profile screen edits it in one box and\n * splits on the first space, so a body carrying `firstName` is the complete\n * name and an absent `lastName` means the surname was deleted. The API applies\n * that rule (`shared/routes/user.route`), because `optionalTrimmedString` maps\n * an empty string to `undefined` and the wire cannot say \"clear it\" otherwise.\n */\nexport const updateUserBodySchema = z.object({\n firstName: optionalTrimmedString(80),\n lastName: optionalTrimmedString(80),\n phone: z.preprocess(\n (v) => (typeof v === 'string' && v.trim() === '' ? undefined : v),\n phoneSchema.optional(),\n ),\n avatar: optionalTrimmedString(2000),\n});\nexport type UpdateUserBody = z.infer<typeof updateUserBodySchema>;\n\n// ─── Notification preferences ────────────────────────────────────────────────\n\n/**\n * What a person wants to be told about.\n *\n * `pauseAll` is a MASTER switch, not a sixth category: it silences everything\n * without the user having to remember which four they had turned on, and\n * un-pausing restores exactly what they had. The dispatcher checks it first\n * (see `notification/gym.dispatch`), so a paused account cannot be woken by a\n * new notification kind added later that nobody thought to gate.\n */\nexport const notificationPreferencesSchema = z.object({\n pauseAll: z.boolean(),\n /** Reminders before a booked or habitual gym session. */\n gymTime: z.boolean(),\n /** The day's assigned workout plan. */\n dailyWorkout: z.boolean(),\n /** Estimated calorie-burn updates through the day. */\n calorieBurn: z.boolean(),\n /** \"Your membership expires in N days\". */\n membershipRenewal: z.boolean(),\n /**\n * The WhatsApp late-fee reminder, and the ONLY switch a member has for it.\n *\n * ── WHY IT IS ON BY DEFAULT, WHICH NONE OF THE OTHERS HAD TO JUSTIFY ──────\n * Product ruling: enrolling at a gym is consent to be told over WhatsApp that\n * a fee is overdue. So this is opt-OUT, unlike anything else here, and that\n * asymmetry is deliberate rather than an oversight — a reminder nobody\n * receives is a debt nobody is told about.\n *\n * ⚠️ IT IS ALSO THE ONLY PREFERENCE THAT COSTS MONEY TO IGNORE. Meta bills\n * per conversation, so a send to somebody who has said no is a charge against\n * the gym's allowance AND a mark against the quality rating of the one\n * business number every gym on the platform shares.\n *\n * ⚠️ `pauseAll` SILENCES THIS TOO. That follows this object's own stated\n * contract — a paused account must not be woken by a kind added later — and\n * it is the safe direction: under-sending is recoverable, an unwanted paid\n * message is not.\n *\n * ⚠️ This flag is NOT the whole answer, because it can only speak for people\n * who have an account. A member the gym wrote down but who never claimed\n * theirs still receives reminders and can still reply STOP, which is recorded\n * against their number instead. Both are checked before a send.\n */\n whatsappLateFee: z.boolean(),\n});\nexport type NotificationPreferences = z.infer<typeof notificationPreferencesSchema>;\n\n/**\n * Everything on, nothing paused — what a new account gets, and what an account\n * with no stored block resolves to.\n *\n * ⚠️ `whatsappLateFee: true` is doing real work here, not padding the object.\n * Every account created before this field existed has NO stored preferences at\n * all, so they all read through to this default — which is the intended\n * outcome under the opt-out ruling, but it means the value below is the actual\n * consent position of the entire existing member base. Changing it to `false`\n * silently switches the feature off for everyone who has never opened the\n * preference screen.\n */\nexport const DEFAULT_NOTIFICATION_PREFERENCES: NotificationPreferences = {\n pauseAll: false,\n gymTime: true,\n dailyWorkout: true,\n calorieBurn: true,\n membershipRenewal: true,\n whatsappLateFee: true,\n};\n\nexport const updateNotificationPreferencesBodySchema = notificationPreferencesSchema.partial();\nexport type UpdateNotificationPreferencesBody = z.infer<\n typeof updateNotificationPreferencesBodySchema\n>;\n\n// ─── Account deletion ────────────────────────────────────────────────────────\n\n/**\n * A request to erase the account.\n *\n * Recorded, not executed. Deleting a gym owner cascades into the organisation,\n * its centres, every member and staff record, the membership ledger and the\n * payment history — some of which the business is legally required to keep for\n * a period. So the account is marked, signed out and queued for an operator,\n * which is also what \"right to erasure with a verification step\" looks like.\n */\nexport const accountDeletionRequestSchema = z.object({\n requestedAt: isoDateTimeSchema,\n});\nexport type AccountDeletionRequest = z.infer<typeof accountDeletionRequestSchema>;\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gymmonk-schema",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"description": "Shared Zod schemas, enums and domain types for GymMonk (fitness SaaS) — single source of truth (SSOT) consumed by gymmonk-backend and gymmonk-web-client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|