@bash-app/bash-common 30.265.0 → 30.273.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.
- package/dist/__tests__/statusEnums.test.js +1 -0
- package/dist/__tests__/statusEnums.test.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/mirroredPrismaEnums.d.ts +1 -0
- package/dist/mirroredPrismaEnums.d.ts.map +1 -1
- package/dist/mirroredPrismaEnums.js +1 -0
- package/dist/mirroredPrismaEnums.js.map +1 -1
- package/dist/partnerStoreTypes.d.ts +93 -3
- package/dist/partnerStoreTypes.d.ts.map +1 -1
- package/dist/partnerStoreTypes.js +39 -1
- package/dist/partnerStoreTypes.js.map +1 -1
- package/dist/requestAuthUser.d.ts.map +1 -1
- package/dist/requestAuthUser.js +5 -3
- package/dist/requestAuthUser.js.map +1 -1
- package/dist/utils/__tests__/eventCitySlug.test.d.ts +2 -0
- package/dist/utils/__tests__/eventCitySlug.test.d.ts.map +1 -0
- package/dist/utils/__tests__/eventCitySlug.test.js +12 -0
- package/dist/utils/__tests__/eventCitySlug.test.js.map +1 -0
- package/dist/utils/__tests__/localPartnerHours.test.d.ts +2 -0
- package/dist/utils/__tests__/localPartnerHours.test.d.ts.map +1 -0
- package/dist/utils/__tests__/localPartnerHours.test.js +35 -0
- package/dist/utils/__tests__/localPartnerHours.test.js.map +1 -0
- package/dist/utils/__tests__/orgPromotionalPackageUtils.test.d.ts +2 -0
- package/dist/utils/__tests__/orgPromotionalPackageUtils.test.d.ts.map +1 -0
- package/dist/utils/__tests__/orgPromotionalPackageUtils.test.js +88 -0
- package/dist/utils/__tests__/orgPromotionalPackageUtils.test.js.map +1 -0
- package/dist/utils/eventCitySlug.d.ts +11 -0
- package/dist/utils/eventCitySlug.d.ts.map +1 -0
- package/dist/utils/eventCitySlug.js +20 -0
- package/dist/utils/eventCitySlug.js.map +1 -0
- package/dist/utils/localPartnerHours.d.ts +12 -0
- package/dist/utils/localPartnerHours.d.ts.map +1 -0
- package/dist/utils/localPartnerHours.js +92 -0
- package/dist/utils/localPartnerHours.js.map +1 -0
- package/dist/utils/orgPromotionalPackageUtils.d.ts +100 -0
- package/dist/utils/orgPromotionalPackageUtils.d.ts.map +1 -0
- package/dist/utils/orgPromotionalPackageUtils.js +201 -0
- package/dist/utils/orgPromotionalPackageUtils.js.map +1 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +137 -9
- package/src/__tests__/statusEnums.test.ts +1 -0
- package/src/index.ts +5 -0
- package/src/mirroredPrismaEnums.ts +1 -0
- package/src/partnerStoreTypes.ts +134 -3
- package/src/requestAuthUser.ts +5 -3
- package/src/utils/__tests__/eventCitySlug.test.ts +17 -0
- package/src/utils/__tests__/localPartnerHours.test.ts +45 -0
- package/src/utils/__tests__/orgPromotionalPackageUtils.test.ts +115 -0
- package/src/utils/eventCitySlug.ts +23 -0
- package/src/utils/localPartnerHours.ts +98 -0
- package/src/utils/orgPromotionalPackageUtils.ts +278 -0
package/prisma/schema.prisma
CHANGED
|
@@ -930,6 +930,7 @@ model BashEvent {
|
|
|
930
930
|
bookingOffers ServiceBookingOffer[]
|
|
931
931
|
promotionBlastEntitlements PromotionBlastEntitlement[]
|
|
932
932
|
promotionBlasts PromotionBlast[]
|
|
933
|
+
orgPromotionalPackages OrgPromotionalPackage[]
|
|
933
934
|
|
|
934
935
|
@@index([templateId])
|
|
935
936
|
@@index([parentEventId])
|
|
@@ -1423,6 +1424,73 @@ model PromotionBlastPushDelivery {
|
|
|
1423
1424
|
@@index([bashEventId])
|
|
1424
1425
|
}
|
|
1425
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
|
+
|
|
1426
1494
|
/// USPS carrier-route catalog (Utah MVP; expand for national rollout).
|
|
1427
1495
|
model PostalCarrierRoute {
|
|
1428
1496
|
id String @id @default(cuid())
|
|
@@ -2794,6 +2862,9 @@ model User {
|
|
|
2794
2862
|
sentBookingOffers ServiceBookingOffer[] @relation("OfferSender")
|
|
2795
2863
|
receivedBookingOffers ServiceBookingOffer[] @relation("OfferRecipient")
|
|
2796
2864
|
promotionBlastEntitlements PromotionBlastEntitlement[] @relation("PromotionBlastPartner")
|
|
2865
|
+
orgPromotionalPackagesHosted OrgPromotionalPackage[] @relation("OrgPromoHost")
|
|
2866
|
+
orgPromotionalPackagesApproved OrgPromotionalPackage[] @relation("OrgPromoApprover")
|
|
2867
|
+
orgPromotionalDeliveries OrgPromotionalDelivery[]
|
|
2797
2868
|
promotionBlastsOwned PromotionBlast[] @relation("PromotionBlastOwner")
|
|
2798
2869
|
promotionBlastsPaused PromotionBlast[] @relation("PromotionBlastPausedBy")
|
|
2799
2870
|
promotionBlastRedemptions PromotionBlastRedemption[]
|
|
@@ -3232,6 +3303,13 @@ model Contact {
|
|
|
3232
3303
|
contactEmail String?
|
|
3233
3304
|
fullName String?
|
|
3234
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?
|
|
3235
3313
|
requestToConnectSent DateTime?
|
|
3236
3314
|
requestToConnectAccepted DateTime?
|
|
3237
3315
|
contactOwner User @relation(fields: [contactOwnerId], references: [id], onDelete: Cascade)
|
|
@@ -4175,6 +4253,10 @@ model Organization {
|
|
|
4175
4253
|
|
|
4176
4254
|
smsSendLogs SmsSendLog[]
|
|
4177
4255
|
|
|
4256
|
+
/// Per-tier promotional package rates for external hosts (community / spotlight / featured).
|
|
4257
|
+
promotionalPackageRates Json?
|
|
4258
|
+
orgPromotionalPackages OrgPromotionalPackage[]
|
|
4259
|
+
|
|
4178
4260
|
@@index([ownerId])
|
|
4179
4261
|
@@index([slug])
|
|
4180
4262
|
@@index([parentOrganizationId])
|
|
@@ -7738,6 +7820,7 @@ enum CreditSourceType {
|
|
|
7738
7820
|
PostAfterEvent // First BashFeed post linked to event after it ends
|
|
7739
7821
|
ReferralCodeUsed // Flat bonus for referrer when code drives a ticket/signup
|
|
7740
7822
|
VenueLoyaltyRedemption // BashPoints moved through a VenueLoyaltyRedemption claim/redeem/refund
|
|
7823
|
+
PartnerPromoRedemption // BashPoints spent to unlock a local partner promo code
|
|
7741
7824
|
}
|
|
7742
7825
|
|
|
7743
7826
|
enum ReferralTier {
|
|
@@ -9143,21 +9226,61 @@ enum PartnerTier {
|
|
|
9143
9226
|
Featured
|
|
9144
9227
|
}
|
|
9145
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
|
+
|
|
9146
9249
|
model PartnerBrand {
|
|
9147
|
-
id String
|
|
9250
|
+
id String @id @default(cuid())
|
|
9148
9251
|
name String
|
|
9149
9252
|
logoUrl String?
|
|
9150
9253
|
websiteUrl String?
|
|
9151
|
-
description String?
|
|
9152
|
-
tier PartnerTier
|
|
9153
|
-
isActive Boolean
|
|
9254
|
+
description String? @db.Text
|
|
9255
|
+
tier PartnerTier @default(Standard)
|
|
9256
|
+
isActive Boolean @default(true)
|
|
9154
9257
|
affiliateBaseUrl String?
|
|
9155
9258
|
listingFeeMonthly Int? // cents; null = affiliate-only
|
|
9156
|
-
|
|
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())
|
|
9157
9277
|
products PartnerProduct[]
|
|
9158
9278
|
adSlots PartnerAdSlot[]
|
|
9279
|
+
sourceInquiry PartnerInquiry?
|
|
9159
9280
|
|
|
9160
9281
|
@@index([isActive, tier])
|
|
9282
|
+
@@index([listingType, citySlug])
|
|
9283
|
+
@@index([serviceCategory])
|
|
9161
9284
|
}
|
|
9162
9285
|
|
|
9163
9286
|
model PartnerProduct {
|
|
@@ -9215,12 +9338,17 @@ model PartnerInquiry {
|
|
|
9215
9338
|
website String?
|
|
9216
9339
|
description String @db.Text
|
|
9217
9340
|
categories String[]
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
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
|
|
9222
9349
|
|
|
9223
9350
|
@@index([status, createdAt])
|
|
9351
|
+
@@index([listingType])
|
|
9224
9352
|
}
|
|
9225
9353
|
|
|
9226
9354
|
// ============================================
|
package/src/index.ts
CHANGED
|
@@ -154,6 +154,8 @@ export {
|
|
|
154
154
|
PrizePaymentMethod,
|
|
155
155
|
PrizePaymentStatus,
|
|
156
156
|
PartnerTier,
|
|
157
|
+
PartnerListingType,
|
|
158
|
+
LocalServiceCategory,
|
|
157
159
|
PaymentProvider,
|
|
158
160
|
PrizeType,
|
|
159
161
|
ProjectorSetupFormat,
|
|
@@ -277,6 +279,8 @@ export type {
|
|
|
277
279
|
SuggestionUpvote,
|
|
278
280
|
} from "@prisma/client";
|
|
279
281
|
|
|
282
|
+
export * from "./utils/localPartnerHours.js";
|
|
283
|
+
export * from "./utils/eventCitySlug.js";
|
|
280
284
|
export * from "./utils/addressUtils.js";
|
|
281
285
|
export * from "./utils/apiUtils.js";
|
|
282
286
|
export * from "./utils/arrayUtils.js";
|
|
@@ -336,6 +340,7 @@ export * from "./utils/blogUtils.js";
|
|
|
336
340
|
export * from "./utils/entityUtils.js";
|
|
337
341
|
export * from "./utils/flyerUtils.js";
|
|
338
342
|
export * from "./utils/promotionBlastUtils.js";
|
|
343
|
+
export * from "./utils/orgPromotionalPackageUtils.js";
|
|
339
344
|
export * from "./utils/guestSurface.js";
|
|
340
345
|
export * from "./utils/ticketTierSchedule.js";
|
|
341
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;
|
package/src/partnerStoreTypes.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
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
|
-
|
|
32
|
-
|
|
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
|
+
};
|
package/src/requestAuthUser.ts
CHANGED
|
@@ -90,16 +90,18 @@ export function parseRequestAuthFromJwtPayload(decoded: unknown): RequestAuthUse
|
|
|
90
90
|
export function userHasSuperUserAccess(
|
|
91
91
|
auth: Pick<RequestAuthUser, "roles" | "isSuperUser">,
|
|
92
92
|
): boolean {
|
|
93
|
-
|
|
93
|
+
const roles = auth.roles ?? [];
|
|
94
|
+
return auth.isSuperUser === true || roles.includes(UserRole.SuperUser);
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
/** Admin or SuperUser role, or legacy `isSuperUser` flag. */
|
|
97
98
|
export function userHasAdminAccess(
|
|
98
99
|
auth: Pick<RequestAuthUser, "roles" | "isSuperUser">,
|
|
99
100
|
): boolean {
|
|
101
|
+
const roles = auth.roles ?? [];
|
|
100
102
|
return (
|
|
101
103
|
auth.isSuperUser === true ||
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
roles.includes(UserRole.SuperUser) ||
|
|
105
|
+
roles.includes(UserRole.Admin)
|
|
104
106
|
);
|
|
105
107
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { resolveEventCitySlug, resolveEventStateSlug } from "../eventCitySlug.js";
|
|
2
|
+
|
|
3
|
+
describe("eventCitySlug", () => {
|
|
4
|
+
it("resolveEventCitySlug slugifies city names", () => {
|
|
5
|
+
expect(resolveEventCitySlug({ city: "New York City", state: "NY" })).toBe(
|
|
6
|
+
"new-york-city"
|
|
7
|
+
);
|
|
8
|
+
expect(resolveEventCitySlug({ city: " ", state: "NY" })).toBeNull();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("resolveEventStateSlug normalizes two-letter states", () => {
|
|
12
|
+
expect(resolveEventStateSlug({ city: "Brooklyn", state: "NY" })).toBe("ny");
|
|
13
|
+
expect(resolveEventStateSlug({ city: "LA", state: "California" })).toBe(
|
|
14
|
+
"california"
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isLocalPartnerOpenNow,
|
|
3
|
+
type LocalPartnerHoursConfig,
|
|
4
|
+
} from "../localPartnerHours.js";
|
|
5
|
+
|
|
6
|
+
describe("isLocalPartnerOpenNow", () => {
|
|
7
|
+
const sampleHours: LocalPartnerHoursConfig = {
|
|
8
|
+
openDays: {
|
|
9
|
+
Monday: true,
|
|
10
|
+
Tuesday: true,
|
|
11
|
+
Wednesday: true,
|
|
12
|
+
Thursday: true,
|
|
13
|
+
Friday: true,
|
|
14
|
+
Saturday: false,
|
|
15
|
+
Sunday: false,
|
|
16
|
+
},
|
|
17
|
+
hours: {
|
|
18
|
+
Monday: [{ open: "09:00", close: "17:00" }],
|
|
19
|
+
Tuesday: [{ open: "09:00", close: "17:00" }],
|
|
20
|
+
Wednesday: [{ open: "09:00", close: "17:00" }],
|
|
21
|
+
Thursday: [{ open: "09:00", close: "17:00" }],
|
|
22
|
+
Friday: [{ open: "09:00", close: "17:00" }],
|
|
23
|
+
Saturday: [{ open: "", close: "" }],
|
|
24
|
+
Sunday: [{ open: "", close: "" }],
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
it("returns null when hours are missing", () => {
|
|
29
|
+
expect(isLocalPartnerOpenNow(null, "America/New_York")).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("returns true during configured weekday hours", () => {
|
|
33
|
+
const mondayMorning = new Date("2026-05-25T15:00:00.000Z");
|
|
34
|
+
expect(
|
|
35
|
+
isLocalPartnerOpenNow(sampleHours, "America/New_York", mondayMorning)
|
|
36
|
+
).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("returns false outside configured hours", () => {
|
|
40
|
+
const mondayNight = new Date("2026-05-26T03:00:00.000Z");
|
|
41
|
+
expect(
|
|
42
|
+
isLocalPartnerOpenNow(sampleHours, "America/New_York", mondayNight)
|
|
43
|
+
).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildOrgPromoPackageTerms,
|
|
3
|
+
chunkArray,
|
|
4
|
+
isBashEventEligibleForOrgPromo,
|
|
5
|
+
orgPromoPackageTermsFromPaymentIntent,
|
|
6
|
+
orgPromoPackageTotalCents,
|
|
7
|
+
ORG_PROMO_PI_METADATA_TYPE,
|
|
8
|
+
parseOrgPromotionalPackageRates,
|
|
9
|
+
selectMembersForOrgPromoReach,
|
|
10
|
+
} from "../orgPromotionalPackageUtils.js";
|
|
11
|
+
|
|
12
|
+
describe("orgPromotionalPackageUtils", () => {
|
|
13
|
+
it("parses valid rates", () => {
|
|
14
|
+
const rates = parseOrgPromotionalPackageRates({
|
|
15
|
+
community: { enabled: true, orgFeeCents: 5000 },
|
|
16
|
+
});
|
|
17
|
+
expect(rates?.community).toEqual({ enabled: true, orgFeeCents: 5000 });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("rejects negative org fee", () => {
|
|
21
|
+
expect(
|
|
22
|
+
parseOrgPromotionalPackageRates({
|
|
23
|
+
community: { enabled: true, orgFeeCents: -1 },
|
|
24
|
+
})
|
|
25
|
+
).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("builds package terms and total", () => {
|
|
29
|
+
const rates = { community: { enabled: true, orgFeeCents: 1000 } };
|
|
30
|
+
const terms = buildOrgPromoPackageTerms("community", rates);
|
|
31
|
+
expect(terms).not.toBeNull();
|
|
32
|
+
expect(orgPromoPackageTotalCents(terms!)).toBe(1000 + 300);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("selectMembersForOrgPromoReach caps by joinedAt order", () => {
|
|
36
|
+
const members = [
|
|
37
|
+
{ id: "b", joinedAt: new Date("2024-02-01") },
|
|
38
|
+
{ id: "a", joinedAt: new Date("2024-01-01") },
|
|
39
|
+
{ id: "c", joinedAt: new Date("2024-03-01") },
|
|
40
|
+
];
|
|
41
|
+
const selected = selectMembersForOrgPromoReach(members, 2);
|
|
42
|
+
expect(selected.map((m) => m.id)).toEqual(["a", "b"]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("chunks arrays for FCM", () => {
|
|
46
|
+
expect(chunkArray([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("rejects unpublished and org-only events", () => {
|
|
50
|
+
expect(isBashEventEligibleForOrgPromo({ status: "Draft" }).eligible).toBe(false);
|
|
51
|
+
const orgOnly = isBashEventEligibleForOrgPromo({
|
|
52
|
+
status: "Published",
|
|
53
|
+
isApproved: true,
|
|
54
|
+
startDateTime: new Date(),
|
|
55
|
+
requiresOrgMembership: true,
|
|
56
|
+
ticketTierCount: 1,
|
|
57
|
+
});
|
|
58
|
+
expect(orgOnly.eligible).toBe(false);
|
|
59
|
+
if (!orgOnly.eligible) {
|
|
60
|
+
expect(orgOnly.reason).toBe("event_org_only");
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("builds terms from payment intent metadata", () => {
|
|
65
|
+
const result = orgPromoPackageTermsFromPaymentIntent(
|
|
66
|
+
{
|
|
67
|
+
amount: 5300,
|
|
68
|
+
metadata: {
|
|
69
|
+
type: ORG_PROMO_PI_METADATA_TYPE,
|
|
70
|
+
tierId: "community",
|
|
71
|
+
orgFeeCents: "5000",
|
|
72
|
+
bashPlatformFeeCents: "300",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
"community"
|
|
76
|
+
);
|
|
77
|
+
expect(result.ok).toBe(true);
|
|
78
|
+
if (result.ok) {
|
|
79
|
+
expect(result.totalCents).toBe(5300);
|
|
80
|
+
expect(result.terms.orgFeeCents).toBe(5000);
|
|
81
|
+
expect(result.terms.bashPlatformFeeCents).toBe(300);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("rejects payment intent amount mismatch", () => {
|
|
86
|
+
const result = orgPromoPackageTermsFromPaymentIntent(
|
|
87
|
+
{
|
|
88
|
+
amount: 9999,
|
|
89
|
+
metadata: {
|
|
90
|
+
type: ORG_PROMO_PI_METADATA_TYPE,
|
|
91
|
+
tierId: "community",
|
|
92
|
+
orgFeeCents: "5000",
|
|
93
|
+
bashPlatformFeeCents: "300",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
"community"
|
|
97
|
+
);
|
|
98
|
+
expect(result.ok).toBe(false);
|
|
99
|
+
if (!result.ok) {
|
|
100
|
+
expect(result.error).toBe("payment_amount_mismatch");
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("accepts published ticketed events", () => {
|
|
105
|
+
expect(
|
|
106
|
+
isBashEventEligibleForOrgPromo({
|
|
107
|
+
status: "Published",
|
|
108
|
+
isApproved: true,
|
|
109
|
+
startDateTime: new Date(Date.now() + 86400000),
|
|
110
|
+
endDateTime: new Date(Date.now() + 172800000),
|
|
111
|
+
ticketTierCount: 2,
|
|
112
|
+
}).eligible
|
|
113
|
+
).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { generateSlug } from "./slugUtils.js";
|
|
2
|
+
|
|
3
|
+
export type EventCityContext = {
|
|
4
|
+
city?: string | null;
|
|
5
|
+
state?: string | null;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Derive a partner-store city slug from bash event location text.
|
|
10
|
+
* Matches admin `citySlug` conventions (kebab-case).
|
|
11
|
+
*/
|
|
12
|
+
export function resolveEventCitySlug(context: EventCityContext): string | null {
|
|
13
|
+
const city = context.city?.trim();
|
|
14
|
+
if (!city) return null;
|
|
15
|
+
return generateSlug(city);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveEventStateSlug(context: EventCityContext): string | null {
|
|
19
|
+
const state = context.state?.trim();
|
|
20
|
+
if (!state) return null;
|
|
21
|
+
if (state.length === 2) return state.toLowerCase();
|
|
22
|
+
return generateSlug(state);
|
|
23
|
+
}
|