@bash-app/bash-common 30.284.0 → 30.285.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.
@@ -237,6 +237,13 @@ model Competition {
237
237
  startTime DateTime? // Competition start time (for voting)
238
238
  endTime DateTime? // Competition end time (for voting)
239
239
  allowSelfRegistration Boolean @default(false)
240
+ /// When true, participant requests are auto-approved (ACCEPTED) instead of queued for host review.
241
+ autoApproveParticipants Boolean @default(false)
242
+ /// How the participant roster appears on the bash page ("All" | "Hidden").
243
+ participantDisplayMode String @default("All")
244
+ /// AUDIENCE_VOTE only: when true, only attendees (ticket holders / RSVPs) may vote.
245
+ /// Default false keeps voting open to any signed-in viewer.
246
+ restrictVotingToAttendees Boolean @default(false)
240
247
  maxParticipants Int? // Optional cap on participants
241
248
 
242
249
  // Tournament bracket fields
@@ -316,6 +323,8 @@ model CompetitionSponsor {
316
323
  sponsorName String?
317
324
  logoUrl String?
318
325
  description String?
326
+ /// Optional outbound link to the sponsor's website (clickable on the public competition page)
327
+ websiteUrl String?
319
328
  paidOn String?
320
329
  competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
321
330
  sponsor User? @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -372,10 +381,9 @@ model CompetitionParticipant {
372
381
  votes CompetitionVote[]
373
382
 
374
383
  // Tournament bracket relations
375
- matchesAsParticipant1 CompetitionMatch[] @relation("MatchParticipant1")
376
- matchesAsParticipant2 CompetitionMatch[] @relation("MatchParticipant2")
377
- matchesWon CompetitionMatch[] @relation("MatchWinner")
378
- judgeScores CompetitionJudgeScore[]
384
+ matchEntries CompetitionMatchEntrant[]
385
+ matchesWon CompetitionMatch[] @relation("MatchWinner")
386
+ judgeScores CompetitionJudgeScore[]
379
387
 
380
388
  @@index([competitionId])
381
389
  @@index([userId])
@@ -384,26 +392,28 @@ model CompetitionParticipant {
384
392
  @@index([status])
385
393
  }
386
394
 
387
- // Tournament bracket matches
395
+ // Tournament bracket matches (1v1, heats, or battle royale via entrants join table)
388
396
  model CompetitionMatch {
389
397
  id String @id @default(cuid())
390
398
  competitionId String
391
399
  round Int // 1 = first round, 2 = semifinals, etc.
392
400
  matchNumber Int // Position in round (1, 2, 3...)
393
401
 
394
- participant1Id String?
395
- participant2Id String?
396
- winnerId String?
402
+ format CompetitionMatchFormat @default(HEAD_TO_HEAD)
403
+ capacity Int? // Max entrants (heat lanes, server slots)
404
+ advanceCount Int? // How many advance from this heat/pool
405
+ heatIndex Int? // Wave number within a round (e.g. heat 12 of 50)
406
+
407
+ winnerId String?
397
408
 
398
409
  scheduledTime DateTime?
399
410
  completedAt DateTime?
400
- score Json? // {participant1: 10, participant2: 8}
411
+ score Json? // Legacy H2H: {participant1, participant2}; multi-entrant scores live on entrants
401
412
  notes String? // Match notes
402
413
 
403
- competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
404
- participant1 CompetitionParticipant? @relation("MatchParticipant1", fields: [participant1Id], references: [id])
405
- participant2 CompetitionParticipant? @relation("MatchParticipant2", fields: [participant2Id], references: [id])
406
- winner CompetitionParticipant? @relation("MatchWinner", fields: [winnerId], references: [id])
414
+ competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
415
+ winner CompetitionParticipant? @relation("MatchWinner", fields: [winnerId], references: [id])
416
+ entrants CompetitionMatchEntrant[]
407
417
 
408
418
  createdAt DateTime @default(now())
409
419
  updatedAt DateTime @updatedAt
@@ -413,6 +423,29 @@ model CompetitionMatch {
413
423
  @@index([round])
414
424
  }
415
425
 
426
+ // N entrants per match — heats, battle royales, and head-to-head (slots 1 & 2)
427
+ model CompetitionMatchEntrant {
428
+ id String @id @default(cuid())
429
+ matchId String
430
+ participantId String
431
+ slot Int // Lane/pod position (1-based)
432
+ placement Int? // Finish order within the match
433
+ score Json? // Time, points, etc.
434
+ eliminatedAt DateTime?
435
+ advanced Boolean? // Heat qualifiers: true when advancing to next stage
436
+
437
+ match CompetitionMatch @relation(fields: [matchId], references: [id], onDelete: Cascade)
438
+ participant CompetitionParticipant @relation(fields: [participantId], references: [id], onDelete: Cascade)
439
+
440
+ createdAt DateTime @default(now())
441
+ updatedAt DateTime @updatedAt
442
+
443
+ @@unique([matchId, slot])
444
+ @@unique([matchId, participantId])
445
+ @@index([matchId])
446
+ @@index([participantId])
447
+ }
448
+
416
449
  model CompetitionVote {
417
450
  id String @id @default(cuid())
418
451
  competitionId String
@@ -828,6 +861,9 @@ model BashEvent {
828
861
  collaborationEnabled Boolean @default(true)
829
862
  /// Host opt-in: show icebreaker prompts on the bash detail page for guests
830
863
  icebreakerEnabled Boolean @default(false)
864
+ /// Optional host-authored / host-picked icebreaker prompt. When set, it is shown
865
+ /// instead of a random canned prompt on the bash detail page.
866
+ icebreakerPrompt String?
831
867
  /// Host-curated merch catalog for this event (informational; not a checkout line item for MVP)
832
868
  merchandiseItems Json?
833
869
  externalPriceMax Int? // Maximum ticket price in cents (for imported events)
@@ -910,6 +946,8 @@ model BashEvent {
910
946
  userConnections UserConnection[]
911
947
  aiRecommendationLogs AIRecommendationLog[]
912
948
  aiRecommendationCaches AIRecommendationCache[]
949
+ venueMatchRuns VenueMatchRun[]
950
+ venueSupplyLeadEvents VenueSupplyLeadEvent[]
913
951
  // Template tracking
914
952
  templateId String?
915
953
  template BashEventTemplate? @relation("CreatedFromTemplate", fields: [templateId], references: [id])
@@ -3025,6 +3063,8 @@ model User {
3025
3063
  userItineraries UserItinerary[]
3026
3064
  aiRecommendationLogs AIRecommendationLog[]
3027
3065
  scoutReferralsReceived ScoutReferral[] @relation("ScoutReferred")
3066
+ venueMatchRuns VenueMatchRun[] @relation("VenueMatchRunHost")
3067
+ assignedSupplyLeads VenueSupplyLead[] @relation("SupplyLeadAssignee")
3028
3068
 
3029
3069
  // Service Category Requests
3030
3070
  serviceCategoryRequests ServiceCategoryRequest[]
@@ -3601,6 +3641,10 @@ model Service {
3601
3641
  verifiedAt DateTime? // When verification was approved
3602
3642
  licenseNumber String? // License/registration number
3603
3643
  certifications String[] @default([]) // Array of certification names
3644
+ /// Geo anchor for distance scoring (Venue Match + future geo queries).
3645
+ /// Backfilled from googlePlaceId / address via api/scripts/backfillServiceCoordinates.ts.
3646
+ latitude Float?
3647
+ longitude Float?
3604
3648
  bashFeedPosts BashFeedPost[]
3605
3649
  bashEventArtists BashEventArtist[]
3606
3650
  bashCreativeSubmissions BashCreativeSubmission[]
@@ -3647,6 +3691,8 @@ model Service {
3647
3691
  competitionPrizesSourced Prize[] @relation("PrizeSourceService")
3648
3692
  competitionSponsorCredits CompetitionSponsor[] @relation("CompetitionSponsorSourceService")
3649
3693
  competitionPrizeOffersSourced CompetitionPrizeOffer[] @relation("CompetitionPrizeOfferSourceService")
3694
+ venueMatchCandidates VenueMatchCandidate[]
3695
+ supplyLeadConversions VenueSupplyLead[] @relation("SupplyLeadConvertedService")
3650
3696
 
3651
3697
  @@index([serviceListingStripeSubscriptionId])
3652
3698
  @@index([paymentAccountId])
@@ -3654,6 +3700,7 @@ model Service {
3654
3700
  @@index([isVerified])
3655
3701
  @@index([isLicensed])
3656
3702
  @@index([isFeaturedService, featuredReason, featuredAt])
3703
+ @@index([latitude, longitude])
3657
3704
  }
3658
3705
 
3659
3706
  model StripeAccount {
@@ -6257,6 +6304,14 @@ enum BracketType {
6257
6304
  DOUBLE_ELIMINATION // Lose twice, you're out
6258
6305
  ROUND_ROBIN // Everyone plays everyone
6259
6306
  SWISS // Pairing based on performance
6307
+ HEAT // Wave-based qualifiers (many entrants per match, top performers advance)
6308
+ BATTLE_ROYALE // Last standing / placement in a multi-entrant match
6309
+ }
6310
+
6311
+ enum CompetitionMatchFormat {
6312
+ HEAD_TO_HEAD // Traditional 1v1 (slots 1 & 2)
6313
+ HEAT // Sequential wave with capacity-limited entrants
6314
+ BATTLE_ROYALE // All entrants compete simultaneously until one remains
6260
6315
  }
6261
6316
 
6262
6317
  enum ParticipantStatus {
@@ -9637,3 +9692,183 @@ enum StripeNonprofitPricingStatus {
9637
9692
  Approved
9638
9693
  Declined
9639
9694
  }
9695
+
9696
+ // ─────────────────────────────────────────────────────────────────────────────
9697
+ // Venue Match pipeline (retrieve → score → explain) + supply sales pipeline.
9698
+ // JSON column shapes live in bash-common/src/venueMatch.ts (VenueMatchIntent,
9699
+ // VenueMatchScoreBreakdown, VenueCandidateDisplay).
9700
+ // ─────────────────────────────────────────────────────────────────────────────
9701
+
9702
+ enum VenueMatchCandidateSource {
9703
+ Marketplace
9704
+ GooglePlaces
9705
+ }
9706
+
9707
+ enum VenueMatchHostAction {
9708
+ None
9709
+ Viewed
9710
+ Accepted
9711
+ AcceptedOffPlatform
9712
+ Dismissed
9713
+ CalledPhone
9714
+ InvitedVenue
9715
+ }
9716
+
9717
+ enum VenueSupplyLeadStage {
9718
+ Surfaced
9719
+ Prioritized
9720
+ Queued
9721
+ Contacted
9722
+ Interested
9723
+ DemoScheduled
9724
+ Listed
9725
+ Declined
9726
+ DoNotContact
9727
+ }
9728
+
9729
+ enum ExternalPlaceSource {
9730
+ OutscraperCsv
9731
+ OutscraperApi
9732
+ Manual
9733
+ }
9734
+
9735
+ /// One scoring run for a bash. Content-hash dedupes identical re-runs.
9736
+ model VenueMatchRun {
9737
+ id String @id @default(cuid())
9738
+ bashEventId String
9739
+ hostUserId String
9740
+ /// `VenueMatchIntent` — occasion, guestCount, anchor coords, vibe, budget.
9741
+ intent Json
9742
+ contentHash String
9743
+ /// Run telemetry: latencyMs, candidateCounts, googleCalls, cacheHits.
9744
+ meta Json?
9745
+ createdAt DateTime @default(now())
9746
+
9747
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
9748
+ hostUser User @relation("VenueMatchRunHost", fields: [hostUserId], references: [id], onDelete: Cascade)
9749
+ candidates VenueMatchCandidate[]
9750
+
9751
+ @@index([bashEventId, createdAt])
9752
+ @@index([hostUserId])
9753
+ }
9754
+
9755
+ /// Every scored candidate in a run (not just the top 3 shown).
9756
+ model VenueMatchCandidate {
9757
+ id String @id @default(cuid())
9758
+ runId String
9759
+ rank Int
9760
+ score Int
9761
+ /// `VenueMatchScoreBreakdown` — per-factor score + applied weight.
9762
+ scoreBreakdown Json
9763
+ /// Host-facing reason strings ("2.1 mi away", "Fits 25 guests").
9764
+ reasons Json
9765
+ source VenueMatchCandidateSource
9766
+ serviceId String?
9767
+ googlePlaceId String?
9768
+ /// `VenueCandidateDisplay` — snapshot so GET never re-hits Google.
9769
+ displaySnapshot Json
9770
+ hostAction VenueMatchHostAction @default(None)
9771
+ hostActionAt DateTime?
9772
+
9773
+ run VenueMatchRun @relation(fields: [runId], references: [id], onDelete: Cascade)
9774
+ service Service? @relation(fields: [serviceId], references: [id], onDelete: SetNull)
9775
+
9776
+ @@index([runId, rank])
9777
+ @@index([googlePlaceId])
9778
+ @@index([serviceId])
9779
+ }
9780
+
9781
+ /// ToS-compatible place data (Outscraper CSV/API imports). The ONLY allowed
9782
+ /// contact-data source for VenueSupplyLead — Google Place fields are never
9783
+ /// copied here (see GooglePlaceCache for the ephemeral display tier).
9784
+ model ExternalPlaceCatalog {
9785
+ id String @id @default(cuid())
9786
+ googlePlaceId String? @unique
9787
+ phoneE164 String?
9788
+ name String
9789
+ fullAddress String? @db.Text
9790
+ city String?
9791
+ state String?
9792
+ postalCode String?
9793
+ latitude Float?
9794
+ longitude Float?
9795
+ rating Float?
9796
+ reviewCount Int?
9797
+ category String?
9798
+ website String? @db.Text
9799
+ source ExternalPlaceSource @default(OutscraperCsv)
9800
+ /// Scraped/ file stem or Outscraper query that produced this row.
9801
+ sourceKey String?
9802
+ importedAt DateTime @default(now())
9803
+ refreshedAt DateTime?
9804
+
9805
+ supplyLeads VenueSupplyLead[]
9806
+
9807
+ @@index([phoneE164])
9808
+ @@index([city, state])
9809
+ }
9810
+
9811
+ /// Off-platform venue prospect for the sales pipeline. Contact fields come
9812
+ /// from ExternalPlaceCatalog (denormalized name/phone kept for fast lists).
9813
+ model VenueSupplyLead {
9814
+ id String @id @default(cuid())
9815
+ catalogId String?
9816
+ googlePlaceId String? @unique
9817
+ /// Set when the venue converts (publishes a Service on Bash).
9818
+ serviceId String?
9819
+ /// Dedup link to the Vapi dialer lead when phoneE164 matches.
9820
+ outreachLeadId String?
9821
+ name String
9822
+ phoneE164 String?
9823
+ city String?
9824
+ state String?
9825
+ /// Count of DISTINCT bash events that surfaced this venue (top 10).
9826
+ distinctEventCount Int @default(0)
9827
+ firstSurfacedAt DateTime @default(now())
9828
+ lastSurfacedAt DateTime @default(now())
9829
+ stage VenueSupplyLeadStage @default(Surfaced)
9830
+ assignedToUserId String?
9831
+ notes String? @db.Text
9832
+ createdAt DateTime @default(now())
9833
+ updatedAt DateTime @updatedAt
9834
+
9835
+ catalog ExternalPlaceCatalog? @relation(fields: [catalogId], references: [id], onDelete: SetNull)
9836
+ service Service? @relation("SupplyLeadConvertedService", fields: [serviceId], references: [id], onDelete: SetNull)
9837
+ assignedTo User? @relation("SupplyLeadAssignee", fields: [assignedToUserId], references: [id], onDelete: SetNull)
9838
+ surfaceEvents VenueSupplyLeadEvent[]
9839
+
9840
+ @@index([stage, distinctEventCount])
9841
+ @@index([city, state])
9842
+ @@index([phoneE164])
9843
+ @@index([assignedToUserId])
9844
+ }
9845
+
9846
+ /// Join row: which bash surfaced this lead, with occasion context for
9847
+ /// cold-call scripts ("4 hosts searched bachelor party venues near you").
9848
+ model VenueSupplyLeadEvent {
9849
+ id String @id @default(cuid())
9850
+ supplyLeadId String
9851
+ bashEventId String
9852
+ matchCandidateId String?
9853
+ occasion String?
9854
+ guestCountEstimate Int?
9855
+ surfacedAt DateTime @default(now())
9856
+
9857
+ supplyLead VenueSupplyLead @relation(fields: [supplyLeadId], references: [id], onDelete: Cascade)
9858
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
9859
+
9860
+ @@unique([supplyLeadId, bashEventId])
9861
+ @@index([bashEventId])
9862
+ }
9863
+
9864
+ /// Ephemeral Google Place display data (48h TTL). NEVER copied into
9865
+ /// VenueSupplyLead or ExternalPlaceCatalog — Google ToS tier C only.
9866
+ model GooglePlaceCache {
9867
+ googlePlaceId String @id
9868
+ /// Display-only snapshot: name, address, rating, reviewCount, photoUrl, phone, lat/lng.
9869
+ displaySnapshot Json
9870
+ fetchedAt DateTime @default(now())
9871
+ expiresAt DateTime
9872
+
9873
+ @@index([expiresAt])
9874
+ }
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  BashEventType,
3
- BracketType,
4
3
  CompetitionType,
5
4
  Contact,
6
5
  DietaryOption,
@@ -1137,9 +1136,10 @@ export const YearsOfExperienceToString: { [key in YearsOfExperienceEnum]: string
1137
1136
  [YearsOfExperience.FivePlusYears]: "More than five years",
1138
1137
  };
1139
1138
 
1139
+ export { BracketType } from "./mirroredPrismaEnums.js";
1140
+
1140
1141
  // Export Prisma enums for use in frontend
1141
1142
  export {
1142
- BracketType,
1143
1143
  CompetitionType,
1144
1144
  EntertainmentServiceType,
1145
1145
  JudgingType,
@@ -1,3 +1,4 @@
1
+ import type { BracketType } from "./mirroredPrismaEnums.js";
1
2
  import {
2
3
  AmountOfGuests,
3
4
  AssociatedBash,
@@ -280,14 +281,23 @@ export interface CompetitionPrizeOfferExt {
280
281
  } | null;
281
282
  }
282
283
 
283
- export interface CompetitionExt extends Competition {
284
- prizes: PrizeExt[];
284
+ /** Competition row + relations; Override keeps new columns visible before consumers refresh Prisma. */
285
+ export type CompetitionExt = Override<
286
+ Competition,
287
+ {
288
+ autoApproveParticipants: boolean;
289
+ participantDisplayMode: string;
290
+ restrictVotingToAttendees: boolean;
291
+ bracketType: BracketType | null;
292
+ prizes: PrizeExt[];
293
+ }
294
+ > & {
285
295
  participants?: CompetitionParticipant[];
286
296
  votes?: CompetitionVote[];
287
297
  entryTicketTier?: TicketTier | null;
288
298
  sponsor?: CompetitionSponsor[];
289
299
  prizeOffers?: CompetitionPrizeOfferExt[];
290
- }
300
+ };
291
301
 
292
302
  /** One row in BashEvent.merchandiseItems (host wizard; informational catalog) */
293
303
  export type BashEventHostMerchItem = {
@@ -356,12 +366,15 @@ export type BashEventHiredServicePublic = {
356
366
  }>;
357
367
  };
358
368
 
359
- export interface BashEventExt extends Omit<
360
- BashEvent,
361
- | "ideaExpiresAt"
362
- | "ideaInterestThreshold"
363
- | "isAutoApprovable"
364
- | "merchandiseItems"
369
+ export interface BashEventExt extends Override<
370
+ Omit<
371
+ BashEvent,
372
+ | "ideaExpiresAt"
373
+ | "ideaInterestThreshold"
374
+ | "isAutoApprovable"
375
+ | "merchandiseItems"
376
+ >,
377
+ { icebreakerPrompt: string | null }
365
378
  > {
366
379
  /** Host-curated merch catalog (wizard); stored as JSON on BashEvent. Null = no merch configured. */
367
380
  merchandiseItems: BashEventMerchCatalog | null;
@@ -984,8 +997,9 @@ export interface ServiceExt extends Service {
984
997
 
985
998
  stripeAccount?: PublicStripeAccount | null;
986
999
 
987
- latitude?: number;
988
- longitude?: number;
1000
+ /** Geo anchor — explicit on ext type for consumers before Prisma client catches up */
1001
+ latitude: number | null;
1002
+ longitude: number | null;
989
1003
 
990
1004
  // availableDateTimes?: Availability[];
991
1005
 
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Curated conversation starters for Bash events (hosts enable per-event).
3
+ * Single source of truth shared by the API (public icebreaker endpoint) and the
4
+ * bash-app wizard (host "Generate a new icebreaker" picker).
5
+ */
6
+ export const ICEBREAKER_PROMPTS: string[] = [
7
+ // Get to know you
8
+ "What’s something small that made your week better?",
9
+ "What’s a hobby you’ve always wanted to try but haven’t yet?",
10
+ "If you could learn one new skill overnight, what would it be?",
11
+ "What’s your go-to comfort food?",
12
+ "What’s a book, show, or podcast you recommend to almost everyone?",
13
+ "What city or country is still on your travel list?",
14
+ "What’s a tradition from your family or culture you love?",
15
+ "What’s the best advice you’ve ever been given?",
16
+ "Who is someone you admire and why (in one sentence)?",
17
+ "What’s a song that instantly puts you in a good mood?",
18
+ // Event / party
19
+ "What brought you out tonight?",
20
+ "What are you most looking forward to at this event?",
21
+ "If this bash had a theme song, what would it be?",
22
+ "What’s one thing you hope to take away from tonight?",
23
+ "Who here do you already know—and how did you meet?",
24
+ "What’s your favorite thing about gatherings like this?",
25
+ "If you could add one activity to this event, what would it be?",
26
+ "What’s a memory from a past party you still think about?",
27
+ "What’s your ideal way to spend an evening with friends?",
28
+ "What’s something you’re celebrating lately (big or small)?",
29
+ // Light / funny
30
+ "What’s a hot take you’re willing to defend at this table?",
31
+ "What’s your most useless talent?",
32
+ "Would you rather: perfect weather forever or perfect sleep forever?",
33
+ "What’s a food you pretend to like but secretly don’t?",
34
+ "What’s the last thing you laughed really hard at?",
35
+ "If animals could talk, which would be the rudest?",
36
+ "What’s a trend you don’t get but respect?",
37
+ "What’s your go-to karaoke song (or what would it be)?",
38
+ "What’s something everyone likes but you’re lukewarm on?",
39
+ "What’s a nickname you’ve had—or wish you had?",
40
+ // Nostalgia
41
+ "What’s a childhood snack or meal you still crave?",
42
+ "What’s the first concert or live event you remember?",
43
+ "What game did you play endlessly as a kid?",
44
+ "What’s a smell that instantly takes you back somewhere?",
45
+ "What’s a lesson from school you still use?",
46
+ "What’s something you believed as a kid that makes you laugh now?",
47
+ "What’s a movie you’ve watched way too many times?",
48
+ "What’s a friendship that started in an unexpected place?",
49
+ "What’s a piece of clothing or merch you wish you still had?",
50
+ "What’s a place you grew up that you’d show someone new?",
51
+ // Deeper (still party-safe)
52
+ "What’s something you’re proud of from the last year?",
53
+ "What’s a goal you’re working on right now?",
54
+ "What’s something kind someone did for you that stuck with you?",
55
+ "What’s a risk you’re glad you took?",
56
+ "What helps you recharge after a busy week?",
57
+ "What’s a cause or community you care about?",
58
+ "What’s a skill you’re trying to get better at?",
59
+ "What’s something you’ve changed your mind about?",
60
+ "What’s a question you wish people asked you more often?",
61
+ "What’s one thing you’re grateful for today?",
62
+ ];
63
+
64
+ /** Random icebreaker, optionally avoiding the current prompt so "generate again" feels fresh. */
65
+ export function getRandomIcebreakerPrompt(exclude?: string | null): string {
66
+ if (ICEBREAKER_PROMPTS.length === 0) return "";
67
+ if (ICEBREAKER_PROMPTS.length === 1) return ICEBREAKER_PROMPTS[0];
68
+ let next = ICEBREAKER_PROMPTS[Math.floor(Math.random() * ICEBREAKER_PROMPTS.length)];
69
+ let guard = 0;
70
+ while (exclude && next === exclude && guard < 10) {
71
+ next = ICEBREAKER_PROMPTS[Math.floor(Math.random() * ICEBREAKER_PROMPTS.length)];
72
+ guard += 1;
73
+ }
74
+ return next;
75
+ }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from "./venueLoyaltyRedemption.js";
7
7
  export * from "./stripeListingSubscriptionMessages.js";
8
8
  export * from "./extendedSchemas.js";
9
9
  export * from "./competitionIdeas.js";
10
+ export * from "./icebreakerPrompts.js";
10
11
  export * from "./competitionValidation.js";
11
12
  export * from "./groupTicketUtils.js";
12
13
  export * from "./utils/groupTicketCartUtils.js";
@@ -18,6 +19,7 @@ export * from "./membershipDefinitions.js";
18
19
  export * from "./onSaleCapabilityRecommendations.js";
19
20
  export * from "./ticketBnplPaymentMethods.js";
20
21
  export * from "./aiApproval.js";
22
+ export * from "./venueMatch.js";
21
23
  export * from "./userReportTypes.js";
22
24
  export * from "./mirroredPrismaEnums.js";
23
25
  export * from "./utils/featuredDiscoveryGeo.js";
@@ -180,3 +180,19 @@ export const CreditSourceType = {
180
180
  } as const satisfies Record<CreditSourceTypeEnum, CreditSourceTypeEnum>;
181
181
 
182
182
  export type CreditSourceType = CreditSourceTypeEnum;
183
+
184
+ /**
185
+ * Mirrors Prisma `BracketType` — includes HEAT / BATTLE_ROYALE before all consumers
186
+ * refresh `@prisma/client` (see README-typecheck.md).
187
+ */
188
+ export const BracketType = {
189
+ NONE: "NONE",
190
+ SINGLE_ELIMINATION: "SINGLE_ELIMINATION",
191
+ DOUBLE_ELIMINATION: "DOUBLE_ELIMINATION",
192
+ ROUND_ROBIN: "ROUND_ROBIN",
193
+ SWISS: "SWISS",
194
+ HEAT: "HEAT",
195
+ BATTLE_ROYALE: "BATTLE_ROYALE",
196
+ } as const;
197
+
198
+ export type BracketType = (typeof BracketType)[keyof typeof BracketType];