@bash-app/bash-common 30.331.0 → 30.334.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.
Files changed (59) hide show
  1. package/dist/__tests__/causeTags.test.d.ts +2 -0
  2. package/dist/__tests__/causeTags.test.d.ts.map +1 -0
  3. package/dist/__tests__/causeTags.test.js +94 -0
  4. package/dist/__tests__/causeTags.test.js.map +1 -0
  5. package/dist/__tests__/definitionsHelpers.test.js +6 -2
  6. package/dist/__tests__/definitionsHelpers.test.js.map +1 -1
  7. package/dist/__tests__/hostCrmGrowthPack.test.js +12 -2
  8. package/dist/__tests__/hostCrmGrowthPack.test.js.map +1 -1
  9. package/dist/__tests__/hostPromoter.test.d.ts +2 -0
  10. package/dist/__tests__/hostPromoter.test.d.ts.map +1 -0
  11. package/dist/__tests__/hostPromoter.test.js +79 -0
  12. package/dist/__tests__/hostPromoter.test.js.map +1 -0
  13. package/dist/causeTags.d.ts +42 -0
  14. package/dist/causeTags.d.ts.map +1 -0
  15. package/dist/causeTags.js +173 -0
  16. package/dist/causeTags.js.map +1 -0
  17. package/dist/definitions.d.ts +38 -5
  18. package/dist/definitions.d.ts.map +1 -1
  19. package/dist/definitions.js +47 -7
  20. package/dist/definitions.js.map +1 -1
  21. package/dist/hostCrmAutomation.d.ts +12 -2
  22. package/dist/hostCrmAutomation.d.ts.map +1 -1
  23. package/dist/hostCrmAutomation.js +16 -2
  24. package/dist/hostCrmAutomation.js.map +1 -1
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +2 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/sms/smsTemplates.d.ts +6 -1
  30. package/dist/sms/smsTemplates.d.ts.map +1 -1
  31. package/dist/sms/smsTemplates.js +1 -0
  32. package/dist/sms/smsTemplates.js.map +1 -1
  33. package/dist/utils/__tests__/coatCheckUtils.test.d.ts +2 -0
  34. package/dist/utils/__tests__/coatCheckUtils.test.d.ts.map +1 -0
  35. package/dist/utils/__tests__/coatCheckUtils.test.js +76 -0
  36. package/dist/utils/__tests__/coatCheckUtils.test.js.map +1 -0
  37. package/dist/utils/coatCheckUtils.d.ts +30 -0
  38. package/dist/utils/coatCheckUtils.d.ts.map +1 -0
  39. package/dist/utils/coatCheckUtils.js +85 -0
  40. package/dist/utils/coatCheckUtils.js.map +1 -0
  41. package/dist/utils/slugUtils.d.ts +3 -4
  42. package/dist/utils/slugUtils.d.ts.map +1 -1
  43. package/dist/utils/slugUtils.js +3 -4
  44. package/dist/utils/slugUtils.js.map +1 -1
  45. package/package.json +1 -1
  46. package/prisma/COMPREHENSIVE-MIGRATION-README.md +1 -1
  47. package/prisma/schema.prisma +431 -3
  48. package/src/__tests__/causeTags.test.ts +143 -0
  49. package/src/__tests__/definitionsHelpers.test.ts +11 -1
  50. package/src/__tests__/hostCrmGrowthPack.test.ts +14 -1
  51. package/src/__tests__/hostPromoter.test.ts +133 -0
  52. package/src/causeTags.ts +243 -0
  53. package/src/definitions.ts +77 -14
  54. package/src/hostCrmAutomation.ts +25 -2
  55. package/src/index.ts +2 -0
  56. package/src/sms/smsTemplates.ts +6 -0
  57. package/src/utils/__tests__/coatCheckUtils.test.ts +110 -0
  58. package/src/utils/coatCheckUtils.ts +134 -0
  59. package/src/utils/slugUtils.ts +3 -4
@@ -0,0 +1,110 @@
1
+ import {
2
+ canTransitionCoatCheckStatus,
3
+ clampCoatCheckPurchaseQty,
4
+ coatCheckScanOutcomeForStatus,
5
+ encodeCoatCheckQrPayload,
6
+ parseCoatCheckQrPayload,
7
+ remainingCoatCheckSlots,
8
+ } from "../coatCheckUtils";
9
+
10
+ describe("coatCheckUtils", () => {
11
+ describe("encode/parse QR payload", () => {
12
+ it("round-trips claim QR fields", () => {
13
+ const raw = encodeCoatCheckQrPayload({
14
+ eventId: "evt1",
15
+ entitlementId: "ent1",
16
+ qrToken: "tok-abc",
17
+ });
18
+ const parsed = parseCoatCheckQrPayload(raw);
19
+ expect(parsed).toMatchObject({
20
+ type: "coat_check",
21
+ eventId: "evt1",
22
+ entitlementId: "ent1",
23
+ qrToken: "tok-abc",
24
+ });
25
+ expect(typeof parsed?.timestamp).toBe("number");
26
+ });
27
+
28
+ it("rejects ticket-style payloads", () => {
29
+ expect(
30
+ parseCoatCheckQrPayload(
31
+ JSON.stringify({ eventId: "e", ticketId: "t", qrToken: "x" })
32
+ )
33
+ ).toBeNull();
34
+ });
35
+
36
+ it("rejects invalid JSON", () => {
37
+ expect(parseCoatCheckQrPayload("not-json")).toBeNull();
38
+ });
39
+ });
40
+
41
+ describe("state machine", () => {
42
+ it("allows Paid → CheckedIn → Claimed", () => {
43
+ expect(canTransitionCoatCheckStatus("Paid", "CheckedIn")).toBe(true);
44
+ expect(canTransitionCoatCheckStatus("CheckedIn", "Claimed")).toBe(true);
45
+ });
46
+
47
+ it("rejects illegal transitions", () => {
48
+ expect(canTransitionCoatCheckStatus("Paid", "Claimed")).toBe(false);
49
+ expect(canTransitionCoatCheckStatus("Claimed", "CheckedIn")).toBe(false);
50
+ expect(canTransitionCoatCheckStatus("CheckedIn", "Paid")).toBe(false);
51
+ expect(canTransitionCoatCheckStatus("Paid", "Paid")).toBe(false);
52
+ });
53
+
54
+ it("allows refund/cancel from Paid or CheckedIn", () => {
55
+ expect(canTransitionCoatCheckStatus("Paid", "Refunded")).toBe(true);
56
+ expect(canTransitionCoatCheckStatus("CheckedIn", "Cancelled")).toBe(true);
57
+ expect(canTransitionCoatCheckStatus("Claimed", "Refunded")).toBe(false);
58
+ });
59
+
60
+ it("maps status to scan outcomes", () => {
61
+ expect(coatCheckScanOutcomeForStatus("Paid")).toBe("ready_for_dropoff");
62
+ expect(coatCheckScanOutcomeForStatus("CheckedIn")).toBe("ready_for_claim");
63
+ expect(coatCheckScanOutcomeForStatus("Claimed")).toBe("already_claimed");
64
+ expect(coatCheckScanOutcomeForStatus("Refunded")).toBe(
65
+ "cancelled_or_refunded"
66
+ );
67
+ });
68
+ });
69
+
70
+ describe("inventory helpers", () => {
71
+ it("treats null capacity as unlimited remaining", () => {
72
+ expect(
73
+ remainingCoatCheckSlots({ capacity: null, occupiedCount: 50 })
74
+ ).toBeNull();
75
+ });
76
+
77
+ it("computes remaining slots", () => {
78
+ expect(
79
+ remainingCoatCheckSlots({ capacity: 100, occupiedCount: 40 })
80
+ ).toBe(60);
81
+ expect(
82
+ remainingCoatCheckSlots({ capacity: 10, occupiedCount: 12 })
83
+ ).toBe(0);
84
+ });
85
+
86
+ it("clamps purchase qty by max and remaining", () => {
87
+ expect(
88
+ clampCoatCheckPurchaseQty({
89
+ requested: 5,
90
+ maxPerPurchase: 2,
91
+ remainingSlots: 10,
92
+ })
93
+ ).toBe(2);
94
+ expect(
95
+ clampCoatCheckPurchaseQty({
96
+ requested: 5,
97
+ maxPerPurchase: 10,
98
+ remainingSlots: 1,
99
+ })
100
+ ).toBe(1);
101
+ expect(
102
+ clampCoatCheckPurchaseQty({
103
+ requested: -3,
104
+ maxPerPurchase: 2,
105
+ remainingSlots: null,
106
+ })
107
+ ).toBe(0);
108
+ });
109
+ });
110
+ });
@@ -0,0 +1,134 @@
1
+ import {
2
+ COAT_CHECK_ENTITLEMENT_STATUSES,
3
+ COAT_CHECK_QR_TYPE,
4
+ COAT_CHECK_SCAN_OUTCOMES,
5
+ COAT_CHECK_SLOT_OCCUPYING_STATUSES,
6
+ type CoatCheckEntitlementStatusName,
7
+ type CoatCheckScanOutcome,
8
+ } from "../definitions.js";
9
+
10
+ export type CoatCheckQrPayload = {
11
+ type: typeof COAT_CHECK_QR_TYPE;
12
+ eventId: string;
13
+ entitlementId: string;
14
+ qrToken: string;
15
+ timestamp: number;
16
+ };
17
+
18
+ export function encodeCoatCheckQrPayload(args: {
19
+ eventId: string;
20
+ entitlementId: string;
21
+ qrToken: string;
22
+ }): string {
23
+ const payload: CoatCheckQrPayload = {
24
+ type: COAT_CHECK_QR_TYPE,
25
+ eventId: args.eventId,
26
+ entitlementId: args.entitlementId,
27
+ qrToken: args.qrToken,
28
+ timestamp: Date.now(),
29
+ };
30
+ return JSON.stringify(payload);
31
+ }
32
+
33
+ export function parseCoatCheckQrPayload(
34
+ raw: string
35
+ ): CoatCheckQrPayload | null {
36
+ try {
37
+ const parsed = JSON.parse(raw) as Partial<CoatCheckQrPayload>;
38
+ if (
39
+ parsed?.type !== COAT_CHECK_QR_TYPE ||
40
+ typeof parsed.eventId !== "string" ||
41
+ typeof parsed.entitlementId !== "string" ||
42
+ typeof parsed.qrToken !== "string"
43
+ ) {
44
+ return null;
45
+ }
46
+ return {
47
+ type: COAT_CHECK_QR_TYPE,
48
+ eventId: parsed.eventId,
49
+ entitlementId: parsed.entitlementId,
50
+ qrToken: parsed.qrToken,
51
+ timestamp:
52
+ typeof parsed.timestamp === "number" ? parsed.timestamp : Date.now(),
53
+ };
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ export function coatCheckScanOutcomeForStatus(
60
+ status: CoatCheckEntitlementStatusName
61
+ ): CoatCheckScanOutcome {
62
+ switch (status) {
63
+ case "Paid":
64
+ return "ready_for_dropoff";
65
+ case "CheckedIn":
66
+ return "ready_for_claim";
67
+ case "Claimed":
68
+ return "already_claimed";
69
+ case "Refunded":
70
+ case "Cancelled":
71
+ return "cancelled_or_refunded";
72
+ default:
73
+ return "invalid_or_expired";
74
+ }
75
+ }
76
+
77
+ export function canTransitionCoatCheckStatus(
78
+ from: CoatCheckEntitlementStatusName,
79
+ to: CoatCheckEntitlementStatusName
80
+ ): boolean {
81
+ if (from === to) return false;
82
+ if (from === "Paid" && to === "CheckedIn") return true;
83
+ if (from === "CheckedIn" && to === "Claimed") return true;
84
+ if (
85
+ (from === "Paid" || from === "CheckedIn") &&
86
+ (to === "Refunded" || to === "Cancelled")
87
+ ) {
88
+ return true;
89
+ }
90
+ return false;
91
+ }
92
+
93
+ export function isCoatCheckSlotOccupyingStatus(
94
+ status: string
95
+ ): status is CoatCheckEntitlementStatusName {
96
+ return (COAT_CHECK_SLOT_OCCUPYING_STATUSES as readonly string[]).includes(
97
+ status
98
+ );
99
+ }
100
+
101
+ export function isCoatCheckEntitlementStatus(
102
+ status: string
103
+ ): status is CoatCheckEntitlementStatusName {
104
+ return (COAT_CHECK_ENTITLEMENT_STATUSES as readonly string[]).includes(status);
105
+ }
106
+
107
+ export function assertCoatCheckScanOutcome(
108
+ outcome: string
109
+ ): outcome is CoatCheckScanOutcome {
110
+ return (COAT_CHECK_SCAN_OUTCOMES as readonly string[]).includes(outcome);
111
+ }
112
+
113
+ /** Remaining slots; null capacity = unlimited (Infinity for math). */
114
+ export function remainingCoatCheckSlots(args: {
115
+ capacity: number | null | undefined;
116
+ occupiedCount: number;
117
+ }): number | null {
118
+ if (args.capacity == null) return null;
119
+ return Math.max(0, args.capacity - args.occupiedCount);
120
+ }
121
+
122
+ export function clampCoatCheckPurchaseQty(args: {
123
+ requested: number;
124
+ maxPerPurchase: number;
125
+ remainingSlots: number | null;
126
+ }): number {
127
+ const maxPer = Math.max(1, args.maxPerPurchase || 1);
128
+ let qty = Math.max(0, Math.floor(args.requested));
129
+ qty = Math.min(qty, maxPer);
130
+ if (args.remainingSlots != null) {
131
+ qty = Math.min(qty, args.remainingSlots);
132
+ }
133
+ return qty;
134
+ }
@@ -53,10 +53,9 @@ export function getPublicBashDetailPath(
53
53
  }
54
54
 
55
55
  /**
56
- * Public, shareable Memories recap path for a bash — nested under the bash
57
- * detail path so it inherits the same id/slug parsing (`/bash/{id}-{slug}/memories`).
58
- * No-login-required by design: the whole point of Memories is that a shared
59
- * link works for people who aren't signed in yet.
56
+ * Path to Your Memories (private vault) for a bash — nested under bash detail
57
+ * (`/bash/{id}-{slug}/memories`). Requires sign-in + eligibility; not a public
58
+ * unauthenticated recap page.
60
59
  */
61
60
  export function getPublicBashMemoriesPath(
62
61
  bashEventId: string,