@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
@@ -667,6 +667,9 @@ type BashEventExtBase = Override<
667
667
  dateOptions?: IdeaDateOption[];
668
668
  /** Public aggregate when poll enabled (no PII) */
669
669
  datePollPublicSummary?: IdeaDatePollPublicSummary;
670
+ hideCancellationPolicy?: boolean;
671
+ hideGroupUp?: boolean;
672
+ hideServicesOpenTo?: boolean;
670
673
  /** GET /event/:id — tickets sold (non-cancelled), for social proof */
671
674
  goingCount?: number;
672
675
  /** GET /event/:id — true when at least one Budget line has `showOnBashDetailPage` set. Drives the "View budget" reveal on the public bash page; never carries dollar amounts. */
@@ -972,6 +975,24 @@ export const SERVICE_SUBTYPE_CROWD_RANGE_INCLUDE = {
972
975
  serviceRange: true,
973
976
  } as const;
974
977
 
978
+ /** Rev-share, in-kind, and min-cash (all partner subtype tables + venue). */
979
+ export const SERVICE_PARTNER_REV_INKIND_SELECT = {
980
+ openToRevenueShareWithHost: true,
981
+ revenueShareWithHostPercentage: true,
982
+ revenueShareWithHostBasis: true,
983
+ openToInKindDeals: true,
984
+ inKindDealOfferText: true,
985
+ inKindEstimatedValueCents: true,
986
+ dealRequiresMinimumCash: true,
987
+ dealMinimumCashCents: true,
988
+ } as const;
989
+
990
+ /** Includes host-bid opt-in (vendor prefs, exhibitor, sponsor only). */
991
+ export const SERVICE_PARTNER_DEAL_PREF_SELECT = {
992
+ ...SERVICE_PARTNER_REV_INKIND_SELECT,
993
+ openToHostBids: true,
994
+ } as const;
995
+
975
996
  export const VENDOR_DATA_TO_INCLUDE = {
976
997
  ...SERVICE_SUBTYPE_CROWD_RANGE_INCLUDE,
977
998
  spaceRequirements: true,
@@ -1066,6 +1087,14 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
1066
1087
  capacity: true,
1067
1088
  amenities: true,
1068
1089
  features: true,
1090
+ revenueShareEnabled: true,
1091
+ revenueShareHostPercentage: true,
1092
+ revenueShareBasis: true,
1093
+ openToInKindDeals: true,
1094
+ inKindDealOfferText: true,
1095
+ inKindEstimatedValueCents: true,
1096
+ dealRequiresMinimumCash: true,
1097
+ dealMinimumCashCents: true,
1069
1098
  },
1070
1099
  },
1071
1100
  eventService: {
@@ -1083,6 +1112,7 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
1083
1112
  goodsOrServices: true,
1084
1113
  fulfillmentModes: true,
1085
1114
  leadTimeDays: true,
1115
+ ...SERVICE_PARTNER_REV_INKIND_SELECT,
1086
1116
  },
1087
1117
  },
1088
1118
  entertainmentService: {
@@ -1091,6 +1121,7 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
1091
1121
  entertainmentServiceTypes: true,
1092
1122
  genre: true,
1093
1123
  crowdSize: true,
1124
+ ...SERVICE_PARTNER_REV_INKIND_SELECT,
1094
1125
  },
1095
1126
  },
1096
1127
  vendor: {
@@ -1105,18 +1136,23 @@ export const SERVICE_PUBLIC_LIST_DATA_TO_INCLUDE = {
1105
1136
  venueTypes: true,
1106
1137
  detailsText: true,
1107
1138
  crowdSize: true,
1139
+ preferences: {
1140
+ select: SERVICE_PARTNER_DEAL_PREF_SELECT,
1141
+ },
1108
1142
  },
1109
1143
  },
1110
1144
  exhibitor: {
1111
1145
  select: {
1112
1146
  id: true,
1113
1147
  crowdSize: true,
1148
+ ...SERVICE_PARTNER_DEAL_PREF_SELECT,
1114
1149
  },
1115
1150
  },
1116
1151
  sponsor: {
1117
1152
  select: {
1118
1153
  id: true,
1119
1154
  crowdSize: true,
1155
+ ...SERVICE_PARTNER_DEAL_PREF_SELECT,
1120
1156
  },
1121
1157
  },
1122
1158
  } satisfies Prisma.ServiceInclude;
@@ -1329,6 +1365,10 @@ export const SERVICE_BOOKING_PUBLIC_DATA_TO_INCLUDE = {
1329
1365
  title: true,
1330
1366
  slug: true,
1331
1367
  status: true,
1368
+ coverPhoto: true,
1369
+ startDateTime: true,
1370
+ endDateTime: true,
1371
+ timezone: true,
1332
1372
  },
1333
1373
  },
1334
1374
  representedByOrganization: {
@@ -1363,7 +1403,14 @@ export const SERVICE_BOOKING_PRIVATE_DATA_TO_INCLUDE = {
1363
1403
  // so call sites can cast prisma.upsert/findUnique results without needing `as unknown`.
1364
1404
  export type ServiceBookingBashEventRef = Pick<
1365
1405
  BashEvent,
1366
- "id" | "title" | "slug" | "status"
1406
+ | "id"
1407
+ | "title"
1408
+ | "slug"
1409
+ | "status"
1410
+ | "coverPhoto"
1411
+ | "startDateTime"
1412
+ | "endDateTime"
1413
+ | "timezone"
1367
1414
  >;
1368
1415
 
1369
1416
  export type ServiceBookingCheckoutRef = Pick<
@@ -2000,10 +2047,10 @@ export interface UserExt extends User {
2000
2047
  socialEventCadenceDisplay?: string;
2001
2048
 
2002
2049
  /**
2003
- * Computed trust badges. Server gates the entire object on
2004
- * `showVerifiedBadgesOnProfile` and strips the underlying raw fields
2050
+ * Computed trust badges. Server strips the underlying raw fields
2005
2051
  * (`paymentVerified`, `stripeAccountId`, `studentIdImageS3Key`, `schoolName`)
2006
- * from the public response.
2052
+ * from the public response. `paymentVerified` stays public once true;
2053
+ * ID / host / student badges still require `showVerifiedBadgesOnProfile`.
2007
2054
  */
2008
2055
  badges?: {
2009
2056
  idVerified: boolean;
@@ -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";
@@ -42,7 +42,7 @@ export interface CircuitVendorOptionExt {
42
42
  status: string;
43
43
  contactEmail: string | null;
44
44
  contactName: string | null;
45
- kindLabel: "Vendor" | "Exhibitor";
45
+ kindLabel: "Vendor" | "Exhibitor" | "Hire";
46
46
  }
47
47
 
48
48
  export interface SideQuestExt {
@@ -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
+ }
@@ -0,0 +1,184 @@
1
+ import type { ServiceExt } from "../../extendedSchemas";
2
+ import {
3
+ readPartnerDealflowFields,
4
+ serviceListingOpenToInKindDeal,
5
+ } from "./partnerDealflow.js";
6
+ import {
7
+ readPartnerRevenueShareFields,
8
+ REVENUE_SHARE_WITH_HOST_BASIS_LABEL,
9
+ serviceListingOpenToRevenueShare,
10
+ type RevenueShareWithHostBasis,
11
+ } from "./partnerRevenueShareOptIn.js";
12
+
13
+ export type ServiceListingDealBadge = {
14
+ id: string;
15
+ label: string;
16
+ };
17
+
18
+ export type ServiceListingDealDetailRow = {
19
+ label: string;
20
+ value: string;
21
+ };
22
+
23
+ function formatUsdFromCents(cents: number): string {
24
+ const dollars = cents / 100;
25
+ return dollars % 1 === 0 ? `$${dollars.toFixed(0)}` : `$${dollars.toFixed(2)}`;
26
+ }
27
+
28
+ function minimumCashLabel(deal: ReturnType<typeof readPartnerDealflowFields>): string | null {
29
+ if (deal?.dealRequiresMinimumCash !== true) return null;
30
+ if (typeof deal.dealMinimumCashCents !== "number" || deal.dealMinimumCashCents <= 0) {
31
+ return null;
32
+ }
33
+ return `${formatUsdFromCents(deal.dealMinimumCashCents)} min cash`;
34
+ }
35
+
36
+ function truncateOffer(text: string, maxLen: number): string {
37
+ const t = text.trim();
38
+ if (t.length <= maxLen) return t;
39
+ return `${t.slice(0, maxLen - 1).trim()}…`;
40
+ }
41
+
42
+ /** Compact chips for cards and list rows. */
43
+ export function getServiceListingDealBadges(
44
+ service: ServiceExt
45
+ ): ServiceListingDealBadge[] {
46
+ const badges: ServiceListingDealBadge[] = [];
47
+ const deal = readPartnerDealflowFields(service);
48
+
49
+ if (serviceListingOpenToRevenueShare(service)) {
50
+ badges.push({ id: "rev-share", label: "Rev share" });
51
+ }
52
+ if (serviceListingOpenToInKindDeal(service)) {
53
+ badges.push({ id: "in-kind", label: "In-kind" });
54
+ }
55
+
56
+ const minCash = minimumCashLabel(deal);
57
+ if (minCash) {
58
+ badges.push({ id: "min-cash", label: minCash });
59
+ }
60
+
61
+ const hostBidOpen =
62
+ service.vendor?.preferences?.openToHostBids === true ||
63
+ service.exhibitor?.openToHostBids === true ||
64
+ service.sponsor?.openToHostBids === true;
65
+ if (hostBidOpen) {
66
+ badges.push({ id: "host-bids", label: "Host bids" });
67
+ }
68
+
69
+ return badges;
70
+ }
71
+
72
+ /** Longer copy for public detail pages. */
73
+ export function getServiceListingDealDetailRows(
74
+ service: ServiceExt
75
+ ): ServiceListingDealDetailRow[] {
76
+ const rows: ServiceListingDealDetailRow[] = [];
77
+ const deal = readPartnerDealflowFields(service);
78
+ const rev = readPartnerRevenueShareFields(service);
79
+ const venue = service.venue;
80
+
81
+ if (venue?.revenueShareEnabled === true && venue.revenueShareHostPercentage != null) {
82
+ rows.push({
83
+ label: "Venue revenue share",
84
+ value: `Host keeps ${venue.revenueShareHostPercentage}% of agreed ticket or bar revenue.`,
85
+ });
86
+ } else if (rev?.openToRevenueShareWithHost === true) {
87
+ const pct = rev.revenueShareWithHostPercentage;
88
+ const basis = rev.revenueShareWithHostBasis as RevenueShareWithHostBasis | null;
89
+ const basisLabel =
90
+ basis && basis in REVENUE_SHARE_WITH_HOST_BASIS_LABEL
91
+ ? REVENUE_SHARE_WITH_HOST_BASIS_LABEL[basis]
92
+ : null;
93
+ rows.push({
94
+ label: "Revenue share with host",
95
+ value:
96
+ typeof pct === "number"
97
+ ? `Shares ${pct}% with the host${basisLabel ? ` (${basisLabel.toLowerCase()})` : ""}.`
98
+ : "Open to sharing revenue with the host (percentage not set).",
99
+ });
100
+ }
101
+
102
+ if (serviceListingOpenToInKindDeal(service) && deal?.inKindDealOfferText) {
103
+ const est =
104
+ typeof deal.inKindEstimatedValueCents === "number" &&
105
+ deal.inKindEstimatedValueCents > 0
106
+ ? ` Approx. value ${formatUsdFromCents(deal.inKindEstimatedValueCents)}.`
107
+ : "";
108
+ rows.push({
109
+ label: "In-kind / trade offer",
110
+ value: `${deal.inKindDealOfferText.trim()}${est}`,
111
+ });
112
+ }
113
+
114
+ const minCash = minimumCashLabel(deal);
115
+ if (minCash) {
116
+ rows.push({
117
+ label: "Minimum cash on hybrid deals",
118
+ value: `Requires ${minCash.replace(" min cash", "")} minimum cash alongside rev-share or in-kind terms so hosts know you're serious.`,
119
+ });
120
+ }
121
+
122
+ return rows;
123
+ }
124
+
125
+ /** Card preview line for in-kind (truncated). */
126
+ export function getServiceListingInKindPreview(
127
+ service: ServiceExt,
128
+ maxLen = 80
129
+ ): string | null {
130
+ const deal = readPartnerDealflowFields(service);
131
+ if (!serviceListingOpenToInKindDeal(service) || !deal?.inKindDealOfferText) {
132
+ return null;
133
+ }
134
+ return truncateOffer(deal.inKindDealOfferText, maxLen);
135
+ }
136
+
137
+ export type PartnerDealflowBookingSnapshot = {
138
+ openToRevenueShareWithHostSnapshot?: boolean;
139
+ revenueShareWithHostPercentageSnapshot?: number | null;
140
+ revenueShareWithHostBasisSnapshot?: string | null;
141
+ openToInKindDealsSnapshot?: boolean;
142
+ inKindDealOfferTextSnapshot?: string | null;
143
+ inKindEstimatedValueCentsSnapshot?: number | null;
144
+ dealRequiresMinimumCashSnapshot?: boolean;
145
+ dealMinimumCashCentsSnapshot?: number | null;
146
+ };
147
+
148
+ /** Copy live listing deal prefs onto a booking at confirmation time. */
149
+ export function buildPartnerDealflowBookingSnapshot(
150
+ service: ServiceExt
151
+ ): PartnerDealflowBookingSnapshot {
152
+ const rev = readPartnerRevenueShareFields(service);
153
+ const deal = readPartnerDealflowFields(service);
154
+ const venue = service.venue;
155
+
156
+ const snap: PartnerDealflowBookingSnapshot = {};
157
+
158
+ if (venue?.revenueShareEnabled === true) {
159
+ snap.openToRevenueShareWithHostSnapshot = true;
160
+ snap.revenueShareWithHostPercentageSnapshot =
161
+ venue.revenueShareHostPercentage ?? null;
162
+ snap.revenueShareWithHostBasisSnapshot = venue.revenueShareBasis ?? null;
163
+ } else if (rev?.openToRevenueShareWithHost === true) {
164
+ snap.openToRevenueShareWithHostSnapshot = true;
165
+ snap.revenueShareWithHostPercentageSnapshot =
166
+ rev.revenueShareWithHostPercentage ?? null;
167
+ snap.revenueShareWithHostBasisSnapshot =
168
+ rev.revenueShareWithHostBasis ?? null;
169
+ }
170
+
171
+ if (deal?.openToInKindDeals === true) {
172
+ snap.openToInKindDealsSnapshot = true;
173
+ snap.inKindDealOfferTextSnapshot = deal.inKindDealOfferText?.trim() ?? null;
174
+ snap.inKindEstimatedValueCentsSnapshot =
175
+ deal.inKindEstimatedValueCents ?? null;
176
+ }
177
+
178
+ if (deal?.dealRequiresMinimumCash === true) {
179
+ snap.dealRequiresMinimumCashSnapshot = true;
180
+ snap.dealMinimumCashCentsSnapshot = deal.dealMinimumCashCents ?? null;
181
+ }
182
+
183
+ return snap;
184
+ }