@bash-app/bash-common 30.215.0 → 30.217.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/definitions.d.ts +7 -6
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js.map +1 -1
- package/dist/extendedSchemas.d.ts +100 -0
- package/dist/extendedSchemas.d.ts.map +1 -1
- package/dist/extendedSchemas.js +2 -0
- package/dist/extendedSchemas.js.map +1 -1
- 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/legalTemplates.d.ts +11 -0
- package/dist/legalTemplates.d.ts.map +1 -0
- package/dist/legalTemplates.js +19 -0
- package/dist/legalTemplates.js.map +1 -0
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/service/__tests__/apiServiceBookingApiUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/apiServiceBookingApiUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/apiServiceBookingApiUtils.test.js +101 -0
- package/dist/utils/service/__tests__/apiServiceBookingApiUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.d.ts +2 -0
- package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.js +125 -0
- package/dist/utils/service/__tests__/cancellationPolicyRefundResolver.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceBookingStatusUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceBookingStatusUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceBookingStatusUtils.test.js +226 -0
- package/dist/utils/service/__tests__/serviceBookingStatusUtils.test.js.map +1 -0
- package/dist/utils/service/__tests__/serviceRateUtils.test.d.ts +2 -0
- package/dist/utils/service/__tests__/serviceRateUtils.test.d.ts.map +1 -0
- package/dist/utils/service/__tests__/serviceRateUtils.test.js +237 -0
- package/dist/utils/service/__tests__/serviceRateUtils.test.js.map +1 -0
- package/dist/utils/service/serviceDBUtils.d.ts +11 -6
- package/dist/utils/service/serviceDBUtils.d.ts.map +1 -1
- package/dist/utils/service/serviceDBUtils.js +9 -4
- package/dist/utils/service/serviceDBUtils.js.map +1 -1
- package/dist/utils/service/serviceUtils.d.ts +5 -0
- package/dist/utils/service/serviceUtils.d.ts.map +1 -1
- package/dist/utils/service/serviceUtils.js +8 -1
- package/dist/utils/service/serviceUtils.js.map +1 -1
- package/dist/utmAttribution.d.ts +1 -5
- package/dist/utmAttribution.d.ts.map +1 -1
- package/dist/utmAttribution.js.map +1 -1
- package/dist/venueLoyaltyRedemption.d.ts +21 -0
- package/dist/venueLoyaltyRedemption.d.ts.map +1 -0
- package/dist/venueLoyaltyRedemption.js +5 -0
- package/dist/venueLoyaltyRedemption.js.map +1 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +115 -3
- package/src/definitions.ts +7 -7
- package/src/extendedSchemas.ts +3 -0
- package/src/index.ts +2 -0
- package/src/legalTemplates.ts +21 -0
- package/src/utils/index.ts +0 -1
- package/src/utils/service/__tests__/apiServiceBookingApiUtils.test.ts +128 -0
- package/src/utils/service/__tests__/cancellationPolicyRefundResolver.test.ts +170 -0
- package/src/utils/service/__tests__/serviceBookingStatusUtils.test.ts +273 -0
- package/src/utils/service/__tests__/serviceRateUtils.test.ts +288 -0
- package/src/utils/service/serviceDBUtils.ts +13 -8
- package/src/utils/service/serviceUtils.ts +8 -0
- package/src/utmAttribution.ts +1 -5
- package/src/venueLoyaltyRedemption.ts +22 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { ServiceRatePricingType, ServiceRateType } from "@prisma/client";
|
|
2
|
+
import { DateTime } from "luxon";
|
|
3
|
+
import {
|
|
4
|
+
SERVICE_DAILY_RATE_HOURS_MIN,
|
|
5
|
+
serviceBookingCalcDayPriceBreakdown,
|
|
6
|
+
serviceBookingPriceBreakdownGetTypeText,
|
|
7
|
+
getServiceRatePricingInfo,
|
|
8
|
+
serviceRatesFilter,
|
|
9
|
+
serviceRatesHasRates,
|
|
10
|
+
} from "../serviceRateUtils";
|
|
11
|
+
import type { ServiceRatesLuxon } from "../serviceRateUtils";
|
|
12
|
+
import type { LuxonDateRange } from "../../luxonUtils";
|
|
13
|
+
import type { ServiceRate } from "@prisma/client";
|
|
14
|
+
import type { ServiceSpecialRatesExtT, ServiceDailyRatesExtT } from "../serviceRateTypes";
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Helpers
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
function makeRange(startISO: string, endISO: string): LuxonDateRange {
|
|
21
|
+
return {
|
|
22
|
+
start: DateTime.fromISO(startISO, { zone: "utc" }),
|
|
23
|
+
end: DateTime.fromISO(endISO, { zone: "utc" }),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function makeRate(overrides: Partial<ServiceRate> = {}): ServiceRate {
|
|
28
|
+
return {
|
|
29
|
+
id: "r1",
|
|
30
|
+
rateType: ServiceRateType.General,
|
|
31
|
+
hourlyRateCents: null,
|
|
32
|
+
dailyRateCents: null,
|
|
33
|
+
flatRateCents: null,
|
|
34
|
+
serviceRatesAssociationId: "sra1",
|
|
35
|
+
createdAt: new Date(),
|
|
36
|
+
updatedAt: new Date(),
|
|
37
|
+
...overrides,
|
|
38
|
+
} as ServiceRate;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const RANGE_8H = makeRange("2025-07-01T09:00:00Z", "2025-07-01T17:00:00Z");
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// serviceBookingPriceBreakdownGetTypeText
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
describe("serviceBookingPriceBreakdownGetTypeText", () => {
|
|
48
|
+
it("returns rateType + space + pricingType", () => {
|
|
49
|
+
const info = {
|
|
50
|
+
rateType: ServiceRateType.General,
|
|
51
|
+
pricingType: ServiceRatePricingType.Hourly,
|
|
52
|
+
hours: 2,
|
|
53
|
+
piTotalCents: 200,
|
|
54
|
+
rateCents: 100,
|
|
55
|
+
unitCount: 2,
|
|
56
|
+
dateTimeRange: RANGE_8H,
|
|
57
|
+
};
|
|
58
|
+
expect(serviceBookingPriceBreakdownGetTypeText(info)).toBe("General Hourly");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// serviceBookingCalcDayPriceBreakdown — flat rate
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
describe("serviceBookingCalcDayPriceBreakdown — flat rate", () => {
|
|
67
|
+
it("uses flat rate when flatRateCents is set, ignoring hourly", () => {
|
|
68
|
+
const rate = makeRate({ flatRateCents: 50000, hourlyRateCents: 10000 });
|
|
69
|
+
const result = serviceBookingCalcDayPriceBreakdown(RANGE_8H, [rate], false, 8);
|
|
70
|
+
|
|
71
|
+
expect(result.pricingType).toBe("Flat");
|
|
72
|
+
expect(result.rateCents).toBe(50000);
|
|
73
|
+
expect(result.piTotalCents).toBe(50000);
|
|
74
|
+
expect(result.unitCount).toBe(1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("assigns rateType from the matching rate", () => {
|
|
78
|
+
const rate = makeRate({ flatRateCents: 20000, rateType: ServiceRateType.Special });
|
|
79
|
+
const result = serviceBookingCalcDayPriceBreakdown(RANGE_8H, [rate], false, 4);
|
|
80
|
+
expect(result.rateType).toBe(ServiceRateType.Special);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// serviceBookingCalcDayPriceBreakdown — hourly rate
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
describe("serviceBookingCalcDayPriceBreakdown — hourly rate", () => {
|
|
89
|
+
it("calculates hourly total correctly", () => {
|
|
90
|
+
const rate = makeRate({ hourlyRateCents: 5000 });
|
|
91
|
+
const result = serviceBookingCalcDayPriceBreakdown(RANGE_8H, [rate], false, 3);
|
|
92
|
+
|
|
93
|
+
expect(result.pricingType).toBe(ServiceRatePricingType.Hourly);
|
|
94
|
+
expect(result.rateCents).toBe(5000);
|
|
95
|
+
expect(result.unitCount).toBe(3);
|
|
96
|
+
expect(result.piTotalCents).toBe(15000);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns zero total when no hourly rate available", () => {
|
|
100
|
+
const result = serviceBookingCalcDayPriceBreakdown(RANGE_8H, [], false, 3);
|
|
101
|
+
expect(result.piTotalCents).toBe(0);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// serviceBookingCalcDayPriceBreakdown — daily rate
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
describe("serviceBookingCalcDayPriceBreakdown — daily rate", () => {
|
|
110
|
+
it("calculates daily total based on full-day slots", () => {
|
|
111
|
+
const rate = makeRate({ dailyRateCents: 40000 });
|
|
112
|
+
// 16 hours = floor(16/8) = 2 days
|
|
113
|
+
const result = serviceBookingCalcDayPriceBreakdown(RANGE_8H, [rate], true, 16);
|
|
114
|
+
|
|
115
|
+
expect(result.pricingType).toBe(ServiceRatePricingType.Daily);
|
|
116
|
+
expect(result.rateCents).toBe(40000);
|
|
117
|
+
expect(result.unitCount).toBe(2);
|
|
118
|
+
expect(result.piTotalCents).toBe(80000);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("falls back to hourly when isFullDay=true but no daily rate available", () => {
|
|
122
|
+
const rate = makeRate({ hourlyRateCents: 3000 });
|
|
123
|
+
const result = serviceBookingCalcDayPriceBreakdown(RANGE_8H, [rate], true, 8);
|
|
124
|
+
expect(result.pricingType).toBe(ServiceRatePricingType.Hourly);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// getServiceRatePricingInfo
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
describe("getServiceRatePricingInfo", () => {
|
|
133
|
+
it("returns hourly pricing when hours < SERVICE_DAILY_RATE_HOURS_MIN", () => {
|
|
134
|
+
const generalRate = makeRate({ hourlyRateCents: 2000 });
|
|
135
|
+
const result = getServiceRatePricingInfo(RANGE_8H, generalRate, [], [], "UTC", 4);
|
|
136
|
+
expect(result.pricingType).toBe(ServiceRatePricingType.Hourly);
|
|
137
|
+
expect(result.piTotalCents).toBe(8000);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("returns daily pricing when hours >= SERVICE_DAILY_RATE_HOURS_MIN", () => {
|
|
141
|
+
const generalRate = makeRate({ dailyRateCents: 30000 });
|
|
142
|
+
const result = getServiceRatePricingInfo(RANGE_8H, generalRate, [], [], "UTC", SERVICE_DAILY_RATE_HOURS_MIN);
|
|
143
|
+
expect(result.pricingType).toBe(ServiceRatePricingType.Daily);
|
|
144
|
+
expect(result.piTotalCents).toBe(30000);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("prefers special rate over general rate", () => {
|
|
148
|
+
const generalRate = makeRate({ hourlyRateCents: 1000 });
|
|
149
|
+
const specialRate = {
|
|
150
|
+
id: "sp1",
|
|
151
|
+
isAvailable: true,
|
|
152
|
+
serviceRate: makeRate({ hourlyRateCents: 9999 }),
|
|
153
|
+
dateTimeRange: RANGE_8H,
|
|
154
|
+
} as unknown as ServiceSpecialRatesExtT;
|
|
155
|
+
|
|
156
|
+
const result = getServiceRatePricingInfo(RANGE_8H, generalRate, [], [specialRate], "UTC", 2);
|
|
157
|
+
expect(result.rateCents).toBe(9999);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// serviceRatesFilter
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
describe("serviceRatesFilter", () => {
|
|
166
|
+
const range = makeRange("2025-07-01T09:00:00Z", "2025-07-01T17:00:00Z");
|
|
167
|
+
|
|
168
|
+
it("keeps special rates that intersect and are available", () => {
|
|
169
|
+
const specialRate: ServiceSpecialRatesExtT = {
|
|
170
|
+
id: "sp1",
|
|
171
|
+
isAvailable: true,
|
|
172
|
+
serviceRate: makeRate({ hourlyRateCents: 5000 }),
|
|
173
|
+
dateTimeRange: makeRange("2025-07-01T10:00:00Z", "2025-07-01T14:00:00Z"),
|
|
174
|
+
} as unknown as ServiceSpecialRatesExtT;
|
|
175
|
+
|
|
176
|
+
const rates: ServiceRatesLuxon = {
|
|
177
|
+
generalRate: null,
|
|
178
|
+
specialRates: [specialRate],
|
|
179
|
+
dailyRates: [],
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const result = serviceRatesFilter(rates, [range]);
|
|
183
|
+
expect(result.specialRates).toHaveLength(1);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("excludes special rates that are unavailable", () => {
|
|
187
|
+
const blockedRate: ServiceSpecialRatesExtT = {
|
|
188
|
+
id: "sp2",
|
|
189
|
+
isAvailable: false,
|
|
190
|
+
serviceRate: makeRate(),
|
|
191
|
+
dateTimeRange: range,
|
|
192
|
+
} as unknown as ServiceSpecialRatesExtT;
|
|
193
|
+
|
|
194
|
+
const rates: ServiceRatesLuxon = {
|
|
195
|
+
generalRate: null,
|
|
196
|
+
specialRates: [blockedRate],
|
|
197
|
+
dailyRates: [],
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const result = serviceRatesFilter(rates, [range]);
|
|
201
|
+
expect(result.specialRates).toHaveLength(0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("excludes special rates outside the requested range", () => {
|
|
205
|
+
const outsideRate: ServiceSpecialRatesExtT = {
|
|
206
|
+
id: "sp3",
|
|
207
|
+
isAvailable: true,
|
|
208
|
+
serviceRate: makeRate({ hourlyRateCents: 5000 }),
|
|
209
|
+
dateTimeRange: makeRange("2025-08-01T09:00:00Z", "2025-08-01T17:00:00Z"),
|
|
210
|
+
} as unknown as ServiceSpecialRatesExtT;
|
|
211
|
+
|
|
212
|
+
const rates: ServiceRatesLuxon = {
|
|
213
|
+
generalRate: null,
|
|
214
|
+
specialRates: [outsideRate],
|
|
215
|
+
dailyRates: [],
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const result = serviceRatesFilter(rates, [range]);
|
|
219
|
+
expect(result.specialRates).toHaveLength(0);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("preserves the generalRate unchanged", () => {
|
|
223
|
+
const generalRate = makeRate({ hourlyRateCents: 1000 });
|
|
224
|
+
const rates: ServiceRatesLuxon = {
|
|
225
|
+
generalRate,
|
|
226
|
+
specialRates: [],
|
|
227
|
+
dailyRates: [],
|
|
228
|
+
};
|
|
229
|
+
expect(serviceRatesFilter(rates, [range]).generalRate).toBe(generalRate);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
// serviceRatesHasRates
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
describe("serviceRatesHasRates", () => {
|
|
238
|
+
it("returns true when generalRate has hourly rate", () => {
|
|
239
|
+
const rates: ServiceRatesLuxon = {
|
|
240
|
+
generalRate: makeRate({ hourlyRateCents: 1000 }),
|
|
241
|
+
specialRates: [],
|
|
242
|
+
dailyRates: [],
|
|
243
|
+
};
|
|
244
|
+
expect(serviceRatesHasRates(rates)).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("returns true when generalRate has flat rate", () => {
|
|
248
|
+
const rates: ServiceRatesLuxon = {
|
|
249
|
+
generalRate: makeRate({ flatRateCents: 5000 }),
|
|
250
|
+
specialRates: [],
|
|
251
|
+
dailyRates: [],
|
|
252
|
+
};
|
|
253
|
+
expect(serviceRatesHasRates(rates)).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("returns false when all rates are zero/null", () => {
|
|
257
|
+
const rates: ServiceRatesLuxon = {
|
|
258
|
+
generalRate: makeRate(),
|
|
259
|
+
specialRates: [],
|
|
260
|
+
dailyRates: [],
|
|
261
|
+
};
|
|
262
|
+
expect(serviceRatesHasRates(rates)).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("returns true when a special rate has a daily rate", () => {
|
|
266
|
+
const specialRate: ServiceSpecialRatesExtT = {
|
|
267
|
+
id: "sp1",
|
|
268
|
+
isAvailable: true,
|
|
269
|
+
serviceRate: makeRate({ dailyRateCents: 10000 }),
|
|
270
|
+
dateTimeRange: RANGE_8H,
|
|
271
|
+
} as unknown as ServiceSpecialRatesExtT;
|
|
272
|
+
const rates: ServiceRatesLuxon = {
|
|
273
|
+
generalRate: null,
|
|
274
|
+
specialRates: [specialRate],
|
|
275
|
+
dailyRates: [],
|
|
276
|
+
};
|
|
277
|
+
expect(serviceRatesHasRates(rates)).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("returns falsy when generalRate is null and no special/daily rates", () => {
|
|
281
|
+
const rates: ServiceRatesLuxon = {
|
|
282
|
+
generalRate: null,
|
|
283
|
+
specialRates: [],
|
|
284
|
+
dailyRates: [],
|
|
285
|
+
};
|
|
286
|
+
expect(serviceRatesHasRates(rates)).toBeFalsy();
|
|
287
|
+
});
|
|
288
|
+
});
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import { ServiceExt } from "../../extendedSchemas.js";
|
|
2
|
-
import { DeepPartial } from "../typeUtils.js";
|
|
1
|
+
import type { ServiceExt } from "../../extendedSchemas.js";
|
|
2
|
+
import type { DeepPartial } from "../typeUtils.js";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Maps a service record from the API / DB into the client `ServiceExt` shape
|
|
6
|
+
* (e.g. date normalization). Identity until a stricter transform is needed.
|
|
7
|
+
*/
|
|
8
|
+
export function serviceFromDb<T extends ServiceExt | null | undefined>(
|
|
9
|
+
service: T
|
|
10
|
+
): T {
|
|
8
11
|
return service;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Maps a partial `ServiceExt` from the client for upsert payloads.
|
|
16
|
+
*/
|
|
17
|
+
export function serviceToDb<T extends DeepPartial<ServiceExt>>(service: T): T {
|
|
13
18
|
return service;
|
|
14
19
|
}
|
|
@@ -915,3 +915,11 @@ export function serviceRelevantData(
|
|
|
915
915
|
)
|
|
916
916
|
);
|
|
917
917
|
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Returns the contextually correct label for a service's sellable items.
|
|
921
|
+
* Vendors sell "Products"; entertainers and other service types sell "Merch".
|
|
922
|
+
*/
|
|
923
|
+
export function getVendorSalesLabel(serviceType: ServiceTypes): "Products" | "Merch" {
|
|
924
|
+
return serviceType === ServiceTypes.Vendors ? "Products" : "Merch";
|
|
925
|
+
}
|
package/src/utmAttribution.ts
CHANGED
|
@@ -16,11 +16,7 @@ const SESSION_KEYS = {
|
|
|
16
16
|
} as const;
|
|
17
17
|
|
|
18
18
|
/** Normalize optional UTM strings from API args (Stripe checkout / payment intent body). */
|
|
19
|
-
export function utmFieldsFromCheckoutArgs(args: {
|
|
20
|
-
utmSource?: string;
|
|
21
|
-
utmMedium?: string;
|
|
22
|
-
utmCampaign?: string;
|
|
23
|
-
}): UtmFields | undefined {
|
|
19
|
+
export function utmFieldsFromCheckoutArgs(args: UtmFields): UtmFields | undefined {
|
|
24
20
|
const s = args.utmSource?.trim();
|
|
25
21
|
const m = args.utmMedium?.trim();
|
|
26
22
|
const c = args.utmCampaign?.trim();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API shapes for venue loyalty perk requests (guest claim + host list/redeem).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface VenueLoyaltyRedemptionGuestDto {
|
|
6
|
+
id: string;
|
|
7
|
+
venueId: string;
|
|
8
|
+
userId: string;
|
|
9
|
+
templateId: string;
|
|
10
|
+
claimedAt: string;
|
|
11
|
+
redeemedAt: string | null;
|
|
12
|
+
guestNotes: string | null;
|
|
13
|
+
hostNotes: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface VenueLoyaltyRedemptionListItem extends VenueLoyaltyRedemptionGuestDto {
|
|
17
|
+
user: {
|
|
18
|
+
email: string;
|
|
19
|
+
givenName: string | null;
|
|
20
|
+
familyName: string | null;
|
|
21
|
+
};
|
|
22
|
+
}
|