@bash-app/bash-common 30.281.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.
- package/dist/__tests__/storePrintArtworkUtils.test.d.ts +2 -0
- package/dist/__tests__/storePrintArtworkUtils.test.d.ts.map +1 -0
- package/dist/__tests__/storePrintArtworkUtils.test.js +35 -0
- package/dist/__tests__/storePrintArtworkUtils.test.js.map +1 -0
- package/dist/definitions.d.ts +3 -2
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +3 -2
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +15 -5
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js.map +1 -1
- package/dist/icebreakerPrompts.d.ts +9 -0
- package/dist/icebreakerPrompts.d.ts.map +1 -0
- package/dist/icebreakerPrompts.js +77 -0
- package/dist/icebreakerPrompts.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mirroredPrismaEnums.d.ts +14 -0
- package/dist/mirroredPrismaEnums.d.ts.map +1 -1
- package/dist/mirroredPrismaEnums.js +13 -0
- package/dist/mirroredPrismaEnums.js.map +1 -1
- package/dist/storePrintArtworkUtils.d.ts +22 -0
- package/dist/storePrintArtworkUtils.d.ts.map +1 -0
- package/dist/storePrintArtworkUtils.js +74 -0
- package/dist/storePrintArtworkUtils.js.map +1 -0
- package/dist/storeTypes.d.ts +81 -0
- package/dist/storeTypes.d.ts.map +1 -0
- package/dist/storeTypes.js +20 -0
- package/dist/storeTypes.js.map +1 -0
- package/dist/venueMatch.d.ts +165 -0
- package/dist/venueMatch.d.ts.map +1 -0
- package/dist/venueMatch.js +68 -0
- package/dist/venueMatch.js.map +1 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +382 -19
- package/src/__tests__/storePrintArtworkUtils.test.ts +53 -0
- package/src/definitions.ts +2 -2
- package/src/extendedSchemas.ts +25 -11
- package/src/icebreakerPrompts.ts +75 -0
- package/src/index.ts +4 -0
- package/src/mirroredPrismaEnums.ts +16 -0
- package/src/storePrintArtworkUtils.ts +93 -0
- package/src/storeTypes.ts +109 -0
- package/src/venueMatch.ts +202 -0
|
@@ -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,15 +7,19 @@ 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";
|
|
13
14
|
export * from "./partnerStoreTypes.js";
|
|
15
|
+
export * from "./storeTypes.js";
|
|
16
|
+
export * from "./storePrintArtworkUtils.js";
|
|
14
17
|
export * from "./bashFeedTypes.js";
|
|
15
18
|
export * from "./membershipDefinitions.js";
|
|
16
19
|
export * from "./onSaleCapabilityRecommendations.js";
|
|
17
20
|
export * from "./ticketBnplPaymentMethods.js";
|
|
18
21
|
export * from "./aiApproval.js";
|
|
22
|
+
export * from "./venueMatch.js";
|
|
19
23
|
export * from "./userReportTypes.js";
|
|
20
24
|
export * from "./mirroredPrismaEnums.js";
|
|
21
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];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { StorePrintArtwork } from "@prisma/client";
|
|
2
|
+
|
|
3
|
+
import { STORE_PRINT_ARTWORK_PATHS } from "./storeTypes.js";
|
|
4
|
+
|
|
5
|
+
/** Royal-blue wordmark → white knock-out for dark garments. */
|
|
6
|
+
export const STORE_PRINT_ARTWORK_WHITE_KNOCKOUT: Partial<
|
|
7
|
+
Record<StorePrintArtwork, StorePrintArtwork>
|
|
8
|
+
> = {
|
|
9
|
+
BashLogoBig: "BashLogoBigWhite",
|
|
10
|
+
BashLogoLetters: "BashLogoLettersWhite",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Base (royal blue) artwork for each enum value, including white variants. */
|
|
14
|
+
export const STORE_PRINT_ARTWORK_BASE: Partial<
|
|
15
|
+
Record<StorePrintArtwork, StorePrintArtwork>
|
|
16
|
+
> = {
|
|
17
|
+
BashLogoBig: "BashLogoBig",
|
|
18
|
+
BashLogoLetters: "BashLogoLetters",
|
|
19
|
+
BashLogoBigWhite: "BashLogoBig",
|
|
20
|
+
BashLogoLettersWhite: "BashLogoLetters",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const DARK_GARMENT_COLOR_TOKENS = new Set([
|
|
24
|
+
"black",
|
|
25
|
+
"charcoal",
|
|
26
|
+
"navy",
|
|
27
|
+
"dark",
|
|
28
|
+
"midnight",
|
|
29
|
+
"graphite",
|
|
30
|
+
"heather-black",
|
|
31
|
+
"dark-heather",
|
|
32
|
+
"dark-heather-grey",
|
|
33
|
+
"dark-heather-gray",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* True when a garment color name/token should use white knock-out print artwork.
|
|
38
|
+
* Accepts labels like "Black / M" or hex codes (#000, #1a1a2e).
|
|
39
|
+
*/
|
|
40
|
+
export function isDarkGarmentColor(garmentColor: string | null | undefined): boolean {
|
|
41
|
+
const raw = garmentColor?.trim();
|
|
42
|
+
if (!raw) return false;
|
|
43
|
+
|
|
44
|
+
const hex = raw.match(/#([0-9a-f]{3}|[0-9a-f]{6})/i)?.[0];
|
|
45
|
+
if (hex) {
|
|
46
|
+
return relativeLuminanceFromHex(hex) < 0.35;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const normalized = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
50
|
+
for (const token of DARK_GARMENT_COLOR_TOKENS) {
|
|
51
|
+
if (normalized.includes(token)) return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function relativeLuminanceFromHex(hex: string): number {
|
|
57
|
+
const h = hex.replace("#", "");
|
|
58
|
+
const full =
|
|
59
|
+
h.length === 3 ? h.split("").map((c) => c + c).join("") : h.slice(0, 6);
|
|
60
|
+
const r = parseInt(full.slice(0, 2), 16) / 255;
|
|
61
|
+
const g = parseInt(full.slice(2, 4), 16) / 255;
|
|
62
|
+
const b = parseInt(full.slice(4, 6), 16) / 255;
|
|
63
|
+
const lin = (c: number) =>
|
|
64
|
+
c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
65
|
+
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Pick print artwork for a product SKU, optionally routing to white knock-out
|
|
70
|
+
* when the selected variant/garment color is dark.
|
|
71
|
+
*/
|
|
72
|
+
export function resolveStorePrintArtwork(
|
|
73
|
+
printArtwork: StorePrintArtwork | null | undefined,
|
|
74
|
+
options?: { garmentColor?: string | null }
|
|
75
|
+
): StorePrintArtwork | null {
|
|
76
|
+
if (!printArtwork) return null;
|
|
77
|
+
|
|
78
|
+
const base = STORE_PRINT_ARTWORK_BASE[printArtwork] ?? printArtwork;
|
|
79
|
+
if (!options?.garmentColor || !isDarkGarmentColor(options.garmentColor)) {
|
|
80
|
+
return base;
|
|
81
|
+
}
|
|
82
|
+
return STORE_PRINT_ARTWORK_WHITE_KNOCKOUT[base] ?? printArtwork;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Public path for resolved artwork (bash-app/public). */
|
|
86
|
+
export function resolveStorePrintArtworkPath(
|
|
87
|
+
printArtwork: StorePrintArtwork | null | undefined,
|
|
88
|
+
options?: { garmentColor?: string | null }
|
|
89
|
+
): string | null {
|
|
90
|
+
const resolved = resolveStorePrintArtwork(printArtwork, options);
|
|
91
|
+
if (!resolved) return null;
|
|
92
|
+
return STORE_PRINT_ARTWORK_PATHS[resolved] ?? null;
|
|
93
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
StoreFulfillmentProvider,
|
|
3
|
+
StorePrintArtwork,
|
|
4
|
+
StoreProductCategory,
|
|
5
|
+
StoreProductStatus,
|
|
6
|
+
} from "@prisma/client";
|
|
7
|
+
|
|
8
|
+
/** Public catalog row for /store listing and product detail. */
|
|
9
|
+
export type StoreProductVariantPublic = {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
priceCents: number | null;
|
|
13
|
+
inventory: number;
|
|
14
|
+
sortOrder: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type StoreProductPublic = {
|
|
18
|
+
id: string;
|
|
19
|
+
slug: string;
|
|
20
|
+
title: string;
|
|
21
|
+
description: string | null;
|
|
22
|
+
images: string[];
|
|
23
|
+
priceCents: number;
|
|
24
|
+
inventory: number;
|
|
25
|
+
category: StoreProductCategory;
|
|
26
|
+
sortOrder: number;
|
|
27
|
+
printArtwork: StorePrintArtwork | null;
|
|
28
|
+
variants: StoreProductVariantPublic[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Admin CRUD shape (includes fulfillment mapping). */
|
|
32
|
+
export type StoreProductAdmin = StoreProductPublic & {
|
|
33
|
+
status: StoreProductStatus;
|
|
34
|
+
fulfillmentProvider: StoreFulfillmentProvider;
|
|
35
|
+
externalProductId: string | null;
|
|
36
|
+
printArtworkUrl: string | null;
|
|
37
|
+
variants: (StoreProductVariantPublic & {
|
|
38
|
+
externalVariantId: string | null;
|
|
39
|
+
})[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type StoreCheckoutSessionRequest = {
|
|
43
|
+
productSlug: string;
|
|
44
|
+
variantId?: string;
|
|
45
|
+
quantity?: number;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type StoreCheckoutSessionResponse = {
|
|
49
|
+
checkoutUrl: string;
|
|
50
|
+
orderId: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type StoreOrderPublic = {
|
|
54
|
+
id: string;
|
|
55
|
+
status: string;
|
|
56
|
+
fulfillmentStatus: string;
|
|
57
|
+
subtotalCents: number;
|
|
58
|
+
totalCents: number;
|
|
59
|
+
currency: string;
|
|
60
|
+
createdAt: string;
|
|
61
|
+
lines: {
|
|
62
|
+
id: string;
|
|
63
|
+
titleSnapshot: string;
|
|
64
|
+
unitPriceCents: number;
|
|
65
|
+
quantity: number;
|
|
66
|
+
}[];
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** Brand print artwork paths served from bash-app/public. */
|
|
70
|
+
export const STORE_PRINT_ARTWORK_PATHS: Record<StorePrintArtwork, string> = {
|
|
71
|
+
BashLogoBig: "/bashLogoBig.png",
|
|
72
|
+
BashLogoLetters: "/bashLogoLetters.png",
|
|
73
|
+
BashLogoBigWhite: "/bashLogoBigWhite.png",
|
|
74
|
+
BashLogoLettersWhite: "/bashLogoLettersWhite.png",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const STORE_PRODUCT_STATUS_VALUES = ["Active", "Draft", "Archived"] as const;
|
|
78
|
+
export const STORE_FULFILLMENT_PROVIDER_VALUES = [
|
|
79
|
+
"Platform",
|
|
80
|
+
"Printify",
|
|
81
|
+
"Printful",
|
|
82
|
+
] as const;
|
|
83
|
+
export const STORE_PRINT_ARTWORK_VALUES = [
|
|
84
|
+
"BashLogoBig",
|
|
85
|
+
"BashLogoLetters",
|
|
86
|
+
"BashLogoBigWhite",
|
|
87
|
+
"BashLogoLettersWhite",
|
|
88
|
+
] as const;
|
|
89
|
+
|
|
90
|
+
/** Printful sync product row for admin import picker. */
|
|
91
|
+
export type PrintfulSyncProductSummary = {
|
|
92
|
+
id: number;
|
|
93
|
+
name: string;
|
|
94
|
+
thumbnailUrl: string | null;
|
|
95
|
+
variantCount: number;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** Normalized Printful sync product payload for Bash Store admin import. */
|
|
99
|
+
export type PrintfulStoreProductImport = {
|
|
100
|
+
syncProductId: string;
|
|
101
|
+
title: string;
|
|
102
|
+
images: string[];
|
|
103
|
+
priceCents: number;
|
|
104
|
+
variants: {
|
|
105
|
+
label: string;
|
|
106
|
+
externalVariantId: string;
|
|
107
|
+
priceCents: number | null;
|
|
108
|
+
}[];
|
|
109
|
+
};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared shapes for the Venue Match pipeline (retrieve → score → explain).
|
|
3
|
+
*
|
|
4
|
+
* The api worker owns the Zod runtime validation (api/src/services/venueMatch/);
|
|
5
|
+
* this file is the type-only source of truth read by both the api and bash-app
|
|
6
|
+
* (host suggestion cards + admin supply-leads pipeline).
|
|
7
|
+
*
|
|
8
|
+
* Persisted JSON columns map exactly to these interfaces:
|
|
9
|
+
* - `VenueMatchRun.intent` → `VenueMatchIntent`
|
|
10
|
+
* - `VenueMatchCandidate.scoreBreakdown` → `VenueMatchScoreBreakdown`
|
|
11
|
+
* - `VenueMatchCandidate.reasons` → `string[]` (host-facing copy)
|
|
12
|
+
* - `VenueMatchCandidate.displaySnapshot` → `VenueCandidateDisplay`
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Where a candidate came from. Mirrors prisma enum `VenueMatchCandidateSource`. */
|
|
16
|
+
export const VENUE_MATCH_SOURCES = {
|
|
17
|
+
Marketplace: "Marketplace",
|
|
18
|
+
GooglePlaces: "GooglePlaces",
|
|
19
|
+
} as const;
|
|
20
|
+
export type VenueMatchCandidateSource =
|
|
21
|
+
(typeof VENUE_MATCH_SOURCES)[keyof typeof VENUE_MATCH_SOURCES];
|
|
22
|
+
|
|
23
|
+
/** Host interaction with a surfaced candidate. Mirrors prisma enum `VenueMatchHostAction`. */
|
|
24
|
+
export const VENUE_MATCH_HOST_ACTIONS = {
|
|
25
|
+
None: "None",
|
|
26
|
+
Viewed: "Viewed",
|
|
27
|
+
Accepted: "Accepted",
|
|
28
|
+
AcceptedOffPlatform: "AcceptedOffPlatform",
|
|
29
|
+
Dismissed: "Dismissed",
|
|
30
|
+
CalledPhone: "CalledPhone",
|
|
31
|
+
InvitedVenue: "InvitedVenue",
|
|
32
|
+
} as const;
|
|
33
|
+
export type VenueMatchHostAction =
|
|
34
|
+
(typeof VENUE_MATCH_HOST_ACTIONS)[keyof typeof VENUE_MATCH_HOST_ACTIONS];
|
|
35
|
+
|
|
36
|
+
/** Sales pipeline stage. Mirrors prisma enum `VenueSupplyLeadStage`. */
|
|
37
|
+
export const VENUE_SUPPLY_LEAD_STAGES = {
|
|
38
|
+
Surfaced: "Surfaced",
|
|
39
|
+
Prioritized: "Prioritized",
|
|
40
|
+
Queued: "Queued",
|
|
41
|
+
Contacted: "Contacted",
|
|
42
|
+
Interested: "Interested",
|
|
43
|
+
DemoScheduled: "DemoScheduled",
|
|
44
|
+
Listed: "Listed",
|
|
45
|
+
Declined: "Declined",
|
|
46
|
+
DoNotContact: "DoNotContact",
|
|
47
|
+
} as const;
|
|
48
|
+
export type VenueSupplyLeadStage =
|
|
49
|
+
(typeof VENUE_SUPPLY_LEAD_STAGES)[keyof typeof VENUE_SUPPLY_LEAD_STAGES];
|
|
50
|
+
|
|
51
|
+
/** Admin display labels — single source for the pipeline UI + CSV export. */
|
|
52
|
+
export const VenueSupplyLeadStageLabel: Record<VenueSupplyLeadStage, string> = {
|
|
53
|
+
Surfaced: "Surfaced",
|
|
54
|
+
Prioritized: "Hot lead",
|
|
55
|
+
Queued: "Queued for outreach",
|
|
56
|
+
Contacted: "Contacted",
|
|
57
|
+
Interested: "Interested",
|
|
58
|
+
DemoScheduled: "Demo scheduled",
|
|
59
|
+
Listed: "Listed on Bash",
|
|
60
|
+
Declined: "Declined",
|
|
61
|
+
DoNotContact: "Do not contact",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Stages a rep can manually transition to from the admin UI. */
|
|
65
|
+
export const VENUE_SUPPLY_LEAD_REP_STAGES: VenueSupplyLeadStage[] = [
|
|
66
|
+
"Queued",
|
|
67
|
+
"Contacted",
|
|
68
|
+
"Interested",
|
|
69
|
+
"DemoScheduled",
|
|
70
|
+
"Declined",
|
|
71
|
+
"DoNotContact",
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Structured intent extracted from the host's prompt + current event fields.
|
|
76
|
+
* Persisted as `VenueMatchRun.intent` (Json column).
|
|
77
|
+
* All fields nullable — the scorer omits weights for missing signals.
|
|
78
|
+
*/
|
|
79
|
+
export interface VenueMatchIntent {
|
|
80
|
+
/** e.g. "bachelor party", "baby shower" — free text, lowercased */
|
|
81
|
+
occasion: string | null;
|
|
82
|
+
/** From amountOfGuests.expected or parsed from prompt */
|
|
83
|
+
guestCount: number | null;
|
|
84
|
+
city: string | null;
|
|
85
|
+
state: string | null;
|
|
86
|
+
/** Geocoded anchor for distance scoring (host geo → profile city → event city) */
|
|
87
|
+
anchorLat: number | null;
|
|
88
|
+
anchorLng: number | null;
|
|
89
|
+
/** e.g. "upscale", "casual" */
|
|
90
|
+
vibe: string | null;
|
|
91
|
+
/** Total budget in cents if the host mentioned one */
|
|
92
|
+
budgetCents: number | null;
|
|
93
|
+
/** BashEvent.eventType at run time */
|
|
94
|
+
eventType: string | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Per-factor contribution. `weight` is the normalized weight actually applied (0..1). */
|
|
98
|
+
export interface VenueMatchFactorScore {
|
|
99
|
+
/** Raw factor score 0..100 before weighting */
|
|
100
|
+
score: number;
|
|
101
|
+
weight: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Persisted as `VenueMatchCandidate.scoreBreakdown` (Json).
|
|
106
|
+
* A factor is null when its signal was unavailable and its weight
|
|
107
|
+
* was re-distributed across the others.
|
|
108
|
+
*/
|
|
109
|
+
export interface VenueMatchScoreBreakdown {
|
|
110
|
+
distance: VenueMatchFactorScore | null;
|
|
111
|
+
capacity: VenueMatchFactorScore | null;
|
|
112
|
+
occasion: VenueMatchFactorScore | null;
|
|
113
|
+
rating: VenueMatchFactorScore | null;
|
|
114
|
+
price: VenueMatchFactorScore | null;
|
|
115
|
+
platformQuality: VenueMatchFactorScore | null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Snapshot of everything the host card needs, captured at run time so
|
|
120
|
+
* `GET /venue-match` never re-hits Google. Persisted as
|
|
121
|
+
* `VenueMatchCandidate.displaySnapshot` (Json).
|
|
122
|
+
*/
|
|
123
|
+
export interface VenueCandidateDisplay {
|
|
124
|
+
name: string;
|
|
125
|
+
photoUrl: string | null;
|
|
126
|
+
formattedAddress: string | null;
|
|
127
|
+
city: string | null;
|
|
128
|
+
state: string | null;
|
|
129
|
+
/** Bash rating for marketplace, Google rating for off-platform */
|
|
130
|
+
rating: number | null;
|
|
131
|
+
reviewCount: number | null;
|
|
132
|
+
capacity: number | null;
|
|
133
|
+
distanceMiles: number | null;
|
|
134
|
+
phone: string | null;
|
|
135
|
+
/** Marketplace only — deep link target */
|
|
136
|
+
serviceDetailPath: string | null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** One candidate as served to the host UI. */
|
|
140
|
+
export interface VenueMatchCandidateView {
|
|
141
|
+
id: string;
|
|
142
|
+
rank: number;
|
|
143
|
+
score: number;
|
|
144
|
+
source: VenueMatchCandidateSource;
|
|
145
|
+
serviceId: string | null;
|
|
146
|
+
/** Venue model id (for bashEvent.venueId) when marketplace */
|
|
147
|
+
venueId: string | null;
|
|
148
|
+
googlePlaceId: string | null;
|
|
149
|
+
reasons: string[];
|
|
150
|
+
scoreBreakdown: VenueMatchScoreBreakdown;
|
|
151
|
+
display: VenueCandidateDisplay;
|
|
152
|
+
hostAction: VenueMatchHostAction;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Response of `GET /event/:id/venue-match`. */
|
|
156
|
+
export interface VenueMatchRunView {
|
|
157
|
+
runId: string;
|
|
158
|
+
createdAt: string;
|
|
159
|
+
intent: VenueMatchIntent;
|
|
160
|
+
candidates: VenueMatchCandidateView[];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Row shape for the admin supply-leads table + CSV export. */
|
|
164
|
+
export interface VenueSupplyLeadRow {
|
|
165
|
+
id: string;
|
|
166
|
+
name: string;
|
|
167
|
+
phoneE164: string | null;
|
|
168
|
+
city: string | null;
|
|
169
|
+
state: string | null;
|
|
170
|
+
fullAddress: string | null;
|
|
171
|
+
rating: number | null;
|
|
172
|
+
reviewCount: number | null;
|
|
173
|
+
category: string | null;
|
|
174
|
+
website: string | null;
|
|
175
|
+
googlePlaceId: string | null;
|
|
176
|
+
distinctEventCount: number;
|
|
177
|
+
topOccasions: string[];
|
|
178
|
+
firstSurfacedAt: string;
|
|
179
|
+
lastSurfacedAt: string;
|
|
180
|
+
stage: VenueSupplyLeadStage;
|
|
181
|
+
assignedToUserId: string | null;
|
|
182
|
+
assignedToName: string | null;
|
|
183
|
+
notes: string | null;
|
|
184
|
+
/** Set once the venue lists on Bash (conversion) */
|
|
185
|
+
serviceId: string | null;
|
|
186
|
+
catalogSource: string | null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface VenueSupplyLeadStats {
|
|
190
|
+
byStage: Record<VenueSupplyLeadStage, number>;
|
|
191
|
+
total: number;
|
|
192
|
+
hotLeads: number;
|
|
193
|
+
convertedTotal: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Feature flag values for `VENUE_MATCH_MODE`. */
|
|
197
|
+
export const VENUE_MATCH_MODES = [
|
|
198
|
+
"off",
|
|
199
|
+
"marketplace_only",
|
|
200
|
+
"marketplace_and_google",
|
|
201
|
+
] as const;
|
|
202
|
+
export type VenueMatchMode = (typeof VENUE_MATCH_MODES)[number];
|