@bash-app/bash-common 30.235.0 → 30.237.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 (180) hide show
  1. package/dist/__tests__/statusEnums.test.js +88 -7
  2. package/dist/__tests__/statusEnums.test.js.map +1 -1
  3. package/dist/definitions.d.ts +45 -14
  4. package/dist/definitions.d.ts.map +1 -1
  5. package/dist/definitions.js +38 -17
  6. package/dist/definitions.js.map +1 -1
  7. package/dist/extendedSchemas.d.ts +8 -0
  8. package/dist/extendedSchemas.d.ts.map +1 -1
  9. package/dist/extendedSchemas.js.map +1 -1
  10. package/dist/index.d.ts +13 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +16 -4
  13. package/dist/index.js.map +1 -1
  14. package/dist/mirroredPrismaEnums.d.ts +145 -0
  15. package/dist/mirroredPrismaEnums.d.ts.map +1 -0
  16. package/dist/mirroredPrismaEnums.js +136 -0
  17. package/dist/mirroredPrismaEnums.js.map +1 -0
  18. package/dist/utils/__tests__/nonprofitUtils.test.d.ts +2 -0
  19. package/dist/utils/__tests__/nonprofitUtils.test.d.ts.map +1 -0
  20. package/dist/utils/__tests__/nonprofitUtils.test.js +84 -0
  21. package/dist/utils/__tests__/nonprofitUtils.test.js.map +1 -0
  22. package/dist/utils/__tests__/paymentUtils.test.js +87 -0
  23. package/dist/utils/__tests__/paymentUtils.test.js.map +1 -1
  24. package/dist/utils/nonprofitUtils.d.ts +21 -0
  25. package/dist/utils/nonprofitUtils.d.ts.map +1 -0
  26. package/dist/utils/nonprofitUtils.js +29 -0
  27. package/dist/utils/nonprofitUtils.js.map +1 -0
  28. package/dist/utils/paymentUtils.d.ts +23 -0
  29. package/dist/utils/paymentUtils.d.ts.map +1 -1
  30. package/dist/utils/paymentUtils.js +24 -7
  31. package/dist/utils/paymentUtils.js.map +1 -1
  32. package/dist/utils/service/__tests__/billingCapUtils.test.d.ts +2 -0
  33. package/dist/utils/service/__tests__/billingCapUtils.test.d.ts.map +1 -0
  34. package/dist/utils/service/__tests__/billingCapUtils.test.js +120 -0
  35. package/dist/utils/service/__tests__/billingCapUtils.test.js.map +1 -0
  36. package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.js +1 -1
  37. package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.js.map +1 -1
  38. package/dist/utils/service/__tests__/eventPromoterPhase3.test.d.ts +2 -0
  39. package/dist/utils/service/__tests__/eventPromoterPhase3.test.d.ts.map +1 -0
  40. package/dist/utils/service/__tests__/eventPromoterPhase3.test.js +243 -0
  41. package/dist/utils/service/__tests__/eventPromoterPhase3.test.js.map +1 -0
  42. package/dist/utils/service/__tests__/eventPromoterUtils.test.d.ts +2 -0
  43. package/dist/utils/service/__tests__/eventPromoterUtils.test.d.ts.map +1 -0
  44. package/dist/utils/service/__tests__/eventPromoterUtils.test.js +333 -0
  45. package/dist/utils/service/__tests__/eventPromoterUtils.test.js.map +1 -0
  46. package/dist/utils/service/__tests__/offerAllowListUtils.test.d.ts +2 -0
  47. package/dist/utils/service/__tests__/offerAllowListUtils.test.d.ts.map +1 -0
  48. package/dist/utils/service/__tests__/offerAllowListUtils.test.js +52 -0
  49. package/dist/utils/service/__tests__/offerAllowListUtils.test.js.map +1 -0
  50. package/dist/utils/service/__tests__/offerComparisonUtils.test.d.ts +2 -0
  51. package/dist/utils/service/__tests__/offerComparisonUtils.test.d.ts.map +1 -0
  52. package/dist/utils/service/__tests__/offerComparisonUtils.test.js +111 -0
  53. package/dist/utils/service/__tests__/offerComparisonUtils.test.js.map +1 -0
  54. package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.d.ts +2 -0
  55. package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.d.ts.map +1 -0
  56. package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.js +155 -0
  57. package/dist/utils/service/__tests__/serviceBookingExposureUtils.test.js.map +1 -0
  58. package/dist/utils/service/__tests__/unifiedPricingResolver.test.d.ts +2 -0
  59. package/dist/utils/service/__tests__/unifiedPricingResolver.test.d.ts.map +1 -0
  60. package/dist/utils/service/__tests__/unifiedPricingResolver.test.js +77 -0
  61. package/dist/utils/service/__tests__/unifiedPricingResolver.test.js.map +1 -0
  62. package/dist/utils/service/__tests__/venueFBSettlementUtils.test.d.ts +2 -0
  63. package/dist/utils/service/__tests__/venueFBSettlementUtils.test.d.ts.map +1 -0
  64. package/dist/utils/service/__tests__/venueFBSettlementUtils.test.js +157 -0
  65. package/dist/utils/service/__tests__/venueFBSettlementUtils.test.js.map +1 -0
  66. package/dist/utils/service/__tests__/venueSettlementUtils.test.d.ts +2 -0
  67. package/dist/utils/service/__tests__/venueSettlementUtils.test.d.ts.map +1 -0
  68. package/dist/utils/service/__tests__/venueSettlementUtils.test.js +175 -0
  69. package/dist/utils/service/__tests__/venueSettlementUtils.test.js.map +1 -0
  70. package/dist/utils/service/__tests__/volunteerCompensationUtils.test.d.ts +2 -0
  71. package/dist/utils/service/__tests__/volunteerCompensationUtils.test.d.ts.map +1 -0
  72. package/dist/utils/service/__tests__/volunteerCompensationUtils.test.js +118 -0
  73. package/dist/utils/service/__tests__/volunteerCompensationUtils.test.js.map +1 -0
  74. package/dist/utils/service/apiServiceBookingApiUtils.d.ts.map +1 -1
  75. package/dist/utils/service/apiServiceBookingApiUtils.js +4 -0
  76. package/dist/utils/service/apiServiceBookingApiUtils.js.map +1 -1
  77. package/dist/utils/service/billingCapUtils.d.ts +86 -0
  78. package/dist/utils/service/billingCapUtils.d.ts.map +1 -0
  79. package/dist/utils/service/billingCapUtils.js +96 -0
  80. package/dist/utils/service/billingCapUtils.js.map +1 -0
  81. package/dist/utils/service/cancellationPolicyRefundResolver.d.ts +1 -1
  82. package/dist/utils/service/cancellationPolicyRefundResolver.d.ts.map +1 -1
  83. package/dist/utils/service/cancellationPolicyRefundResolver.js +1 -1
  84. package/dist/utils/service/cancellationPolicyRefundResolver.js.map +1 -1
  85. package/dist/utils/service/eventPromoterUtils.d.ts +319 -0
  86. package/dist/utils/service/eventPromoterUtils.d.ts.map +1 -0
  87. package/dist/utils/service/eventPromoterUtils.js +372 -0
  88. package/dist/utils/service/eventPromoterUtils.js.map +1 -0
  89. package/dist/utils/service/offerAllowListUtils.d.ts +42 -0
  90. package/dist/utils/service/offerAllowListUtils.d.ts.map +1 -0
  91. package/dist/utils/service/offerAllowListUtils.js +49 -0
  92. package/dist/utils/service/offerAllowListUtils.js.map +1 -0
  93. package/dist/utils/service/offerComparisonUtils.d.ts +69 -0
  94. package/dist/utils/service/offerComparisonUtils.d.ts.map +1 -0
  95. package/dist/utils/service/offerComparisonUtils.js +110 -0
  96. package/dist/utils/service/offerComparisonUtils.js.map +1 -0
  97. package/dist/utils/service/serviceBookingExposureUtils.d.ts +111 -0
  98. package/dist/utils/service/serviceBookingExposureUtils.d.ts.map +1 -0
  99. package/dist/utils/service/serviceBookingExposureUtils.js +133 -0
  100. package/dist/utils/service/serviceBookingExposureUtils.js.map +1 -0
  101. package/dist/utils/service/serviceBookingTypes.d.ts +1 -1
  102. package/dist/utils/service/serviceBookingTypes.d.ts.map +1 -1
  103. package/dist/utils/service/serviceUtils.d.ts +2 -1
  104. package/dist/utils/service/serviceUtils.d.ts.map +1 -1
  105. package/dist/utils/service/serviceUtils.js +17 -16
  106. package/dist/utils/service/serviceUtils.js.map +1 -1
  107. package/dist/utils/service/unifiedPricingResolver.d.ts +67 -0
  108. package/dist/utils/service/unifiedPricingResolver.d.ts.map +1 -0
  109. package/dist/utils/service/unifiedPricingResolver.js +75 -0
  110. package/dist/utils/service/unifiedPricingResolver.js.map +1 -0
  111. package/dist/utils/service/venueFBSettlementUtils.d.ts +122 -0
  112. package/dist/utils/service/venueFBSettlementUtils.d.ts.map +1 -0
  113. package/dist/utils/service/venueFBSettlementUtils.js +106 -0
  114. package/dist/utils/service/venueFBSettlementUtils.js.map +1 -0
  115. package/dist/utils/service/venueSettlementUtils.d.ts +107 -0
  116. package/dist/utils/service/venueSettlementUtils.d.ts.map +1 -0
  117. package/dist/utils/service/venueSettlementUtils.js +117 -0
  118. package/dist/utils/service/venueSettlementUtils.js.map +1 -0
  119. package/dist/utils/service/volunteerCompensationUtils.d.ts +75 -0
  120. package/dist/utils/service/volunteerCompensationUtils.d.ts.map +1 -0
  121. package/dist/utils/service/volunteerCompensationUtils.js +88 -0
  122. package/dist/utils/service/volunteerCompensationUtils.js.map +1 -0
  123. package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.d.ts +2 -0
  124. package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.d.ts.map +1 -0
  125. package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.js +67 -0
  126. package/dist/utils/venueLoyalty/__tests__/eligibilityUtils.test.js.map +1 -0
  127. package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.d.ts +2 -0
  128. package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.d.ts.map +1 -0
  129. package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.js +71 -0
  130. package/dist/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.js.map +1 -0
  131. package/dist/utils/venueLoyalty/eligibilityUtils.d.ts +33 -0
  132. package/dist/utils/venueLoyalty/eligibilityUtils.d.ts.map +1 -0
  133. package/dist/utils/venueLoyalty/eligibilityUtils.js +49 -0
  134. package/dist/utils/venueLoyalty/eligibilityUtils.js.map +1 -0
  135. package/dist/utils/venueLoyalty/redemptionCodeUtils.d.ts +35 -0
  136. package/dist/utils/venueLoyalty/redemptionCodeUtils.d.ts.map +1 -0
  137. package/dist/utils/venueLoyalty/redemptionCodeUtils.js +74 -0
  138. package/dist/utils/venueLoyalty/redemptionCodeUtils.js.map +1 -0
  139. package/dist/venueLoyaltyRedemption.d.ts +146 -1
  140. package/dist/venueLoyaltyRedemption.d.ts.map +1 -1
  141. package/dist/venueLoyaltyRedemption.js +18 -2
  142. package/dist/venueLoyaltyRedemption.js.map +1 -1
  143. package/package.json +1 -1
  144. package/prisma/schema.prisma +638 -214
  145. package/src/__tests__/statusEnums.test.ts +119 -11
  146. package/src/definitions.ts +65 -22
  147. package/src/extendedSchemas.ts +8 -0
  148. package/src/index.ts +16 -6
  149. package/src/mirroredPrismaEnums.ts +181 -0
  150. package/src/utils/__tests__/nonprofitUtils.test.ts +117 -0
  151. package/src/utils/__tests__/paymentUtils.test.ts +93 -0
  152. package/src/utils/nonprofitUtils.ts +44 -0
  153. package/src/utils/paymentUtils.ts +33 -7
  154. package/src/utils/service/__tests__/billingCapUtils.test.ts +175 -0
  155. package/src/utils/service/__tests__/cancellationPolicyRefundResolver.test.ts +1 -1
  156. package/src/utils/service/__tests__/eventPromoterPhase3.test.ts +296 -0
  157. package/src/utils/service/__tests__/eventPromoterUtils.test.ts +396 -0
  158. package/src/utils/service/__tests__/offerAllowListUtils.test.ts +76 -0
  159. package/src/utils/service/__tests__/offerComparisonUtils.test.ts +124 -0
  160. package/src/utils/service/__tests__/serviceBookingExposureUtils.test.ts +171 -0
  161. package/src/utils/service/__tests__/unifiedPricingResolver.test.ts +83 -0
  162. package/src/utils/service/__tests__/venueSettlementUtils.test.ts +219 -0
  163. package/src/utils/service/__tests__/volunteerCompensationUtils.test.ts +172 -0
  164. package/src/utils/service/apiServiceBookingApiUtils.ts +4 -0
  165. package/src/utils/service/billingCapUtils.ts +145 -0
  166. package/src/utils/service/cancellationPolicyRefundResolver.ts +1 -1
  167. package/src/utils/service/eventPromoterUtils.ts +644 -0
  168. package/src/utils/service/offerAllowListUtils.ts +81 -0
  169. package/src/utils/service/offerComparisonUtils.ts +197 -0
  170. package/src/utils/service/serviceBookingExposureUtils.ts +218 -0
  171. package/src/utils/service/serviceBookingTypes.ts +4 -0
  172. package/src/utils/service/serviceUtils.ts +18 -21
  173. package/src/utils/service/unifiedPricingResolver.ts +149 -0
  174. package/src/utils/service/venueSettlementUtils.ts +198 -0
  175. package/src/utils/service/volunteerCompensationUtils.ts +145 -0
  176. package/src/utils/venueLoyalty/__tests__/eligibilityUtils.test.ts +103 -0
  177. package/src/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.ts +92 -0
  178. package/src/utils/venueLoyalty/eligibilityUtils.ts +84 -0
  179. package/src/utils/venueLoyalty/redemptionCodeUtils.ts +94 -0
  180. package/src/venueLoyaltyRedemption.ts +177 -2
@@ -0,0 +1,88 @@
1
+ /** Suggested starter BP stipend per shift. Hosts can override in the wizard. */
2
+ export const DEFAULT_VOLUNTEER_STIPEND_BP_PER_SHIFT = 100;
3
+ /** Suggested starter BP stipend per hour. */
4
+ export const DEFAULT_VOLUNTEER_STIPEND_BP_PER_HOUR = 25;
5
+ /** Allowed values for `VolunteerService.rateUnit`. */
6
+ export const VOLUNTEER_RATE_UNITS = ["shift", "hour"];
7
+ /**
8
+ * Returns the suggested starter BP stipend for a volunteer profile based on
9
+ * how the host wants to denominate it. Wizard pre-fills `bashPointsStipend`
10
+ * with this; host can override.
11
+ */
12
+ export function defaultStipendBpFor(args) {
13
+ if (args.rateUnit === "hour") {
14
+ return DEFAULT_VOLUNTEER_STIPEND_BP_PER_HOUR;
15
+ }
16
+ return DEFAULT_VOLUNTEER_STIPEND_BP_PER_SHIFT;
17
+ }
18
+ /**
19
+ * Returns true if the given compensation type pays any BashPoints. Used by
20
+ * the booking detail page + settlement worker to decide whether to surface
21
+ * BP-stipend fields.
22
+ */
23
+ export function compensationTypePaysBashPoints(t) {
24
+ return t === "BashPointsStipend" || t === "BashPointsAndPerks";
25
+ }
26
+ /**
27
+ * Returns true if the given compensation type includes on-site perks.
28
+ */
29
+ export function compensationTypeIncludesPerks(t) {
30
+ return t === "PerksOnly" || t === "BashPointsAndPerks";
31
+ }
32
+ export function validateVolunteerCompensation(args) {
33
+ const paysBp = compensationTypePaysBashPoints(args.compensationType);
34
+ const hasPerks = compensationTypeIncludesPerks(args.compensationType);
35
+ if (paysBp) {
36
+ if (args.bashPointsStipend == null || args.bashPointsStipend <= 0) {
37
+ return {
38
+ ok: false,
39
+ reason: "BashPoints stipend amount required when compensation type pays BashPoints.",
40
+ };
41
+ }
42
+ if (!args.rateUnit) {
43
+ return {
44
+ ok: false,
45
+ reason: "Rate unit (shift or hour) required when paying a BashPoints stipend.",
46
+ };
47
+ }
48
+ }
49
+ if (!paysBp && args.bashPointsStipend != null && args.bashPointsStipend > 0) {
50
+ return {
51
+ ok: false,
52
+ reason: "BashPoints stipend set but compensation type doesn't pay BashPoints.",
53
+ };
54
+ }
55
+ if (hasPerks && !args.perksDescription) {
56
+ return {
57
+ ok: false,
58
+ reason: "Describe the perks volunteers will receive.",
59
+ };
60
+ }
61
+ return { ok: true };
62
+ }
63
+ /**
64
+ * Plain-English label for the booking detail page + booking thread system
65
+ * messages. Stable across renders.
66
+ */
67
+ export function volunteerCompensationSummary(args) {
68
+ switch (args.compensationType) {
69
+ case "None":
70
+ return "Pure volunteer — no stipend or perks beyond appreciation.";
71
+ case "BashPointsStipend":
72
+ return `${args.bashPointsStipend ?? 0} BashPoints per ${args.rateUnit ?? "shift"}.`;
73
+ case "PerksOnly":
74
+ return args.perksDescription
75
+ ? `Perks only: ${args.perksDescription}`
76
+ : "Perks only.";
77
+ case "BashPointsAndPerks":
78
+ return `${args.bashPointsStipend ?? 0} BashPoints per ${args.rateUnit ?? "shift"} + perks${args.perksDescription ? ` (${args.perksDescription})` : ""}.`;
79
+ default:
80
+ return "Compensation unspecified.";
81
+ }
82
+ }
83
+ /**
84
+ * Disclaimer copy for the volunteer wizard step + help-center page + booking
85
+ * detail. Kept here so wizard, api, and docs cite the exact same wording.
86
+ */
87
+ export const VOLUNTEER_COMPENSATION_DISCLAIMER = "Volunteers are not employees or contractors. BashPoints stipends are platform credit redeemable on Bash, not wages, and don't count toward employment or 1099 reporting.";
88
+ //# sourceMappingURL=volunteerCompensationUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volunteerCompensationUtils.js","sourceRoot":"","sources":["../../../src/utils/service/volunteerCompensationUtils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=eligibilityUtils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eligibilityUtils.test.d.ts","sourceRoot":"","sources":["../../../../src/utils/venueLoyalty/__tests__/eligibilityUtils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Pure eligibility evaluator — same logic runs on API + bash-app, so this
3
+ * suite locks the truth table for both call sites.
4
+ */
5
+ import { evaluateEligibility, evaluateOffersEligibility, } from "../eligibilityUtils.js";
6
+ const baseMetrics = { ticketedEventsAttended: 2, bookingsAsHost: 0 };
7
+ describe("evaluateEligibility", () => {
8
+ test("offer with no gates and no cooldown is eligible", () => {
9
+ const result = evaluateEligibility(baseMetrics, {
10
+ id: "o1",
11
+ minTicketedEventsAttended: null,
12
+ minBookingsAsHost: null,
13
+ });
14
+ expect(result.eligible).toBe(true);
15
+ expect(result.missing.ticketedEventsShort).toBeNull();
16
+ expect(result.missing.bookingsAsHostShort).toBeNull();
17
+ expect(result.onCooldownUntil).toBeNull();
18
+ });
19
+ test("guest meets ticketed gate exactly is eligible", () => {
20
+ const result = evaluateEligibility({ ticketedEventsAttended: 3, bookingsAsHost: 0 }, { id: "o2", minTicketedEventsAttended: 3, minBookingsAsHost: null });
21
+ expect(result.eligible).toBe(true);
22
+ expect(result.missing.ticketedEventsShort).toBeNull();
23
+ });
24
+ test("guest one short on ticketed gate surfaces shortfall + locked state", () => {
25
+ const result = evaluateEligibility({ ticketedEventsAttended: 2, bookingsAsHost: 0 }, { id: "o3", minTicketedEventsAttended: 3, minBookingsAsHost: null });
26
+ expect(result.eligible).toBe(false);
27
+ expect(result.missing.ticketedEventsShort).toBe(1);
28
+ expect(result.missing.bookingsAsHostShort).toBeNull();
29
+ });
30
+ test("guest short on both gates surfaces both shortfalls", () => {
31
+ const result = evaluateEligibility({ ticketedEventsAttended: 1, bookingsAsHost: 0 }, { id: "o4", minTicketedEventsAttended: 4, minBookingsAsHost: 2 });
32
+ expect(result.eligible).toBe(false);
33
+ expect(result.missing.ticketedEventsShort).toBe(3);
34
+ expect(result.missing.bookingsAsHostShort).toBe(2);
35
+ });
36
+ test("gates met but cooldown active surfaces onCooldownUntil + ineligible", () => {
37
+ const futureDate = new Date(Date.now() + 86400_000);
38
+ const result = evaluateEligibility({ ticketedEventsAttended: 5, bookingsAsHost: 5 }, { id: "o5", minTicketedEventsAttended: 1, minBookingsAsHost: 1 }, futureDate);
39
+ expect(result.eligible).toBe(false);
40
+ expect(result.onCooldownUntil).toBe(futureDate.toISOString());
41
+ });
42
+ test("gates met and cooldown elapsed flips back to eligible", () => {
43
+ const pastDate = new Date(Date.now() - 86400_000);
44
+ const result = evaluateEligibility({ ticketedEventsAttended: 5, bookingsAsHost: 5 }, { id: "o6", minTicketedEventsAttended: 1, minBookingsAsHost: 1 }, pastDate);
45
+ expect(result.eligible).toBe(true);
46
+ expect(result.onCooldownUntil).toBeNull();
47
+ });
48
+ test("cooldown accepts ISO string input", () => {
49
+ const futureIso = new Date(Date.now() + 3600_000).toISOString();
50
+ const result = evaluateEligibility({ ticketedEventsAttended: 5, bookingsAsHost: 5 }, { id: "o7", minTicketedEventsAttended: null, minBookingsAsHost: null }, futureIso);
51
+ expect(result.eligible).toBe(false);
52
+ expect(result.onCooldownUntil).toBe(futureIso);
53
+ });
54
+ });
55
+ describe("evaluateOffersEligibility", () => {
56
+ test("maps each offer with the right cooldown from the keyed map", () => {
57
+ const future = new Date(Date.now() + 86400_000).toISOString();
58
+ const result = evaluateOffersEligibility({ ticketedEventsAttended: 2, bookingsAsHost: 1 }, [
59
+ { id: "a", minTicketedEventsAttended: null, minBookingsAsHost: null },
60
+ { id: "b", minTicketedEventsAttended: 3, minBookingsAsHost: null },
61
+ { id: "c", minTicketedEventsAttended: 1, minBookingsAsHost: 1 },
62
+ ], { c: future });
63
+ expect(result.map((r) => r.eligible)).toEqual([true, false, false]);
64
+ expect(result[2].onCooldownUntil).toBe(future);
65
+ });
66
+ });
67
+ //# sourceMappingURL=eligibilityUtils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eligibilityUtils.test.js","sourceRoot":"","sources":["../../../../src/utils/venueLoyalty/__tests__/eligibilityUtils.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=redemptionCodeUtils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redemptionCodeUtils.test.d.ts","sourceRoot":"","sources":["../../../../src/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Pin the redemption code alphabet, length, normalization, and uniqueness
3
+ * retry loop. Hosts type these codes from a phone — fat-finger characters
4
+ * (I, O, 0, 1) must never appear.
5
+ */
6
+ import { REDEMPTION_CODE_LENGTH, __testInternals, generateRedemptionCode, generateUniqueRedemptionCode, isValidRedemptionCodeShape, normalizeRedemptionCode, } from "../redemptionCodeUtils.js";
7
+ describe("redemption code shape", () => {
8
+ test("alphabet excludes ambiguous characters", () => {
9
+ const alphabet = __testInternals.REDEMPTION_CODE_ALPHABET;
10
+ expect(alphabet).not.toMatch(/[IO01]/);
11
+ expect(alphabet).toHaveLength(32);
12
+ });
13
+ test("isValidRedemptionCodeShape accepts canonical codes", () => {
14
+ expect(isValidRedemptionCodeShape("ABCDEF23")).toBe(true);
15
+ });
16
+ test("isValidRedemptionCodeShape rejects wrong length", () => {
17
+ expect(isValidRedemptionCodeShape("ABCDEF2")).toBe(false);
18
+ expect(isValidRedemptionCodeShape("ABCDEF234")).toBe(false);
19
+ });
20
+ test("isValidRedemptionCodeShape rejects ambiguous chars", () => {
21
+ expect(isValidRedemptionCodeShape("ABCDEFI2")).toBe(false);
22
+ expect(isValidRedemptionCodeShape("ABCDEFO2")).toBe(false);
23
+ expect(isValidRedemptionCodeShape("ABCDEF02")).toBe(false);
24
+ expect(isValidRedemptionCodeShape("ABCDEF12")).toBe(false);
25
+ });
26
+ test("isValidRedemptionCodeShape rejects lowercase", () => {
27
+ expect(isValidRedemptionCodeShape("abcdef23")).toBe(false);
28
+ });
29
+ });
30
+ describe("normalizeRedemptionCode", () => {
31
+ test("strips whitespace + dashes and uppercases", () => {
32
+ expect(normalizeRedemptionCode("ab12-cd34")).toBe("AB12CD34");
33
+ expect(normalizeRedemptionCode(" abcdef23 ")).toBe("ABCDEF23");
34
+ expect(normalizeRedemptionCode("a b\tc-d\n2 3 4 5")).toBe("ABCD2345");
35
+ });
36
+ });
37
+ describe("generateRedemptionCode", () => {
38
+ test("produces 8-char codes from the alphabet", () => {
39
+ const code = generateRedemptionCode();
40
+ expect(code).toHaveLength(REDEMPTION_CODE_LENGTH);
41
+ expect(isValidRedemptionCodeShape(code)).toBe(true);
42
+ });
43
+ test("respects injected random bytes", () => {
44
+ const bytes = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
45
+ const code = generateRedemptionCode(() => bytes);
46
+ const alphabet = __testInternals.REDEMPTION_CODE_ALPHABET;
47
+ expect(code).toBe(Array.from(bytes)
48
+ .map((b) => alphabet[b % alphabet.length])
49
+ .join(""));
50
+ });
51
+ });
52
+ describe("generateUniqueRedemptionCode", () => {
53
+ test("returns the first non-colliding candidate", async () => {
54
+ const seen = new Set();
55
+ const code = await generateUniqueRedemptionCode(async (c) => seen.has(c));
56
+ expect(isValidRedemptionCodeShape(code)).toBe(true);
57
+ });
58
+ test("retries until one succeeds", async () => {
59
+ let calls = 0;
60
+ const code = await generateUniqueRedemptionCode(async () => {
61
+ calls += 1;
62
+ return calls < 3;
63
+ });
64
+ expect(calls).toBe(3);
65
+ expect(isValidRedemptionCodeShape(code)).toBe(true);
66
+ });
67
+ test("throws after exhausting attempts", async () => {
68
+ await expect(generateUniqueRedemptionCode(async () => true, { maxAttempts: 4 })).rejects.toThrow(/Could not generate a unique/);
69
+ });
70
+ });
71
+ //# sourceMappingURL=redemptionCodeUtils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redemptionCodeUtils.test.js","sourceRoot":"","sources":["../../../../src/utils/venueLoyalty/__tests__/redemptionCodeUtils.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Pure (no Prisma, no `bash-app` imports) eligibility evaluator for venue
3
+ * loyalty offers. Runs identically on the server (gating claim creation) and
4
+ * the client (rendering locked / progress states on the public loyalty
5
+ * section).
6
+ *
7
+ * The actual metric collection lives in
8
+ * `api/src/utils/venueLoyalty/eligibilityCalculator.ts` for the server and
9
+ * comes pre-computed from the eligibility endpoint for the client.
10
+ */
11
+ import type { VenueLoyaltyEligibilitySnapshot, VenueLoyaltyOfferDto, VenueLoyaltyOfferEligibility } from "../../venueLoyaltyRedemption.js";
12
+ /**
13
+ * Subset of `VenueLoyaltyOfferDto` the evaluator needs. Kept narrow so callers
14
+ * can pass in the Prisma row directly without remapping.
15
+ */
16
+ export interface OfferGates {
17
+ id: string;
18
+ minTicketedEventsAttended: number | null;
19
+ minBookingsAsHost: number | null;
20
+ }
21
+ /**
22
+ * Evaluate one offer against one guest's metric snapshot. Optional
23
+ * `onCooldownUntil` (already computed from the latest non-active redemption +
24
+ * `offer.cooldownDays`) shadows eligibility so the UI shows "available in N
25
+ * days" instead of unlock progress.
26
+ */
27
+ export declare function evaluateEligibility(metrics: VenueLoyaltyEligibilitySnapshot, offer: OfferGates, onCooldownUntil?: Date | string | null): VenueLoyaltyOfferEligibility;
28
+ /**
29
+ * Batch helper for rendering a venue's full offer list with eligibility in
30
+ * one pass. `cooldownMap` is keyed by `offerId`.
31
+ */
32
+ export declare function evaluateOffersEligibility(metrics: VenueLoyaltyEligibilitySnapshot, offers: ReadonlyArray<Pick<VenueLoyaltyOfferDto, "id" | "minTicketedEventsAttended" | "minBookingsAsHost">>, cooldownMap?: Record<string, Date | string | null>): VenueLoyaltyOfferEligibility[];
33
+ //# sourceMappingURL=eligibilityUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eligibilityUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/venueLoyalty/eligibilityUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,+BAA+B,EAC/B,oBAAoB,EACpB,4BAA4B,EAC7B,MAAM,iCAAiC,CAAC;AAEzC;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,yBAAyB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,+BAA+B,EACxC,KAAK,EAAE,UAAU,EACjB,eAAe,GAAE,IAAI,GAAG,MAAM,GAAG,IAAW,GAC3C,4BAA4B,CAgC9B;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,+BAA+B,EACxC,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,oBAAoB,EAAE,IAAI,GAAG,2BAA2B,GAAG,mBAAmB,CAAC,CAAC,EAC3G,WAAW,GAAE,MAAM,CAAC,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAM,GACrD,4BAA4B,EAAE,CAIhC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Pure (no Prisma, no `bash-app` imports) eligibility evaluator for venue
3
+ * loyalty offers. Runs identically on the server (gating claim creation) and
4
+ * the client (rendering locked / progress states on the public loyalty
5
+ * section).
6
+ *
7
+ * The actual metric collection lives in
8
+ * `api/src/utils/venueLoyalty/eligibilityCalculator.ts` for the server and
9
+ * comes pre-computed from the eligibility endpoint for the client.
10
+ */
11
+ /**
12
+ * Evaluate one offer against one guest's metric snapshot. Optional
13
+ * `onCooldownUntil` (already computed from the latest non-active redemption +
14
+ * `offer.cooldownDays`) shadows eligibility so the UI shows "available in N
15
+ * days" instead of unlock progress.
16
+ */
17
+ export function evaluateEligibility(metrics, offer, onCooldownUntil = null) {
18
+ const ticketedEventsShort = offer.minTicketedEventsAttended !== null &&
19
+ offer.minTicketedEventsAttended > metrics.ticketedEventsAttended
20
+ ? offer.minTicketedEventsAttended - metrics.ticketedEventsAttended
21
+ : null;
22
+ const bookingsAsHostShort = offer.minBookingsAsHost !== null &&
23
+ offer.minBookingsAsHost > metrics.bookingsAsHost
24
+ ? offer.minBookingsAsHost - metrics.bookingsAsHost
25
+ : null;
26
+ const cooldownIso = onCooldownUntil instanceof Date
27
+ ? onCooldownUntil.toISOString()
28
+ : onCooldownUntil;
29
+ const cooldownActive = cooldownIso !== null && new Date(cooldownIso) > new Date();
30
+ const gatesMet = ticketedEventsShort === null && bookingsAsHostShort === null;
31
+ return {
32
+ offerId: offer.id,
33
+ eligible: gatesMet && !cooldownActive,
34
+ missing: {
35
+ ticketedEventsShort,
36
+ bookingsAsHostShort,
37
+ },
38
+ onCooldownUntil: cooldownActive ? cooldownIso : null,
39
+ metrics,
40
+ };
41
+ }
42
+ /**
43
+ * Batch helper for rendering a venue's full offer list with eligibility in
44
+ * one pass. `cooldownMap` is keyed by `offerId`.
45
+ */
46
+ export function evaluateOffersEligibility(metrics, offers, cooldownMap = {}) {
47
+ return offers.map((offer) => evaluateEligibility(metrics, offer, cooldownMap[offer.id] ?? null));
48
+ }
49
+ //# sourceMappingURL=eligibilityUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eligibilityUtils.js","sourceRoot":"","sources":["../../../src/utils/venueLoyalty/eligibilityUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAkBH;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAwC,EACxC,KAAiB,EACjB,kBAAwC,IAAI;IAE5C,MAAM,mBAAmB,GACvB,KAAK,CAAC,yBAAyB,KAAK,IAAI;QACxC,KAAK,CAAC,yBAAyB,GAAG,OAAO,CAAC,sBAAsB;QAC9D,CAAC,CAAC,KAAK,CAAC,yBAAyB,GAAG,OAAO,CAAC,sBAAsB;QAClE,CAAC,CAAC,IAAI,CAAC;IAEX,MAAM,mBAAmB,GACvB,KAAK,CAAC,iBAAiB,KAAK,IAAI;QAChC,KAAK,CAAC,iBAAiB,GAAG,OAAO,CAAC,cAAc;QAC9C,CAAC,CAAC,KAAK,CAAC,iBAAiB,GAAG,OAAO,CAAC,cAAc;QAClD,CAAC,CAAC,IAAI,CAAC;IAEX,MAAM,WAAW,GACf,eAAe,YAAY,IAAI;QAC7B,CAAC,CAAC,eAAe,CAAC,WAAW,EAAE;QAC/B,CAAC,CAAC,eAAe,CAAC;IAEtB,MAAM,cAAc,GAAG,WAAW,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;IAElF,MAAM,QAAQ,GAAG,mBAAmB,KAAK,IAAI,IAAI,mBAAmB,KAAK,IAAI,CAAC;IAE9E,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,EAAE;QACjB,QAAQ,EAAE,QAAQ,IAAI,CAAC,cAAc;QACrC,OAAO,EAAE;YACP,mBAAmB;YACnB,mBAAmB;SACpB;QACD,eAAe,EAAE,cAAc,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI;QACpD,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CACvC,OAAwC,EACxC,MAA2G,EAC3G,cAAoD,EAAE;IAEtD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAC1B,mBAAmB,CAAC,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC,CACnE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Redemption code generation + validation for venue loyalty claims.
3
+ *
4
+ * Codes are 8 characters from a 32-letter alphabet that excludes the visually
5
+ * ambiguous `I`, `O`, `0`, `1` so a host typing the code from a phone screen
6
+ * doesn't fat-finger it. Collisions are scoped per-venue (see the unique
7
+ * constraint on `VenueLoyaltyRedemption(venueId, redemptionCode)`), so the
8
+ * 32^8 ≈ 1.1 trillion keyspace is enormous overkill — the retry loop in
9
+ * `generateUniqueRedemptionCode` exists to be defensive, not because we
10
+ * expect contention.
11
+ */
12
+ export declare const REDEMPTION_CODE_LENGTH = 8;
13
+ /** True iff `code` matches the canonical 8-char alphabet. */
14
+ export declare function isValidRedemptionCodeShape(code: string): boolean;
15
+ /**
16
+ * Normalize user input — uppercase, strip whitespace + dashes — before
17
+ * validation or DB lookup. Lets us accept "ab12-cd34" from a phone keyboard.
18
+ */
19
+ export declare function normalizeRedemptionCode(input: string): string;
20
+ /** Single 8-char code, sampled from a CSPRNG when available. */
21
+ export declare function generateRedemptionCode(randomBytes?: (length: number) => Uint8Array): string;
22
+ /**
23
+ * Sample fresh codes until `existsInVenue(code)` returns false. Bounded to
24
+ * `maxAttempts` so a misbehaving uniqueness probe can't spin forever. Throws
25
+ * after exhausting attempts — caller should surface a 5xx, this would mean
26
+ * the keyspace is exhausted (impossible in practice) or the DB is unhealthy.
27
+ */
28
+ export declare function generateUniqueRedemptionCode(existsInVenue: (code: string) => Promise<boolean>, options?: {
29
+ randomBytes?: (length: number) => Uint8Array;
30
+ maxAttempts?: number;
31
+ }): Promise<string>;
32
+ export declare const __testInternals: {
33
+ REDEMPTION_CODE_ALPHABET: string;
34
+ };
35
+ //# sourceMappingURL=redemptionCodeUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redemptionCodeUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/venueLoyalty/redemptionCodeUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAMxC,6DAA6D;AAC7D,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,gEAAgE;AAChE,wBAAgB,sBAAsB,CACpC,WAAW,GAAE,CAAC,MAAM,EAAE,MAAM,KAAK,UAA+B,GAC/D,MAAM,CAOR;AAED;;;;;GAKG;AACH,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,EACjD,OAAO,GAAE;IACP,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,UAAU,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,GACL,OAAO,CAAC,MAAM,CAAC,CAYjB;AAuBD,eAAO,MAAM,eAAe;;CAE3B,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Redemption code generation + validation for venue loyalty claims.
3
+ *
4
+ * Codes are 8 characters from a 32-letter alphabet that excludes the visually
5
+ * ambiguous `I`, `O`, `0`, `1` so a host typing the code from a phone screen
6
+ * doesn't fat-finger it. Collisions are scoped per-venue (see the unique
7
+ * constraint on `VenueLoyaltyRedemption(venueId, redemptionCode)`), so the
8
+ * 32^8 ≈ 1.1 trillion keyspace is enormous overkill — the retry loop in
9
+ * `generateUniqueRedemptionCode` exists to be defensive, not because we
10
+ * expect contention.
11
+ */
12
+ const REDEMPTION_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
13
+ export const REDEMPTION_CODE_LENGTH = 8;
14
+ const REDEMPTION_CODE_REGEX = new RegExp(`^[${REDEMPTION_CODE_ALPHABET}]{${REDEMPTION_CODE_LENGTH}}$`);
15
+ /** True iff `code` matches the canonical 8-char alphabet. */
16
+ export function isValidRedemptionCodeShape(code) {
17
+ return REDEMPTION_CODE_REGEX.test(code);
18
+ }
19
+ /**
20
+ * Normalize user input — uppercase, strip whitespace + dashes — before
21
+ * validation or DB lookup. Lets us accept "ab12-cd34" from a phone keyboard.
22
+ */
23
+ export function normalizeRedemptionCode(input) {
24
+ return input.replace(/[\s-]/g, "").toUpperCase();
25
+ }
26
+ /** Single 8-char code, sampled from a CSPRNG when available. */
27
+ export function generateRedemptionCode(randomBytes = defaultRandomBytes) {
28
+ const bytes = randomBytes(REDEMPTION_CODE_LENGTH);
29
+ let out = "";
30
+ for (let i = 0; i < REDEMPTION_CODE_LENGTH; i += 1) {
31
+ out += REDEMPTION_CODE_ALPHABET[bytes[i] % REDEMPTION_CODE_ALPHABET.length];
32
+ }
33
+ return out;
34
+ }
35
+ /**
36
+ * Sample fresh codes until `existsInVenue(code)` returns false. Bounded to
37
+ * `maxAttempts` so a misbehaving uniqueness probe can't spin forever. Throws
38
+ * after exhausting attempts — caller should surface a 5xx, this would mean
39
+ * the keyspace is exhausted (impossible in practice) or the DB is unhealthy.
40
+ */
41
+ export async function generateUniqueRedemptionCode(existsInVenue, options = {}) {
42
+ const maxAttempts = options.maxAttempts ?? 10;
43
+ const randomBytes = options.randomBytes ?? defaultRandomBytes;
44
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
45
+ const candidate = generateRedemptionCode(randomBytes);
46
+ if (!(await existsInVenue(candidate))) {
47
+ return candidate;
48
+ }
49
+ }
50
+ throw new Error(`Could not generate a unique venue loyalty redemption code after ${maxAttempts} attempts`);
51
+ }
52
+ function defaultRandomBytes(length) {
53
+ const cryptoObj = typeof globalThis !== "undefined" &&
54
+ globalThis.crypto
55
+ ? globalThis.crypto
56
+ : undefined;
57
+ if (cryptoObj?.getRandomValues) {
58
+ const buf = new Uint8Array(length);
59
+ cryptoObj.getRandomValues(buf);
60
+ return buf;
61
+ }
62
+ // Last-resort fallback for environments without WebCrypto (legacy tests).
63
+ // Not security-critical because redemption codes are scoped per-venue
64
+ // behind auth, but still tries to spread entropy across the buffer.
65
+ const buf = new Uint8Array(length);
66
+ for (let i = 0; i < length; i += 1) {
67
+ buf[i] = Math.floor(Math.random() * 256);
68
+ }
69
+ return buf;
70
+ }
71
+ export const __testInternals = {
72
+ REDEMPTION_CODE_ALPHABET,
73
+ };
74
+ //# sourceMappingURL=redemptionCodeUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redemptionCodeUtils.js","sourceRoot":"","sources":["../../../src/utils/venueLoyalty/redemptionCodeUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,wBAAwB,GAAG,kCAAkC,CAAC;AACpE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC;AAExC,MAAM,qBAAqB,GAAG,IAAI,MAAM,CACtC,KAAK,wBAAwB,KAAK,sBAAsB,IAAI,CAC7D,CAAC;AAEF,6DAA6D;AAC7D,MAAM,UAAU,0BAA0B,CAAC,IAAY;IACrD,OAAO,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACnD,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,sBAAsB,CACpC,cAA8C,kBAAkB;IAEhE,MAAM,KAAK,GAAG,WAAW,CAAC,sBAAsB,CAAC,CAAC;IAClD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,sBAAsB,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,GAAG,IAAI,wBAAwB,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,aAAiD,EACjD,UAGI,EAAE;IAEN,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;IAC9C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,kBAAkB,CAAC;IAC9D,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,sBAAsB,CAAC,WAAW,CAAC,CAAC;QACtD,IAAI,CAAC,CAAC,MAAM,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YACtC,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CACb,mEAAmE,WAAW,WAAW,CAC1F,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,MAAc;IACxC,MAAM,SAAS,GACb,OAAO,UAAU,KAAK,WAAW;QAChC,UAAkC,CAAC,MAAM;QACxC,CAAC,CAAE,UAAkC,CAAC,MAAM;QAC5C,CAAC,CAAC,SAAS,CAAC;IAChB,IAAI,SAAS,EAAE,eAAe,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;QACnC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,GAAG,CAAC;IACb,CAAC;IACD,0EAA0E;IAC1E,sEAAsE;IACtE,oEAAoE;IACpE,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,wBAAwB;CACzB,CAAC"}
@@ -1,21 +1,166 @@
1
1
  /**
2
- * API shapes for venue loyalty perk requests (guest claim + host list/redeem).
2
+ * API shapes for venue loyalty perk offers + redemptions (guest claim / decline
3
+ * / redeem / expire). All BashPoints movements through these flows ledger via
4
+ * `CreditSourceType.VenueLoyaltyRedemption` — see
5
+ * `api/src/services/venueLoyaltyRedemptionService.ts`.
6
+ */
7
+ import type { VenueLoyaltyTemplateId } from "./utils/service/serviceUtils.js";
8
+ /**
9
+ * Public DTO for a `VenueLoyaltyOffer` row. `bashPointsCost` of `null` means
10
+ * the offer is a legacy "polite request" with no BashPoints price (host
11
+ * fulfills on goodwill).
12
+ */
13
+ export interface VenueLoyaltyOfferDto {
14
+ id: string;
15
+ venueId: string;
16
+ templateId: string;
17
+ title: string;
18
+ description: string | null;
19
+ bashPointsCost: number | null;
20
+ minTicketedEventsAttended: number | null;
21
+ minBookingsAsHost: number | null;
22
+ cooldownDays: number;
23
+ claimExpiryDays: number;
24
+ enabled: boolean;
25
+ createdAt: string;
26
+ updatedAt: string;
27
+ }
28
+ /**
29
+ * Payload for `POST /venue-loyalty/offers`. `title` defaults to the template
30
+ * label client-side if omitted; server validates it's non-empty before write.
31
+ */
32
+ export interface CreateVenueLoyaltyOfferRequest {
33
+ venueId: string;
34
+ templateId: VenueLoyaltyTemplateId;
35
+ title: string;
36
+ description?: string | null;
37
+ bashPointsCost?: number | null;
38
+ minTicketedEventsAttended?: number | null;
39
+ minBookingsAsHost?: number | null;
40
+ cooldownDays?: number;
41
+ claimExpiryDays?: number;
42
+ enabled?: boolean;
43
+ }
44
+ /** Payload for `PATCH /venue-loyalty/offers/:id`. */
45
+ export type UpdateVenueLoyaltyOfferRequest = Partial<Omit<CreateVenueLoyaltyOfferRequest, "venueId" | "templateId">>;
46
+ /**
47
+ * Snapshot of the gating metrics at claim time, persisted on the
48
+ * redemption row for audit. Stored as a Prisma `Json` column on the
49
+ * server; this type defines the canonical shape.
50
+ */
51
+ export interface VenueLoyaltyEligibilitySnapshot {
52
+ ticketedEventsAttended: number;
53
+ bookingsAsHost: number;
54
+ }
55
+ /**
56
+ * Per-offer eligibility result for a specific guest viewing a venue's loyalty
57
+ * section. Drives the locked-state / progress-copy UI on the public detail page.
58
+ */
59
+ export interface VenueLoyaltyOfferEligibility {
60
+ offerId: string;
61
+ eligible: boolean;
62
+ /**
63
+ * How short the guest is on each gate, if any. `null` if no gate of that
64
+ * kind exists on the offer.
65
+ */
66
+ missing: {
67
+ ticketedEventsShort: number | null;
68
+ bookingsAsHostShort: number | null;
69
+ };
70
+ /**
71
+ * `claimedAt + cooldownDays` for the guest's most recent non-active claim
72
+ * against this offer, when the cooldown has not yet elapsed. `null` when no
73
+ * cooldown applies.
74
+ */
75
+ onCooldownUntil: string | null;
76
+ /** Current metric values, useful for "1 of 3" style copy. */
77
+ metrics: VenueLoyaltyEligibilitySnapshot;
78
+ }
79
+ /** Response for `GET /venue-loyalty/eligibility/:venueId`. */
80
+ export interface VenueLoyaltyEligibilityResponse {
81
+ venueId: string;
82
+ offers: VenueLoyaltyOfferEligibility[];
83
+ }
84
+ /**
85
+ * Guest-facing redemption DTO. `redemptionCode` is only populated for active
86
+ * (un-redeemed, un-declined, un-expired) claims so the guest can show it to
87
+ * the host. Once the redemption resolves, the code is preserved for history.
3
88
  */
4
89
  export interface VenueLoyaltyRedemptionGuestDto {
5
90
  id: string;
6
91
  venueId: string;
92
+ offerId: string | null;
7
93
  userId: string;
8
94
  templateId: string;
95
+ bashPointsCost: number;
96
+ redemptionCode: string | null;
97
+ expiresAt: string | null;
9
98
  claimedAt: string;
10
99
  redeemedAt: string | null;
100
+ declinedAt: string | null;
101
+ expiredAt: string | null;
102
+ cancelReason: string | null;
11
103
  guestNotes: string | null;
12
104
  hostNotes: string | null;
13
105
  }
106
+ /** Host-side row with guest identity for the venue's claims inbox. */
14
107
  export interface VenueLoyaltyRedemptionListItem extends VenueLoyaltyRedemptionGuestDto {
15
108
  user: {
16
109
  email: string;
17
110
  givenName: string | null;
18
111
  familyName: string | null;
19
112
  };
113
+ eligibilitySnapshot: VenueLoyaltyEligibilitySnapshot | null;
114
+ }
115
+ /** Lifecycle states a guest sees on the "My loyalty claims" page. */
116
+ export type VenueLoyaltyRedemptionStatus = "Active" | "Redeemed" | "Declined" | "Expired";
117
+ /**
118
+ * Compose the guest-facing status from the timestamp fields. Mirrors the
119
+ * server-side state machine; kept here so guards in UI + tests share one
120
+ * source of truth.
121
+ */
122
+ export declare function getVenueLoyaltyRedemptionStatus(redemption: {
123
+ redeemedAt: string | null;
124
+ declinedAt: string | null;
125
+ expiredAt: string | null;
126
+ }): VenueLoyaltyRedemptionStatus;
127
+ /**
128
+ * Extended view returned by `GET /venue-loyalty/my-claims` so the guest sees
129
+ * which venue + offer the code is for without a second round trip.
130
+ */
131
+ export interface VenueLoyaltyRedemptionMyClaimItem extends VenueLoyaltyRedemptionGuestDto {
132
+ status: VenueLoyaltyRedemptionStatus;
133
+ venue: {
134
+ id: string;
135
+ name: string;
136
+ serviceId: string | null;
137
+ };
138
+ offer: Pick<VenueLoyaltyOfferDto, "id" | "title" | "description" | "templateId"> | null;
139
+ }
140
+ /** Body for `POST /venue-loyalty/claim` (extended with `offerId`). */
141
+ export interface ClaimVenueLoyaltyRequest {
142
+ venueId: string;
143
+ offerId: string;
144
+ guestNotes?: string;
145
+ }
146
+ /** Successful response for `POST /venue-loyalty/claim`. */
147
+ export interface ClaimVenueLoyaltyResponse {
148
+ redemption: VenueLoyaltyRedemptionGuestDto;
149
+ /**
150
+ * Convenience copy of `redemption.redemptionCode` so the success UI can
151
+ * pull it without branching on null. Equals `null` only when the offer was
152
+ * free (bashPointsCost == null) — currently still issued for traceability.
153
+ */
154
+ code: string;
155
+ remainingBashPoints: number;
156
+ }
157
+ /** Body for `PATCH /venue-loyalty/:id/redeem` (extended with `redemptionCode`). */
158
+ export interface RedeemVenueLoyaltyRequest {
159
+ redemptionCode: string;
160
+ hostNotes?: string;
161
+ }
162
+ /** Body for `PATCH /venue-loyalty/:id/decline`. */
163
+ export interface DeclineVenueLoyaltyRequest {
164
+ hostNotes?: string;
20
165
  }
21
166
  //# sourceMappingURL=venueLoyaltyRedemption.d.ts.map