@bash-app/bash-common 30.263.0 → 30.272.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 (69) hide show
  1. package/dist/__tests__/requestAuthUser.test.d.ts +2 -0
  2. package/dist/__tests__/requestAuthUser.test.d.ts.map +1 -0
  3. package/dist/__tests__/requestAuthUser.test.js +84 -0
  4. package/dist/__tests__/requestAuthUser.test.js.map +1 -0
  5. package/dist/__tests__/statusEnums.test.js +1 -0
  6. package/dist/__tests__/statusEnums.test.js.map +1 -1
  7. package/dist/definitions.d.ts +1 -0
  8. package/dist/definitions.d.ts.map +1 -1
  9. package/dist/definitions.js +1 -0
  10. package/dist/definitions.js.map +1 -1
  11. package/dist/extendedSchemas.d.ts +3 -0
  12. package/dist/extendedSchemas.d.ts.map +1 -1
  13. package/dist/extendedSchemas.js.map +1 -1
  14. package/dist/index.d.ts +5 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +5 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/mirroredPrismaEnums.d.ts +1 -0
  19. package/dist/mirroredPrismaEnums.d.ts.map +1 -1
  20. package/dist/mirroredPrismaEnums.js +1 -0
  21. package/dist/mirroredPrismaEnums.js.map +1 -1
  22. package/dist/partnerStoreTypes.d.ts +93 -3
  23. package/dist/partnerStoreTypes.d.ts.map +1 -1
  24. package/dist/partnerStoreTypes.js +39 -1
  25. package/dist/partnerStoreTypes.js.map +1 -1
  26. package/dist/requestAuthUser.d.ts +36 -0
  27. package/dist/requestAuthUser.d.ts.map +1 -0
  28. package/dist/requestAuthUser.js +76 -0
  29. package/dist/requestAuthUser.js.map +1 -0
  30. package/dist/utils/__tests__/eventCitySlug.test.d.ts +2 -0
  31. package/dist/utils/__tests__/eventCitySlug.test.d.ts.map +1 -0
  32. package/dist/utils/__tests__/eventCitySlug.test.js +12 -0
  33. package/dist/utils/__tests__/eventCitySlug.test.js.map +1 -0
  34. package/dist/utils/__tests__/localPartnerHours.test.d.ts +2 -0
  35. package/dist/utils/__tests__/localPartnerHours.test.d.ts.map +1 -0
  36. package/dist/utils/__tests__/localPartnerHours.test.js +35 -0
  37. package/dist/utils/__tests__/localPartnerHours.test.js.map +1 -0
  38. package/dist/utils/__tests__/orgPromotionalPackageUtils.test.d.ts +2 -0
  39. package/dist/utils/__tests__/orgPromotionalPackageUtils.test.d.ts.map +1 -0
  40. package/dist/utils/__tests__/orgPromotionalPackageUtils.test.js +56 -0
  41. package/dist/utils/__tests__/orgPromotionalPackageUtils.test.js.map +1 -0
  42. package/dist/utils/eventCitySlug.d.ts +11 -0
  43. package/dist/utils/eventCitySlug.d.ts.map +1 -0
  44. package/dist/utils/eventCitySlug.js +20 -0
  45. package/dist/utils/eventCitySlug.js.map +1 -0
  46. package/dist/utils/localPartnerHours.d.ts +12 -0
  47. package/dist/utils/localPartnerHours.d.ts.map +1 -0
  48. package/dist/utils/localPartnerHours.js +92 -0
  49. package/dist/utils/localPartnerHours.js.map +1 -0
  50. package/dist/utils/orgPromotionalPackageUtils.d.ts +75 -0
  51. package/dist/utils/orgPromotionalPackageUtils.d.ts.map +1 -0
  52. package/dist/utils/orgPromotionalPackageUtils.js +151 -0
  53. package/dist/utils/orgPromotionalPackageUtils.js.map +1 -0
  54. package/package.json +1 -1
  55. package/prisma/schema.prisma +151 -17
  56. package/src/__tests__/requestAuthUser.test.ts +120 -0
  57. package/src/__tests__/statusEnums.test.ts +1 -0
  58. package/src/definitions.ts +2 -0
  59. package/src/extendedSchemas.ts +3 -0
  60. package/src/index.ts +6 -0
  61. package/src/mirroredPrismaEnums.ts +1 -0
  62. package/src/partnerStoreTypes.ts +134 -3
  63. package/src/requestAuthUser.ts +107 -0
  64. package/src/utils/__tests__/eventCitySlug.test.ts +17 -0
  65. package/src/utils/__tests__/localPartnerHours.test.ts +45 -0
  66. package/src/utils/__tests__/orgPromotionalPackageUtils.test.ts +73 -0
  67. package/src/utils/eventCitySlug.ts +23 -0
  68. package/src/utils/localPartnerHours.ts +98 -0
  69. package/src/utils/orgPromotionalPackageUtils.ts +212 -0
@@ -78,6 +78,7 @@ model BashFeedPost {
78
78
  repostOfPostId String?
79
79
  repostOf BashFeedPost? @relation("BashFeedPostRepost", fields: [repostOfPostId], references: [id], onDelete: Cascade)
80
80
  repostShells BashFeedPost[] @relation("BashFeedPostRepost")
81
+ visibility BashFeedPostVisibility @default(Public)
81
82
 
82
83
  // Relationships
83
84
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -589,6 +590,7 @@ model Invitation {
589
590
  inviteDate DateTime? @default(now())
590
591
  acceptedDate DateTime?
591
592
  rejectedDate DateTime?
593
+ revokedDate DateTime?
592
594
  maybeDate DateTime?
593
595
  isFreeGuest Boolean @default(false)
594
596
  sentCount Int @default(1) // Number of times this invitation email has been sent (1 on initial send; incremented on each host-confirmed re-invite for the same email + bash)
@@ -928,6 +930,7 @@ model BashEvent {
928
930
  bookingOffers ServiceBookingOffer[]
929
931
  promotionBlastEntitlements PromotionBlastEntitlement[]
930
932
  promotionBlasts PromotionBlast[]
933
+ orgPromotionalPackages OrgPromotionalPackage[]
931
934
 
932
935
  @@index([templateId])
933
936
  @@index([parentEventId])
@@ -1421,6 +1424,73 @@ model PromotionBlastPushDelivery {
1421
1424
  @@index([bashEventId])
1422
1425
  }
1423
1426
 
1427
+ // --- Org Promotional Packages (host pays org to endorse event to members) ---
1428
+ enum OrgPromotionalPackageStatus {
1429
+ PENDING_APPROVAL
1430
+ ACTIVE
1431
+ COMPLETED
1432
+ REJECTED
1433
+ EXPIRED
1434
+ REFUNDED
1435
+ }
1436
+
1437
+ model OrgPromotionalPackage {
1438
+ id String @id @default(cuid())
1439
+ createdAt DateTime @default(now())
1440
+ updatedAt DateTime @updatedAt
1441
+
1442
+ organizationId String
1443
+ organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
1444
+ bashEventId String
1445
+ bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
1446
+ hostUserId String
1447
+ hostUser User @relation("OrgPromoHost", fields: [hostUserId], references: [id], onDelete: Cascade)
1448
+
1449
+ tierId String
1450
+ maxReach Int
1451
+ orgFeeCents Int
1452
+ bashPlatformFeeCents Int
1453
+ totalCents Int
1454
+
1455
+ stripePaymentIntentId String @unique
1456
+ authorizedAt DateTime
1457
+ capturedAt DateTime?
1458
+ status OrgPromotionalPackageStatus @default(PENDING_APPROVAL)
1459
+
1460
+ hostNote String?
1461
+ orgResponseNote String?
1462
+ approvedByUserId String?
1463
+ approvedBy User? @relation("OrgPromoApprover", fields: [approvedByUserId], references: [id], onDelete: SetNull)
1464
+ activatedAt DateTime?
1465
+
1466
+ membersNotifiedAt DateTime?
1467
+ notifyError String?
1468
+ notifyAttemptCount Int @default(0)
1469
+
1470
+ deliveries OrgPromotionalDelivery[]
1471
+
1472
+ @@index([organizationId, status])
1473
+ @@index([bashEventId, status])
1474
+ @@index([hostUserId])
1475
+ @@index([status])
1476
+ }
1477
+
1478
+ model OrgPromotionalDelivery {
1479
+ id String @id @default(cuid())
1480
+ createdAt DateTime @default(now())
1481
+
1482
+ packageId String
1483
+ package OrgPromotionalPackage @relation(fields: [packageId], references: [id], onDelete: Cascade)
1484
+ userId String
1485
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
1486
+
1487
+ channel String
1488
+
1489
+ @@unique([packageId, userId, channel])
1490
+ @@index([packageId])
1491
+ @@index([userId])
1492
+ }
1493
+
1424
1494
  /// USPS carrier-route catalog (Utah MVP; expand for national rollout).
1425
1495
  model PostalCarrierRoute {
1426
1496
  id String @id @default(cuid())
@@ -2013,6 +2083,7 @@ model Ticket {
2013
2083
  allowPromiseToPay Boolean?
2014
2084
  status TicketStatus @default(Pending)
2015
2085
  isFreeGuest Boolean?
2086
+ isNamedGuest Boolean @default(false)
2016
2087
  geoFenceCheckInUnnecessary Boolean?
2017
2088
  checkedInAt DateTime?
2018
2089
  checkedOutAt DateTime?
@@ -2791,6 +2862,9 @@ model User {
2791
2862
  sentBookingOffers ServiceBookingOffer[] @relation("OfferSender")
2792
2863
  receivedBookingOffers ServiceBookingOffer[] @relation("OfferRecipient")
2793
2864
  promotionBlastEntitlements PromotionBlastEntitlement[] @relation("PromotionBlastPartner")
2865
+ orgPromotionalPackagesHosted OrgPromotionalPackage[] @relation("OrgPromoHost")
2866
+ orgPromotionalPackagesApproved OrgPromotionalPackage[] @relation("OrgPromoApprover")
2867
+ orgPromotionalDeliveries OrgPromotionalDelivery[]
2794
2868
  promotionBlastsOwned PromotionBlast[] @relation("PromotionBlastOwner")
2795
2869
  promotionBlastsPaused PromotionBlast[] @relation("PromotionBlastPausedBy")
2796
2870
  promotionBlastRedemptions PromotionBlastRedemption[]
@@ -3229,6 +3303,13 @@ model Contact {
3229
3303
  contactEmail String?
3230
3304
  fullName String?
3231
3305
  phone String?
3306
+ /// Mailing address line 1 (street number and name). Maps to Lob `address1`.
3307
+ street String?
3308
+ /// Mailing address line 2 (apt, suite, unit). Maps to Lob `address2`.
3309
+ addressLine2 String?
3310
+ city String?
3311
+ state String?
3312
+ zipCode String?
3232
3313
  requestToConnectSent DateTime?
3233
3314
  requestToConnectAccepted DateTime?
3234
3315
  contactOwner User @relation(fields: [contactOwnerId], references: [id], onDelete: Cascade)
@@ -4172,6 +4253,10 @@ model Organization {
4172
4253
 
4173
4254
  smsSendLogs SmsSendLog[]
4174
4255
 
4256
+ /// Per-tier promotional package rates for external hosts (community / spotlight / featured).
4257
+ promotionalPackageRates Json?
4258
+ orgPromotionalPackages OrgPromotionalPackage[]
4259
+
4175
4260
  @@index([ownerId])
4176
4261
  @@index([slug])
4177
4262
  @@index([parentOrganizationId])
@@ -5889,6 +5974,15 @@ enum AiApprovalVerdict {
5889
5974
  Reject
5890
5975
  }
5891
5976
 
5977
+ /// Feed post audience (future: filter feed queries by visibility).
5978
+ enum BashFeedPostVisibility {
5979
+ Public
5980
+ FollowersOnly
5981
+ ContactsOnly
5982
+ ConnectionsOnly
5983
+ OnlyMe
5984
+ }
5985
+
5892
5986
  /// How a user is associated with a bash (for BashFeedPost display: "Sarah attended...", "Mike is hosting...")
5893
5987
  enum BashAssociationType {
5894
5988
  Hosting
@@ -7561,14 +7655,8 @@ enum MusicGenreType {
7561
7655
 
7562
7656
  enum UserRole {
7563
7657
  User
7564
- Vendor
7565
- Sponsor
7566
- Investor
7567
- LoanProvider
7568
- VenueProvider
7569
- ServiceProvider
7570
- SuperAdmin
7571
- Exhibitor
7658
+ Admin
7659
+ SuperUser
7572
7660
  }
7573
7661
 
7574
7662
  enum UserStatus {
@@ -7732,6 +7820,7 @@ enum CreditSourceType {
7732
7820
  PostAfterEvent // First BashFeed post linked to event after it ends
7733
7821
  ReferralCodeUsed // Flat bonus for referrer when code drives a ticket/signup
7734
7822
  VenueLoyaltyRedemption // BashPoints moved through a VenueLoyaltyRedemption claim/redeem/refund
7823
+ PartnerPromoRedemption // BashPoints spent to unlock a local partner promo code
7735
7824
  }
7736
7825
 
7737
7826
  enum ReferralTier {
@@ -9137,21 +9226,61 @@ enum PartnerTier {
9137
9226
  Featured
9138
9227
  }
9139
9228
 
9229
+ enum PartnerListingType {
9230
+ Gear
9231
+ LocalService
9232
+ }
9233
+
9234
+ enum LocalServiceCategory {
9235
+ GetReady
9236
+ WearAndStyle
9237
+ EatAndDrink
9238
+ Move
9239
+ Recover
9240
+ Wellness
9241
+ HealthAndEssentials
9242
+ HomeAndLife
9243
+ ContentAndKeepsakes
9244
+ HostShortcuts
9245
+ PerksAndRetail
9246
+ Other
9247
+ }
9248
+
9140
9249
  model PartnerBrand {
9141
- id String @id @default(cuid())
9250
+ id String @id @default(cuid())
9142
9251
  name String
9143
9252
  logoUrl String?
9144
9253
  websiteUrl String?
9145
- description String? @db.Text
9146
- tier PartnerTier @default(Standard)
9147
- isActive Boolean @default(true)
9254
+ description String? @db.Text
9255
+ tier PartnerTier @default(Standard)
9256
+ isActive Boolean @default(true)
9148
9257
  affiliateBaseUrl String?
9149
9258
  listingFeeMonthly Int? // cents; null = affiliate-only
9150
- createdAt DateTime @default(now())
9259
+ listingType PartnerListingType @default(Gear)
9260
+ address String? @db.Text
9261
+ phone String?
9262
+ citySlug String?
9263
+ stateSlug String?
9264
+ serviceTimings String[] @default([])
9265
+ serviceCategory LocalServiceCategory?
9266
+ promoHeadline String?
9267
+ promoCode String?
9268
+ bookingUrl String?
9269
+ /// Weekly hours: { hours: { Monday: [{ open, close }] }, openDays: { Monday: true } }
9270
+ businessHours Json?
9271
+ timezone String?
9272
+ latitude Float?
9273
+ longitude Float?
9274
+ bashPointsCost Int?
9275
+ clicks Int @default(0)
9276
+ createdAt DateTime @default(now())
9151
9277
  products PartnerProduct[]
9152
9278
  adSlots PartnerAdSlot[]
9279
+ sourceInquiry PartnerInquiry?
9153
9280
 
9154
9281
  @@index([isActive, tier])
9282
+ @@index([listingType, citySlug])
9283
+ @@index([serviceCategory])
9155
9284
  }
9156
9285
 
9157
9286
  model PartnerProduct {
@@ -9209,12 +9338,17 @@ model PartnerInquiry {
9209
9338
  website String?
9210
9339
  description String @db.Text
9211
9340
  categories String[]
9212
- status PartnerInquiryStatus @default(Pending)
9213
- adminNote String? @db.Text
9214
- createdAt DateTime @default(now())
9215
- updatedAt DateTime @updatedAt
9341
+ listingType PartnerListingType @default(Gear)
9342
+ city String?
9343
+ status PartnerInquiryStatus @default(Pending)
9344
+ adminNote String? @db.Text
9345
+ createdBrandId String? @unique
9346
+ createdBrand PartnerBrand? @relation(fields: [createdBrandId], references: [id], onDelete: SetNull)
9347
+ createdAt DateTime @default(now())
9348
+ updatedAt DateTime @updatedAt
9216
9349
 
9217
9350
  @@index([status, createdAt])
9351
+ @@index([listingType])
9218
9352
  }
9219
9353
 
9220
9354
  // ============================================
@@ -0,0 +1,120 @@
1
+ import { UserRole } from "@prisma/client";
2
+ import {
3
+ parseRequestAuthFromJwtPayload,
4
+ parseUserRolesFromJwtClaim,
5
+ userHasSuperUserAccess,
6
+ userHasAdminAccess,
7
+ } from "../requestAuthUser.js";
8
+
9
+ describe("parseUserRolesFromJwtClaim", () => {
10
+ it("returns undefined when claim is absent", () => {
11
+ expect(parseUserRolesFromJwtClaim(undefined)).toBeUndefined();
12
+ });
13
+
14
+ it("defaults empty array to User", () => {
15
+ expect(parseUserRolesFromJwtClaim([])).toEqual([UserRole.User]);
16
+ });
17
+
18
+ it("accepts valid enum values", () => {
19
+ expect(parseUserRolesFromJwtClaim([UserRole.User, UserRole.SuperUser])).toEqual([
20
+ UserRole.User,
21
+ UserRole.SuperUser,
22
+ ]);
23
+ });
24
+
25
+ it("rejects non-array and unknown role strings", () => {
26
+ expect(parseUserRolesFromJwtClaim("User")).toBeNull();
27
+ expect(parseUserRolesFromJwtClaim(["NotARealRole"])).toBeNull();
28
+ });
29
+ });
30
+
31
+ describe("parseRequestAuthFromJwtPayload", () => {
32
+ it("parses session-style payload with roles", () => {
33
+ expect(
34
+ parseRequestAuthFromJwtPayload({
35
+ id: "u-1",
36
+ email: "a@test.com",
37
+ roles: [UserRole.SuperUser],
38
+ isSuperUser: false,
39
+ givenName: "Ada",
40
+ }),
41
+ ).toEqual({
42
+ id: "u-1",
43
+ email: "a@test.com",
44
+ roles: [UserRole.SuperUser],
45
+ isSuperUser: undefined,
46
+ username: undefined,
47
+ givenName: "Ada",
48
+ familyName: undefined,
49
+ });
50
+ });
51
+
52
+ it("returns null when roles claim is absent", () => {
53
+ expect(
54
+ parseRequestAuthFromJwtPayload({
55
+ id: "u-legacy",
56
+ email: "legacy@test.com",
57
+ }),
58
+ ).toBeNull();
59
+ });
60
+
61
+ it("returns null when identity claims are missing or roles are invalid", () => {
62
+ expect(parseRequestAuthFromJwtPayload(null)).toBeNull();
63
+ expect(parseRequestAuthFromJwtPayload({ email: "a@test.com" })).toBeNull();
64
+ expect(
65
+ parseRequestAuthFromJwtPayload({
66
+ id: "u-1",
67
+ email: "a@test.com",
68
+ roles: ["BadRole"],
69
+ }),
70
+ ).toBeNull();
71
+ });
72
+ });
73
+
74
+ describe("userHasSuperUserAccess", () => {
75
+ it("allows SuperUser role or isSuperUser flag", () => {
76
+ expect(
77
+ userHasSuperUserAccess({
78
+ roles: [UserRole.User, UserRole.SuperUser],
79
+ }),
80
+ ).toBe(true);
81
+ expect(
82
+ userHasSuperUserAccess({
83
+ roles: [UserRole.User],
84
+ isSuperUser: true,
85
+ }),
86
+ ).toBe(true);
87
+ expect(
88
+ userHasSuperUserAccess({
89
+ roles: [UserRole.User],
90
+ isSuperUser: false,
91
+ }),
92
+ ).toBe(false);
93
+ });
94
+ });
95
+
96
+ describe("userHasAdminAccess", () => {
97
+ it("allows Admin role", () => {
98
+ expect(
99
+ userHasAdminAccess({ roles: [UserRole.Admin] }),
100
+ ).toBe(true);
101
+ });
102
+
103
+ it("allows SuperUser role", () => {
104
+ expect(
105
+ userHasAdminAccess({ roles: [UserRole.SuperUser] }),
106
+ ).toBe(true);
107
+ });
108
+
109
+ it("allows isSuperUser flag regardless of role", () => {
110
+ expect(
111
+ userHasAdminAccess({ roles: [UserRole.User], isSuperUser: true }),
112
+ ).toBe(true);
113
+ });
114
+
115
+ it("rejects plain User role", () => {
116
+ expect(
117
+ userHasAdminAccess({ roles: [UserRole.User], isSuperUser: false }),
118
+ ).toBe(false);
119
+ });
120
+ });
@@ -138,6 +138,7 @@ describe("status enum value sets", () => {
138
138
  "PostAfterEvent",
139
139
  "ReferralCodeUsed",
140
140
  "VenueLoyaltyRedemption",
141
+ "PartnerPromoRedemption",
141
142
  ].sort(),
142
143
  );
143
144
  });
@@ -473,6 +473,8 @@ export const HTTP_CODE_FORBIDDEN = 403 as const;
473
473
  export const HTTP_CODE_NOT_FOUND = 404 as const;
474
474
  export const ERR_UNAUTHORIZED_REQUEST =
475
475
  "Unauthorized to perform requested action. Have you logged in?" as const;
476
+ export const ERR_SESSION_EXPIRED =
477
+ "Your session has expired — please refresh or log in again." as const;
476
478
 
477
479
  export const DEFAULT_PRISMA_TTL_SECONDS = 60 as const;
478
480
  export const PRISMA_MEDIA_TTL_SECONDS = 60 * 5; // 5 hours
@@ -1217,6 +1217,9 @@ export interface InvitationExtraData extends Invitation {
1217
1217
  organizerTitle?: string;
1218
1218
  isOwnershipTransfer?: boolean;
1219
1219
  includeQRCode?: boolean; // Controls whether QR code should be included in the invitation email
1220
+ sendEmail?: boolean; // When false, skip email notification (default true)
1221
+ existingTicketId?: string; // Link invitation to an existing ticket instead of creating a new one
1222
+ revoke?: boolean; // Host action: set revokedDate
1220
1223
  }
1221
1224
 
1222
1225
  export interface AssociatedBashExt extends AssociatedBash {
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./definitions.js";
2
+ export * from "./requestAuthUser.js";
2
3
  export * from "./sms/smsTemplates.js";
3
4
  export * from "./legalTemplates.js";
4
5
  export * from "./utmAttribution.js";
@@ -153,6 +154,8 @@ export {
153
154
  PrizePaymentMethod,
154
155
  PrizePaymentStatus,
155
156
  PartnerTier,
157
+ PartnerListingType,
158
+ LocalServiceCategory,
156
159
  PaymentProvider,
157
160
  PrizeType,
158
161
  ProjectorSetupFormat,
@@ -276,6 +279,8 @@ export type {
276
279
  SuggestionUpvote,
277
280
  } from "@prisma/client";
278
281
 
282
+ export * from "./utils/localPartnerHours.js";
283
+ export * from "./utils/eventCitySlug.js";
279
284
  export * from "./utils/addressUtils.js";
280
285
  export * from "./utils/apiUtils.js";
281
286
  export * from "./utils/arrayUtils.js";
@@ -335,6 +340,7 @@ export * from "./utils/blogUtils.js";
335
340
  export * from "./utils/entityUtils.js";
336
341
  export * from "./utils/flyerUtils.js";
337
342
  export * from "./utils/promotionBlastUtils.js";
343
+ export * from "./utils/orgPromotionalPackageUtils.js";
338
344
  export * from "./utils/guestSurface.js";
339
345
  export * from "./utils/ticketTierSchedule.js";
340
346
  export * from "./utils/lobFlyerUtils.js";
@@ -176,6 +176,7 @@ export const CreditSourceType = {
176
176
  PostAfterEvent: "PostAfterEvent",
177
177
  ReferralCodeUsed: "ReferralCodeUsed",
178
178
  VenueLoyaltyRedemption: "VenueLoyaltyRedemption",
179
+ PartnerPromoRedemption: "PartnerPromoRedemption",
179
180
  } as const satisfies Record<CreditSourceTypeEnum, CreditSourceTypeEnum>;
180
181
 
181
182
  export type CreditSourceType = CreditSourceTypeEnum;
@@ -1,4 +1,8 @@
1
- import type { PartnerTier } from "@prisma/client";
1
+ import type {
2
+ LocalServiceCategory,
3
+ PartnerListingType,
4
+ PartnerTier,
5
+ } from "@prisma/client";
2
6
 
3
7
  /** Product row returned by BashStore / wizard suggestion APIs (no internal counters). */
4
8
  export type PartnerProductPublic = {
@@ -28,9 +32,136 @@ export type PartnerStoreResponse = {
28
32
  brands: (PartnerBrandSummary & { products: PartnerProductPublic[] })[];
29
33
  };
30
34
 
31
- /** GET /api/partner/suggestions at most one suggestion */
32
- export type PartnerSuggestionResponse = {
35
+ export type LocalPartnerOfferPublic = {
36
+ id: string;
37
+ name: string;
38
+ description: string | null;
39
+ imageUrl: string | null;
40
+ affiliateUrl: string;
41
+ priceSummary: string | null;
42
+ };
43
+
44
+ export type LocalPartnerPublic = {
45
+ id: string;
46
+ name: string;
47
+ logoUrl: string | null;
48
+ websiteUrl: string | null;
49
+ description: string | null;
50
+ tier: PartnerTier;
51
+ address: string | null;
52
+ phone: string | null;
53
+ citySlug: string | null;
54
+ stateSlug: string | null;
55
+ serviceTimings: string[];
56
+ serviceCategory: LocalServiceCategory | null;
57
+ promoHeadline: string | null;
58
+ promoCode: string | null;
59
+ bookingUrl: string | null;
60
+ businessHours: unknown;
61
+ timezone: string | null;
62
+ latitude: number | null;
63
+ longitude: number | null;
64
+ bashPointsCost: number | null;
65
+ distanceMiles: number | null;
66
+ offers: LocalPartnerOfferPublic[];
67
+ };
68
+
69
+ /** Gear wizard ad slot suggestion */
70
+ export type PartnerGearSuggestion = {
71
+ kind: "gear";
33
72
  slotId: string;
34
73
  product: PartnerProductPublic;
35
74
  brand: PartnerBrandSummary;
36
75
  };
76
+
77
+ /** Local service partner surfaced in wizard or feed contexts */
78
+ export type PartnerLocalSuggestion = {
79
+ kind: "local";
80
+ partner: LocalPartnerPublic;
81
+ };
82
+
83
+ /** GET /api/partner/suggestions — at most one suggestion */
84
+ export type PartnerSuggestionResponse = PartnerGearSuggestion | PartnerLocalSuggestion;
85
+
86
+ export type ClaimPartnerPromoResponse = {
87
+ promoCode: string;
88
+ bashPointsCost: number;
89
+ balanceAfter: number;
90
+ };
91
+
92
+ /** GET /api/partner/local-services */
93
+ export type LocalServiceListingResponse = {
94
+ featured: LocalPartnerPublic | null;
95
+ partners: LocalPartnerPublic[];
96
+ };
97
+
98
+ /** Display labels for LocalServiceCategory enum values. */
99
+ export const LOCAL_SERVICE_CATEGORY_LABELS: Record<LocalServiceCategory, string> = {
100
+ GetReady: "Get Ready",
101
+ WearAndStyle: "Wear & Style",
102
+ EatAndDrink: "Eat & Drink",
103
+ Move: "Move",
104
+ Recover: "Recover",
105
+ Wellness: "Wellness",
106
+ HealthAndEssentials: "Health & Essentials",
107
+ HomeAndLife: "Home & Life",
108
+ ContentAndKeepsakes: "Content & Keepsakes",
109
+ HostShortcuts: "Host Shortcuts",
110
+ PerksAndRetail: "Perks & Retail",
111
+ Other: "Other",
112
+ };
113
+
114
+ /** Ordered list for filter chips in BashStore Local Partners tab. */
115
+ export const LOCAL_SERVICE_CATEGORY_ORDER: LocalServiceCategory[] = [
116
+ "GetReady",
117
+ "WearAndStyle",
118
+ "EatAndDrink",
119
+ "Move",
120
+ "Recover",
121
+ "Wellness",
122
+ "HealthAndEssentials",
123
+ "HomeAndLife",
124
+ "ContentAndKeepsakes",
125
+ "HostShortcuts",
126
+ "PerksAndRetail",
127
+ "Other",
128
+ ];
129
+
130
+ export const LOCAL_SERVICE_TIMING_OPTIONS = [
131
+ { value: "pre_event", label: "Pre-event" },
132
+ { value: "day_of", label: "Day-of" },
133
+ { value: "post_event", label: "Post-event" },
134
+ ] as const;
135
+
136
+ export type LocalServiceTiming = (typeof LOCAL_SERVICE_TIMING_OPTIONS)[number]["value"];
137
+
138
+ export const PARTNER_LISTING_TYPE_LABELS: Record<PartnerListingType, string> = {
139
+ Gear: "Gear",
140
+ LocalService: "Local Service",
141
+ };
142
+
143
+ /** Admin / inquiry payloads for BashStore partner listing requests. */
144
+ export type PartnerInquiryRecord = {
145
+ id: string;
146
+ contactName: string;
147
+ email: string;
148
+ brandName: string;
149
+ website: string | null;
150
+ description: string;
151
+ categories: string[];
152
+ listingType: PartnerListingType;
153
+ city: string | null;
154
+ status: "Pending" | "Reviewed" | "Approved" | "Rejected";
155
+ adminNote: string | null;
156
+ createdBrandId: string | null;
157
+ createdAt: string;
158
+ };
159
+
160
+ /** GET /api/partner/inquiry-status */
161
+ export type PartnerInquiryStatusResponse = {
162
+ brandName: string;
163
+ status: string;
164
+ listingLive: boolean;
165
+ storeUrl: string | null;
166
+ createdBrandId: string | null;
167
+ };