@bash-app/bash-common 30.307.0 → 30.309.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__/guestPartySize.test.d.ts +2 -0
- package/dist/__tests__/guestPartySize.test.d.ts.map +1 -0
- package/dist/__tests__/guestPartySize.test.js +22 -0
- package/dist/__tests__/guestPartySize.test.js.map +1 -0
- package/dist/__tests__/sideQuestIdeas.test.d.ts +2 -0
- package/dist/__tests__/sideQuestIdeas.test.d.ts.map +1 -0
- package/dist/__tests__/sideQuestIdeas.test.js +13 -0
- package/dist/__tests__/sideQuestIdeas.test.js.map +1 -0
- package/dist/__tests__/sideQuestTypes.test.d.ts +2 -0
- package/dist/__tests__/sideQuestTypes.test.d.ts.map +1 -0
- package/dist/__tests__/sideQuestTypes.test.js +29 -0
- package/dist/__tests__/sideQuestTypes.test.js.map +1 -0
- package/dist/guestPartySize.d.ts +18 -0
- package/dist/guestPartySize.d.ts.map +1 -0
- package/dist/guestPartySize.js +72 -0
- package/dist/guestPartySize.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mirroredPrismaEnums.d.ts +3 -0
- package/dist/mirroredPrismaEnums.d.ts.map +1 -1
- package/dist/mirroredPrismaEnums.js +3 -0
- package/dist/mirroredPrismaEnums.js.map +1 -1
- package/dist/sideQuestIdeas.d.ts +51 -0
- package/dist/sideQuestIdeas.d.ts.map +1 -0
- package/dist/sideQuestIdeas.js +215 -0
- package/dist/sideQuestIdeas.js.map +1 -0
- package/dist/sideQuestTypes.d.ts +115 -0
- package/dist/sideQuestTypes.d.ts.map +1 -0
- package/dist/sideQuestTypes.js +67 -0
- package/dist/sideQuestTypes.js.map +1 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +206 -0
- package/src/__tests__/guestPartySize.test.ts +31 -0
- package/src/__tests__/sideQuestIdeas.test.ts +16 -0
- package/src/__tests__/sideQuestTypes.test.ts +44 -0
- package/src/guestPartySize.ts +83 -0
- package/src/index.ts +3 -0
- package/src/mirroredPrismaEnums.ts +3 -0
- package/src/sideQuestIdeas.ts +260 -0
- package/src/sideQuestTypes.ts +189 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Side Quest / Adventures API shapes and QR deep-link helpers.
|
|
3
|
+
*/
|
|
4
|
+
import type { SideQuestCategoryId, SideQuestTypeId } from "./sideQuestIdeas.js";
|
|
5
|
+
export interface EventBadgeExt {
|
|
6
|
+
id: string;
|
|
7
|
+
bashEventId: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description: string | null;
|
|
10
|
+
imageUrl: string;
|
|
11
|
+
createdByUserId?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SideQuestExt {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
description: string | null;
|
|
17
|
+
sortOrder: number;
|
|
18
|
+
category: SideQuestCategoryId | string;
|
|
19
|
+
questType: SideQuestTypeId | string;
|
|
20
|
+
pointsReward: number;
|
|
21
|
+
raffleEntriesOnComplete: number;
|
|
22
|
+
eventBadgeId: string | null;
|
|
23
|
+
eventBadge?: Pick<EventBadgeExt, "id" | "name" | "imageUrl" | "description"> | null;
|
|
24
|
+
startsAt: string | Date | null;
|
|
25
|
+
endsAt: string | Date | null;
|
|
26
|
+
maxCompletions: number | null;
|
|
27
|
+
isHidden: boolean;
|
|
28
|
+
isLocked?: boolean;
|
|
29
|
+
unlockedAt: string | Date | null;
|
|
30
|
+
sponsorLabel: string | null;
|
|
31
|
+
completed?: boolean;
|
|
32
|
+
code?: string;
|
|
33
|
+
completionCount?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface SideQuestMilestoneExt {
|
|
36
|
+
id: string;
|
|
37
|
+
requiredCompletions: number;
|
|
38
|
+
bashPointsReward: number | null;
|
|
39
|
+
eventBadgeId: string | null;
|
|
40
|
+
raffleEntries: number | null;
|
|
41
|
+
unlockSideQuestId: string | null;
|
|
42
|
+
sortOrder: number;
|
|
43
|
+
}
|
|
44
|
+
export interface CommunityQuestGoalExt {
|
|
45
|
+
id?: string;
|
|
46
|
+
targetCompletions: number;
|
|
47
|
+
currentCompletions?: number;
|
|
48
|
+
rewardLabel: string;
|
|
49
|
+
eventBadgeId?: string | null;
|
|
50
|
+
bashPointsReward?: number | null;
|
|
51
|
+
unlockedAt?: string | Date | null;
|
|
52
|
+
}
|
|
53
|
+
export interface SideQuestLogExt {
|
|
54
|
+
quests: SideQuestExt[];
|
|
55
|
+
availableCount: number;
|
|
56
|
+
completedCount: number;
|
|
57
|
+
hiddenCount: number;
|
|
58
|
+
badgesEarned: EventBadgeExt[];
|
|
59
|
+
eventBadges: EventBadgeExt[];
|
|
60
|
+
raffleEntries: number;
|
|
61
|
+
pointsFromQuests: number;
|
|
62
|
+
community: CommunityQuestGoalExt | null;
|
|
63
|
+
}
|
|
64
|
+
export interface SideQuestHostBundleExt {
|
|
65
|
+
quests: SideQuestExt[];
|
|
66
|
+
milestones: SideQuestMilestoneExt[];
|
|
67
|
+
community: CommunityQuestGoalExt | null;
|
|
68
|
+
badges: EventBadgeExt[];
|
|
69
|
+
}
|
|
70
|
+
export interface SideQuestAnalyticsExt {
|
|
71
|
+
uniqueCompleters: number;
|
|
72
|
+
totalCompletions: number;
|
|
73
|
+
raffleEntriesTotal: number;
|
|
74
|
+
community: CommunityQuestGoalExt | null;
|
|
75
|
+
perQuest: Array<{
|
|
76
|
+
questId: string;
|
|
77
|
+
title: string;
|
|
78
|
+
sponsorLabel: string | null;
|
|
79
|
+
completionCount: number;
|
|
80
|
+
}>;
|
|
81
|
+
sponsorRollup: Array<{
|
|
82
|
+
sponsorLabel: string;
|
|
83
|
+
completionCount: number;
|
|
84
|
+
}>;
|
|
85
|
+
recentCompletions: Array<{
|
|
86
|
+
completionId: string;
|
|
87
|
+
questId: string;
|
|
88
|
+
questTitle: string;
|
|
89
|
+
userId: string;
|
|
90
|
+
displayName: string;
|
|
91
|
+
completedAt: string | Date;
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
94
|
+
/** Absolute or path-only HTTPS deep link for booth QR codes. */
|
|
95
|
+
export declare function buildSideQuestDeepLink(params: {
|
|
96
|
+
bashEventId: string;
|
|
97
|
+
questId: string;
|
|
98
|
+
code: string;
|
|
99
|
+
/** Origin without trailing slash, e.g. https://bash.community */
|
|
100
|
+
frontendOrigin?: string;
|
|
101
|
+
slug?: string | null;
|
|
102
|
+
}): string;
|
|
103
|
+
/** Legacy opaque payload (still accepted when pasted). */
|
|
104
|
+
export declare function buildSideQuestLegacyPayload(bashEventId: string, questId: string, code: string): string;
|
|
105
|
+
export type ParsedSideQuestScan = {
|
|
106
|
+
bashEventId?: string;
|
|
107
|
+
questId?: string;
|
|
108
|
+
code: string;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Parse a scanned/pasted value into quest id + code.
|
|
112
|
+
* Supports HTTPS ticket-details URLs and legacy `bash-side-quest:` payloads.
|
|
113
|
+
*/
|
|
114
|
+
export declare function parseSideQuestScanPayload(raw: string): ParsedSideQuestScan | null;
|
|
115
|
+
//# sourceMappingURL=sideQuestTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sideQuestTypes.d.ts","sourceRoot":"","sources":["../src/sideQuestTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Side Quest / Adventures API shapes and QR deep-link helpers.
|
|
3
|
+
*/
|
|
4
|
+
import { TICKET_DETAILS } from "./definitions.js";
|
|
5
|
+
const LEGACY_PREFIX = "bash-side-quest:";
|
|
6
|
+
/** Absolute or path-only HTTPS deep link for booth QR codes. */
|
|
7
|
+
export function buildSideQuestDeepLink(params) {
|
|
8
|
+
const code = params.code.trim().toUpperCase().replace(/\s+/g, "");
|
|
9
|
+
const qs = new URLSearchParams({
|
|
10
|
+
quest: params.questId,
|
|
11
|
+
code,
|
|
12
|
+
});
|
|
13
|
+
const path = `${TICKET_DETAILS}/${params.bashEventId}?${qs.toString()}`;
|
|
14
|
+
const origin = (params.frontendOrigin ?? "").replace(/\/$/, "");
|
|
15
|
+
return origin ? `${origin}${path}` : path;
|
|
16
|
+
}
|
|
17
|
+
/** Legacy opaque payload (still accepted when pasted). */
|
|
18
|
+
export function buildSideQuestLegacyPayload(bashEventId, questId, code) {
|
|
19
|
+
const normalized = code.trim().toUpperCase().replace(/\s+/g, "");
|
|
20
|
+
return `${LEGACY_PREFIX}${bashEventId}:${questId}:${normalized}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse a scanned/pasted value into quest id + code.
|
|
24
|
+
* Supports HTTPS ticket-details URLs and legacy `bash-side-quest:` payloads.
|
|
25
|
+
*/
|
|
26
|
+
export function parseSideQuestScanPayload(raw) {
|
|
27
|
+
const trimmed = raw.trim();
|
|
28
|
+
if (!trimmed)
|
|
29
|
+
return null;
|
|
30
|
+
if (trimmed.startsWith(LEGACY_PREFIX)) {
|
|
31
|
+
const rest = trimmed.slice(LEGACY_PREFIX.length);
|
|
32
|
+
const parts = rest.split(":");
|
|
33
|
+
if (parts.length >= 3) {
|
|
34
|
+
const [bashEventId, questId, ...codeParts] = parts;
|
|
35
|
+
const code = codeParts.join(":").trim().toUpperCase();
|
|
36
|
+
if (code)
|
|
37
|
+
return { bashEventId, questId, code };
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const url = trimmed.includes("://")
|
|
43
|
+
? new URL(trimmed)
|
|
44
|
+
: new URL(trimmed, "https://bash.community");
|
|
45
|
+
const questId = url.searchParams.get("quest") ?? undefined;
|
|
46
|
+
const codeParam = url.searchParams.get("code");
|
|
47
|
+
if (codeParam) {
|
|
48
|
+
return {
|
|
49
|
+
questId: questId || undefined,
|
|
50
|
+
code: codeParam.trim().toUpperCase().replace(/\s+/g, ""),
|
|
51
|
+
bashEventId: url.pathname.includes("/ticket-details/")
|
|
52
|
+
? url.pathname.split("/ticket-details/")[1]?.split(/[/?#]/)[0]
|
|
53
|
+
: undefined,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// not a URL — treat as raw code
|
|
59
|
+
}
|
|
60
|
+
// Plain code (4–6 chars)
|
|
61
|
+
const plain = trimmed.toUpperCase().replace(/\s+/g, "");
|
|
62
|
+
if (/^[A-Z0-9]{4,8}$/.test(plain)) {
|
|
63
|
+
return { code: plain };
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=sideQuestTypes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sideQuestTypes.js","sourceRoot":"","sources":["../src/sideQuestTypes.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/prisma/schema.prisma
CHANGED
|
@@ -842,6 +842,9 @@ model BashEvent {
|
|
|
842
842
|
smsConfirmationsEnabled Boolean @default(false)
|
|
843
843
|
/// Collect named holder per seat at checkout.
|
|
844
844
|
namedTicketsEnabled Boolean @default(false)
|
|
845
|
+
/// Max companions a guest may bring (0 = alone, 1 = +1, 2 = +2, null = unlimited).
|
|
846
|
+
/// Applies to RSVP plus-ones and ticketed seats-per-buyer. Null preserves open defaults.
|
|
847
|
+
maxAdditionalGuests Int?
|
|
845
848
|
attendeeDisplayMode String @default("All") // "All" | "CountOnly" | "NetworkOnly" | "Hidden"
|
|
846
849
|
/// Optional headline above featured guests on bash detail (curated guest visibility).
|
|
847
850
|
featuredGuestsHeadline String?
|
|
@@ -1038,6 +1041,13 @@ model BashEvent {
|
|
|
1038
1041
|
discussionComments EventDiscussionComment[]
|
|
1039
1042
|
bashEventDonations BashEventDonation[]
|
|
1040
1043
|
|
|
1044
|
+
// Adventures / Side Quests
|
|
1045
|
+
sideQuests SideQuest[]
|
|
1046
|
+
eventBadges EventBadge[]
|
|
1047
|
+
sideQuestMilestones SideQuestMilestone[]
|
|
1048
|
+
sideQuestPrizeEntries SideQuestPrizeEntry[]
|
|
1049
|
+
communityQuestGoal CommunityQuestGoal?
|
|
1050
|
+
|
|
1041
1051
|
// User night itineraries (events in user's "My Night" plan)
|
|
1042
1052
|
userItineraryItems UserItineraryItem[]
|
|
1043
1053
|
|
|
@@ -1981,6 +1991,192 @@ model EventReferralReward {
|
|
|
1981
1991
|
@@index([bashEventId])
|
|
1982
1992
|
}
|
|
1983
1993
|
|
|
1994
|
+
// --- Adventures / Side Quests (behavior design engine) ---
|
|
1995
|
+
|
|
1996
|
+
enum SideQuestCategory {
|
|
1997
|
+
Explore
|
|
1998
|
+
Connect
|
|
1999
|
+
Discover
|
|
2000
|
+
SupportLocal
|
|
2001
|
+
Compete
|
|
2002
|
+
Transit
|
|
2003
|
+
Create
|
|
2004
|
+
GiveBack
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
/// How the guest proves completion. Phase 1: Qr + Code. Others reserved.
|
|
2008
|
+
enum SideQuestType {
|
|
2009
|
+
Qr
|
|
2010
|
+
Code
|
|
2011
|
+
Gps
|
|
2012
|
+
Photo
|
|
2013
|
+
CheckIn
|
|
2014
|
+
Purchase
|
|
2015
|
+
Vote
|
|
2016
|
+
Sponsor
|
|
2017
|
+
Social
|
|
2018
|
+
Trivia
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
enum EventBadgeAwardSource {
|
|
2022
|
+
SideQuest
|
|
2023
|
+
Milestone
|
|
2024
|
+
Manual
|
|
2025
|
+
Community
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
model SideQuest {
|
|
2029
|
+
id String @id @default(cuid())
|
|
2030
|
+
bashEventId String
|
|
2031
|
+
title String
|
|
2032
|
+
description String?
|
|
2033
|
+
sortOrder Int @default(0)
|
|
2034
|
+
category SideQuestCategory @default(Explore)
|
|
2035
|
+
questType SideQuestType @default(Code)
|
|
2036
|
+
/// Short display code for hosts/guests (e.g. 4-digit). Guests submit this; never treat as the only secret.
|
|
2037
|
+
code String
|
|
2038
|
+
/// SHA-256 hex of normalized code — server verification
|
|
2039
|
+
codeHash String
|
|
2040
|
+
pointsReward Int @default(0)
|
|
2041
|
+
raffleEntriesOnComplete Int @default(0)
|
|
2042
|
+
eventBadgeId String?
|
|
2043
|
+
startsAt DateTime?
|
|
2044
|
+
endsAt DateTime?
|
|
2045
|
+
maxCompletions Int?
|
|
2046
|
+
isHidden Boolean @default(false)
|
|
2047
|
+
unlockedAt DateTime?
|
|
2048
|
+
sponsorLabel String?
|
|
2049
|
+
sponsorServiceId String?
|
|
2050
|
+
createdAt DateTime @default(now())
|
|
2051
|
+
updatedAt DateTime @updatedAt
|
|
2052
|
+
|
|
2053
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
2054
|
+
eventBadge EventBadge? @relation(fields: [eventBadgeId], references: [id], onDelete: SetNull)
|
|
2055
|
+
completions SideQuestCompletion[]
|
|
2056
|
+
milestonesUnlocking SideQuestMilestone[] @relation("MilestoneUnlocksQuest")
|
|
2057
|
+
|
|
2058
|
+
@@index([bashEventId])
|
|
2059
|
+
@@index([bashEventId, sortOrder])
|
|
2060
|
+
@@index([eventBadgeId])
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
model SideQuestCompletion {
|
|
2064
|
+
id String @id @default(cuid())
|
|
2065
|
+
sideQuestId String
|
|
2066
|
+
userId String
|
|
2067
|
+
completedAt DateTime @default(now())
|
|
2068
|
+
questType SideQuestType
|
|
2069
|
+
|
|
2070
|
+
sideQuest SideQuest @relation(fields: [sideQuestId], references: [id], onDelete: Cascade)
|
|
2071
|
+
user User @relation("SideQuestCompletions", fields: [userId], references: [id], onDelete: Cascade)
|
|
2072
|
+
|
|
2073
|
+
@@unique([sideQuestId, userId])
|
|
2074
|
+
@@index([userId])
|
|
2075
|
+
@@index([sideQuestId])
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
model EventBadge {
|
|
2079
|
+
id String @id @default(cuid())
|
|
2080
|
+
bashEventId String
|
|
2081
|
+
name String
|
|
2082
|
+
description String?
|
|
2083
|
+
imageUrl String
|
|
2084
|
+
createdByUserId String
|
|
2085
|
+
createdAt DateTime @default(now())
|
|
2086
|
+
updatedAt DateTime @updatedAt
|
|
2087
|
+
|
|
2088
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
2089
|
+
createdBy User @relation("EventBadgesCreated", fields: [createdByUserId], references: [id], onDelete: Cascade)
|
|
2090
|
+
awards EventBadgeAward[]
|
|
2091
|
+
sideQuests SideQuest[]
|
|
2092
|
+
milestones SideQuestMilestone[]
|
|
2093
|
+
communityGoals CommunityQuestGoal[]
|
|
2094
|
+
|
|
2095
|
+
@@index([bashEventId])
|
|
2096
|
+
@@index([createdByUserId])
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
model EventBadgeAward {
|
|
2100
|
+
id String @id @default(cuid())
|
|
2101
|
+
eventBadgeId String
|
|
2102
|
+
userId String
|
|
2103
|
+
awardedAt DateTime @default(now())
|
|
2104
|
+
sourceType EventBadgeAwardSource
|
|
2105
|
+
sourceId String?
|
|
2106
|
+
|
|
2107
|
+
eventBadge EventBadge @relation(fields: [eventBadgeId], references: [id], onDelete: Cascade)
|
|
2108
|
+
user User @relation("EventBadgeAwards", fields: [userId], references: [id], onDelete: Cascade)
|
|
2109
|
+
|
|
2110
|
+
@@unique([eventBadgeId, userId])
|
|
2111
|
+
@@index([userId])
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
model SideQuestMilestone {
|
|
2115
|
+
id String @id @default(cuid())
|
|
2116
|
+
bashEventId String
|
|
2117
|
+
requiredCompletions Int
|
|
2118
|
+
bashPointsReward Int?
|
|
2119
|
+
eventBadgeId String?
|
|
2120
|
+
raffleEntries Int?
|
|
2121
|
+
unlockSideQuestId String?
|
|
2122
|
+
sortOrder Int @default(0)
|
|
2123
|
+
createdAt DateTime @default(now())
|
|
2124
|
+
updatedAt DateTime @updatedAt
|
|
2125
|
+
|
|
2126
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
2127
|
+
eventBadge EventBadge? @relation(fields: [eventBadgeId], references: [id], onDelete: SetNull)
|
|
2128
|
+
unlockSideQuest SideQuest? @relation("MilestoneUnlocksQuest", fields: [unlockSideQuestId], references: [id], onDelete: SetNull)
|
|
2129
|
+
claims SideQuestMilestoneClaim[]
|
|
2130
|
+
|
|
2131
|
+
@@index([bashEventId])
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
model SideQuestMilestoneClaim {
|
|
2135
|
+
id String @id @default(cuid())
|
|
2136
|
+
milestoneId String
|
|
2137
|
+
userId String
|
|
2138
|
+
claimedAt DateTime @default(now())
|
|
2139
|
+
|
|
2140
|
+
milestone SideQuestMilestone @relation(fields: [milestoneId], references: [id], onDelete: Cascade)
|
|
2141
|
+
user User @relation("SideQuestMilestoneClaims", fields: [userId], references: [id], onDelete: Cascade)
|
|
2142
|
+
|
|
2143
|
+
@@unique([milestoneId, userId])
|
|
2144
|
+
@@index([userId])
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
model SideQuestPrizeEntry {
|
|
2148
|
+
id String @id @default(cuid())
|
|
2149
|
+
bashEventId String
|
|
2150
|
+
userId String
|
|
2151
|
+
entries Int @default(1)
|
|
2152
|
+
sourceCompletionId String?
|
|
2153
|
+
sourceMilestoneId String?
|
|
2154
|
+
sourceType String @default("SideQuest")
|
|
2155
|
+
createdAt DateTime @default(now())
|
|
2156
|
+
|
|
2157
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
2158
|
+
user User @relation("SideQuestPrizeEntries", fields: [userId], references: [id], onDelete: Cascade)
|
|
2159
|
+
|
|
2160
|
+
@@index([bashEventId, userId])
|
|
2161
|
+
@@index([userId])
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
model CommunityQuestGoal {
|
|
2165
|
+
id String @id @default(cuid())
|
|
2166
|
+
bashEventId String @unique
|
|
2167
|
+
targetCompletions Int
|
|
2168
|
+
currentCompletions Int @default(0)
|
|
2169
|
+
rewardLabel String
|
|
2170
|
+
eventBadgeId String?
|
|
2171
|
+
bashPointsReward Int?
|
|
2172
|
+
unlockedAt DateTime?
|
|
2173
|
+
createdAt DateTime @default(now())
|
|
2174
|
+
updatedAt DateTime @updatedAt
|
|
2175
|
+
|
|
2176
|
+
bashEvent BashEvent @relation(fields: [bashEventId], references: [id], onDelete: Cascade)
|
|
2177
|
+
eventBadge EventBadge? @relation(fields: [eventBadgeId], references: [id], onDelete: SetNull)
|
|
2178
|
+
}
|
|
2179
|
+
|
|
1984
2180
|
enum BackgroundEffect {
|
|
1985
2181
|
None
|
|
1986
2182
|
Confetti
|
|
@@ -3414,6 +3610,11 @@ model User {
|
|
|
3414
3610
|
competitionVotes CompetitionVote[]
|
|
3415
3611
|
competitionJudgeAssignments CompetitionJudgeAssignment[] @relation("CompetitionJudgeAssignments")
|
|
3416
3612
|
competitionJudgeScoresAuthored CompetitionJudgeScore[] @relation("CompetitionJudgeScoresAuthored")
|
|
3613
|
+
sideQuestCompletions SideQuestCompletion[] @relation("SideQuestCompletions")
|
|
3614
|
+
eventBadgesCreated EventBadge[] @relation("EventBadgesCreated")
|
|
3615
|
+
eventBadgeAwards EventBadgeAward[] @relation("EventBadgeAwards")
|
|
3616
|
+
sideQuestMilestoneClaims SideQuestMilestoneClaim[] @relation("SideQuestMilestoneClaims")
|
|
3617
|
+
sideQuestPrizeEntries SideQuestPrizeEntry[] @relation("SideQuestPrizeEntries")
|
|
3417
3618
|
contacts Contact[]
|
|
3418
3619
|
contactLists ContactList[] @relation("UserContactLists")
|
|
3419
3620
|
contactlist ContactList[]
|
|
@@ -6613,6 +6814,8 @@ enum NotificationType {
|
|
|
6613
6814
|
FeaturedGuestInviteAccepted // Host: guest accepted featured guest invite
|
|
6614
6815
|
FeaturedGuestInviteDeclined // Host: guest declined featured guest invite
|
|
6615
6816
|
PartnerPromotionBlast // Attendee: partner on-site promo deal at an event
|
|
6817
|
+
SideQuestRevealed // Attendee: host revealed a secret Side Quest
|
|
6818
|
+
CommunityQuestUnlocked // Attendee: community quest goal unlocked
|
|
6616
6819
|
}
|
|
6617
6820
|
|
|
6618
6821
|
/// Opt-in reminder for a scheduled tier release (v1.5).
|
|
@@ -9050,6 +9253,9 @@ enum CreditSourceType {
|
|
|
9050
9253
|
ReferralCodeUsed // Flat bonus for referrer when code drives a ticket/signup
|
|
9051
9254
|
VenueLoyaltyRedemption // BashPoints moved through a VenueLoyaltyRedemption claim/redeem/refund
|
|
9052
9255
|
PartnerPromoRedemption // BashPoints spent to unlock a local partner promo code
|
|
9256
|
+
SideQuestCompletion // Completing a Side Quest on a bash
|
|
9257
|
+
SideQuestMilestone // Hitting a Side Quest milestone (e.g. complete N quests)
|
|
9258
|
+
CommunityQuestUnlock // Digital reward when a Community Quest goal unlocks
|
|
9053
9259
|
}
|
|
9054
9260
|
|
|
9055
9261
|
enum ReferralTier {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveMaxPlusOnes,
|
|
3
|
+
resolveMaxSeatsPerBuyer,
|
|
4
|
+
rsvpPartyPolicyGuestCopy,
|
|
5
|
+
seatsForTicketQuantity,
|
|
6
|
+
ticketPartyPolicyGuestCopy,
|
|
7
|
+
} from "../guestPartySize.js";
|
|
8
|
+
|
|
9
|
+
describe("guestPartySize", () => {
|
|
10
|
+
it("resolveMaxSeatsPerBuyer", () => {
|
|
11
|
+
expect(resolveMaxSeatsPerBuyer(0)).toBe(1);
|
|
12
|
+
expect(resolveMaxSeatsPerBuyer(1)).toBe(2);
|
|
13
|
+
expect(resolveMaxSeatsPerBuyer(null)).toBeNull();
|
|
14
|
+
expect(resolveMaxSeatsPerBuyer(2, 1)).toBe(1);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("resolveMaxPlusOnes soft-caps unlimited", () => {
|
|
18
|
+
expect(resolveMaxPlusOnes(null)).toBe(10);
|
|
19
|
+
expect(resolveMaxPlusOnes(0)).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("seatsForTicketQuantity expands group packages", () => {
|
|
23
|
+
expect(seatsForTicketQuantity(1, 6, true)).toBe(6);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("guest copy helpers", () => {
|
|
27
|
+
expect(rsvpPartyPolicyGuestCopy(0)).toMatch(/no plus-ones/i);
|
|
28
|
+
expect(ticketPartyPolicyGuestCopy(1)).toBe("Up to 2 tickets per person");
|
|
29
|
+
expect(ticketPartyPolicyGuestCopy(null)).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { filterSideQuestIdeas, SIDE_QUEST_IDEAS } from "../sideQuestIdeas";
|
|
2
|
+
|
|
3
|
+
describe("sideQuestIdeas", () => {
|
|
4
|
+
it("includes festival and market templates", () => {
|
|
5
|
+
expect(SIDE_QUEST_IDEAS.some((i) => i.tags.includes("festival"))).toBe(
|
|
6
|
+
true
|
|
7
|
+
);
|
|
8
|
+
expect(SIDE_QUEST_IDEAS.some((i) => i.tags.includes("market"))).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("filters by search", () => {
|
|
12
|
+
const hits = filterSideQuestIdeas("transit");
|
|
13
|
+
expect(hits.length).toBeGreaterThan(0);
|
|
14
|
+
expect(hits.every((h) => JSON.stringify(h).toLowerCase().includes("transit") || h.category === "Transit")).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildSideQuestDeepLink,
|
|
3
|
+
buildSideQuestLegacyPayload,
|
|
4
|
+
parseSideQuestScanPayload,
|
|
5
|
+
} from "../sideQuestTypes.js";
|
|
6
|
+
|
|
7
|
+
describe("sideQuestTypes deep links", () => {
|
|
8
|
+
it("builds HTTPS ticket-details URLs", () => {
|
|
9
|
+
expect(
|
|
10
|
+
buildSideQuestDeepLink({
|
|
11
|
+
bashEventId: "evt1",
|
|
12
|
+
questId: "q1",
|
|
13
|
+
code: "ab12",
|
|
14
|
+
frontendOrigin: "https://bash.community",
|
|
15
|
+
})
|
|
16
|
+
).toBe("https://bash.community/ticket-details/evt1?quest=q1&code=AB12");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("parses HTTPS deep links", () => {
|
|
20
|
+
expect(
|
|
21
|
+
parseSideQuestScanPayload(
|
|
22
|
+
"https://bash.community/ticket-details/evt1?quest=q1&code=XY99"
|
|
23
|
+
)
|
|
24
|
+
).toEqual({
|
|
25
|
+
bashEventId: "evt1",
|
|
26
|
+
questId: "q1",
|
|
27
|
+
code: "XY99",
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("parses legacy bash-side-quest payloads", () => {
|
|
32
|
+
expect(
|
|
33
|
+
parseSideQuestScanPayload(buildSideQuestLegacyPayload("evt1", "q1", "ab12"))
|
|
34
|
+
).toEqual({
|
|
35
|
+
bashEventId: "evt1",
|
|
36
|
+
questId: "q1",
|
|
37
|
+
code: "AB12",
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("parses plain codes", () => {
|
|
42
|
+
expect(parseSideQuestScanPayload("ab12")).toEqual({ code: "AB12" });
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event-level guest party size (plus-ones / seats-per-buyer).
|
|
3
|
+
* maxAdditionalGuests: 0 = alone, 1 = +1, 2 = +2, null = unlimited.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const RSVP_PLUS_ONES_UI_SOFT_CAP = 10;
|
|
7
|
+
export const RSVP_PLUS_ONES_API_HARD_CAP = 20;
|
|
8
|
+
|
|
9
|
+
/** Max seats one buyer may hold (owned + cart). null = unlimited. */
|
|
10
|
+
export function resolveMaxSeatsPerBuyer(
|
|
11
|
+
maxAdditionalGuests: number | null | undefined,
|
|
12
|
+
tierMaxTicketsPerUser?: number | null
|
|
13
|
+
): number | null {
|
|
14
|
+
let eventCap: number | null = null;
|
|
15
|
+
if (maxAdditionalGuests != null && Number.isFinite(maxAdditionalGuests)) {
|
|
16
|
+
eventCap = Math.max(0, Math.floor(maxAdditionalGuests)) + 1;
|
|
17
|
+
}
|
|
18
|
+
const tierCap =
|
|
19
|
+
tierMaxTicketsPerUser != null && Number.isFinite(tierMaxTicketsPerUser)
|
|
20
|
+
? Math.max(0, Math.floor(tierMaxTicketsPerUser))
|
|
21
|
+
: null;
|
|
22
|
+
|
|
23
|
+
if (eventCap == null && tierCap == null) return null;
|
|
24
|
+
if (eventCap == null) return tierCap;
|
|
25
|
+
if (tierCap == null) return eventCap;
|
|
26
|
+
return Math.min(eventCap, tierCap);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Max plus-ones for RSVP UI/API. */
|
|
30
|
+
export function resolveMaxPlusOnes(
|
|
31
|
+
maxAdditionalGuests: number | null | undefined,
|
|
32
|
+
softCap = RSVP_PLUS_ONES_UI_SOFT_CAP
|
|
33
|
+
): number {
|
|
34
|
+
if (maxAdditionalGuests == null) return softCap;
|
|
35
|
+
return Math.max(0, Math.min(softCap, Math.floor(maxAdditionalGuests)));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function seatsForTicketQuantity(
|
|
39
|
+
quantity: number,
|
|
40
|
+
groupTicketSeatCount?: number | null,
|
|
41
|
+
isGroupTicket?: boolean | null
|
|
42
|
+
): number {
|
|
43
|
+
const qty = Math.max(0, Math.floor(quantity));
|
|
44
|
+
if (isGroupTicket && groupTicketSeatCount && groupTicketSeatCount > 0) {
|
|
45
|
+
return qty * groupTicketSeatCount;
|
|
46
|
+
}
|
|
47
|
+
return qty;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function guestPartyPolicyLabel(
|
|
51
|
+
maxAdditionalGuests: number | null | undefined
|
|
52
|
+
): string {
|
|
53
|
+
if (maxAdditionalGuests === 0) return "No plus-ones";
|
|
54
|
+
if (maxAdditionalGuests === 1) return "Plus one";
|
|
55
|
+
if (maxAdditionalGuests === 2) return "Plus two";
|
|
56
|
+
return "Unlimited";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Guest-facing RSVP copy. */
|
|
60
|
+
export function rsvpPartyPolicyGuestCopy(
|
|
61
|
+
maxAdditionalGuests: number | null | undefined
|
|
62
|
+
): string {
|
|
63
|
+
if (maxAdditionalGuests === 0) return "Just you — no plus-ones";
|
|
64
|
+
if (maxAdditionalGuests === 1) return "You can bring up to 1 guest";
|
|
65
|
+
if (maxAdditionalGuests === 2) return "You can bring up to 2 guests";
|
|
66
|
+
return `You can bring up to ${RSVP_PLUS_ONES_UI_SOFT_CAP} guests`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Guest-facing ticketed copy. */
|
|
70
|
+
export function ticketPartyPolicyGuestCopy(
|
|
71
|
+
maxAdditionalGuests: number | null | undefined
|
|
72
|
+
): string | null {
|
|
73
|
+
if (maxAdditionalGuests == null) return null;
|
|
74
|
+
const seats = maxAdditionalGuests + 1;
|
|
75
|
+
if (seats === 1) return "1 ticket per person";
|
|
76
|
+
return `Up to ${seats} tickets per person`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function nearCapacityPartyError(spotsLeft: number): string {
|
|
80
|
+
if (spotsLeft <= 0) return "This bash is at capacity.";
|
|
81
|
+
if (spotsLeft === 1) return "Only 1 spot left — you can’t bring that many guests.";
|
|
82
|
+
return `Only ${spotsLeft} spots left — you can’t bring that many guests.`;
|
|
83
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,9 +7,12 @@ 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 "./sideQuestIdeas.js";
|
|
11
|
+
export * from "./sideQuestTypes.js";
|
|
10
12
|
export * from "./icebreakerPrompts.js";
|
|
11
13
|
export * from "./competitionValidation.js";
|
|
12
14
|
export * from "./groupTicketUtils.js";
|
|
15
|
+
export * from "./guestPartySize.js";
|
|
13
16
|
export * from "./utils/groupTicketCartUtils.js";
|
|
14
17
|
export * from "./partnerStoreTypes.js";
|
|
15
18
|
export * from "./storeTypes.js";
|
|
@@ -177,6 +177,9 @@ export const CreditSourceType = {
|
|
|
177
177
|
ReferralCodeUsed: "ReferralCodeUsed",
|
|
178
178
|
VenueLoyaltyRedemption: "VenueLoyaltyRedemption",
|
|
179
179
|
PartnerPromoRedemption: "PartnerPromoRedemption",
|
|
180
|
+
SideQuestCompletion: "SideQuestCompletion",
|
|
181
|
+
SideQuestMilestone: "SideQuestMilestone",
|
|
182
|
+
CommunityQuestUnlock: "CommunityQuestUnlock",
|
|
180
183
|
} as const satisfies Record<CreditSourceTypeEnum, CreditSourceTypeEnum>;
|
|
181
184
|
|
|
182
185
|
export type CreditSourceType = CreditSourceTypeEnum;
|