@bash-app/bash-common 30.309.0 → 30.310.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 (52) hide show
  1. package/dist/__tests__/competitionValidation.test.d.ts +2 -0
  2. package/dist/__tests__/competitionValidation.test.d.ts.map +1 -0
  3. package/dist/__tests__/competitionValidation.test.js +28 -0
  4. package/dist/__tests__/competitionValidation.test.js.map +1 -0
  5. package/dist/__tests__/definitionsHelpers.test.js +8 -1
  6. package/dist/__tests__/definitionsHelpers.test.js.map +1 -1
  7. package/dist/__tests__/membershipDefinitions.test.js +5 -0
  8. package/dist/__tests__/membershipDefinitions.test.js.map +1 -1
  9. package/dist/__tests__/sideQuestIdeas.test.js +5 -1
  10. package/dist/__tests__/sideQuestIdeas.test.js.map +1 -1
  11. package/dist/__tests__/statusEnums.test.js +7 -0
  12. package/dist/__tests__/statusEnums.test.js.map +1 -1
  13. package/dist/competitionValidation.d.ts +11 -2
  14. package/dist/competitionValidation.d.ts.map +1 -1
  15. package/dist/competitionValidation.js +40 -2
  16. package/dist/competitionValidation.js.map +1 -1
  17. package/dist/definitions.d.ts +7 -0
  18. package/dist/definitions.d.ts.map +1 -1
  19. package/dist/definitions.js +10 -0
  20. package/dist/definitions.js.map +1 -1
  21. package/dist/extendedSchemas.d.ts +51 -11
  22. package/dist/extendedSchemas.d.ts.map +1 -1
  23. package/dist/extendedSchemas.js +5 -0
  24. package/dist/extendedSchemas.js.map +1 -1
  25. package/dist/membershipDefinitions.d.ts +2 -0
  26. package/dist/membershipDefinitions.d.ts.map +1 -1
  27. package/dist/membershipDefinitions.js +3 -0
  28. package/dist/membershipDefinitions.js.map +1 -1
  29. package/dist/mirroredPrismaEnums.d.ts +4 -0
  30. package/dist/mirroredPrismaEnums.d.ts.map +1 -1
  31. package/dist/mirroredPrismaEnums.js +4 -0
  32. package/dist/mirroredPrismaEnums.js.map +1 -1
  33. package/dist/sideQuestIdeas.d.ts.map +1 -1
  34. package/dist/sideQuestIdeas.js +2 -2
  35. package/dist/sideQuestIdeas.js.map +1 -1
  36. package/dist/sideQuestTypes.d.ts +1 -0
  37. package/dist/sideQuestTypes.d.ts.map +1 -1
  38. package/dist/sideQuestTypes.js.map +1 -1
  39. package/package.json +1 -1
  40. package/prisma/schema.prisma +109 -0
  41. package/src/__tests__/competitionValidation.test.ts +43 -0
  42. package/src/__tests__/definitionsHelpers.test.ts +16 -0
  43. package/src/__tests__/membershipDefinitions.test.ts +18 -0
  44. package/src/__tests__/sideQuestIdeas.test.ts +11 -1
  45. package/src/__tests__/statusEnums.test.ts +7 -0
  46. package/src/competitionValidation.ts +56 -2
  47. package/src/definitions.ts +13 -0
  48. package/src/extendedSchemas.ts +23 -11
  49. package/src/membershipDefinitions.ts +3 -0
  50. package/src/mirroredPrismaEnums.ts +4 -0
  51. package/src/sideQuestIdeas.ts +2 -2
  52. package/src/sideQuestTypes.ts +1 -0
@@ -139,6 +139,13 @@ describe("status enum value sets", () => {
139
139
  "ReferralCodeUsed",
140
140
  "VenueLoyaltyRedemption",
141
141
  "PartnerPromoRedemption",
142
+ "SideQuestCompletion",
143
+ "SideQuestMilestone",
144
+ "CommunityQuestUnlock",
145
+ "CompetitionVote",
146
+ "CompetitionParticipation",
147
+ "CompetitionFinalist",
148
+ "CompetitionWin",
142
149
  ].sort(),
143
150
  );
144
151
  });
@@ -17,14 +17,20 @@ export function parseJudgingType(
17
17
  return value as JudgingType;
18
18
  }
19
19
 
20
- /** Judging modes disallowed for new competitions (lottery / auction-adjacent). */
20
+ /**
21
+ * Judging modes disallowed for new competitions: lottery / auction-adjacent
22
+ * (RANDOM_DRAW, HIGHEST_BID), plus METRICS and FIRST_COME which have no
23
+ * winner-resolution implementation yet (dead ends for hosts).
24
+ */
21
25
  export const DISALLOWED_JUDGING_TYPES_FOR_NEW: ReadonlySet<JudgingType> = new Set([
22
26
  JudgingType.RANDOM_DRAW,
23
27
  JudgingType.HIGHEST_BID,
28
+ JudgingType.METRICS,
29
+ JudgingType.FIRST_COME,
24
30
  ]);
25
31
 
26
32
  export const DISALLOWED_JUDGING_TYPE_MESSAGE =
27
- "Random draw and highest-bid judging are not supported on Bash. Use host choice, skill judging, audience vote, metrics, first come, or tournament bracket.";
33
+ "Random draw, highest-bid, metrics, and first-come judging are not supported on Bash yet. Use host choice, skill judging, audience vote, or tournament bracket.";
28
34
 
29
35
  export function isDisallowedJudgingTypeForNew(
30
36
  judgingType: JudgingType | null | undefined
@@ -45,6 +51,54 @@ export function assertAllowedJudgingTypeForNew(
45
51
  export const DEFAULT_COMPETITION_JUDGING_TYPE: JudgingType =
46
52
  JudgingType.HOST_CHOOSES;
47
53
 
54
+ /** Single source of truth for judging-type copy — shared by the host wizard and guest-facing display. */
55
+ export const JUDGING_TYPE_LABELS: Record<JudgingType, string> = {
56
+ [JudgingType.RANDOM_DRAW]: "Drawing (legacy)",
57
+ [JudgingType.HIGHEST_BID]: "Highest Bid (legacy)",
58
+ [JudgingType.HOST_CHOOSES]: "Host Selection",
59
+ [JudgingType.JUDGES]: "Panel of Judges",
60
+ [JudgingType.AUDIENCE_VOTE]: "Audience Vote",
61
+ [JudgingType.METRICS]: "Performance Metrics",
62
+ [JudgingType.FIRST_COME]: "First Come First Serve",
63
+ [JudgingType.BRACKET_TOURNAMENT]: "Bracket Tournament",
64
+ };
65
+
66
+ export const JUDGING_TYPE_DESCRIPTIONS: Record<JudgingType, string> = {
67
+ [JudgingType.RANDOM_DRAW]:
68
+ "This competition uses a legacy random drawing configuration. New competitions on Bash use host choice, skill judging, audience vote, or tournament formats.",
69
+ [JudgingType.HIGHEST_BID]:
70
+ "Legacy auction-style configuration. New competitions on Bash do not support highest-bid judging.",
71
+ [JudgingType.HOST_CHOOSES]:
72
+ "The event host will personally select the winner(s) based on their criteria.",
73
+ [JudgingType.JUDGES]:
74
+ "A panel of judges will evaluate participants based on specific criteria and select the winner(s).",
75
+ [JudgingType.AUDIENCE_VOTE]:
76
+ "Attendees will vote to determine the winner(s). The participant(s) with the most votes win!",
77
+ [JudgingType.METRICS]:
78
+ "Winners are determined by measurable performance metrics and objective criteria.",
79
+ [JudgingType.FIRST_COME]:
80
+ "Winners are determined by order of participation - first to sign up or complete wins!",
81
+ [JudgingType.BRACKET_TOURNAMENT]:
82
+ "Participants compete head-to-head in a tournament bracket. Winners advance through rounds until a champion is determined.",
83
+ };
84
+
85
+ export function getJudgingTypeLabel(
86
+ judgingType: JudgingType | null | undefined
87
+ ): string {
88
+ if (!judgingType) return "TBD";
89
+ return JUDGING_TYPE_LABELS[judgingType] ?? "TBD";
90
+ }
91
+
92
+ export function getJudgingTypeDescription(
93
+ judgingType: JudgingType | null | undefined
94
+ ): string {
95
+ if (!judgingType) return "The judging method has not been determined yet.";
96
+ return (
97
+ JUDGING_TYPE_DESCRIPTIONS[judgingType] ??
98
+ "The judging method has not been determined yet."
99
+ );
100
+ }
101
+
48
102
  export function normalizeJudgingTypeForSave(
49
103
  judgingType: JudgingType | null | undefined,
50
104
  isExistingLegacy: boolean
@@ -44,6 +44,19 @@ import type { UtmFields } from "./utmAttribution.js";
44
44
  * values — importing them from `@prisma/client` yields `undefined` and crashes on first
45
45
  * property access. These mirrors match the Prisma schema / full client enum strings.
46
46
  */
47
+ /**
48
+ * "Also list as [complementary type]" — a one-time identity-field prefill copy
49
+ * (see Service.complementaryOfServiceId), not an ongoing synced link. Scoped to
50
+ * the Vendors <-> EventServices pairing only (e.g. a food truck that vends
51
+ * publicly at festivals and is also paid a flat fee at private weddings).
52
+ */
53
+ export const COMPLEMENTARY_SERVICE_TYPE: Partial<
54
+ Record<ServiceTypes, ServiceTypes>
55
+ > = {
56
+ [ServiceTypes.Vendors]: ServiceTypes.EventServices,
57
+ [ServiceTypes.EventServices]: ServiceTypes.Vendors,
58
+ };
59
+
47
60
  export const ServiceBookingOfferDirection = {
48
61
  HostToProvider: "HostToProvider",
49
62
  ProviderToHost: "ProviderToHost",
@@ -270,14 +270,17 @@ export const PRIVATE_USER_ACCOUNT_TO_SELECT = {
270
270
 
271
271
  export interface SponsoredEventExt extends SponsoredEvent {}
272
272
 
273
+ /** Minimal service identity used wherever a competition prize/offer just needs to point at a listing. */
274
+ export interface ServiceRefLite {
275
+ id: string;
276
+ serviceName: string | null;
277
+ coverPhoto: string | null;
278
+ }
279
+
273
280
  export interface PrizeExt extends Prize {
274
281
  winner?: PublicUser | null;
275
282
  contributor?: PublicUser | null;
276
- sourceService?: {
277
- id: string;
278
- serviceName: string | null;
279
- coverPhoto: string | null;
280
- } | null;
283
+ sourceService?: ServiceRefLite | null;
281
284
  }
282
285
 
283
286
  export interface CompetitionPrizeOfferExt {
@@ -296,11 +299,7 @@ export interface CompetitionPrizeOfferExt {
296
299
  createdAt: Date | string;
297
300
  respondedAt?: Date | string | null;
298
301
  offeredBy?: PublicUser;
299
- sourceService?: {
300
- id: string;
301
- serviceName: string | null;
302
- coverPhoto: string | null;
303
- } | null;
302
+ sourceService?: ServiceRefLite | null;
304
303
  targetPrize?: {
305
304
  id: string;
306
305
  name: string;
@@ -309,6 +308,11 @@ export interface CompetitionPrizeOfferExt {
309
308
  } | null;
310
309
  }
311
310
 
311
+ /** One competition participant row + resolved user; shared by CompetitionExt.participants and the host roster endpoint. */
312
+ export interface CompetitionParticipantExt extends CompetitionParticipant {
313
+ user?: Pick<PublicUser, "uploadedImage" | "image"> | null;
314
+ }
315
+
312
316
  /** Competition row + relations; Override keeps new columns visible before consumers refresh Prisma. */
313
317
  export type CompetitionExt = Override<
314
318
  Competition,
@@ -320,7 +324,7 @@ export type CompetitionExt = Override<
320
324
  prizes: PrizeExt[];
321
325
  }
322
326
  > & {
323
- participants?: CompetitionParticipant[];
327
+ participants?: CompetitionParticipantExt[];
324
328
  votes?: CompetitionVote[];
325
329
  entryTicketTier?: TicketTier | null;
326
330
  sponsor?: CompetitionSponsor[];
@@ -848,6 +852,11 @@ export const SERVICE_DATA_TO_INCLUDE = {
848
852
  sportsProfile: true,
849
853
  }
850
854
  }, // Include organization data with member counts
855
+ // Lightweight — just enough for the UI to know a complementary Vendor <-> EventServices
856
+ // profile already exists (see Part B "also list as" flow) without a second fetch.
857
+ complementaryProfiles: {
858
+ select: { id: true, serviceType: true },
859
+ },
851
860
  // bookings: {
852
861
  // include: SERVICE_BOOKING_PRIVATE_DATA_TO_INCLUDE, //make sure only to include owned bookedDays
853
862
  // },
@@ -1101,6 +1110,9 @@ export interface ServiceExt extends Service {
1101
1110
  associatedBashesReferencingMe?: AssociatedBashExt[];
1102
1111
  associatedServicesReferencingMe?: AssociatedServiceExt[];
1103
1112
 
1113
+ /** Reverse side of `complementaryOfServiceId` — the "also list as" profile(s) this service already spawned. */
1114
+ complementaryProfiles?: Pick<Service, "id" | "serviceType">[];
1115
+
1104
1116
  // googleReviews: GoogleReview[];
1105
1117
 
1106
1118
  // For availability filtering - matches Prisma relation name "bookings"
@@ -813,6 +813,8 @@ export const MEMBERSHIP_FEATURES = {
813
813
  SEARCH_PRIORITY: 'search_priority',
814
814
  BASIC_ANALYTICS: 'basic_analytics',
815
815
  VENDOR_DIRECTORY: 'vendor_directory',
816
+ /** Host-side perk: offer a vendor exclusive-category status for an event when sending a booking request. */
817
+ VENDOR_CATEGORY_EXCLUSIVITY: 'vendor_category_exclusivity',
816
818
  TIER_BADGE: 'tier_badge',
817
819
 
818
820
  // Elite features
@@ -854,6 +856,7 @@ export function hasFeatureAccess(userTier: MembershipTier, feature: string): boo
854
856
  case MEMBERSHIP_FEATURES.SEARCH_PRIORITY:
855
857
  case MEMBERSHIP_FEATURES.BASIC_ANALYTICS:
856
858
  case MEMBERSHIP_FEATURES.VENDOR_DIRECTORY:
859
+ case MEMBERSHIP_FEATURES.VENDOR_CATEGORY_EXCLUSIVITY:
857
860
  case MEMBERSHIP_FEATURES.TIER_BADGE:
858
861
  return tierIndex >= 2; // Pro+
859
862
 
@@ -180,6 +180,10 @@ export const CreditSourceType = {
180
180
  SideQuestCompletion: "SideQuestCompletion",
181
181
  SideQuestMilestone: "SideQuestMilestone",
182
182
  CommunityQuestUnlock: "CommunityQuestUnlock",
183
+ CompetitionVote: "CompetitionVote",
184
+ CompetitionParticipation: "CompetitionParticipation",
185
+ CompetitionFinalist: "CompetitionFinalist",
186
+ CompetitionWin: "CompetitionWin",
183
187
  } as const satisfies Record<CreditSourceTypeEnum, CreditSourceTypeEnum>;
184
188
 
185
189
  export type CreditSourceType = CreditSourceTypeEnum;
@@ -40,7 +40,7 @@ export const SIDE_QUEST_CATEGORY_META: Record<
40
40
  };
41
41
 
42
42
  /** Phase 1 host-selectable quest types */
43
- export const SIDE_QUEST_PHASE1_TYPES: SideQuestTypeId[] = ["Qr", "Code"];
43
+ export const SIDE_QUEST_PHASE1_TYPES: SideQuestTypeId[] = ["Qr", "Code", "Vote"];
44
44
 
45
45
  export const SIDE_QUEST_TYPE_META: Record<
46
46
  SideQuestTypeId,
@@ -79,7 +79,7 @@ export const SIDE_QUEST_TYPE_META: Record<
79
79
  Vote: {
80
80
  label: "Vote Quest",
81
81
  description: "Completes after voting in a competition.",
82
- phase1: false,
82
+ phase1: true,
83
83
  },
84
84
  Sponsor: {
85
85
  label: "Sponsor Quest",
@@ -28,6 +28,7 @@ export interface SideQuestExt {
28
28
  EventBadgeExt,
29
29
  "id" | "name" | "imageUrl" | "description"
30
30
  > | null;
31
+ competitionId?: string | null;
31
32
  startsAt: string | Date | null;
32
33
  endsAt: string | Date | null;
33
34
  maxCompletions: number | null;