@bash-app/bash-common 30.292.0 → 30.295.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 (61) hide show
  1. package/dist/__tests__/bashSectionTitleForBashType.test.js +3 -0
  2. package/dist/__tests__/bashSectionTitleForBashType.test.js.map +1 -1
  3. package/dist/definitions.d.ts +77 -0
  4. package/dist/definitions.d.ts.map +1 -1
  5. package/dist/definitions.js +1 -0
  6. package/dist/definitions.js.map +1 -1
  7. package/dist/extendedSchemas.d.ts +7 -0
  8. package/dist/extendedSchemas.d.ts.map +1 -1
  9. package/dist/extendedSchemas.js.map +1 -1
  10. package/dist/hostCrmConstants.d.ts +11 -0
  11. package/dist/hostCrmConstants.d.ts.map +1 -0
  12. package/dist/hostCrmConstants.js +10 -0
  13. package/dist/hostCrmConstants.js.map +1 -0
  14. package/dist/hostCrmSegments.d.ts +47 -0
  15. package/dist/hostCrmSegments.d.ts.map +1 -0
  16. package/dist/hostCrmSegments.js +65 -0
  17. package/dist/hostCrmSegments.js.map +1 -0
  18. package/dist/index.d.ts +4 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +4 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/membershipDefinitions.d.ts +9 -0
  23. package/dist/membershipDefinitions.d.ts.map +1 -1
  24. package/dist/membershipDefinitions.js +10 -1
  25. package/dist/membershipDefinitions.js.map +1 -1
  26. package/dist/utils/__tests__/reputationUtils.serviceListing.test.js +18 -0
  27. package/dist/utils/__tests__/reputationUtils.serviceListing.test.js.map +1 -1
  28. package/dist/utils/__tests__/reviewDimensionUtils.test.d.ts +2 -0
  29. package/dist/utils/__tests__/reviewDimensionUtils.test.d.ts.map +1 -0
  30. package/dist/utils/__tests__/reviewDimensionUtils.test.js +48 -0
  31. package/dist/utils/__tests__/reviewDimensionUtils.test.js.map +1 -0
  32. package/dist/utils/__tests__/trustScoreUtils.test.d.ts +2 -0
  33. package/dist/utils/__tests__/trustScoreUtils.test.d.ts.map +1 -0
  34. package/dist/utils/__tests__/trustScoreUtils.test.js +101 -0
  35. package/dist/utils/__tests__/trustScoreUtils.test.js.map +1 -0
  36. package/dist/utils/reputationUtils.d.ts.map +1 -1
  37. package/dist/utils/reputationUtils.js +7 -1
  38. package/dist/utils/reputationUtils.js.map +1 -1
  39. package/dist/utils/reviewDimensionUtils.d.ts +33 -0
  40. package/dist/utils/reviewDimensionUtils.d.ts.map +1 -0
  41. package/dist/utils/reviewDimensionUtils.js +35 -0
  42. package/dist/utils/reviewDimensionUtils.js.map +1 -0
  43. package/dist/utils/trustScoreUtils.d.ts +25 -0
  44. package/dist/utils/trustScoreUtils.d.ts.map +1 -0
  45. package/dist/utils/trustScoreUtils.js +99 -0
  46. package/dist/utils/trustScoreUtils.js.map +1 -0
  47. package/package.json +1 -1
  48. package/prisma/schema.prisma +181 -15
  49. package/src/__tests__/bashSectionTitleForBashType.test.ts +4 -0
  50. package/src/definitions.ts +85 -1
  51. package/src/extendedSchemas.ts +7 -0
  52. package/src/hostCrmConstants.ts +13 -0
  53. package/src/hostCrmSegments.ts +126 -0
  54. package/src/index.ts +4 -0
  55. package/src/membershipDefinitions.ts +12 -1
  56. package/src/utils/__tests__/reputationUtils.serviceListing.test.ts +24 -0
  57. package/src/utils/__tests__/reviewDimensionUtils.test.ts +65 -0
  58. package/src/utils/__tests__/trustScoreUtils.test.ts +105 -0
  59. package/src/utils/reputationUtils.ts +7 -1
  60. package/src/utils/reviewDimensionUtils.ts +70 -0
  61. package/src/utils/trustScoreUtils.ts +136 -0
@@ -91,6 +91,7 @@ model BashFeedPost {
91
91
  saves BashFeedSave[]
92
92
  reposts BashFeedRepost[]
93
93
  helpfulVotes ReviewHelpfulVote[]
94
+ serviceReviews ServiceReview[]
94
95
 
95
96
  @@unique([userId, repostOfPostId])
96
97
  @@index([bashId])
@@ -345,6 +346,8 @@ model CompetitionPrizeOffer {
345
346
  sponsorLabel String?
346
347
  sourceServiceId String?
347
348
  sourceSku String?
349
+ /// Existing prize slot the offerer wants to sponsor (optional)
350
+ targetPrizeId String?
348
351
  hostNote String?
349
352
  createdAt DateTime @default(now())
350
353
  updatedAt DateTime @updatedAt
@@ -352,10 +355,12 @@ model CompetitionPrizeOffer {
352
355
  competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
353
356
  offeredBy User @relation("CompetitionPrizeOffersSubmitted", fields: [offeredByUserId], references: [id], onDelete: Cascade)
354
357
  sourceService Service? @relation("CompetitionPrizeOfferSourceService", fields: [sourceServiceId], references: [id], onDelete: SetNull)
358
+ targetPrize Prize? @relation("CompetitionPrizeOfferTarget", fields: [targetPrizeId], references: [id], onDelete: SetNull)
355
359
 
356
360
  @@index([competitionId])
357
361
  @@index([offeredByUserId])
358
362
  @@index([status])
363
+ @@index([targetPrizeId])
359
364
  }
360
365
 
361
366
  model CompetitionParticipant {
@@ -911,6 +916,9 @@ model BashEvent {
911
916
  postEventMessageSentAt DateTime?
912
917
  broadcastCount Int @default(0)
913
918
  lastBroadcastAt DateTime?
919
+ /// When true, notify opted-in CRM audience on publish (optional tag in crmPublishNotifyTag).
920
+ crmPublishNotifyEnabled Boolean @default(false)
921
+ crmPublishNotifyTag String?
914
922
 
915
923
  // Idea-specific fields (only used when status = 'Idea')
916
924
  ideaExpiresAt DateTime?
@@ -966,6 +974,8 @@ model BashEvent {
966
974
  sentReminders SentReminder[]
967
975
  recurrence Recurrence?
968
976
  reviews Review[]
977
+ serviceReviews ServiceReview[]
978
+ providerHostReviews ProviderHostReview[]
969
979
  serviceBookings ServiceBooking[]
970
980
  sponsorBookingRequests SponsorBookingRequest[] @relation("SponsorBookingEvent")
971
981
  sponsorships SponsoredEvent[]
@@ -1960,15 +1970,23 @@ enum BackgroundEffect {
1960
1970
  Starfield
1961
1971
  }
1962
1972
 
1973
+ enum EventDiscussionTopic {
1974
+ General
1975
+ Icebreaker
1976
+ }
1977
+
1963
1978
  model EventDiscussionComment {
1964
- id String @id @default(cuid())
1979
+ id String @id @default(cuid())
1965
1980
  bashEventId String
1966
1981
  authorId String
1967
- body String @db.Text
1982
+ body String @db.Text
1968
1983
  mentionedUserIds String[] // User IDs mentioned in the comment
1969
1984
  parentId String? // For nested replies (mirrors BashFeedComment)
1970
- createdAt DateTime @default(now())
1971
- updatedAt DateTime @updatedAt
1985
+ topic EventDiscussionTopic @default(General)
1986
+ /// Snapshot of the icebreaker prompt this answer belongs to (Icebreaker topic only).
1987
+ icebreakerPrompt String? @db.Text
1988
+ createdAt DateTime @default(now())
1989
+ updatedAt DateTime @updatedAt
1972
1990
 
1973
1991
  bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
1974
1992
  author User @relation("EventDiscussionComments", fields: [authorId], references: [id], onDelete: Cascade)
@@ -1979,6 +1997,7 @@ model EventDiscussionComment {
1979
1997
  @@index([bashEventId])
1980
1998
  @@index([authorId])
1981
1999
  @@index([parentId])
2000
+ @@index([bashEventId, topic, icebreakerPrompt])
1982
2001
  }
1983
2002
 
1984
2003
  model EventDiscussionCommentLike {
@@ -2099,6 +2118,38 @@ model HostAttendeeProfile {
2099
2118
  @@index([hostUserId])
2100
2119
  }
2101
2120
 
2121
+ /// Host-defined reusable CRM audience filters (tag, cadence, lapsed, cohorts).
2122
+ model HostCrmSavedSegment {
2123
+ id String @id @default(cuid())
2124
+ hostUserId String
2125
+ name String
2126
+ rules Json
2127
+ createdAt DateTime @default(now())
2128
+ updatedAt DateTime @updatedAt
2129
+
2130
+ host User @relation("HostCrmSavedSegments", fields: [hostUserId], references: [id], onDelete: Cascade)
2131
+
2132
+ @@unique([hostUserId, name])
2133
+ @@index([hostUserId])
2134
+ }
2135
+
2136
+ /// Per-recipient CRM email cooldown (parity with smsSendLog for CRM_BLAST).
2137
+ model HostCrmEmailSendLog {
2138
+ id String @id @default(cuid())
2139
+ hostUserId String
2140
+ userId String?
2141
+ email String?
2142
+ templateKey String
2143
+ sentAt DateTime @default(now())
2144
+
2145
+ host User @relation("HostCrmEmailSendLogsHost", fields: [hostUserId], references: [id], onDelete: Cascade)
2146
+ user User? @relation("HostCrmEmailSendLogsUser", fields: [userId], references: [id], onDelete: SetNull)
2147
+
2148
+ @@index([hostUserId, sentAt])
2149
+ @@index([userId, templateKey, sentAt])
2150
+ @@index([email, templateKey, sentAt])
2151
+ }
2152
+
2102
2153
  // Tracks referrals: attendee A referred person B who bought a ticket
2103
2154
  model EventReferral {
2104
2155
  id String @id @default(cuid())
@@ -2393,6 +2444,8 @@ model TicketTier {
2393
2444
  preReleaseDisplay String @default("Tease") // "Tease" | "Hidden" before availableAt when OnDate
2394
2445
  isManuallySoldOut Boolean @default(false)
2395
2446
  notifyOnRelease Boolean @default(true)
2447
+ /// Optional CRM tag: also notify opted-in past attendees with this tag when tier releases.
2448
+ crmNotifyTag String?
2396
2449
  tierReleaseNotifiedAt DateTime?
2397
2450
  availableWhenInterestReached Int? // UnlockWhenInterestReached: min IdeaInterest count
2398
2451
  unlockWhenTicketsSold Int? // UnlockWhenTicketsSold: min tickets sold across event to unlock this tier
@@ -2679,7 +2732,7 @@ model Recurrence {
2679
2732
  repeatYearlyDate DateTime?
2680
2733
  // Per-occurrence exceptions for the computed series (no occurrence rows are materialized):
2681
2734
  // dates the host removed from the series (matched against the generated occurrence start instants).
2682
- excludedDates DateTime[]
2735
+ excludedDates DateTime[] @default([])
2683
2736
  // Per-occurrence date/time moves: JSON map of original-occurrence ISO start -> { startDateTime, endDateTime? }.
2684
2737
  occurrenceOverrides Json?
2685
2738
  createdAt DateTime @default(now())
@@ -2875,6 +2928,8 @@ model Prize {
2875
2928
  winnerUserId String?
2876
2929
  wonAt DateTime? // When the prize was awarded
2877
2930
  claimedAt DateTime? // When the prize was claimed
2931
+ /// Host-set amount sponsors pay to fund this prize slot (cents). Shown when seeking prize contributions.
2932
+ sponsorCostCents Int?
2878
2933
  /// Platform user who donated this prize (optional)
2879
2934
  contributorUserId String?
2880
2935
  /// Sponsor marketplace service the prize was picked from (optional)
@@ -2907,6 +2962,7 @@ model Prize {
2907
2962
  winner User? @relation(fields: [winnerUserId], references: [id], onDelete: Cascade)
2908
2963
  contributor User? @relation("PrizeContributor", fields: [contributorUserId], references: [id], onDelete: SetNull)
2909
2964
  sourceService Service? @relation("PrizeSourceService", fields: [sourceServiceId], references: [id], onDelete: SetNull)
2965
+ sponsorshipOffers CompetitionPrizeOffer[] @relation("CompetitionPrizeOfferTarget")
2910
2966
  bashPointsTransaction BashCreditTransaction? @relation(fields: [bashPointsTransactionId], references: [id], onDelete: SetNull)
2911
2967
 
2912
2968
  @@index([competitionId])
@@ -2918,11 +2974,22 @@ model Prize {
2918
2974
 
2919
2975
  model Review {
2920
2976
  id String @id @default(cuid())
2977
+ /// Overall rating (1–5). Headline aggregate uses this field.
2921
2978
  rating Int
2922
2979
  creatorId String
2923
2980
  bashEventId String
2924
2981
  createdAt DateTime @default(now())
2925
2982
  updatedAt DateTime @updatedAt
2983
+ /// Optional multi-dimensional event ratings (Overall required via rating).
2984
+ organizationRating Int?
2985
+ atmosphereRating Int?
2986
+ valueRating Int?
2987
+ safetyRating Int?
2988
+ wouldAttendAgain Boolean?
2989
+ /// Trust badges stamped at write time from platform verification checks.
2990
+ verifiedBooking Boolean @default(false)
2991
+ verifiedAttendance Boolean @default(false)
2992
+ verifiedServiceCompletion Boolean @default(false)
2926
2993
  /// Public reply from the event host (service provider) to the reviewer.
2927
2994
  hostReplyText String? @db.Text
2928
2995
  hostReplyAt DateTime?
@@ -2934,22 +3001,93 @@ model Review {
2934
3001
  @@unique([creatorId, bashEventId])
2935
3002
  }
2936
3003
 
3004
+ /// Bash-native review of a service listing at a specific event (attendee or host).
3005
+ model ServiceReview {
3006
+ id String @id @default(cuid())
3007
+ creatorId String
3008
+ serviceId String
3009
+ bashEventId String
3010
+ reviewerRole ServiceReviewerRole
3011
+ overallRating Int
3012
+ communicationRating Int?
3013
+ professionalismRating Int?
3014
+ qualityRating Int?
3015
+ wouldHireAgain Boolean?
3016
+ verifiedBooking Boolean @default(false)
3017
+ verifiedAttendance Boolean @default(false)
3018
+ verifiedServiceCompletion Boolean @default(false)
3019
+ comment String? @db.Text
3020
+ providerReplyText String? @db.Text
3021
+ providerReplyAt DateTime?
3022
+ /// Null until blind host↔provider window reveals (attendee reviews publish immediately).
3023
+ publishedAt DateTime?
3024
+ feedPostId String?
3025
+ createdAt DateTime @default(now())
3026
+ updatedAt DateTime @updatedAt
3027
+
3028
+ creator User @relation("ServiceReviewsCreated", fields: [creatorId], references: [id], onDelete: Cascade)
3029
+ service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
3030
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
3031
+ feedPost BashFeedPost? @relation(fields: [feedPostId], references: [id], onDelete: SetNull)
3032
+ helpfulVotes ReviewHelpfulVote[]
3033
+
3034
+ @@unique([creatorId, serviceId, bashEventId])
3035
+ @@index([serviceId, publishedAt])
3036
+ @@index([bashEventId])
3037
+ }
3038
+
3039
+ /// Provider reviews the host after a confirmed booking (blind mutual window with host ServiceReview).
3040
+ model ProviderHostReview {
3041
+ id String @id @default(cuid())
3042
+ creatorId String
3043
+ hostUserId String
3044
+ serviceId String
3045
+ bashEventId String
3046
+ overallRating Int
3047
+ communicationRating Int?
3048
+ professionalismRating Int?
3049
+ qualityRating Int?
3050
+ wouldWorkAgain Boolean?
3051
+ verifiedBooking Boolean @default(false)
3052
+ verifiedServiceCompletion Boolean @default(false)
3053
+ comment String? @db.Text
3054
+ publishedAt DateTime?
3055
+ createdAt DateTime @default(now())
3056
+ updatedAt DateTime @updatedAt
3057
+
3058
+ creator User @relation("ProviderHostReviewsCreated", fields: [creatorId], references: [id], onDelete: Cascade)
3059
+ host User @relation("ProviderHostReviewsReceived", fields: [hostUserId], references: [id], onDelete: Cascade)
3060
+ service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
3061
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
3062
+
3063
+ @@unique([creatorId, bashEventId, serviceId])
3064
+ @@index([hostUserId, publishedAt])
3065
+ }
3066
+
3067
+ enum ServiceReviewerRole {
3068
+ Attendee
3069
+ Host
3070
+ }
3071
+
2937
3072
  /// Profile-only helpfulness votes on host reviews (Review rows or BashFeed posts shown as reviews).
2938
3073
  model ReviewHelpfulVote {
2939
- id String @id @default(cuid())
2940
- userId String
2941
- reviewId String?
2942
- feedPostId String?
2943
- isHelpful Boolean
2944
- createdAt DateTime @default(now())
2945
- updatedAt DateTime @updatedAt
3074
+ id String @id @default(cuid())
3075
+ userId String
3076
+ reviewId String?
3077
+ feedPostId String?
3078
+ serviceReviewId String?
3079
+ isHelpful Boolean
3080
+ createdAt DateTime @default(now())
3081
+ updatedAt DateTime @updatedAt
2946
3082
 
2947
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
2948
- review Review? @relation(fields: [reviewId], references: [id], onDelete: Cascade)
2949
- feedPost BashFeedPost? @relation(fields: [feedPostId], references: [id], onDelete: Cascade)
3083
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
3084
+ review Review? @relation(fields: [reviewId], references: [id], onDelete: Cascade)
3085
+ feedPost BashFeedPost? @relation(fields: [feedPostId], references: [id], onDelete: Cascade)
3086
+ serviceReview ServiceReview? @relation(fields: [serviceReviewId], references: [id], onDelete: Cascade)
2950
3087
 
2951
3088
  @@index([reviewId])
2952
3089
  @@index([feedPostId])
3090
+ @@index([serviceReviewId])
2953
3091
  @@index([userId])
2954
3092
  }
2955
3093
 
@@ -2960,6 +3098,9 @@ model GoogleReview {
2960
3098
  text String?
2961
3099
  createdAt DateTime @default(now())
2962
3100
  serviceId String
3101
+ service Service @relation(fields: [serviceId], references: [id], onDelete: Cascade)
3102
+
3103
+ @@index([serviceId])
2963
3104
  }
2964
3105
 
2965
3106
  model Session {
@@ -3085,6 +3226,10 @@ model User {
3085
3226
  status UserStatus
3086
3227
  hostRating Float?
3087
3228
  totalRatings Int?
3229
+ /// Secondary composite trust signal (0–100) for host profile detail.
3230
+ trustScore Int?
3231
+ trustScoreUpdatedAt DateTime?
3232
+ verifiedBookingsCount Int @default(0)
3088
3233
  totalEventsHosted Int? @default(0) // Total published/completed events hosted
3089
3234
  totalAttendeesHosted Int? @default(0) // Total checked-in attendees across all events
3090
3235
  totalBookingsAccepted Int? @default(0) // Service provider stat
@@ -3278,6 +3423,9 @@ model User {
3278
3423
  remindersAssignedToMe Reminder[] @relation("RemindersAssignedToMe")
3279
3424
  sentReminders SentReminder[]
3280
3425
  reviews Review[]
3426
+ serviceReviewsCreated ServiceReview[] @relation("ServiceReviewsCreated")
3427
+ providerHostReviewsCreated ProviderHostReview[] @relation("ProviderHostReviewsCreated")
3428
+ providerHostReviewsReceived ProviderHostReview[] @relation("ProviderHostReviewsReceived")
3281
3429
  firstFreeListingId String?
3282
3430
  createdServices Service[] @relation("CreatedService")
3283
3431
  ownedServices Service[] @relation("OwnedService")
@@ -3369,6 +3517,9 @@ model User {
3369
3517
  eventDiscussionComments EventDiscussionComment[] @relation("EventDiscussionComments")
3370
3518
  hostAttendeeProfilesOwned HostAttendeeProfile[] @relation("HostAttendeeProfilesOwned")
3371
3519
  hostAttendeeProfilesTarget HostAttendeeProfile[] @relation("HostAttendeeProfilesTarget")
3520
+ hostCrmSavedSegments HostCrmSavedSegment[] @relation("HostCrmSavedSegments")
3521
+ hostCrmEmailSendLogsAsHost HostCrmEmailSendLog[] @relation("HostCrmEmailSendLogsHost")
3522
+ hostCrmEmailSendLogsAsUser HostCrmEmailSendLog[] @relation("HostCrmEmailSendLogsUser")
3372
3523
  bashListsCreated BashList[]
3373
3524
  userItineraries UserItinerary[]
3374
3525
  aiRecommendationLogs AIRecommendationLog[]
@@ -3528,6 +3679,14 @@ model UserPreferences {
3528
3679
  preferredCurrency String @default("USD")
3529
3680
  savePaymentInfo Boolean @default(false)
3530
3681
  showEventPrices Boolean @default(true)
3682
+ /// Default: notify CRM audience when publishing a new public bash.
3683
+ hostCrmPublishNotifyDefault Boolean @default(false)
3684
+ /// Optional host-authored post-event email subject (CRM); cron uses when set.
3685
+ postEventCrmTemplateSubject String?
3686
+ postEventCrmTemplateBody String? @db.Text
3687
+ /// When true, weekly cron emails lapsed CRM attendees (6+ months).
3688
+ hostCrmWinBackAutoEnabled Boolean @default(false)
3689
+ hostCrmWinBackLastRunAt DateTime?
3531
3690
 
3532
3691
  // BashFeed Preferences
3533
3692
  bashFeedDefaultTab String @default("all") // "all", "upcoming", "live", "past", "saved", "my-posts"
@@ -3948,6 +4107,10 @@ model Service {
3948
4107
  externalReviewSource String?
3949
4108
  externalRating Float?
3950
4109
  externalReviewCount Int?
4110
+ /// Secondary composite trust signal (0–100) for detail pages and ranking.
4111
+ trustScore Int?
4112
+ trustScoreUpdatedAt DateTime?
4113
+ verifiedCompletionsCount Int @default(0)
3951
4114
  stripeAccountId String?
3952
4115
  paymentAccountId String?
3953
4116
  targetAudienceId String? @unique
@@ -4033,6 +4196,9 @@ model Service {
4033
4196
  competitionPrizeOffersSourced CompetitionPrizeOffer[] @relation("CompetitionPrizeOfferSourceService")
4034
4197
  venueMatchCandidates VenueMatchCandidate[]
4035
4198
  supplyLeadConversions VenueSupplyLead[] @relation("SupplyLeadConvertedService")
4199
+ googleReviews GoogleReview[]
4200
+ serviceReviews ServiceReview[]
4201
+ providerHostReviews ProviderHostReview[]
4036
4202
 
4037
4203
  @@index([serviceListingStripeSubscriptionId])
4038
4204
  @@index([paymentAccountId])
@@ -44,6 +44,10 @@ describe("bashSectionTitleForBashType", () => {
44
44
  it("'Trending' → 'Trending'", () => {
45
45
  expect(bashSectionTitleForBashType("Trending")).toBe("Trending");
46
46
  });
47
+
48
+ it("'Ideas' → 'Ideas'", () => {
49
+ expect(bashSectionTitleForBashType("Ideas")).toBe("Ideas");
50
+ });
47
51
  });
48
52
 
49
53
  describe("BashEventType enum values get pluralized", () => {
@@ -1471,6 +1471,7 @@ export const INCOME_RANGE_VALUES: ReadonlyArray<IncomeRange> = [
1471
1471
 
1472
1472
  /** Homepage / discovery section labels for synthetic rows (not `BashEventType` enum values). */
1473
1473
  const SYNTHETIC_BASH_SECTION_TITLE: Record<string, string> = {
1474
+ Ideas: "Ideas",
1474
1475
  Featured: "Featured",
1475
1476
  Trending: "Trending",
1476
1477
  };
@@ -1817,11 +1818,19 @@ export interface UserTemporaryBadge {
1817
1818
 
1818
1819
  export interface Review {
1819
1820
  id: string;
1820
- rating: number; // 1-5 stars
1821
+ rating: number; // 1-5 stars (Overall)
1821
1822
  creatorId: string;
1822
1823
  bashEventId: string;
1823
1824
  createdAt: string;
1824
1825
  updatedAt: string;
1826
+ organizationRating?: number | null;
1827
+ atmosphereRating?: number | null;
1828
+ valueRating?: number | null;
1829
+ safetyRating?: number | null;
1830
+ wouldAttendAgain?: boolean | null;
1831
+ verifiedBooking?: boolean;
1832
+ verifiedAttendance?: boolean;
1833
+ verifiedServiceCompletion?: boolean;
1825
1834
  comments?: ReviewComment[];
1826
1835
  }
1827
1836
 
@@ -1853,10 +1862,83 @@ export interface ReviewWithUser extends Review {
1853
1862
 
1854
1863
  export interface ReviewWithUserAndEvent extends ReviewWithUser, ReviewWithEvent {}
1855
1864
 
1865
+ /** Wizard / API shape for BashEvent.itinerary JSON (see prisma BashEvent.itinerary). */
1866
+ export interface ItineraryItem {
1867
+ id: string;
1868
+ title: string;
1869
+ startTime: string;
1870
+ endTime: string;
1871
+ description?: string;
1872
+ }
1873
+
1856
1874
  export interface NewReview {
1857
1875
  rating: number;
1858
1876
  bashEventId: string;
1859
1877
  commentContent?: string;
1878
+ organizationRating?: number;
1879
+ atmosphereRating?: number;
1880
+ valueRating?: number;
1881
+ safetyRating?: number;
1882
+ wouldAttendAgain?: boolean;
1883
+ }
1884
+
1885
+ export interface ServiceReviewDimensions {
1886
+ communicationRating?: number | null;
1887
+ professionalismRating?: number | null;
1888
+ qualityRating?: number | null;
1889
+ wouldHireAgain?: boolean | null;
1890
+ }
1891
+
1892
+ export interface NewServiceReview extends ServiceReviewDimensions {
1893
+ overallRating: number;
1894
+ bashEventId: string;
1895
+ comment?: string;
1896
+ createPost?: boolean;
1897
+ postContent?: string;
1898
+ }
1899
+
1900
+ export interface ServiceReviewPublic {
1901
+ id: string;
1902
+ overallRating: number;
1903
+ reviewerRole: "Attendee" | "Host";
1904
+ comment?: string | null;
1905
+ createdAt: string;
1906
+ publishedAt?: string | null;
1907
+ verifiedBooking: boolean;
1908
+ verifiedAttendance: boolean;
1909
+ verifiedServiceCompletion: boolean;
1910
+ communicationRating?: number | null;
1911
+ professionalismRating?: number | null;
1912
+ qualityRating?: number | null;
1913
+ wouldHireAgain?: boolean | null;
1914
+ providerReplyText?: string | null;
1915
+ providerReplyAt?: string | null;
1916
+ feedPostId?: string | null;
1917
+ creator?: {
1918
+ id: string;
1919
+ givenName?: string | null;
1920
+ familyName?: string | null;
1921
+ image?: string | null;
1922
+ };
1923
+ bashEvent?: {
1924
+ id: string;
1925
+ title: string;
1926
+ startDateTime: string;
1927
+ };
1928
+ }
1929
+
1930
+ export interface ServiceReviewStats {
1931
+ overallAverage: number | null;
1932
+ totalReviews: number;
1933
+ dimensionAverages: import("./utils/reviewDimensionUtils.js").ReviewDimensionAverages;
1934
+ googleAverage?: number | null;
1935
+ googleCount?: number;
1936
+ trustScore?: number | null;
1937
+ }
1938
+
1939
+ export interface TrustScoreBreakdownPublic {
1940
+ score: number | null;
1941
+ components: Record<string, number>;
1860
1942
  }
1861
1943
 
1862
1944
  export interface ReviewStats {
@@ -1869,6 +1951,8 @@ export interface ReviewStats {
1869
1951
  4: number;
1870
1952
  5: number;
1871
1953
  };
1954
+ dimensionAverages?: import("./utils/reviewDimensionUtils.js").ReviewDimensionAverages;
1955
+ trustScore?: number | null;
1872
1956
  }
1873
1957
 
1874
1958
  /** Helpful / Not helpful tallies on profile review cards (API: host-reviews). */
@@ -287,6 +287,7 @@ export interface CompetitionPrizeOfferExt {
287
287
  sponsorLabel?: string | null;
288
288
  sourceServiceId?: string | null;
289
289
  sourceSku?: string | null;
290
+ targetPrizeId?: string | null;
290
291
  hostNote?: string | null;
291
292
  createdAt: Date | string;
292
293
  respondedAt?: Date | string | null;
@@ -296,6 +297,12 @@ export interface CompetitionPrizeOfferExt {
296
297
  serviceName: string | null;
297
298
  coverPhoto: string | null;
298
299
  } | null;
300
+ targetPrize?: {
301
+ id: string;
302
+ name: string;
303
+ placeName: string | null;
304
+ sponsorCostCents: number | null;
305
+ } | null;
299
306
  }
300
307
 
301
308
  /** Competition row + relations; Override keeps new columns visible before consumers refresh Prisma. */
@@ -0,0 +1,13 @@
1
+ /** Per-recipient CRM email cooldown — matches SMS CRM_BLAST window. */
2
+ export const HOST_CRM_EMAIL_COOLDOWN_MS = 48 * 60 * 60 * 1000;
3
+
4
+ export const HOST_CRM_EMAIL_TEMPLATE_KEYS = {
5
+ blast: "CRM_BLAST",
6
+ publishNotify: "CRM_PUBLISH_NOTIFY",
7
+ winBack: "CRM_WINBACK",
8
+ postEvent: "CRM_POST_EVENT",
9
+ tierRelease: "CRM_TIER_RELEASE",
10
+ } as const;
11
+
12
+ export type HostCrmEmailTemplateKey =
13
+ (typeof HOST_CRM_EMAIL_TEMPLATE_KEYS)[keyof typeof HOST_CRM_EMAIL_TEMPLATE_KEYS];
@@ -0,0 +1,126 @@
1
+ import type { SocialEventCadence } from "@prisma/client";
2
+
3
+ /** Rule-based CRM segment (stored as JSON on HostCrmSavedSegment.rules). */
4
+ export type HostCrmSegmentRules = {
5
+ tag?: string;
6
+ cadence?: SocialEventCadence;
7
+ minEventCount?: number;
8
+ lapsedDays?: number;
9
+ /** Ticket buyers for this bash event id */
10
+ sourceEventId?: string;
11
+ /** Exclude anyone who bought tickets to this bash */
12
+ excludeEventId?: string;
13
+ /** Attendees who purchased a tier with this exact title on any host bash */
14
+ tierTitle?: string;
15
+ /** Match attendee city (case-insensitive substring) */
16
+ city?: string;
17
+ /** Built-in preset id (e.g. cadence_often) when rules alone are insufficient */
18
+ presetId?: string;
19
+ };
20
+
21
+ export type HostCrmAudienceMember = {
22
+ userId: string | null;
23
+ email: string | null;
24
+ name: string;
25
+ image: string | null;
26
+ eventCount: number;
27
+ eventTitles: string[];
28
+ tags: string[];
29
+ notes: string | null;
30
+ profileId: string | null;
31
+ socialEventCadence: SocialEventCadence | null;
32
+ reachableEmail: boolean;
33
+ reachableSms: boolean;
34
+ lastAttendedAt: string | null;
35
+ city: string | null;
36
+ tierTitles: string[];
37
+ /** Bash event ids this person attended (for cohort / source filters) */
38
+ sourceEventIds?: string[];
39
+ /** True when row is email-only (guest checkout) without a linked user id */
40
+ emailOnly: boolean;
41
+ };
42
+
43
+ export function applyHostCrmSegmentRules(
44
+ audience: HostCrmAudienceMember[],
45
+ rules: HostCrmSegmentRules,
46
+ now: Date = new Date()
47
+ ): HostCrmAudienceMember[] {
48
+ let rows = audience;
49
+
50
+ if (rules.tag?.trim()) {
51
+ const tag = rules.tag.trim();
52
+ rows = rows.filter((r) => r.tags.includes(tag));
53
+ }
54
+
55
+ if (rules.cadence) {
56
+ rows = rows.filter((r) => r.socialEventCadence === rules.cadence);
57
+ }
58
+
59
+ if (typeof rules.minEventCount === "number" && rules.minEventCount > 0) {
60
+ rows = rows.filter((r) => r.eventCount >= rules.minEventCount!);
61
+ }
62
+
63
+ if (typeof rules.lapsedDays === "number" && rules.lapsedDays > 0) {
64
+ const cutoff = now.getTime() - rules.lapsedDays * 24 * 60 * 60 * 1000;
65
+ rows = rows.filter((r) => {
66
+ if (!r.lastAttendedAt) return true;
67
+ return new Date(r.lastAttendedAt).getTime() < cutoff;
68
+ });
69
+ }
70
+
71
+ if (rules.sourceEventId?.trim()) {
72
+ const sourceId = rules.sourceEventId.trim();
73
+ rows = rows.filter((r) => r.sourceEventIds?.includes(sourceId));
74
+ }
75
+
76
+ if (rules.excludeEventId?.trim()) {
77
+ const excludeId = rules.excludeEventId.trim();
78
+ rows = rows.filter((r) => !r.sourceEventIds?.includes(excludeId));
79
+ }
80
+
81
+ if (rules.tierTitle?.trim()) {
82
+ const tier = rules.tierTitle.trim().toLowerCase();
83
+ rows = rows.filter((r) =>
84
+ r.tierTitles.some((t) => t.toLowerCase() === tier)
85
+ );
86
+ }
87
+
88
+ if (rules.city?.trim()) {
89
+ const cityNeedle = rules.city.trim().toLowerCase();
90
+ rows = rows.filter((r) => (r.city ?? "").toLowerCase().includes(cityNeedle));
91
+ }
92
+
93
+ return rows;
94
+ }
95
+
96
+ export const HOST_CRM_PRESET_SEGMENTS: Array<{
97
+ id: string;
98
+ label: string;
99
+ description: string;
100
+ rules: HostCrmSegmentRules;
101
+ }> = [
102
+ {
103
+ id: "repeat_guests",
104
+ label: "Repeat guests (2+ bashes)",
105
+ description: "People who have been to at least two of your bashes",
106
+ rules: { minEventCount: 2 },
107
+ },
108
+ {
109
+ id: "lapsed_90",
110
+ label: "Haven't been in 90 days",
111
+ description: "Past attendees whose last bash was more than 90 days ago",
112
+ rules: { lapsedDays: 90 },
113
+ },
114
+ {
115
+ id: "lapsed_180",
116
+ label: "Win-back (6+ months)",
117
+ description: "Past attendees who haven't been out with you in six months",
118
+ rules: { lapsedDays: 180 },
119
+ },
120
+ {
121
+ id: "cadence_often",
122
+ label: "Goes out often",
123
+ description: "Self-reported cadence: Often or Always",
124
+ rules: {},
125
+ },
126
+ ];
package/src/index.ts CHANGED
@@ -26,6 +26,8 @@ export * from "./venueMatch.js";
26
26
  export * from "./entertainmentNotableVenues.js";
27
27
  export * from "./productLaunch.js";
28
28
  export * from "./accessScreening.js";
29
+ export * from "./hostCrmSegments.js";
30
+ export * from "./hostCrmConstants.js";
29
31
  export * from "./userReportTypes.js";
30
32
  export * from "./mirroredPrismaEnums.js";
31
33
  export * from "./utils/featuredDiscoveryGeo.js";
@@ -405,6 +407,8 @@ export * from "./utils/service/serviceRateUtils.js";
405
407
  export * from "./utils/serviceAvailabilityDisplay.js";
406
408
  export * from "./utils/entertainmentServiceTypeOptions.js";
407
409
  export * from "./utils/reputationUtils.js";
410
+ export * from "./utils/reviewDimensionUtils.js";
411
+ export * from "./utils/trustScoreUtils.js";
408
412
  export * from "./utils/stripeAccountUtils.js";
409
413
  export * from "./utils/userUtils.js";
410
414
  export * from "./utils/venueTypeFormatter.js";