@bash-app/bash-common 30.406.0 → 30.410.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 (60) hide show
  1. package/dist/__tests__/partnerDealflow.test.d.ts +2 -0
  2. package/dist/__tests__/partnerDealflow.test.d.ts.map +1 -0
  3. package/dist/__tests__/partnerDealflow.test.js +51 -0
  4. package/dist/__tests__/partnerDealflow.test.js.map +1 -0
  5. package/dist/__tests__/partnerRevenueShareOptIn.test.d.ts +2 -0
  6. package/dist/__tests__/partnerRevenueShareOptIn.test.d.ts.map +1 -0
  7. package/dist/__tests__/partnerRevenueShareOptIn.test.js +55 -0
  8. package/dist/__tests__/partnerRevenueShareOptIn.test.js.map +1 -0
  9. package/dist/__tests__/reservedCustomBashEventType.test.d.ts +2 -0
  10. package/dist/__tests__/reservedCustomBashEventType.test.d.ts.map +1 -0
  11. package/dist/__tests__/reservedCustomBashEventType.test.js +19 -0
  12. package/dist/__tests__/reservedCustomBashEventType.test.js.map +1 -0
  13. package/dist/__tests__/serviceListingDealPreferences.test.d.ts +2 -0
  14. package/dist/__tests__/serviceListingDealPreferences.test.d.ts.map +1 -0
  15. package/dist/__tests__/serviceListingDealPreferences.test.js +84 -0
  16. package/dist/__tests__/serviceListingDealPreferences.test.js.map +1 -0
  17. package/dist/definitions.d.ts +13 -2
  18. package/dist/definitions.d.ts.map +1 -1
  19. package/dist/definitions.js +57 -2
  20. package/dist/definitions.js.map +1 -1
  21. package/dist/extendedSchemas.d.ts +97 -4
  22. package/dist/extendedSchemas.d.ts.map +1 -1
  23. package/dist/extendedSchemas.js +35 -0
  24. package/dist/extendedSchemas.js.map +1 -1
  25. package/dist/homepageSyntheticRows.d.ts +9 -0
  26. package/dist/homepageSyntheticRows.d.ts.map +1 -0
  27. package/dist/homepageSyntheticRows.js +26 -0
  28. package/dist/homepageSyntheticRows.js.map +1 -0
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +4 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/sideQuestTypes.d.ts +1 -1
  34. package/dist/sideQuestTypes.d.ts.map +1 -1
  35. package/dist/utils/service/partnerDealflow.d.ts +12 -0
  36. package/dist/utils/service/partnerDealflow.d.ts.map +1 -0
  37. package/dist/utils/service/partnerDealflow.js +33 -0
  38. package/dist/utils/service/partnerDealflow.js.map +1 -0
  39. package/dist/utils/service/partnerRevenueShareOptIn.d.ts +18 -0
  40. package/dist/utils/service/partnerRevenueShareOptIn.d.ts.map +1 -0
  41. package/dist/utils/service/partnerRevenueShareOptIn.js +46 -0
  42. package/dist/utils/service/partnerRevenueShareOptIn.js.map +1 -0
  43. package/dist/utils/service/serviceListingDealPreferences.d.ts +28 -0
  44. package/dist/utils/service/serviceListingDealPreferences.d.ts.map +1 -0
  45. package/dist/utils/service/serviceListingDealPreferences.js +126 -0
  46. package/dist/utils/service/serviceListingDealPreferences.js.map +1 -0
  47. package/package.json +5 -2
  48. package/prisma/schema.prisma +67 -1
  49. package/src/__tests__/partnerDealflow.test.ts +62 -0
  50. package/src/__tests__/partnerRevenueShareOptIn.test.ts +68 -0
  51. package/src/__tests__/reservedCustomBashEventType.test.ts +27 -0
  52. package/src/__tests__/serviceListingDealPreferences.test.ts +112 -0
  53. package/src/definitions.ts +67 -2
  54. package/src/extendedSchemas.ts +51 -4
  55. package/src/homepageSyntheticRows.ts +32 -0
  56. package/src/index.ts +4 -0
  57. package/src/sideQuestTypes.ts +1 -1
  58. package/src/utils/service/partnerDealflow.ts +47 -0
  59. package/src/utils/service/partnerRevenueShareOptIn.ts +70 -0
  60. package/src/utils/service/serviceListingDealPreferences.ts +184 -0
@@ -0,0 +1,126 @@
1
+ import { readPartnerDealflowFields, serviceListingOpenToInKindDeal, } from "./partnerDealflow.js";
2
+ import { readPartnerRevenueShareFields, REVENUE_SHARE_WITH_HOST_BASIS_LABEL, serviceListingOpenToRevenueShare, } from "./partnerRevenueShareOptIn.js";
3
+ function formatUsdFromCents(cents) {
4
+ const dollars = cents / 100;
5
+ return dollars % 1 === 0 ? `$${dollars.toFixed(0)}` : `$${dollars.toFixed(2)}`;
6
+ }
7
+ function minimumCashLabel(deal) {
8
+ if (deal?.dealRequiresMinimumCash !== true)
9
+ return null;
10
+ if (typeof deal.dealMinimumCashCents !== "number" || deal.dealMinimumCashCents <= 0) {
11
+ return null;
12
+ }
13
+ return `${formatUsdFromCents(deal.dealMinimumCashCents)} min cash`;
14
+ }
15
+ function truncateOffer(text, maxLen) {
16
+ const t = text.trim();
17
+ if (t.length <= maxLen)
18
+ return t;
19
+ return `${t.slice(0, maxLen - 1).trim()}…`;
20
+ }
21
+ /** Compact chips for cards and list rows. */
22
+ export function getServiceListingDealBadges(service) {
23
+ const badges = [];
24
+ const deal = readPartnerDealflowFields(service);
25
+ if (serviceListingOpenToRevenueShare(service)) {
26
+ badges.push({ id: "rev-share", label: "Rev share" });
27
+ }
28
+ if (serviceListingOpenToInKindDeal(service)) {
29
+ badges.push({ id: "in-kind", label: "In-kind" });
30
+ }
31
+ const minCash = minimumCashLabel(deal);
32
+ if (minCash) {
33
+ badges.push({ id: "min-cash", label: minCash });
34
+ }
35
+ const hostBidOpen = service.vendor?.preferences?.openToHostBids === true ||
36
+ service.exhibitor?.openToHostBids === true ||
37
+ service.sponsor?.openToHostBids === true;
38
+ if (hostBidOpen) {
39
+ badges.push({ id: "host-bids", label: "Host bids" });
40
+ }
41
+ return badges;
42
+ }
43
+ /** Longer copy for public detail pages. */
44
+ export function getServiceListingDealDetailRows(service) {
45
+ const rows = [];
46
+ const deal = readPartnerDealflowFields(service);
47
+ const rev = readPartnerRevenueShareFields(service);
48
+ const venue = service.venue;
49
+ if (venue?.revenueShareEnabled === true && venue.revenueShareHostPercentage != null) {
50
+ rows.push({
51
+ label: "Venue revenue share",
52
+ value: `Host keeps ${venue.revenueShareHostPercentage}% of agreed ticket or bar revenue.`,
53
+ });
54
+ }
55
+ else if (rev?.openToRevenueShareWithHost === true) {
56
+ const pct = rev.revenueShareWithHostPercentage;
57
+ const basis = rev.revenueShareWithHostBasis;
58
+ const basisLabel = basis && basis in REVENUE_SHARE_WITH_HOST_BASIS_LABEL
59
+ ? REVENUE_SHARE_WITH_HOST_BASIS_LABEL[basis]
60
+ : null;
61
+ rows.push({
62
+ label: "Revenue share with host",
63
+ value: typeof pct === "number"
64
+ ? `Shares ${pct}% with the host${basisLabel ? ` (${basisLabel.toLowerCase()})` : ""}.`
65
+ : "Open to sharing revenue with the host (percentage not set).",
66
+ });
67
+ }
68
+ if (serviceListingOpenToInKindDeal(service) && deal?.inKindDealOfferText) {
69
+ const est = typeof deal.inKindEstimatedValueCents === "number" &&
70
+ deal.inKindEstimatedValueCents > 0
71
+ ? ` Approx. value ${formatUsdFromCents(deal.inKindEstimatedValueCents)}.`
72
+ : "";
73
+ rows.push({
74
+ label: "In-kind / trade offer",
75
+ value: `${deal.inKindDealOfferText.trim()}${est}`,
76
+ });
77
+ }
78
+ const minCash = minimumCashLabel(deal);
79
+ if (minCash) {
80
+ rows.push({
81
+ label: "Minimum cash on hybrid deals",
82
+ value: `Requires ${minCash.replace(" min cash", "")} minimum cash alongside rev-share or in-kind terms so hosts know you're serious.`,
83
+ });
84
+ }
85
+ return rows;
86
+ }
87
+ /** Card preview line for in-kind (truncated). */
88
+ export function getServiceListingInKindPreview(service, maxLen = 80) {
89
+ const deal = readPartnerDealflowFields(service);
90
+ if (!serviceListingOpenToInKindDeal(service) || !deal?.inKindDealOfferText) {
91
+ return null;
92
+ }
93
+ return truncateOffer(deal.inKindDealOfferText, maxLen);
94
+ }
95
+ /** Copy live listing deal prefs onto a booking at confirmation time. */
96
+ export function buildPartnerDealflowBookingSnapshot(service) {
97
+ const rev = readPartnerRevenueShareFields(service);
98
+ const deal = readPartnerDealflowFields(service);
99
+ const venue = service.venue;
100
+ const snap = {};
101
+ if (venue?.revenueShareEnabled === true) {
102
+ snap.openToRevenueShareWithHostSnapshot = true;
103
+ snap.revenueShareWithHostPercentageSnapshot =
104
+ venue.revenueShareHostPercentage ?? null;
105
+ snap.revenueShareWithHostBasisSnapshot = venue.revenueShareBasis ?? null;
106
+ }
107
+ else if (rev?.openToRevenueShareWithHost === true) {
108
+ snap.openToRevenueShareWithHostSnapshot = true;
109
+ snap.revenueShareWithHostPercentageSnapshot =
110
+ rev.revenueShareWithHostPercentage ?? null;
111
+ snap.revenueShareWithHostBasisSnapshot =
112
+ rev.revenueShareWithHostBasis ?? null;
113
+ }
114
+ if (deal?.openToInKindDeals === true) {
115
+ snap.openToInKindDealsSnapshot = true;
116
+ snap.inKindDealOfferTextSnapshot = deal.inKindDealOfferText?.trim() ?? null;
117
+ snap.inKindEstimatedValueCentsSnapshot =
118
+ deal.inKindEstimatedValueCents ?? null;
119
+ }
120
+ if (deal?.dealRequiresMinimumCash === true) {
121
+ snap.dealRequiresMinimumCashSnapshot = true;
122
+ snap.dealMinimumCashCentsSnapshot = deal.dealMinimumCashCents ?? null;
123
+ }
124
+ return snap;
125
+ }
126
+ //# sourceMappingURL=serviceListingDealPreferences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serviceListingDealPreferences.js","sourceRoot":"","sources":["../../../src/utils/service/serviceListingDealPreferences.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bash-app/bash-common",
3
- "version": "30.406.0",
3
+ "version": "30.410.0",
4
4
  "description": "Common data and scripts to use on the frontend and backend",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,7 +37,7 @@
37
37
  "@aws-sdk/client-textract": "^3.1004.0",
38
38
  "@aws-sdk/s3-request-presigner": "^3.1004.0",
39
39
  "@types/qrcode": "^1.5.5",
40
- "lodash-es": "^4.17.21",
40
+ "lodash-es": "4.18.1",
41
41
  "mixpanel-browser": "^2.75.0",
42
42
  "qrcode": "^1.5.3",
43
43
  "slugify": "^1.6.6"
@@ -76,5 +76,8 @@
76
76
  ],
77
77
  "publishConfig": {
78
78
  "access": "public"
79
+ },
80
+ "overrides": {
81
+ "lodash-es": "4.18.1"
79
82
  }
80
83
  }
@@ -549,6 +549,8 @@ model EventTask {
549
549
  status TaskStatus?
550
550
  neededCount Int? // Number of volunteers needed
551
551
  estimatedDuration Int? // Estimated duration in minutes
552
+ doBy DateTime? // Suggested "do this by" — stay-ahead, not a deadline
553
+ dueBy DateTime? // Hard deadline for the volunteer
552
554
  createdAt DateTime? @default(now())
553
555
  organizationId String? // Organization association
554
556
  assignedTo User? @relation("TasksAssignedToMe", fields: [assignedToId], references: [id], onDelete: Cascade)
@@ -1003,6 +1005,12 @@ model BashEvent {
1003
1005
  allowAttendeeRefundRequests Boolean @default(true)
1004
1006
  /// Free-text cancellation / refund policy shown at checkout and on the guest's ticket.
1005
1007
  cancellationPolicy String?
1008
+ /// When true, the cancellation policy block is hidden on the bash detail page (default false for ticketed events, effectively true for RSVP).
1009
+ hideCancellationPolicy Boolean @default(false)
1010
+ /// When true, the "Group up" section is hidden on the bash detail page.
1011
+ hideGroupUp Boolean @default(false)
1012
+ /// When true, the "Services open to" box is hidden on the bash detail page.
1013
+ hideServicesOpenTo Boolean @default(false)
1006
1014
  /// When true (default), cancelling the bash auto-refunds paid ticket holders. Hosts may set false only before any ticket/RSVP exists.
1007
1015
  refundsGuaranteedOnCancellation Boolean @default(true)
1008
1016
  /// When true, host may postpone after tickets exist. May only flip false→true before any ticket/RSVP exists.
@@ -1059,7 +1067,7 @@ model BashEvent {
1059
1067
  topPromoterPrize String? // Optional prize label: "Free table for your crew"
1060
1068
  promoterLeaderboardIsPublic Boolean @default(false) // Host opts in to public leaderboard
1061
1069
  /// When true, the bash detail promo-code request CTA is hidden from guests (in-app notifications still work for hosts).
1062
- hideGuestPromoCodeRequest Boolean @default(false)
1070
+ hideGuestPromoCodeRequest Boolean @default(true)
1063
1071
  showAttendees Boolean @default(true)
1064
1072
  /// Host opt-in: show soft "public interest" controls on the bash detail page (signed-in viewers). Not a guest list; invite RSVP stays separate.
1065
1073
  allowPublicInterestRsvp Boolean @default(false)
@@ -5892,6 +5900,14 @@ model EventService {
5892
5900
  fulfillmentModes String[] @default([])
5893
5901
  /// Minimum advance-notice days required for F&B bookings (e.g. 7 = one week).
5894
5902
  leadTimeDays Int?
5903
+ openToRevenueShareWithHost Boolean @default(false)
5904
+ revenueShareWithHostPercentage Int?
5905
+ revenueShareWithHostBasis String?
5906
+ openToInKindDeals Boolean @default(false)
5907
+ inKindDealOfferText String?
5908
+ inKindEstimatedValueCents Int?
5909
+ dealRequiresMinimumCash Boolean @default(false)
5910
+ dealMinimumCashCents Int?
5895
5911
  crowdSize AmountOfGuests? @relation(fields: [crowdSizeId], references: [id], onDelete: Cascade)
5896
5912
  serviceRange ServiceRange? @relation(fields: [serviceRangeId], references: [id])
5897
5913
  service Service?
@@ -5950,6 +5966,14 @@ model EntertainmentService {
5950
5966
  minimumQuantity Int?
5951
5967
  tierPricing Json?
5952
5968
  pricingNotes String?
5969
+ openToRevenueShareWithHost Boolean @default(false)
5970
+ revenueShareWithHostPercentage Int?
5971
+ revenueShareWithHostBasis String?
5972
+ openToInKindDeals Boolean @default(false)
5973
+ inKindDealOfferText String?
5974
+ inKindEstimatedValueCents Int?
5975
+ dealRequiresMinimumCash Boolean @default(false)
5976
+ dealMinimumCashCents Int?
5953
5977
  crowdSize AmountOfGuests? @relation(fields: [crowdSizeId], references: [id], onDelete: Cascade)
5954
5978
  serviceRange ServiceRange? @relation(fields: [serviceRangeId], references: [id])
5955
5979
  service Service?
@@ -6064,6 +6088,18 @@ model VendorPreferences {
6064
6088
  bidMinLeadDays Int?
6065
6089
  /// small | medium | large — typical event size the vendor prefers.
6066
6090
  bidProjectSize String?
6091
+ /// Partner offers a revenue share with the host instead of (or in addition to) upfront fees.
6092
+ openToRevenueShareWithHost Boolean @default(false)
6093
+ /// 0..100 — share of the partner's relevant sales paid to the host.
6094
+ revenueShareWithHostPercentage Int?
6095
+ /// ALL_SALES | REFERRAL_ATTRIBUTED — which sales count toward the share.
6096
+ revenueShareWithHostBasis String?
6097
+ openToInKindDeals Boolean @default(false)
6098
+ inKindDealOfferText String?
6099
+ inKindEstimatedValueCents Int?
6100
+ /// Minimum cash (cents) required alongside rev-share or in-kind deals.
6101
+ dealRequiresMinimumCash Boolean @default(false)
6102
+ dealMinimumCashCents Int?
6067
6103
  createdAt DateTime @default(now())
6068
6104
  updatedAt DateTime @updatedAt
6069
6105
  vendor Vendor @relation(fields: [vendorId], references: [id], onDelete: Cascade)
@@ -6142,6 +6178,14 @@ model Exhibitor {
6142
6178
  bidMinLeadDays Int?
6143
6179
  /// small | medium | large — typical event size the exhibitor is comfortable with.
6144
6180
  bidProjectSize String?
6181
+ openToRevenueShareWithHost Boolean @default(false)
6182
+ revenueShareWithHostPercentage Int?
6183
+ revenueShareWithHostBasis String?
6184
+ openToInKindDeals Boolean @default(false)
6185
+ inKindDealOfferText String?
6186
+ inKindEstimatedValueCents Int?
6187
+ dealRequiresMinimumCash Boolean @default(false)
6188
+ dealMinimumCashCents Int?
6145
6189
 
6146
6190
  crowdSize AmountOfGuests? @relation(fields: [crowdSizeId], references: [id], onDelete: Cascade)
6147
6191
  serviceRange ServiceRange? @relation(fields: [serviceRangeId], references: [id])
@@ -6178,6 +6222,14 @@ model Sponsor {
6178
6222
  bidMinLeadDays Int?
6179
6223
  /// small | medium | large — typical event size the sponsor is comfortable with.
6180
6224
  bidProjectSize String?
6225
+ openToRevenueShareWithHost Boolean @default(false)
6226
+ revenueShareWithHostPercentage Int?
6227
+ revenueShareWithHostBasis String?
6228
+ openToInKindDeals Boolean @default(false)
6229
+ inKindDealOfferText String?
6230
+ inKindEstimatedValueCents Int?
6231
+ dealRequiresMinimumCash Boolean @default(false)
6232
+ dealMinimumCashCents Int?
6181
6233
 
6182
6234
  service Service?
6183
6235
  crowdSize AmountOfGuests? @relation(fields: [crowdSizeId], references: [id])
@@ -6269,6 +6321,11 @@ model Venue {
6269
6321
  revenueShareBasis String?
6270
6322
  /// Free-form notes shown next to the rev-share split.
6271
6323
  revenueShareNotes String?
6324
+ openToInKindDeals Boolean @default(false)
6325
+ inKindDealOfferText String?
6326
+ inKindEstimatedValueCents Int?
6327
+ dealRequiresMinimumCash Boolean @default(false)
6328
+ dealMinimumCashCents Int?
6272
6329
 
6273
6330
  fbCarryoverCredits VenueFBCarryoverCredit[]
6274
6331
  fbMinimumOverrides VenueFBMinimumOverride[]
@@ -8067,6 +8124,15 @@ model ServiceBooking {
8067
8124
  revenueShareHostPctSnapshot Int?
8068
8125
  /// Snapshot of `Venue.revenueShareBasis`.
8069
8126
  revenueShareBasisSnapshot String?
8127
+ /// Partner / venue deal prefs at confirmation (rev-share, in-kind, min cash).
8128
+ openToRevenueShareWithHostSnapshot Boolean?
8129
+ revenueShareWithHostPercentageSnapshot Int?
8130
+ revenueShareWithHostBasisSnapshot String?
8131
+ openToInKindDealsSnapshot Boolean?
8132
+ inKindDealOfferTextSnapshot String?
8133
+ inKindEstimatedValueCentsSnapshot Int?
8134
+ dealRequiresMinimumCashSnapshot Boolean?
8135
+ dealMinimumCashCentsSnapshot Int?
8070
8136
  /// Computed venue rev-share owed (cents) at settlement.
8071
8137
  revenueShareCalculatedCents Int?
8072
8138
  // Settlement lifecycle state (drives dunning, retries, dispute window).
@@ -0,0 +1,62 @@
1
+ import type { ServiceExt } from "../extendedSchemas";
2
+ import {
3
+ readPartnerDealflowFields,
4
+ serviceListingOpenToInKindDeal,
5
+ } from "../utils/service/partnerDealflow";
6
+
7
+ describe("partnerDealflow", () => {
8
+ it("detects in-kind listings with non-empty offer text", () => {
9
+ const service = {
10
+ sponsor: {
11
+ openToInKindDeals: true,
12
+ inKindDealOfferText: "100 free socks",
13
+ },
14
+ } as ServiceExt;
15
+
16
+ expect(readPartnerDealflowFields(service)?.openToInKindDeals).toBe(true);
17
+ expect(serviceListingOpenToInKindDeal(service)).toBe(true);
18
+ });
19
+
20
+ it("rejects in-kind when offer text is blank", () => {
21
+ const service = {
22
+ sponsor: {
23
+ openToInKindDeals: true,
24
+ inKindDealOfferText: " ",
25
+ },
26
+ } as ServiceExt;
27
+
28
+ expect(serviceListingOpenToInKindDeal(service)).toBe(false);
29
+ });
30
+
31
+ it("reads dealflow from vendor preferences before flat partner rows", () => {
32
+ const service = {
33
+ vendor: {
34
+ preferences: {
35
+ openToInKindDeals: true,
36
+ inKindDealOfferText: "Sample trays",
37
+ dealRequiresMinimumCash: true,
38
+ dealMinimumCashCents: 5000,
39
+ },
40
+ },
41
+ sponsor: {
42
+ openToInKindDeals: true,
43
+ inKindDealOfferText: "should not win",
44
+ },
45
+ } as ServiceExt;
46
+
47
+ const row = readPartnerDealflowFields(service);
48
+ expect(row?.inKindDealOfferText).toBe("Sample trays");
49
+ expect(row?.dealMinimumCashCents).toBe(5000);
50
+ });
51
+
52
+ it("does not treat in-kind flag alone as a listing when toggle is off", () => {
53
+ const service = {
54
+ venue: {
55
+ openToInKindDeals: false,
56
+ inKindDealOfferText: "Free room night",
57
+ },
58
+ } as ServiceExt;
59
+
60
+ expect(serviceListingOpenToInKindDeal(service)).toBe(false);
61
+ });
62
+ });
@@ -0,0 +1,68 @@
1
+ import type { ServiceExt } from "../extendedSchemas";
2
+ import {
3
+ readPartnerRevenueShareFields,
4
+ serviceListingOpenToRevenueShare,
5
+ } from "../utils/service/partnerRevenueShareOptIn";
6
+
7
+ describe("partnerRevenueShareOptIn", () => {
8
+ it("treats venue ticket/bar rev share as discoverable without partner opt-in", () => {
9
+ const service = {
10
+ venue: {
11
+ revenueShareEnabled: true,
12
+ revenueShareHostPercentage: 10,
13
+ },
14
+ } as ServiceExt;
15
+
16
+ expect(serviceListingOpenToRevenueShare(service)).toBe(true);
17
+ expect(readPartnerRevenueShareFields(service)).toBeNull();
18
+ });
19
+
20
+ it("requires partner opt-in plus a non-null percentage", () => {
21
+ const openNoPct = {
22
+ vendor: {
23
+ preferences: {
24
+ openToRevenueShareWithHost: true,
25
+ revenueShareWithHostPercentage: null,
26
+ },
27
+ },
28
+ } as ServiceExt;
29
+
30
+ expect(serviceListingOpenToRevenueShare(openNoPct)).toBe(false);
31
+
32
+ const closedWithPct = {
33
+ vendor: {
34
+ preferences: {
35
+ openToRevenueShareWithHost: false,
36
+ revenueShareWithHostPercentage: 20,
37
+ },
38
+ },
39
+ } as ServiceExt;
40
+
41
+ expect(serviceListingOpenToRevenueShare(closedWithPct)).toBe(false);
42
+
43
+ const openZero = {
44
+ exhibitor: {
45
+ openToRevenueShareWithHost: true,
46
+ revenueShareWithHostPercentage: 0,
47
+ },
48
+ } as ServiceExt;
49
+
50
+ expect(serviceListingOpenToRevenueShare(openZero)).toBe(true);
51
+ });
52
+
53
+ it("reads rev-share fields from the first matching partner subtype", () => {
54
+ const service = {
55
+ eventService: {
56
+ openToRevenueShareWithHost: true,
57
+ revenueShareWithHostPercentage: 12,
58
+ revenueShareWithHostBasis: "REFERRAL_ATTRIBUTED",
59
+ },
60
+ } as ServiceExt;
61
+
62
+ expect(readPartnerRevenueShareFields(service)).toEqual({
63
+ openToRevenueShareWithHost: true,
64
+ revenueShareWithHostPercentage: 12,
65
+ revenueShareWithHostBasis: "REFERRAL_ATTRIBUTED",
66
+ });
67
+ });
68
+ });
@@ -0,0 +1,27 @@
1
+ import {
2
+ isReservedCustomBashEventTypeDisplayName,
3
+ } from "../definitions.js";
4
+
5
+ describe("isReservedCustomBashEventTypeDisplayName", () => {
6
+ it("blocks homepage synthetic section titles", () => {
7
+ expect(isReservedCustomBashEventTypeDisplayName("Featured")).toBe(true);
8
+ expect(isReservedCustomBashEventTypeDisplayName("Popular")).toBe(true);
9
+ expect(isReservedCustomBashEventTypeDisplayName("Trending")).toBe(true);
10
+ expect(isReservedCustomBashEventTypeDisplayName("Ideas")).toBe(true);
11
+ expect(isReservedCustomBashEventTypeDisplayName("Pro Events")).toBe(true);
12
+ expect(isReservedCustomBashEventTypeDisplayName("The crew's going")).toBe(
13
+ true
14
+ );
15
+ });
16
+
17
+ it("blocks built-in bash type labels", () => {
18
+ expect(isReservedCustomBashEventTypeDisplayName("Birthday")).toBe(true);
19
+ expect(isReservedCustomBashEventTypeDisplayName("House Party")).toBe(true);
20
+ });
21
+
22
+ it("allows novel custom labels", () => {
23
+ expect(isReservedCustomBashEventTypeDisplayName("Rooftop Party")).toBe(
24
+ false
25
+ );
26
+ });
27
+ });
@@ -0,0 +1,112 @@
1
+ import type { ServiceExt } from "../extendedSchemas";
2
+ import {
3
+ buildPartnerDealflowBookingSnapshot,
4
+ getServiceListingDealBadges,
5
+ getServiceListingDealDetailRows,
6
+ } from "../utils/service/serviceListingDealPreferences";
7
+
8
+ describe("serviceListingDealPreferences", () => {
9
+ it("shows min cash badge only when amount is set", () => {
10
+ const withMin = {
11
+ sponsor: {
12
+ dealRequiresMinimumCash: true,
13
+ dealMinimumCashCents: 25000,
14
+ },
15
+ } as ServiceExt;
16
+
17
+ expect(getServiceListingDealBadges(withMin).some((b) => b.id === "min-cash")).toBe(
18
+ true
19
+ );
20
+
21
+ const checkboxOnly = {
22
+ sponsor: {
23
+ dealRequiresMinimumCash: true,
24
+ dealMinimumCashCents: null,
25
+ },
26
+ } as ServiceExt;
27
+
28
+ expect(
29
+ getServiceListingDealBadges(checkboxOnly).some((b) => b.id === "min-cash")
30
+ ).toBe(false);
31
+ });
32
+
33
+ it("snapshots venue rev share instead of partner fields when venue is enabled", () => {
34
+ const service = {
35
+ venue: {
36
+ revenueShareEnabled: true,
37
+ revenueShareHostPercentage: 18,
38
+ revenueShareBasis: "ALL_SALES",
39
+ },
40
+ eventService: {
41
+ openToRevenueShareWithHost: true,
42
+ revenueShareWithHostPercentage: 99,
43
+ },
44
+ } as ServiceExt;
45
+
46
+ const snap = buildPartnerDealflowBookingSnapshot(service);
47
+
48
+ expect(snap.openToRevenueShareWithHostSnapshot).toBe(true);
49
+ expect(snap.revenueShareWithHostPercentageSnapshot).toBe(18);
50
+ expect(snap.revenueShareWithHostBasisSnapshot).toBe("ALL_SALES");
51
+ });
52
+
53
+ it("snapshots hybrid min-cash flag even when cents are unset (Stripe amount is separate)", () => {
54
+ const service = {
55
+ sponsor: {
56
+ dealRequiresMinimumCash: true,
57
+ dealMinimumCashCents: null,
58
+ openToInKindDeals: true,
59
+ inKindDealOfferText: "Branded socks",
60
+ },
61
+ } as ServiceExt;
62
+
63
+ const snap = buildPartnerDealflowBookingSnapshot(service);
64
+
65
+ expect(snap.dealRequiresMinimumCashSnapshot).toBe(true);
66
+ expect(snap.dealMinimumCashCentsSnapshot).toBeNull();
67
+ expect(snap.openToInKindDealsSnapshot).toBe(true);
68
+ expect(snap.inKindDealOfferTextSnapshot).toBe("Branded socks");
69
+ });
70
+
71
+ it("snapshots rev-share opt-in with null percentage when partner saved incomplete prefs", () => {
72
+ const service = {
73
+ sponsor: {
74
+ openToRevenueShareWithHost: true,
75
+ revenueShareWithHostPercentage: null,
76
+ },
77
+ } as ServiceExt;
78
+
79
+ expect(buildPartnerDealflowBookingSnapshot(service)).toEqual({
80
+ openToRevenueShareWithHostSnapshot: true,
81
+ revenueShareWithHostPercentageSnapshot: null,
82
+ revenueShareWithHostBasisSnapshot: null,
83
+ });
84
+ });
85
+
86
+ it("includes min-cash detail copy only when cents are positive", () => {
87
+ const hybrid = {
88
+ sponsor: {
89
+ dealRequiresMinimumCash: true,
90
+ dealMinimumCashCents: 25000,
91
+ openToInKindDeals: true,
92
+ inKindDealOfferText: "100 pairs",
93
+ },
94
+ } as ServiceExt;
95
+
96
+ const rows = getServiceListingDealDetailRows(hybrid);
97
+ expect(rows.some((r) => r.label.includes("Minimum cash"))).toBe(true);
98
+
99
+ const flagOnly = {
100
+ sponsor: {
101
+ dealRequiresMinimumCash: true,
102
+ dealMinimumCashCents: 0,
103
+ },
104
+ } as ServiceExt;
105
+
106
+ expect(
107
+ getServiceListingDealDetailRows(flagOnly).some((r) =>
108
+ r.label.includes("Minimum cash")
109
+ )
110
+ ).toBe(false);
111
+ });
112
+ });
@@ -32,6 +32,8 @@ import {
32
32
  ServiceBookingExt,
33
33
  type UserExt,
34
34
  } from "./extendedSchemas.js";
35
+ import { DISCOVERY_SCOPE_SECTION_TITLE } from "./discoveryScope.js";
36
+ import { HOMEPAGE_SYNTHETIC_SECTION_ROW_KEYS } from "./homepageSyntheticRows.js";
35
37
  import { LuxonDateRange } from "./utils/luxonUtils.js";
36
38
  import { ApiServiceCantBookReason } from "./utils/service/apiServiceBookingApiUtils.js";
37
39
  import { ServiceAttendeeOption } from "./utils/service/attendeeOptionUtils.js";
@@ -705,8 +707,8 @@ export const BASH_EVENT_WIZARD_WHAT_SUBSTEP = {
705
707
  } as const;
706
708
 
707
709
  export const BASH_EVENT_WIZARD_WHERE_SUBSTEP = {
708
- LOCATION: 0,
709
- UPLOAD_MEDIA: 1,
710
+ UPLOAD_MEDIA: 0,
711
+ LOCATION: 1,
710
712
  } as const;
711
713
 
712
714
  export const BASH_EVENT_WIZARD_WHEN_SUBSTEP = {
@@ -1904,6 +1906,20 @@ export const DIETARY_OPTION_VALUES: ReadonlyArray<DietaryOption> = [
1904
1906
  "AlcoholFreeOptions",
1905
1907
  ];
1906
1908
 
1909
+ /**
1910
+ * Guest-facing filter chips — everyday accommodations only.
1911
+ * Religious, drink, and diet-trend tags stay on the host form / enum
1912
+ * so existing events keep their data, but they do not crowd the filter menu.
1913
+ */
1914
+ export const DIETARY_OPTION_FILTER_VALUES: ReadonlyArray<DietaryOption> = [
1915
+ "VegetarianOptions",
1916
+ "VeganOptions",
1917
+ "GlutenFreeOptions",
1918
+ "DairyFreeOptions",
1919
+ "NutFreeOptions",
1920
+ "SugarFreeOptions",
1921
+ ];
1922
+
1907
1923
  /** Display labels for `IncomeRange` (wizard primary/secondary chips). */
1908
1924
  export type IncomeRangeToStringType = {
1909
1925
  [key in IncomeRange]: string;
@@ -2168,6 +2184,55 @@ export function bashSectionTitleForBashType(bashType: string): string {
2168
2184
  return pluralizeBashEventSingularLabel(spaced);
2169
2185
  }
2170
2186
 
2187
+ function normalizeReservedCustomBashEventTypeLabel(label: string): string {
2188
+ return label.trim().toLowerCase().replace(/\s+/g, " ");
2189
+ }
2190
+
2191
+ let reservedCustomBashEventTypeLabels: Set<string> | null = null;
2192
+
2193
+ function reservedCustomBashEventTypeLabelSet(): Set<string> {
2194
+ if (reservedCustomBashEventTypeLabels) {
2195
+ return reservedCustomBashEventTypeLabels;
2196
+ }
2197
+ const set = new Set<string>();
2198
+ const add = (value: string) => {
2199
+ const normalized = normalizeReservedCustomBashEventTypeLabel(value);
2200
+ if (normalized) set.add(normalized);
2201
+ };
2202
+
2203
+ for (const key of HOMEPAGE_SYNTHETIC_SECTION_ROW_KEYS) {
2204
+ add(key);
2205
+ add(bashSectionTitleForBashType(key));
2206
+ }
2207
+ for (const title of Object.values(DISCOVERY_SCOPE_SECTION_TITLE)) {
2208
+ add(title);
2209
+ }
2210
+ for (const title of Object.values(SYNTHETIC_BASH_SECTION_TITLE)) {
2211
+ add(title);
2212
+ }
2213
+ for (const enumKey of BASH_EVENT_TYPE_ENUM_VALUES) {
2214
+ const k = enumKey as BashEventType;
2215
+ add(enumKey);
2216
+ add(BashEventTypeToString[k]);
2217
+ add(bashSectionTitleForBashType(enumKey));
2218
+ }
2219
+
2220
+ reservedCustomBashEventTypeLabels = set;
2221
+ return set;
2222
+ }
2223
+
2224
+ /**
2225
+ * Homepage section titles and built-in bash types cannot be created as custom
2226
+ * types in the wizard (would collide with synthetic discovery rows).
2227
+ */
2228
+ export function isReservedCustomBashEventTypeDisplayName(
2229
+ displayName: string
2230
+ ): boolean {
2231
+ return reservedCustomBashEventTypeLabelSet().has(
2232
+ normalizeReservedCustomBashEventTypeLabel(displayName)
2233
+ );
2234
+ }
2235
+
2171
2236
  export type RecordKey = string | number | symbol;
2172
2237
  export type PartialExcept<T, K extends keyof T> = Partial<T> & {
2173
2238
  [P in K]: T[P];