@bash-app/bash-common 30.408.0 → 30.413.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 (78) 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/__tests__/statusEnums.test.js +1 -0
  18. package/dist/__tests__/statusEnums.test.js.map +1 -1
  19. package/dist/definitions.d.ts +7 -0
  20. package/dist/definitions.d.ts.map +1 -1
  21. package/dist/definitions.js +42 -0
  22. package/dist/definitions.js.map +1 -1
  23. package/dist/extendedSchemas.d.ts +101 -1
  24. package/dist/extendedSchemas.d.ts.map +1 -1
  25. package/dist/extendedSchemas.js +31 -0
  26. package/dist/extendedSchemas.js.map +1 -1
  27. package/dist/homepageSyntheticRows.d.ts +9 -0
  28. package/dist/homepageSyntheticRows.d.ts.map +1 -0
  29. package/dist/homepageSyntheticRows.js +26 -0
  30. package/dist/homepageSyntheticRows.js.map +1 -0
  31. package/dist/index.d.ts +4 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +4 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/membershipDefinitions.d.ts +2 -0
  36. package/dist/membershipDefinitions.d.ts.map +1 -1
  37. package/dist/membershipDefinitions.js +2 -0
  38. package/dist/membershipDefinitions.js.map +1 -1
  39. package/dist/mirroredPrismaEnums.d.ts +1 -0
  40. package/dist/mirroredPrismaEnums.d.ts.map +1 -1
  41. package/dist/mirroredPrismaEnums.js +1 -0
  42. package/dist/mirroredPrismaEnums.js.map +1 -1
  43. package/dist/utils/__tests__/slugUtils.test.js +4 -4
  44. package/dist/utils/__tests__/slugUtils.test.js.map +1 -1
  45. package/dist/utils/service/partnerDealflow.d.ts +12 -0
  46. package/dist/utils/service/partnerDealflow.d.ts.map +1 -0
  47. package/dist/utils/service/partnerDealflow.js +33 -0
  48. package/dist/utils/service/partnerDealflow.js.map +1 -0
  49. package/dist/utils/service/partnerRevenueShareOptIn.d.ts +18 -0
  50. package/dist/utils/service/partnerRevenueShareOptIn.d.ts.map +1 -0
  51. package/dist/utils/service/partnerRevenueShareOptIn.js +46 -0
  52. package/dist/utils/service/partnerRevenueShareOptIn.js.map +1 -0
  53. package/dist/utils/service/serviceListingDealPreferences.d.ts +28 -0
  54. package/dist/utils/service/serviceListingDealPreferences.d.ts.map +1 -0
  55. package/dist/utils/service/serviceListingDealPreferences.js +126 -0
  56. package/dist/utils/service/serviceListingDealPreferences.js.map +1 -0
  57. package/dist/utils/slugUtils.d.ts +4 -1
  58. package/dist/utils/slugUtils.d.ts.map +1 -1
  59. package/dist/utils/slugUtils.js +8 -2
  60. package/dist/utils/slugUtils.js.map +1 -1
  61. package/package.json +5 -2
  62. package/prisma/schema.prisma +69 -0
  63. package/src/__tests__/partnerDealflow.test.ts +62 -0
  64. package/src/__tests__/partnerRevenueShareOptIn.test.ts +68 -0
  65. package/src/__tests__/reservedCustomBashEventType.test.ts +27 -0
  66. package/src/__tests__/serviceListingDealPreferences.test.ts +112 -0
  67. package/src/__tests__/statusEnums.test.ts +1 -0
  68. package/src/definitions.ts +53 -0
  69. package/src/extendedSchemas.ts +60 -0
  70. package/src/homepageSyntheticRows.ts +32 -0
  71. package/src/index.ts +4 -0
  72. package/src/membershipDefinitions.ts +2 -0
  73. package/src/mirroredPrismaEnums.ts +1 -0
  74. package/src/utils/__tests__/slugUtils.test.ts +4 -3
  75. package/src/utils/service/partnerDealflow.ts +47 -0
  76. package/src/utils/service/partnerRevenueShareOptIn.ts +70 -0
  77. package/src/utils/service/serviceListingDealPreferences.ts +184 -0
  78. package/src/utils/slugUtils.ts +12 -1
@@ -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
+ });
@@ -132,6 +132,7 @@ describe("status enum value sets", () => {
132
132
  "BirthdayReward",
133
133
  "EventReferralReward",
134
134
  "RsvpBonus",
135
+ "AvailabilitySignal",
135
136
  "TicketCheckout",
136
137
  "AttendanceBonus",
137
138
  "PostBeforeEvent",
@@ -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";
@@ -386,6 +388,8 @@ export type DoorConfigPublic = {
386
388
  doorPaymentRail: DoorPaymentRail;
387
389
  /** Host has verified Square + location when rail is or can be Square. */
388
390
  squareLocationReady: boolean;
391
+ /** How this viewer may use the door app. Omitted on older API. */
392
+ viewerDoorAccess?: "none" | "pin" | "staff";
389
393
  };
390
394
 
391
395
  /** Door scanner: ticket QR verification result. */
@@ -2182,6 +2186,55 @@ export function bashSectionTitleForBashType(bashType: string): string {
2182
2186
  return pluralizeBashEventSingularLabel(spaced);
2183
2187
  }
2184
2188
 
2189
+ function normalizeReservedCustomBashEventTypeLabel(label: string): string {
2190
+ return label.trim().toLowerCase().replace(/\s+/g, " ");
2191
+ }
2192
+
2193
+ let reservedCustomBashEventTypeLabels: Set<string> | null = null;
2194
+
2195
+ function reservedCustomBashEventTypeLabelSet(): Set<string> {
2196
+ if (reservedCustomBashEventTypeLabels) {
2197
+ return reservedCustomBashEventTypeLabels;
2198
+ }
2199
+ const set = new Set<string>();
2200
+ const add = (value: string) => {
2201
+ const normalized = normalizeReservedCustomBashEventTypeLabel(value);
2202
+ if (normalized) set.add(normalized);
2203
+ };
2204
+
2205
+ for (const key of HOMEPAGE_SYNTHETIC_SECTION_ROW_KEYS) {
2206
+ add(key);
2207
+ add(bashSectionTitleForBashType(key));
2208
+ }
2209
+ for (const title of Object.values(DISCOVERY_SCOPE_SECTION_TITLE)) {
2210
+ add(title);
2211
+ }
2212
+ for (const title of Object.values(SYNTHETIC_BASH_SECTION_TITLE)) {
2213
+ add(title);
2214
+ }
2215
+ for (const enumKey of BASH_EVENT_TYPE_ENUM_VALUES) {
2216
+ const k = enumKey as BashEventType;
2217
+ add(enumKey);
2218
+ add(BashEventTypeToString[k]);
2219
+ add(bashSectionTitleForBashType(enumKey));
2220
+ }
2221
+
2222
+ reservedCustomBashEventTypeLabels = set;
2223
+ return set;
2224
+ }
2225
+
2226
+ /**
2227
+ * Homepage section titles and built-in bash types cannot be created as custom
2228
+ * types in the wizard (would collide with synthetic discovery rows).
2229
+ */
2230
+ export function isReservedCustomBashEventTypeDisplayName(
2231
+ displayName: string
2232
+ ): boolean {
2233
+ return reservedCustomBashEventTypeLabelSet().has(
2234
+ normalizeReservedCustomBashEventTypeLabel(displayName)
2235
+ );
2236
+ }
2237
+
2185
2238
  export type RecordKey = string | number | symbol;
2186
2239
  export type PartialExcept<T, K extends keyof T> = Partial<T> & {
2187
2240
  [P in K]: T[P];
@@ -610,6 +610,8 @@ type BashEventExtBase = Override<
610
610
  | "postponedAt"
611
611
  | "postponeResponseDeadline"
612
612
  | "seatMap"
613
+ | "eventAccessPasswordHash"
614
+ | "doorAccessCodeDigest"
613
615
  >,
614
616
  {
615
617
  icebreakerPrompt: string | null;
@@ -751,6 +753,20 @@ type BashEventExtBase = Override<
751
753
  * Host opt-in: guests must have a profile photo to RSVP or buy tickets.
752
754
  */
753
755
  requireProfilePhoto: boolean;
756
+ /**
757
+ * True when the host set an event-page password. The hash is never returned.
758
+ */
759
+ eventAccessPasswordRequired?: boolean;
760
+ /**
761
+ * True when the viewer still needs to unlock a password-protected bash.
762
+ */
763
+ eventAccessLocked?: boolean;
764
+ /**
765
+ * Present on server reads. Stripped from JSON responses before they leave the API.
766
+ */
767
+ eventAccessPasswordHash?: string | null;
768
+ /** Present on server reads. Stripped from JSON responses before they leave the API. */
769
+ doorAccessCodeDigest?: string | null;
754
770
  };
755
771
 
756
772
  export type BashEventExt = Omit<
@@ -786,6 +802,17 @@ export type BashEventUpsertWireInput = Override<
786
802
  startDateTime?: Date | string | null;
787
803
  endDateTime?: Date | string | null;
788
804
  ideaExpiresAt?: Date | string | null;
805
+ pendingScheduleClock?:
806
+ | {
807
+ startHour: number;
808
+ startMinute: number;
809
+ endHour: number | null;
810
+ endMinute: number | null;
811
+ endsNextDay: boolean;
812
+ }
813
+ | import("@prisma/client").Prisma.JsonValue
814
+ | null;
815
+ advertiseRecurrence?: boolean;
789
816
  }
790
817
  >,
791
818
  {
@@ -975,6 +1002,24 @@ export const SERVICE_SUBTYPE_CROWD_RANGE_INCLUDE = {
975
1002
  serviceRange: true,
976
1003
  } as const;
977
1004
 
1005
+ /** Rev-share, in-kind, and min-cash (all partner subtype tables + venue). */
1006
+ export const SERVICE_PARTNER_REV_INKIND_SELECT = {
1007
+ openToRevenueShareWithHost: true,
1008
+ revenueShareWithHostPercentage: true,
1009
+ revenueShareWithHostBasis: true,
1010
+ openToInKindDeals: true,
1011
+ inKindDealOfferText: true,
1012
+ inKindEstimatedValueCents: true,
1013
+ dealRequiresMinimumCash: true,
1014
+ dealMinimumCashCents: true,
1015
+ } as const;
1016
+
1017
+ /** Includes host-bid opt-in (vendor prefs, exhibitor, sponsor only). */
1018
+ export const SERVICE_PARTNER_DEAL_PREF_SELECT = {
1019
+ ...SERVICE_PARTNER_REV_INKIND_SELECT,
1020
+ openToHostBids: true,
1021
+ } as const;
1022
+
978
1023
  export const VENDOR_DATA_TO_INCLUDE = {
979
1024
  ...SERVICE_SUBTYPE_CROWD_RANGE_INCLUDE,
980
1025
  spaceRequirements: true,
@@ -1069,6 +1114,14 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
1069
1114
  capacity: true,
1070
1115
  amenities: true,
1071
1116
  features: true,
1117
+ revenueShareEnabled: true,
1118
+ revenueShareHostPercentage: true,
1119
+ revenueShareBasis: true,
1120
+ openToInKindDeals: true,
1121
+ inKindDealOfferText: true,
1122
+ inKindEstimatedValueCents: true,
1123
+ dealRequiresMinimumCash: true,
1124
+ dealMinimumCashCents: true,
1072
1125
  },
1073
1126
  },
1074
1127
  eventService: {
@@ -1086,6 +1139,7 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
1086
1139
  goodsOrServices: true,
1087
1140
  fulfillmentModes: true,
1088
1141
  leadTimeDays: true,
1142
+ ...SERVICE_PARTNER_REV_INKIND_SELECT,
1089
1143
  },
1090
1144
  },
1091
1145
  entertainmentService: {
@@ -1094,6 +1148,7 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
1094
1148
  entertainmentServiceTypes: true,
1095
1149
  genre: true,
1096
1150
  crowdSize: true,
1151
+ ...SERVICE_PARTNER_REV_INKIND_SELECT,
1097
1152
  },
1098
1153
  },
1099
1154
  vendor: {
@@ -1108,18 +1163,23 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
1108
1163
  venueTypes: true,
1109
1164
  detailsText: true,
1110
1165
  crowdSize: true,
1166
+ preferences: {
1167
+ select: SERVICE_PARTNER_DEAL_PREF_SELECT,
1168
+ },
1111
1169
  },
1112
1170
  },
1113
1171
  exhibitor: {
1114
1172
  select: {
1115
1173
  id: true,
1116
1174
  crowdSize: true,
1175
+ ...SERVICE_PARTNER_DEAL_PREF_SELECT,
1117
1176
  },
1118
1177
  },
1119
1178
  sponsor: {
1120
1179
  select: {
1121
1180
  id: true,
1122
1181
  crowdSize: true,
1182
+ ...SERVICE_PARTNER_DEAL_PREF_SELECT,
1123
1183
  },
1124
1184
  },
1125
1185
  } satisfies Prisma.ServiceInclude;
@@ -0,0 +1,32 @@
1
+ import { DISCOVERY_SCOPE_ROW_KEYS } from "./discoveryScope.js";
2
+
3
+ /** Algorithm / geo / social rows — not host-picked `BashEventType` values. */
4
+ export const HOMEPAGE_DISCOVERY_SYNTHETIC_ROW_KEYS = [
5
+ "Ideas",
6
+ "Featured",
7
+ "Popular",
8
+ "Trending",
9
+ "CrewGoing",
10
+ ...DISCOVERY_SCOPE_ROW_KEYS,
11
+ ] as const;
12
+
13
+ /** Membership-tier inventory rows (creator tier, not event type). */
14
+ export const MEMBERSHIP_TIER_HOMEPAGE_ROW_KEYS = [
15
+ "Pro Events",
16
+ "Elite Events",
17
+ "Legend Events",
18
+ ] as const;
19
+
20
+ export const HOMEPAGE_SYNTHETIC_SECTION_ROW_KEYS: readonly string[] = [
21
+ ...HOMEPAGE_DISCOVERY_SYNTHETIC_ROW_KEYS,
22
+ ...MEMBERSHIP_TIER_HOMEPAGE_ROW_KEYS,
23
+ ];
24
+
25
+ export function isHomepageSyntheticSectionRowKey(key: string): boolean {
26
+ return (HOMEPAGE_SYNTHETIC_SECTION_ROW_KEYS as readonly string[]).includes(
27
+ key
28
+ );
29
+ }
30
+
31
+ /** @deprecated Prefer `HOMEPAGE_DISCOVERY_SYNTHETIC_ROW_KEYS` — kept for app imports. */
32
+ export const HOMEPAGE_DISCOVERY_ROW_KEYS = HOMEPAGE_DISCOVERY_SYNTHETIC_ROW_KEYS;
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export * from "./sideQuestTypes.js";
15
15
  export * from "./icebreakerPrompts.js";
16
16
  export * from "./causeTags.js";
17
17
  export * from "./discoveryScope.js";
18
+ export * from "./homepageSyntheticRows.js";
18
19
  export * from "./userTaste.js";
19
20
  export * from "./crewSocialCopy.js";
20
21
  export * from "./suggestionScore.js";
@@ -407,6 +408,9 @@ export * from "./utils/agencyBookingCheckoutCopy.js";
407
408
  export * from "./utils/service/serviceUtils.js";
408
409
  export * from "./utils/service/serviceListingMediaUtils.js";
409
410
  export * from "./utils/service/venueSettlementUtils.js";
411
+ export * from "./utils/service/partnerRevenueShareOptIn.js";
412
+ export * from "./utils/service/partnerDealflow.js";
413
+ export * from "./utils/service/serviceListingDealPreferences.js";
410
414
  export * from "./utils/service/offerAllowListUtils.js";
411
415
  export * from "./utils/service/offerComparisonUtils.js";
412
416
  export * from "./utils/service/unifiedPricingResolver.js";
@@ -266,6 +266,8 @@ export const BASHPOINTS_CONFIG = {
266
266
 
267
267
  // Recurring engagement rewards (once per user per event)
268
268
  rsvpBonus: 25, // First positive RSVP (Going/Maybe/WantsToGo) per event
269
+ /** Once per window period: Now/Tonight/Custom = UTC day, ThisWeekend = that weekend */
270
+ availabilityWindowBonus: 25,
269
271
  ticketCheckoutBonus: 50, // Claiming or buying a ticket (free or paid) per event
270
272
  attendanceBonus: 150, // Checking in at the event (attending > posting)
271
273
  postBeforeEventBonus: 50, // First BashFeed post linked to event before it ends
@@ -186,6 +186,7 @@ export const CreditSourceType = {
186
186
  BirthdayReward: "BirthdayReward",
187
187
  EventReferralReward: "EventReferralReward",
188
188
  RsvpBonus: "RsvpBonus",
189
+ AvailabilitySignal: "AvailabilitySignal",
189
190
  TicketCheckout: "TicketCheckout",
190
191
  AttendanceBonus: "AttendanceBonus",
191
192
  PostBeforeEvent: "PostBeforeEvent",
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, test } from "@jest/globals";
2
2
  import {
3
3
  BASH_DETAIL_URL,
4
+ BASH_EVENT_WIZARD_GENERATE_SUBSTEP,
4
5
  BASH_EVENT_WIZARD_HOW_MAJOR_STEP,
5
6
  BASH_EVENT_WIZARD_HOW_SUBSTEP,
6
7
  BASH_EVENT_WIZARD_HOW_SUBSTEP_PAYOUT,
@@ -143,13 +144,13 @@ describe("getBashEventWizardPayoutStepPath", () => {
143
144
  });
144
145
 
145
146
  describe("getBashEventWizardStartPath", () => {
146
- test("defaults to the new draft at Templates", () => {
147
+ test("opens a new draft on Generate", () => {
147
148
  expect(getBashEventWizardStartPath()).toBe(
148
- `${BASH_EVENT_WIZARD_PATH_PREFIX}/new/${BASH_EVENT_WIZARD_MAJOR.TEMPLATES}/${BASH_EVENT_WIZARD_TEMPLATES_SUBSTEP.START}`
149
+ `${BASH_EVENT_WIZARD_PATH_PREFIX}/new/${BASH_EVENT_WIZARD_MAJOR.GENERATE}/${BASH_EVENT_WIZARD_GENERATE_SUBSTEP.START}`
149
150
  );
150
151
  });
151
152
 
152
- test("uses an event id when provided", () => {
153
+ test("opens an existing event on Quick bash", () => {
153
154
  expect(getBashEventWizardStartPath("evt-1")).toBe(
154
155
  `${BASH_EVENT_WIZARD_PATH_PREFIX}/evt-1/${BASH_EVENT_WIZARD_MAJOR.TEMPLATES}/${BASH_EVENT_WIZARD_TEMPLATES_SUBSTEP.START}`
155
156
  );
@@ -0,0 +1,47 @@
1
+ import type { ServiceExt } from "../../extendedSchemas";
2
+
3
+ export type PartnerDealflowFields = {
4
+ openToInKindDeals?: boolean | null;
5
+ inKindDealOfferText?: string | null;
6
+ inKindEstimatedValueCents?: number | null;
7
+ dealRequiresMinimumCash?: boolean | null;
8
+ dealMinimumCashCents?: number | null;
9
+ };
10
+
11
+ function rowFromPartner(
12
+ row: PartnerDealflowFields | null | undefined
13
+ ): PartnerDealflowFields | null {
14
+ if (!row) return null;
15
+ return row;
16
+ }
17
+
18
+ /** Reads in-kind + hybrid cash prefs from any partner subtype payload. */
19
+ export function readPartnerDealflowFields(
20
+ service: ServiceExt
21
+ ): PartnerDealflowFields | null {
22
+ if (service.vendor?.preferences) {
23
+ return rowFromPartner(service.vendor.preferences);
24
+ }
25
+ if (service.exhibitor) {
26
+ return rowFromPartner(service.exhibitor);
27
+ }
28
+ if (service.sponsor) {
29
+ return rowFromPartner(service.sponsor);
30
+ }
31
+ if (service.entertainmentService) {
32
+ return rowFromPartner(service.entertainmentService);
33
+ }
34
+ if (service.eventService) {
35
+ return rowFromPartner(service.eventService);
36
+ }
37
+ if (service.venue) {
38
+ return rowFromPartner(service.venue);
39
+ }
40
+ return null;
41
+ }
42
+
43
+ export function serviceListingOpenToInKindDeal(service: ServiceExt): boolean {
44
+ const row = readPartnerDealflowFields(service);
45
+ const text = row?.inKindDealOfferText?.trim();
46
+ return row?.openToInKindDeals === true && Boolean(text && text.length > 0);
47
+ }
@@ -0,0 +1,70 @@
1
+ import type { ServiceExt } from "../../extendedSchemas";
2
+
3
+ /** Which sales count when a partner shares revenue with the host. */
4
+ export const REVENUE_SHARE_WITH_HOST_BASIS = {
5
+ ALL_SALES: "ALL_SALES",
6
+ REFERRAL_ATTRIBUTED: "REFERRAL_ATTRIBUTED",
7
+ } as const;
8
+
9
+ export type RevenueShareWithHostBasis =
10
+ (typeof REVENUE_SHARE_WITH_HOST_BASIS)[keyof typeof REVENUE_SHARE_WITH_HOST_BASIS];
11
+
12
+ export const REVENUE_SHARE_WITH_HOST_BASIS_LABEL: Record<
13
+ RevenueShareWithHostBasis,
14
+ string
15
+ > = {
16
+ ALL_SALES: "All sales at the bash",
17
+ REFERRAL_ATTRIBUTED: "Sales from guests using my code only",
18
+ };
19
+
20
+ export type PartnerRevenueShareFields = {
21
+ openToRevenueShareWithHost?: boolean | null;
22
+ revenueShareWithHostPercentage?: number | null;
23
+ revenueShareWithHostBasis?: string | null;
24
+ };
25
+
26
+ function partnerFieldsFromRecord(
27
+ row: PartnerRevenueShareFields | null | undefined
28
+ ): PartnerRevenueShareFields | null {
29
+ if (!row) return null;
30
+ return row;
31
+ }
32
+
33
+ /** Reads partner rev-share opt-in from any service subtype payload. */
34
+ export function readPartnerRevenueShareFields(
35
+ service: ServiceExt
36
+ ): PartnerRevenueShareFields | null {
37
+ if (service.vendor?.preferences) {
38
+ return partnerFieldsFromRecord(service.vendor.preferences);
39
+ }
40
+ if (service.exhibitor) {
41
+ return partnerFieldsFromRecord(service.exhibitor);
42
+ }
43
+ if (service.sponsor) {
44
+ return partnerFieldsFromRecord(service.sponsor);
45
+ }
46
+ if (service.entertainmentService) {
47
+ return partnerFieldsFromRecord(service.entertainmentService);
48
+ }
49
+ if (service.eventService) {
50
+ return partnerFieldsFromRecord(service.eventService);
51
+ }
52
+ return null;
53
+ }
54
+
55
+ /** True when listing is open to a revenue-share deal (partner or venue ticket split). */
56
+ export function serviceListingOpenToRevenueShare(service: ServiceExt): boolean {
57
+ const venue = service.venue;
58
+ if (
59
+ venue?.revenueShareEnabled === true &&
60
+ venue.revenueShareHostPercentage != null
61
+ ) {
62
+ return true;
63
+ }
64
+ const partner = readPartnerRevenueShareFields(service);
65
+ return (
66
+ partner?.openToRevenueShareWithHost === true &&
67
+ partner.revenueShareWithHostPercentage != null &&
68
+ partner.revenueShareWithHostPercentage >= 0
69
+ );
70
+ }