@kahitsan/ksui 0.39.2 → 0.39.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahitsan/ksui",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.3",
|
|
4
4
|
"description": "ksui is a standalone set of SolidJS UI components for KahitSan/Hilinga and any SolidJS app. Published to the public npm registry and consumed as a normal dependency. Ships source under a `solid` export condition so the consumer's vite-plugin-solid compiles it with only solid-js externalized; it depends on nothing but solid-js + lucide-solid and injects its own CSS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -37,6 +37,7 @@ function voucher(over: Partial<VoucherOption> & Pick<VoucherOption, "id" | "code
|
|
|
37
37
|
value: 20,
|
|
38
38
|
max_discount_amount: null,
|
|
39
39
|
applicable_packages: null,
|
|
40
|
+
applicable_package_lineages: null,
|
|
40
41
|
minimum_purchase: 0,
|
|
41
42
|
valid_from: null,
|
|
42
43
|
valid_until: null,
|
|
@@ -379,6 +380,52 @@ describe("VoucherPicker dialog", () => {
|
|
|
379
380
|
);
|
|
380
381
|
});
|
|
381
382
|
|
|
383
|
+
it("accepts a voucher when each package matches by aligned lineage", async () => {
|
|
384
|
+
mockPagedFetch([
|
|
385
|
+
voucher({
|
|
386
|
+
id: 35,
|
|
387
|
+
code: "LINEAGEOK",
|
|
388
|
+
applicable_packages: [99],
|
|
389
|
+
applicable_package_lineages: ["day-pass"],
|
|
390
|
+
}),
|
|
391
|
+
]);
|
|
392
|
+
const { getByTestId } = render(() => (
|
|
393
|
+
<VoucherPicker
|
|
394
|
+
selected={null}
|
|
395
|
+
onChange={vi.fn()}
|
|
396
|
+
subtotal={1000}
|
|
397
|
+
packageIds={[1]}
|
|
398
|
+
packageLineages={["day-pass"]}
|
|
399
|
+
/>
|
|
400
|
+
));
|
|
401
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
402
|
+
await waitFor(() => expect(getByTestId("voucher-picker-result-35")).toBeTruthy());
|
|
403
|
+
});
|
|
404
|
+
it("rejects a voucher when aligned package lineage does not match", async () => {
|
|
405
|
+
mockPagedFetch([
|
|
406
|
+
voucher({
|
|
407
|
+
id: 36,
|
|
408
|
+
code: "LINEAGEBAD",
|
|
409
|
+
applicable_packages: [99],
|
|
410
|
+
applicable_package_lineages: ["day-pass"],
|
|
411
|
+
}),
|
|
412
|
+
]);
|
|
413
|
+
const { getByTestId, queryByTestId } = render(() => (
|
|
414
|
+
<VoucherPicker
|
|
415
|
+
selected={null}
|
|
416
|
+
onChange={vi.fn()}
|
|
417
|
+
subtotal={1000}
|
|
418
|
+
packageIds={[1]}
|
|
419
|
+
packageLineages={["single-use"]}
|
|
420
|
+
/>
|
|
421
|
+
));
|
|
422
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
423
|
+
await waitFor(() => expect(getByTestId("voucher-picker-inapplicable-36")).toBeTruthy());
|
|
424
|
+
expect(queryByTestId("voucher-picker-result-36")).toBeNull();
|
|
425
|
+
expect(getByTestId("voucher-picker-inapplicable-36").textContent).toContain(
|
|
426
|
+
"Doesn't cover every item",
|
|
427
|
+
);
|
|
428
|
+
});
|
|
382
429
|
it("surfaces a load failure instead of rendering an empty list", async () => {
|
|
383
430
|
vi.stubGlobal(
|
|
384
431
|
"fetch",
|
|
@@ -23,6 +23,7 @@ export interface VoucherOption {
|
|
|
23
23
|
value: string | number | null;
|
|
24
24
|
max_discount_amount: string | number | null;
|
|
25
25
|
applicable_packages: number[] | null;
|
|
26
|
+
applicable_package_lineages?: string[] | null;
|
|
26
27
|
minimum_purchase: string | number;
|
|
27
28
|
valid_from: string | null;
|
|
28
29
|
valid_until: string | null;
|
|
@@ -42,6 +43,7 @@ interface VoucherPickerProps {
|
|
|
42
43
|
onChange: (next: VoucherOption | null) => void;
|
|
43
44
|
subtotal: number;
|
|
44
45
|
packageIds: number[];
|
|
46
|
+
packageLineages?: (string | null)[];
|
|
45
47
|
disabled?: boolean;
|
|
46
48
|
compact?: boolean;
|
|
47
49
|
/** Same-shape endpoint override (defaults to the vouchers plugin's own API) —
|
|
@@ -104,6 +106,7 @@ function ineligibilityReason(
|
|
|
104
106
|
voucher: VoucherOption,
|
|
105
107
|
subtotal: number,
|
|
106
108
|
packageIds: number[],
|
|
109
|
+
packageLineages: (string | null)[],
|
|
107
110
|
todayIso: string,
|
|
108
111
|
): string | null {
|
|
109
112
|
if (!voucher.is_active) return "Inactive";
|
|
@@ -116,10 +119,17 @@ function ineligibilityReason(
|
|
|
116
119
|
if (usageExhausted(voucher)) return "Fully redeemed";
|
|
117
120
|
if (asNumber(voucher.minimum_purchase) > subtotal)
|
|
118
121
|
return `Needs ${formatCurrency(asNumber(voucher.minimum_purchase))} minimum`;
|
|
119
|
-
|
|
122
|
+
const allowedIds = voucher.applicable_packages ?? [];
|
|
123
|
+
const allowedLineages = voucher.applicable_package_lineages ?? [];
|
|
124
|
+
if (allowedIds.length > 0 || allowedLineages.length > 0) {
|
|
120
125
|
if (packageIds.length === 0) return "Only for specific items";
|
|
121
|
-
const allowed = new Set(
|
|
122
|
-
if (
|
|
126
|
+
const allowed = new Set(allowedIds);
|
|
127
|
+
if (packageIds.some((id, index) =>
|
|
128
|
+
!allowed.has(id) &&
|
|
129
|
+
!(packageLineages[index] != null && allowedLineages.includes(packageLineages[index]!))
|
|
130
|
+
)) {
|
|
131
|
+
return "Doesn't cover every item";
|
|
132
|
+
}
|
|
123
133
|
}
|
|
124
134
|
return null;
|
|
125
135
|
}
|
|
@@ -129,9 +139,10 @@ function isApplicable(
|
|
|
129
139
|
voucher: VoucherOption,
|
|
130
140
|
subtotal: number,
|
|
131
141
|
packageIds: number[],
|
|
142
|
+
packageLineages: (string | null)[],
|
|
132
143
|
todayIso: string,
|
|
133
144
|
): boolean {
|
|
134
|
-
return ineligibilityReason(voucher, subtotal, packageIds, todayIso) === null;
|
|
145
|
+
return ineligibilityReason(voucher, subtotal, packageIds, packageLineages, todayIso) === null;
|
|
135
146
|
}
|
|
136
147
|
|
|
137
148
|
function formatVoucherDescription(v: VoucherOption): string {
|
|
@@ -293,16 +304,16 @@ export default function VoucherPicker(props: VoucherPickerProps): JSX.Element {
|
|
|
293
304
|
|
|
294
305
|
const applicable = createMemo(() => {
|
|
295
306
|
const today_ = today();
|
|
296
|
-
return vouchers().filter((v) => isApplicable(v, props.subtotal, props.packageIds, today_));
|
|
307
|
+
return vouchers().filter((v) => isApplicable(v, props.subtotal, props.packageIds, props.packageLineages ?? [], today_));
|
|
297
308
|
});
|
|
298
309
|
|
|
299
310
|
const inapplicable = createMemo(() => {
|
|
300
311
|
const today_ = today();
|
|
301
312
|
return vouchers()
|
|
302
|
-
.filter((v) => !isApplicable(v, props.subtotal, props.packageIds, today_))
|
|
313
|
+
.filter((v) => !isApplicable(v, props.subtotal, props.packageIds, props.packageLineages ?? [], today_))
|
|
303
314
|
.map((v) => ({
|
|
304
315
|
voucher: v,
|
|
305
|
-
reason: ineligibilityReason(v, props.subtotal, props.packageIds, today_) ?? "",
|
|
316
|
+
reason: ineligibilityReason(v, props.subtotal, props.packageIds, props.packageLineages ?? [], today_) ?? "",
|
|
306
317
|
}));
|
|
307
318
|
});
|
|
308
319
|
|