@kahitsan/ksui 0.36.1 → 0.37.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kahitsan/ksui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
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",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// fetchUrl prop threading — the default stays the vouchers plugin's own API so
|
|
2
|
+
// existing consumers are unaffected; a consumer without vouchers.view can
|
|
3
|
+
// point the picker at a peer proxy route with the same response shape.
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { fireEvent, render, waitFor } from "@solidjs/testing-library";
|
|
6
|
+
import VoucherPicker from "./VoucherPicker";
|
|
7
|
+
|
|
8
|
+
function mockFetchOnce(): typeof fetch {
|
|
9
|
+
const impl = vi.fn(async () => ({
|
|
10
|
+
ok: true,
|
|
11
|
+
json: async () => ({ data: [] }),
|
|
12
|
+
})) as unknown as typeof fetch;
|
|
13
|
+
vi.stubGlobal("fetch", impl);
|
|
14
|
+
return impl;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("VoucherPicker fetchUrl", () => {
|
|
18
|
+
it("defaults to the vouchers plugin's own API when fetchUrl is omitted", async () => {
|
|
19
|
+
const fetchMock = mockFetchOnce();
|
|
20
|
+
const { getByTestId } = render(() => (
|
|
21
|
+
<VoucherPicker selected={null} onChange={vi.fn()} subtotal={100} packageIds={[]} />
|
|
22
|
+
));
|
|
23
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
24
|
+
|
|
25
|
+
await waitFor(() => expect(fetchMock).toHaveBeenCalled());
|
|
26
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
27
|
+
"/api/vouchers?status=active&limit=200",
|
|
28
|
+
expect.objectContaining({ credentials: "include" }),
|
|
29
|
+
);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("fetches the overridden URL when fetchUrl is provided", async () => {
|
|
33
|
+
const fetchMock = mockFetchOnce();
|
|
34
|
+
const { getByTestId } = render(() => (
|
|
35
|
+
<VoucherPicker
|
|
36
|
+
selected={null}
|
|
37
|
+
onChange={vi.fn()}
|
|
38
|
+
subtotal={100}
|
|
39
|
+
packageIds={[]}
|
|
40
|
+
fetchUrl="/api/counter/vouchers"
|
|
41
|
+
/>
|
|
42
|
+
));
|
|
43
|
+
fireEvent.click(getByTestId("voucher-picker-trigger"));
|
|
44
|
+
|
|
45
|
+
await waitFor(() => expect(fetchMock).toHaveBeenCalled());
|
|
46
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
47
|
+
"/api/counter/vouchers",
|
|
48
|
+
expect.objectContaining({ credentials: "include" }),
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
// Vendored into plugin remotes.
|
|
2
2
|
//
|
|
3
|
-
// Cross-plugin picker: fetches
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// Cross-plugin picker: fetches vouchers over HTTP and degrades gracefully:
|
|
4
|
+
// when the endpoint isn't reachable the popup shows a "couldn't load" notice
|
|
5
|
+
// and the sale records with no voucher (the manual-discount field stays
|
|
6
|
+
// available). Defaults to the vouchers plugin's own public API
|
|
7
|
+
// (/api/vouchers); `fetchUrl` overrides it for a consumer that reaches
|
|
8
|
+
// vouchers through a peer proxy route instead (same response shape required).
|
|
7
9
|
|
|
8
10
|
import { Portal } from "solid-js/web";
|
|
9
11
|
import { createEffect, createMemo, createSignal, For, onCleanup, Show, type JSX } from "solid-js";
|
|
@@ -24,6 +26,8 @@ export interface VoucherOption {
|
|
|
24
26
|
is_active: boolean;
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
const DEFAULT_FETCH_URL = "/api/vouchers?status=active&limit=200";
|
|
30
|
+
|
|
27
31
|
interface VoucherPickerProps {
|
|
28
32
|
selected: VoucherOption | null;
|
|
29
33
|
onChange: (next: VoucherOption | null) => void;
|
|
@@ -31,6 +35,10 @@ interface VoucherPickerProps {
|
|
|
31
35
|
packageIds: number[];
|
|
32
36
|
disabled?: boolean;
|
|
33
37
|
compact?: boolean;
|
|
38
|
+
/** Same-shape endpoint override (defaults to the vouchers plugin's own API) —
|
|
39
|
+
* a consumer with no `vouchers.view` grant can point this at a peer proxy
|
|
40
|
+
* route instead. */
|
|
41
|
+
fetchUrl?: string;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
const POPUP_MAX_HEIGHT = 360;
|
|
@@ -105,7 +113,7 @@ export default function VoucherPicker(props: VoucherPickerProps): JSX.Element {
|
|
|
105
113
|
const token = ++activeFetchToken;
|
|
106
114
|
setLoading(true);
|
|
107
115
|
setError(null);
|
|
108
|
-
fetch(
|
|
116
|
+
fetch(props.fetchUrl ?? DEFAULT_FETCH_URL, { credentials: "include" })
|
|
109
117
|
.then((r) => {
|
|
110
118
|
if (!r.ok)
|
|
111
119
|
throw new Error(
|