@bash-app/bash-common 30.329.0 → 30.331.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__/locationShare.test.d.ts +2 -0
- package/dist/__tests__/locationShare.test.d.ts.map +1 -0
- package/dist/__tests__/locationShare.test.js +74 -0
- package/dist/__tests__/locationShare.test.js.map +1 -0
- package/dist/hostPromoter.d.ts +200 -0
- package/dist/hostPromoter.d.ts.map +1 -0
- package/dist/hostPromoter.js +123 -0
- package/dist/hostPromoter.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/locationShare.d.ts +98 -0
- package/dist/locationShare.d.ts.map +1 -0
- package/dist/locationShare.js +95 -0
- package/dist/locationShare.js.map +1 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +125 -0
- package/src/__tests__/locationShare.test.ts +122 -0
- package/src/hostPromoter.ts +321 -0
- package/src/index.ts +2 -0
- package/src/locationShare.ts +173 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host street-team roster (Community Builder → Promoters).
|
|
3
|
+
* Distinct from per-bash `Promoter` promo-code rows and hireable marketing bookings.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { InfluencerAudienceTier } from "@prisma/client";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
INFLUENCER_AUDIENCE_TIERS,
|
|
10
|
+
type InfluencerAudienceTierId,
|
|
11
|
+
} from "./influencerAudience.js";
|
|
12
|
+
|
|
13
|
+
export const HOST_PROMOTER_KIND = {
|
|
14
|
+
PromoCode: "PromoCode",
|
|
15
|
+
EventPromoter: "EventPromoter",
|
|
16
|
+
Influencer: "Influencer",
|
|
17
|
+
ContentCreator: "ContentCreator",
|
|
18
|
+
Model: "Model",
|
|
19
|
+
BrandAmbassador: "BrandAmbassador",
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
export type HostPromoterKind =
|
|
23
|
+
(typeof HOST_PROMOTER_KIND)[keyof typeof HOST_PROMOTER_KIND];
|
|
24
|
+
|
|
25
|
+
export const HOST_PROMOTER_KIND_LABELS: Record<HostPromoterKind, string> = {
|
|
26
|
+
PromoCode: "Promo code",
|
|
27
|
+
EventPromoter: "Event Promoter",
|
|
28
|
+
Influencer: "Influencer",
|
|
29
|
+
ContentCreator: "Content creator",
|
|
30
|
+
Model: "Model",
|
|
31
|
+
BrandAmbassador: "Brand ambassador",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Hireable Promotion & Marketing subtypes that sync onto the street-team roster. */
|
|
35
|
+
export const HOST_PROMOTER_SYNC_SERVICE_SUBTYPES = [
|
|
36
|
+
"Promoter",
|
|
37
|
+
"Influencer",
|
|
38
|
+
"ContentCreator",
|
|
39
|
+
"Model",
|
|
40
|
+
"BrandAmbassador",
|
|
41
|
+
] as const;
|
|
42
|
+
|
|
43
|
+
export type HostPromoterSyncServiceSubType =
|
|
44
|
+
(typeof HOST_PROMOTER_SYNC_SERVICE_SUBTYPES)[number];
|
|
45
|
+
|
|
46
|
+
export function hostPromoterKindFromEventServiceSubType(
|
|
47
|
+
subType: string | null | undefined
|
|
48
|
+
): HostPromoterKind | null {
|
|
49
|
+
switch (subType) {
|
|
50
|
+
case "Promoter":
|
|
51
|
+
return HOST_PROMOTER_KIND.EventPromoter;
|
|
52
|
+
case "Influencer":
|
|
53
|
+
return HOST_PROMOTER_KIND.Influencer;
|
|
54
|
+
case "ContentCreator":
|
|
55
|
+
return HOST_PROMOTER_KIND.ContentCreator;
|
|
56
|
+
case "Model":
|
|
57
|
+
return HOST_PROMOTER_KIND.Model;
|
|
58
|
+
case "BrandAmbassador":
|
|
59
|
+
return HOST_PROMOTER_KIND.BrandAmbassador;
|
|
60
|
+
default:
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Inverse of sync mapping — PromoCode has no hireable EventService subtype. */
|
|
66
|
+
export function eventServiceSubTypeFromHostPromoterKind(
|
|
67
|
+
kind: HostPromoterKind | string | null | undefined
|
|
68
|
+
): HostPromoterSyncServiceSubType | null {
|
|
69
|
+
switch (kind) {
|
|
70
|
+
case HOST_PROMOTER_KIND.EventPromoter:
|
|
71
|
+
return "Promoter";
|
|
72
|
+
case HOST_PROMOTER_KIND.Influencer:
|
|
73
|
+
return "Influencer";
|
|
74
|
+
case HOST_PROMOTER_KIND.ContentCreator:
|
|
75
|
+
return "ContentCreator";
|
|
76
|
+
case HOST_PROMOTER_KIND.Model:
|
|
77
|
+
return "Model";
|
|
78
|
+
case HOST_PROMOTER_KIND.BrandAmbassador:
|
|
79
|
+
return "BrandAmbassador";
|
|
80
|
+
default:
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Kinds that can be hired via EventService marketplace booking. */
|
|
86
|
+
export const HOST_PROMOTER_HIREABLE_KINDS = [
|
|
87
|
+
HOST_PROMOTER_KIND.EventPromoter,
|
|
88
|
+
HOST_PROMOTER_KIND.Influencer,
|
|
89
|
+
HOST_PROMOTER_KIND.ContentCreator,
|
|
90
|
+
HOST_PROMOTER_KIND.Model,
|
|
91
|
+
HOST_PROMOTER_KIND.BrandAmbassador,
|
|
92
|
+
] as const;
|
|
93
|
+
|
|
94
|
+
export function isHostPromoterHireableKind(
|
|
95
|
+
kind: string | null | undefined
|
|
96
|
+
): kind is (typeof HOST_PROMOTER_HIREABLE_KINDS)[number] {
|
|
97
|
+
return (
|
|
98
|
+
!!kind &&
|
|
99
|
+
(HOST_PROMOTER_HIREABLE_KINDS as readonly string[]).includes(kind)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Deep link into a hireable EventService detail page, optionally preselecting a bash.
|
|
105
|
+
*/
|
|
106
|
+
export function buildHireStreetTeamServiceDeepLink(args: {
|
|
107
|
+
serviceId: string;
|
|
108
|
+
eventServiceId: string;
|
|
109
|
+
bashEventId?: string | null;
|
|
110
|
+
}): string {
|
|
111
|
+
const base = `/services/${args.serviceId}/EventServices/${args.eventServiceId}`;
|
|
112
|
+
if (!args.bashEventId) return base;
|
|
113
|
+
return `${base}?bashEventId=${encodeURIComponent(args.bashEventId)}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export type HostPromoterHireableService = {
|
|
117
|
+
serviceId: string;
|
|
118
|
+
eventServiceId: string;
|
|
119
|
+
eventServiceSubType: string;
|
|
120
|
+
title: string | null;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export function isHostPromoterSyncServiceSubType(
|
|
124
|
+
subType: string | null | undefined
|
|
125
|
+
): subType is HostPromoterSyncServiceSubType {
|
|
126
|
+
return (
|
|
127
|
+
!!subType &&
|
|
128
|
+
(HOST_PROMOTER_SYNC_SERVICE_SUBTYPES as readonly string[]).includes(subType)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const HOST_PROMOTER_SOURCE = {
|
|
133
|
+
Manual: "Manual",
|
|
134
|
+
PastPromoCode: "PastPromoCode",
|
|
135
|
+
PastEventBooking: "PastEventBooking",
|
|
136
|
+
} as const;
|
|
137
|
+
|
|
138
|
+
export type HostPromoterSource =
|
|
139
|
+
(typeof HOST_PROMOTER_SOURCE)[keyof typeof HOST_PROMOTER_SOURCE];
|
|
140
|
+
|
|
141
|
+
/** Mirrors Prisma `HostPromoterStatus` for browser-safe imports. */
|
|
142
|
+
export const HostPromoterStatus = {
|
|
143
|
+
Active: "Active",
|
|
144
|
+
Archived: "Archived",
|
|
145
|
+
} as const;
|
|
146
|
+
export type HostPromoterStatus =
|
|
147
|
+
(typeof HostPromoterStatus)[keyof typeof HostPromoterStatus];
|
|
148
|
+
|
|
149
|
+
/** Mirrors Prisma `HostPromoterPayoutStatus`. */
|
|
150
|
+
export const HostPromoterPayoutStatus = {
|
|
151
|
+
Unpaid: "Unpaid",
|
|
152
|
+
Paid: "Paid",
|
|
153
|
+
Cancelled: "Cancelled",
|
|
154
|
+
} as const;
|
|
155
|
+
export type HostPromoterPayoutStatus =
|
|
156
|
+
(typeof HostPromoterPayoutStatus)[keyof typeof HostPromoterPayoutStatus];
|
|
157
|
+
|
|
158
|
+
/** Mirrors Prisma `HostPromoterPayoutMethod`. */
|
|
159
|
+
export const HostPromoterPayoutMethod = {
|
|
160
|
+
Venmo: "Venmo",
|
|
161
|
+
Zelle: "Zelle",
|
|
162
|
+
CashApp: "CashApp",
|
|
163
|
+
Cash: "Cash",
|
|
164
|
+
Other: "Other",
|
|
165
|
+
} as const;
|
|
166
|
+
export type HostPromoterPayoutMethod =
|
|
167
|
+
(typeof HostPromoterPayoutMethod)[keyof typeof HostPromoterPayoutMethod];
|
|
168
|
+
|
|
169
|
+
/** Mirrors Prisma `HostPromoterPayoutChannel`. */
|
|
170
|
+
export const HostPromoterPayoutChannel = {
|
|
171
|
+
OffPlatform: "OffPlatform",
|
|
172
|
+
Platform: "Platform",
|
|
173
|
+
} as const;
|
|
174
|
+
export type HostPromoterPayoutChannel =
|
|
175
|
+
(typeof HostPromoterPayoutChannel)[keyof typeof HostPromoterPayoutChannel];
|
|
176
|
+
|
|
177
|
+
/** Re-export influencer tier ids for roster forms (Nano / Micro / Macro / Mega). */
|
|
178
|
+
export const HOST_PROMOTER_INFLUENCER_TIERS = INFLUENCER_AUDIENCE_TIERS;
|
|
179
|
+
export type HostPromoterInfluencerTier = InfluencerAudienceTierId;
|
|
180
|
+
|
|
181
|
+
export function isHostPromoterInfluencerTier(
|
|
182
|
+
value: string | null | undefined
|
|
183
|
+
): value is HostPromoterInfluencerTier {
|
|
184
|
+
return (
|
|
185
|
+
!!value &&
|
|
186
|
+
(INFLUENCER_AUDIENCE_TIERS as readonly string[]).includes(value)
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export type HostPromoterPayoutSummary = {
|
|
191
|
+
id: string;
|
|
192
|
+
amountCents: number;
|
|
193
|
+
currency: string;
|
|
194
|
+
status: HostPromoterPayoutStatus;
|
|
195
|
+
method: HostPromoterPayoutMethod;
|
|
196
|
+
channel: HostPromoterPayoutChannel;
|
|
197
|
+
stripeCheckoutSessionId: string | null;
|
|
198
|
+
platformFeeCents: number;
|
|
199
|
+
paidAt: string | null;
|
|
200
|
+
note: string | null;
|
|
201
|
+
bashEventId: string | null;
|
|
202
|
+
bashEventTitle: string | null;
|
|
203
|
+
serviceBookingId: string | null;
|
|
204
|
+
createdAt: string;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export type HostPromoterPayoutReadiness = {
|
|
208
|
+
payable: boolean;
|
|
209
|
+
reason?: "no_bash_account" | "not_connected" | "onboarding_incomplete";
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export type HostPromoterStripePayoutCreateInput = {
|
|
213
|
+
amountCents: number;
|
|
214
|
+
bashEventId?: string | null;
|
|
215
|
+
note?: string | null;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export type HostPromoterListItem = {
|
|
219
|
+
id: string;
|
|
220
|
+
email: string;
|
|
221
|
+
displayName: string | null;
|
|
222
|
+
promoterUserId: string | null;
|
|
223
|
+
kinds: HostPromoterKind[];
|
|
224
|
+
status: HostPromoterStatus;
|
|
225
|
+
notes: string | null;
|
|
226
|
+
defaultBashPointsRewardPerTicket: number | null;
|
|
227
|
+
/** Nano / Micro / Macro / Mega when this person is (also) an Influencer. */
|
|
228
|
+
influencerAudienceTier: InfluencerAudienceTier | null;
|
|
229
|
+
sources: HostPromoterSource[];
|
|
230
|
+
lastPromotedAt: string | null;
|
|
231
|
+
createdAt: string;
|
|
232
|
+
updatedAt: string;
|
|
233
|
+
/** Known Venmo/Zelle/CashApp handle from User.paymentHandle when linked. */
|
|
234
|
+
paymentHandle: string | null;
|
|
235
|
+
/** Distinct bashes this person had a Promoter / marketing booking on for this host. */
|
|
236
|
+
bashesPromotedCount: number;
|
|
237
|
+
/** Non-cancelled tickets attributed via this person's promo codes on host bashes. */
|
|
238
|
+
ticketsAttributed: number;
|
|
239
|
+
/** Sum of Promoter.creditsGenerated across host bashes (BashPoints). */
|
|
240
|
+
bashPointsEarned: number;
|
|
241
|
+
unpaidCashCents: number;
|
|
242
|
+
/** Open (non-Settled) Event Promoter booking count for this person. */
|
|
243
|
+
openEventPromoterBookings: number;
|
|
244
|
+
payouts: HostPromoterPayoutSummary[];
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export type HostPromoterCreateInput = {
|
|
248
|
+
email: string;
|
|
249
|
+
displayName?: string | null;
|
|
250
|
+
kinds?: HostPromoterKind[];
|
|
251
|
+
notes?: string | null;
|
|
252
|
+
defaultBashPointsRewardPerTicket?: number | null;
|
|
253
|
+
influencerAudienceTier?: HostPromoterInfluencerTier | null;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export type HostPromoterUpdateInput = {
|
|
257
|
+
displayName?: string | null;
|
|
258
|
+
kinds?: HostPromoterKind[];
|
|
259
|
+
status?: HostPromoterStatus;
|
|
260
|
+
notes?: string | null;
|
|
261
|
+
defaultBashPointsRewardPerTicket?: number | null;
|
|
262
|
+
influencerAudienceTier?: HostPromoterInfluencerTier | null;
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export type HostPromoterInviteToBashInput = {
|
|
266
|
+
hostPromoterIds: string[];
|
|
267
|
+
bashEventId: string;
|
|
268
|
+
ticketTierId: string;
|
|
269
|
+
codePrefix?: string | null;
|
|
270
|
+
bashPointsRewardPerTicket?: number | null;
|
|
271
|
+
discountAmountPercentage?: number | null;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export type HostPromoterInviteResult = {
|
|
275
|
+
hostPromoterId: string;
|
|
276
|
+
email: string;
|
|
277
|
+
promoCodeId: string;
|
|
278
|
+
code: string;
|
|
279
|
+
error?: string;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export type HostPromoterPayoutCreateInput = {
|
|
283
|
+
amountCents: number;
|
|
284
|
+
method?: HostPromoterPayoutMethod;
|
|
285
|
+
note?: string | null;
|
|
286
|
+
bashEventId?: string | null;
|
|
287
|
+
serviceBookingId?: string | null;
|
|
288
|
+
channel?: HostPromoterPayoutChannel;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
export type HostPromoterPayoutUpdateInput = {
|
|
292
|
+
status?: HostPromoterPayoutStatus;
|
|
293
|
+
method?: HostPromoterPayoutMethod;
|
|
294
|
+
note?: string | null;
|
|
295
|
+
paidAt?: string | null;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export type HostPromoterEventBookingSummary = {
|
|
299
|
+
id: string;
|
|
300
|
+
bashEventId: string | null;
|
|
301
|
+
bashEventTitle: string | null;
|
|
302
|
+
promoterUserId: string | null;
|
|
303
|
+
promoterName: string;
|
|
304
|
+
promoterEmail: string | null;
|
|
305
|
+
hostPromoterId: string | null;
|
|
306
|
+
eventServiceSubType: string | null;
|
|
307
|
+
expectedAttendeesAtBooking: number | null;
|
|
308
|
+
checkedInAttendeesAtSettle: number | null;
|
|
309
|
+
maxAttendeesAttributable: number | null;
|
|
310
|
+
depositCents: number | null;
|
|
311
|
+
settlementDeltaCents: number | null;
|
|
312
|
+
settlementStatus: string;
|
|
313
|
+
settledAt: string | null;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
export type HostPromoterSettlementSummary = {
|
|
317
|
+
unpaidCashCents: number;
|
|
318
|
+
unpaidCashCount: number;
|
|
319
|
+
openEventPromoterBookings: HostPromoterEventBookingSummary[];
|
|
320
|
+
totalBashPointsEarned: number;
|
|
321
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -35,9 +35,11 @@ export * from "./hostCrmInsights.js";
|
|
|
35
35
|
export * from "./hostCrmAutomation.js";
|
|
36
36
|
export * from "./relationshipHealth.js";
|
|
37
37
|
export * from "./hostCrmConstants.js";
|
|
38
|
+
export * from "./hostPromoter.js";
|
|
38
39
|
export * from "./bashEventCancellationPolicy.js";
|
|
39
40
|
export * from "./userReportTypes.js";
|
|
40
41
|
export * from "./mirroredPrismaEnums.js";
|
|
42
|
+
export * from "./locationShare.js";
|
|
41
43
|
export * from "./utils/featuredDiscoveryGeo.js";
|
|
42
44
|
export * from "./utils/organizationEntitlements.js";
|
|
43
45
|
export * from "./serviceTypeLabels.js";
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event location sharing — consented near-live pins for bash participants
|
|
3
|
+
* (host / organizers / co-hosts, live task assignees, hired providers).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Prisma } from "@prisma/client";
|
|
7
|
+
|
|
8
|
+
import { PUBLIC_TEASER_USER_DATA_TO_SELECT } from "./extendedSchemas.js";
|
|
9
|
+
|
|
10
|
+
/** Grace after event end before an Active session auto-expires. */
|
|
11
|
+
export const LOCATION_SHARE_GRACE_MS = 4 * 60 * 60 * 1000;
|
|
12
|
+
|
|
13
|
+
/** Fallback TTL when the bash has no start/end times. */
|
|
14
|
+
export const LOCATION_SHARE_DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
15
|
+
|
|
16
|
+
/** Fallback window after start when end is missing. */
|
|
17
|
+
export const LOCATION_SHARE_START_FALLBACK_MS = 12 * 60 * 60 * 1000;
|
|
18
|
+
|
|
19
|
+
export const LocationShareStatus = {
|
|
20
|
+
Active: "Active",
|
|
21
|
+
Stopped: "Stopped",
|
|
22
|
+
Expired: "Expired",
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
export type LocationShareStatus =
|
|
26
|
+
(typeof LocationShareStatus)[keyof typeof LocationShareStatus];
|
|
27
|
+
|
|
28
|
+
/** EventTask statuses that allow location share (assignee). */
|
|
29
|
+
export const LOCATION_SHARE_TASK_STATUSES = [
|
|
30
|
+
"Accepted",
|
|
31
|
+
"Assigned",
|
|
32
|
+
"InProgress",
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
export type LocationShareTaskStatus =
|
|
36
|
+
(typeof LOCATION_SHARE_TASK_STATUSES)[number];
|
|
37
|
+
|
|
38
|
+
/** ServiceBooking statuses that allow location share (provider). */
|
|
39
|
+
export const LOCATION_SHARE_BOOKING_STATUSES = [
|
|
40
|
+
"Approved",
|
|
41
|
+
"Confirmed",
|
|
42
|
+
] as const;
|
|
43
|
+
|
|
44
|
+
export type LocationShareBookingStatus =
|
|
45
|
+
(typeof LOCATION_SHARE_BOOKING_STATUSES)[number];
|
|
46
|
+
|
|
47
|
+
export function isLocationShareLiveTaskStatus(
|
|
48
|
+
status: string | null | undefined
|
|
49
|
+
): status is LocationShareTaskStatus {
|
|
50
|
+
return (
|
|
51
|
+
status != null &&
|
|
52
|
+
(LOCATION_SHARE_TASK_STATUSES as readonly string[]).includes(status)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isLocationShareLiveBookingStatus(
|
|
57
|
+
status: string | null | undefined
|
|
58
|
+
): status is LocationShareBookingStatus {
|
|
59
|
+
return (
|
|
60
|
+
status != null &&
|
|
61
|
+
(LOCATION_SHARE_BOOKING_STATUSES as readonly string[]).includes(status)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* True when the user is the event creator or an AssociatedBash organizer/co-host.
|
|
67
|
+
* Super-users are intentionally not included.
|
|
68
|
+
*/
|
|
69
|
+
export function isLocationShareCrewMember(
|
|
70
|
+
userId: string | null | undefined,
|
|
71
|
+
bash: { creatorId: string },
|
|
72
|
+
association?: {
|
|
73
|
+
isOrganizer?: boolean | null;
|
|
74
|
+
isCoHost?: boolean | null;
|
|
75
|
+
} | null
|
|
76
|
+
): boolean {
|
|
77
|
+
if (!userId) return false;
|
|
78
|
+
if (userId === bash.creatorId) return true;
|
|
79
|
+
if (association?.isOrganizer === true || association?.isCoHost === true) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Sync eligibility when the caller already knows task/booking flags
|
|
87
|
+
* (e.g. tests or UI that prefetched live assignment). API resolves flags via Prisma.
|
|
88
|
+
*/
|
|
89
|
+
export function isLocationShareEventParticipant(
|
|
90
|
+
userId: string | null | undefined,
|
|
91
|
+
bash: { creatorId: string },
|
|
92
|
+
options?: {
|
|
93
|
+
association?: {
|
|
94
|
+
isOrganizer?: boolean | null;
|
|
95
|
+
isCoHost?: boolean | null;
|
|
96
|
+
} | null;
|
|
97
|
+
hasLiveTaskAssignment?: boolean;
|
|
98
|
+
hasLiveHiredBooking?: boolean;
|
|
99
|
+
/** Active promo-code promoter row for this bash (`Promoter.userId` + `isActive`). */
|
|
100
|
+
hasActivePromoterRole?: boolean;
|
|
101
|
+
}
|
|
102
|
+
): boolean {
|
|
103
|
+
if (
|
|
104
|
+
isLocationShareCrewMember(userId, bash, options?.association ?? null)
|
|
105
|
+
) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (!userId) return false;
|
|
109
|
+
return Boolean(
|
|
110
|
+
options?.hasLiveTaskAssignment ||
|
|
111
|
+
options?.hasLiveHiredBooking ||
|
|
112
|
+
options?.hasActivePromoterRole
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Compute session expiry from bash times (end + grace, else start + 12h, else now + 24h).
|
|
118
|
+
*/
|
|
119
|
+
export function computeLocationShareExpiresAt(
|
|
120
|
+
bash: {
|
|
121
|
+
startDateTime?: Date | string | null;
|
|
122
|
+
endDateTime?: Date | string | null;
|
|
123
|
+
},
|
|
124
|
+
now: Date = new Date()
|
|
125
|
+
): Date {
|
|
126
|
+
const end = bash.endDateTime ? new Date(bash.endDateTime) : null;
|
|
127
|
+
if (end && !Number.isNaN(end.getTime())) {
|
|
128
|
+
return new Date(end.getTime() + LOCATION_SHARE_GRACE_MS);
|
|
129
|
+
}
|
|
130
|
+
const start = bash.startDateTime ? new Date(bash.startDateTime) : null;
|
|
131
|
+
if (start && !Number.isNaN(start.getTime())) {
|
|
132
|
+
return new Date(start.getTime() + LOCATION_SHARE_START_FALLBACK_MS);
|
|
133
|
+
}
|
|
134
|
+
return new Date(now.getTime() + LOCATION_SHARE_DEFAULT_TTL_MS);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const LOCATION_SHARE_SESSION_USER_SELECT = {
|
|
138
|
+
...PUBLIC_TEASER_USER_DATA_TO_SELECT,
|
|
139
|
+
} satisfies Prisma.UserSelect;
|
|
140
|
+
|
|
141
|
+
export const LOCATION_SHARE_SESSION_LIST_SELECT = {
|
|
142
|
+
id: true,
|
|
143
|
+
bashEventId: true,
|
|
144
|
+
userId: true,
|
|
145
|
+
status: true,
|
|
146
|
+
startedAt: true,
|
|
147
|
+
expiresAt: true,
|
|
148
|
+
stoppedAt: true,
|
|
149
|
+
lastLat: true,
|
|
150
|
+
lastLng: true,
|
|
151
|
+
lastAccuracyM: true,
|
|
152
|
+
lastUpdatedAt: true,
|
|
153
|
+
user: { select: LOCATION_SHARE_SESSION_USER_SELECT },
|
|
154
|
+
} satisfies Prisma.LocationShareSessionSelect;
|
|
155
|
+
|
|
156
|
+
export type LocationShareSessionListItem = Prisma.LocationShareSessionGetPayload<{
|
|
157
|
+
select: typeof LOCATION_SHARE_SESSION_LIST_SELECT;
|
|
158
|
+
}>;
|
|
159
|
+
|
|
160
|
+
export type LocationShareMySession = Pick<
|
|
161
|
+
LocationShareSessionListItem,
|
|
162
|
+
| "id"
|
|
163
|
+
| "bashEventId"
|
|
164
|
+
| "userId"
|
|
165
|
+
| "status"
|
|
166
|
+
| "startedAt"
|
|
167
|
+
| "expiresAt"
|
|
168
|
+
| "stoppedAt"
|
|
169
|
+
| "lastLat"
|
|
170
|
+
| "lastLng"
|
|
171
|
+
| "lastAccuracyM"
|
|
172
|
+
| "lastUpdatedAt"
|
|
173
|
+
>;
|