@huskel/sdk 0.4.2 → 0.4.6
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/index.css +1045 -159
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +66 -28
- package/dist/index.d.ts +66 -28
- package/dist/index.js +887 -277
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +879 -272
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
package/dist/index.mjs
CHANGED
|
@@ -63,7 +63,15 @@ var HuskelAPI = class {
|
|
|
63
63
|
});
|
|
64
64
|
if (!res.ok) {
|
|
65
65
|
const text = await res.text();
|
|
66
|
-
|
|
66
|
+
let message = text;
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(text);
|
|
69
|
+
if (parsed && typeof parsed.error === "string") {
|
|
70
|
+
message = parsed.error;
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
}
|
|
74
|
+
const err = { status: res.status, message };
|
|
67
75
|
if (res.status >= 400 && res.status < 500) {
|
|
68
76
|
log("error", `${path} failed [${res.status}]`, text);
|
|
69
77
|
throw err;
|
|
@@ -114,6 +122,52 @@ var HuskelAPI = class {
|
|
|
114
122
|
log("info", "chat query", query);
|
|
115
123
|
return this.post("/chat", { query, siteId: this.siteId, history });
|
|
116
124
|
}
|
|
125
|
+
// --- Cart System ---
|
|
126
|
+
buildHeaders() {
|
|
127
|
+
var _a, _b;
|
|
128
|
+
const headers = {
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
"X-Huskel-Token": this.apiToken,
|
|
131
|
+
"X-Huskel-Site": this.siteId
|
|
132
|
+
};
|
|
133
|
+
const shopperId = (_a = this.getShopperId) == null ? void 0 : _a.call(this);
|
|
134
|
+
if (shopperId) headers["X-Huskel-Shopper-Id"] = shopperId;
|
|
135
|
+
const sessionId = (_b = this.getSessionId) == null ? void 0 : _b.call(this);
|
|
136
|
+
if (sessionId) headers["X-Huskel-Session-Id"] = sessionId;
|
|
137
|
+
return headers;
|
|
138
|
+
}
|
|
139
|
+
async getCart() {
|
|
140
|
+
const res = await fetch(`${this.apiUrl}/cart?siteId=${this.siteId}`, {
|
|
141
|
+
headers: this.buildHeaders()
|
|
142
|
+
});
|
|
143
|
+
if (!res.ok) throw new Error("Failed to fetch cart");
|
|
144
|
+
return res.json();
|
|
145
|
+
}
|
|
146
|
+
async clearCart() {
|
|
147
|
+
const res = await fetch(`${this.apiUrl}/cart?siteId=${this.siteId}`, {
|
|
148
|
+
method: "DELETE",
|
|
149
|
+
headers: this.buildHeaders()
|
|
150
|
+
});
|
|
151
|
+
if (!res.ok) throw new Error("Failed to clear cart");
|
|
152
|
+
return res.json();
|
|
153
|
+
}
|
|
154
|
+
async checkoutCart() {
|
|
155
|
+
const res = await fetch(`${this.apiUrl}/cart/checkout`, {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: this.buildHeaders(),
|
|
158
|
+
body: JSON.stringify({ siteId: this.siteId })
|
|
159
|
+
});
|
|
160
|
+
if (!res.ok) throw new Error("Failed to checkout cart");
|
|
161
|
+
return res.json();
|
|
162
|
+
}
|
|
163
|
+
async getCheckoutConfig() {
|
|
164
|
+
const res = await fetch(`${this.apiUrl}/checkout/config?site_id=${this.siteId}`, {
|
|
165
|
+
method: "GET",
|
|
166
|
+
headers: this.buildHeaders()
|
|
167
|
+
});
|
|
168
|
+
if (!res.ok) throw new Error("Failed to fetch checkout config");
|
|
169
|
+
return res.json();
|
|
170
|
+
}
|
|
117
171
|
};
|
|
118
172
|
|
|
119
173
|
// src/client.ts
|
|
@@ -221,13 +275,14 @@ var _HuskelClient = class _HuskelClient {
|
|
|
221
275
|
if (!apiUrl) console.error('[Huskel] Missing apiUrl. Set it via <HuskelProvider apiUrl="..."> or NEXT_PUBLIC_HUSKEL_API_URL.');
|
|
222
276
|
if (!apiToken) console.error('[Huskel] Missing apiToken. Set it via <HuskelProvider apiToken="..."> or NEXT_PUBLIC_HUSKEL_API_TOKEN.');
|
|
223
277
|
this.shopperId = config.shopperId;
|
|
278
|
+
this.onCheckout = config.onCheckout;
|
|
224
279
|
this.initSession();
|
|
225
280
|
this.loadIngestedCache();
|
|
226
281
|
this.api = new HuskelAPI(
|
|
227
282
|
apiUrl,
|
|
228
283
|
siteId,
|
|
229
284
|
apiToken,
|
|
230
|
-
() => this.
|
|
285
|
+
() => this.getShopperId(),
|
|
231
286
|
() => this.sessionId
|
|
232
287
|
);
|
|
233
288
|
instance = this;
|
|
@@ -264,11 +319,14 @@ var _HuskelClient = class _HuskelClient {
|
|
|
264
319
|
} catch (e) {
|
|
265
320
|
}
|
|
266
321
|
}
|
|
322
|
+
reRegister() {
|
|
323
|
+
instance = this;
|
|
324
|
+
}
|
|
267
325
|
setShopperId(id) {
|
|
268
326
|
this.shopperId = id;
|
|
269
327
|
}
|
|
270
328
|
getShopperId() {
|
|
271
|
-
return this.shopperId;
|
|
329
|
+
return this.shopperId || "guest_" + this.sessionId;
|
|
272
330
|
}
|
|
273
331
|
getSessionId() {
|
|
274
332
|
return this.sessionId;
|
|
@@ -397,11 +455,17 @@ function HuskelProvider({ siteId, apiUrl, apiToken, shopperId, children }) {
|
|
|
397
455
|
const clientRef = useRef2(null);
|
|
398
456
|
if (!clientRef.current) {
|
|
399
457
|
clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken, shopperId });
|
|
458
|
+
} else {
|
|
459
|
+
clientRef.current.reRegister();
|
|
400
460
|
}
|
|
401
461
|
useEffect(() => {
|
|
402
462
|
var _a;
|
|
403
463
|
(_a = clientRef.current) == null ? void 0 : _a.setShopperId(shopperId);
|
|
404
464
|
}, [shopperId]);
|
|
465
|
+
useEffect(() => {
|
|
466
|
+
var _a;
|
|
467
|
+
(_a = clientRef.current) == null ? void 0 : _a.reRegister();
|
|
468
|
+
}, []);
|
|
405
469
|
useEffect(() => {
|
|
406
470
|
return () => {
|
|
407
471
|
var _a;
|
|
@@ -433,6 +497,7 @@ function useSearch() {
|
|
|
433
497
|
return;
|
|
434
498
|
}
|
|
435
499
|
const gen = ++genRef.current;
|
|
500
|
+
setLoading(true);
|
|
436
501
|
setError(null);
|
|
437
502
|
try {
|
|
438
503
|
const res = await client.api.searchAutocomplete(query, limit);
|
|
@@ -441,7 +506,15 @@ function useSearch() {
|
|
|
441
506
|
}
|
|
442
507
|
} catch (e) {
|
|
443
508
|
if (gen === genRef.current) {
|
|
444
|
-
|
|
509
|
+
let msg = (_b = e == null ? void 0 : e.message) != null ? _b : "Search failed";
|
|
510
|
+
try {
|
|
511
|
+
const parsed = JSON.parse(msg);
|
|
512
|
+
if (parsed && parsed.error) {
|
|
513
|
+
msg = parsed.error;
|
|
514
|
+
}
|
|
515
|
+
} catch (e2) {
|
|
516
|
+
}
|
|
517
|
+
setError(msg);
|
|
445
518
|
}
|
|
446
519
|
} finally {
|
|
447
520
|
if (gen === genRef.current) setLoading(false);
|
|
@@ -516,13 +589,13 @@ function useChat() {
|
|
|
516
589
|
const [loading, setLoading] = useState3(false);
|
|
517
590
|
const [error, setError] = useState3(null);
|
|
518
591
|
const abortRef = useRef5(null);
|
|
519
|
-
const send = useCallback3(async (query) => {
|
|
520
|
-
var _a, _b, _c;
|
|
592
|
+
const send = useCallback3(async (query, displayQuery) => {
|
|
593
|
+
var _a, _b, _c, _d, _e;
|
|
521
594
|
if (!query.trim() || loading) return;
|
|
522
595
|
(_a = abortRef.current) == null ? void 0 : _a.abort();
|
|
523
596
|
abortRef.current = new AbortController();
|
|
524
597
|
const signal = abortRef.current.signal;
|
|
525
|
-
const userMsg = { role: "user", content: query };
|
|
598
|
+
const userMsg = { role: "user", content: displayQuery != null ? displayQuery : query };
|
|
526
599
|
setMessages((prev) => [...prev, userMsg]);
|
|
527
600
|
setLoading(true);
|
|
528
601
|
setError(null);
|
|
@@ -548,9 +621,30 @@ function useChat() {
|
|
|
548
621
|
}
|
|
549
622
|
if (signal.aborted) return;
|
|
550
623
|
setSources((_b = res.sources) != null ? _b : []);
|
|
624
|
+
if (((_c = res.action) == null ? void 0 : _c.type) === "add_to_cart" || res.checkout) {
|
|
625
|
+
if (typeof window !== "undefined") {
|
|
626
|
+
window.dispatchEvent(new CustomEvent("huskel:cart_updated", { detail: res.checkout }));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (((_d = res.action) == null ? void 0 : _d.type) === "checkout") {
|
|
630
|
+
if (typeof window !== "undefined") {
|
|
631
|
+
window.dispatchEvent(new CustomEvent("huskel:trigger_checkout", { detail: res.checkout }));
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (res.checkout && client.onCheckout) {
|
|
635
|
+
client.onCheckout(res.checkout);
|
|
636
|
+
}
|
|
551
637
|
} catch (e) {
|
|
552
638
|
if (signal.aborted) return;
|
|
553
|
-
|
|
639
|
+
let msg = (_e = e == null ? void 0 : e.message) != null ? _e : "Chat request failed";
|
|
640
|
+
try {
|
|
641
|
+
const parsed = JSON.parse(msg);
|
|
642
|
+
if (parsed && parsed.error) {
|
|
643
|
+
msg = parsed.error;
|
|
644
|
+
}
|
|
645
|
+
} catch (e2) {
|
|
646
|
+
}
|
|
647
|
+
setError(msg);
|
|
554
648
|
setMessages((prev) => prev.slice(0, -1));
|
|
555
649
|
} finally {
|
|
556
650
|
if (!signal.aborted) {
|
|
@@ -569,9 +663,54 @@ function useChat() {
|
|
|
569
663
|
return { messages, sources, loading, error, send, reset };
|
|
570
664
|
}
|
|
571
665
|
|
|
666
|
+
// src/hooks/useCart.ts
|
|
667
|
+
import { useState as useState4, useEffect as useEffect3, useCallback as useCallback4 } from "react";
|
|
668
|
+
function useCart() {
|
|
669
|
+
const client = useHuskelContext();
|
|
670
|
+
const [cart, setCart] = useState4(null);
|
|
671
|
+
const [loading, setLoading] = useState4(false);
|
|
672
|
+
const shopperId = client.getShopperId();
|
|
673
|
+
const fetchCart = useCallback4(async () => {
|
|
674
|
+
if (!shopperId) return;
|
|
675
|
+
setLoading(true);
|
|
676
|
+
try {
|
|
677
|
+
const res = await client.api.getCart();
|
|
678
|
+
setCart(res);
|
|
679
|
+
} catch (e) {
|
|
680
|
+
console.error("[Huskel] Failed to fetch cart", e);
|
|
681
|
+
} finally {
|
|
682
|
+
setLoading(false);
|
|
683
|
+
}
|
|
684
|
+
}, [client, shopperId]);
|
|
685
|
+
useEffect3(() => {
|
|
686
|
+
fetchCart();
|
|
687
|
+
const handleCartUpdate = (e) => {
|
|
688
|
+
if (e.detail) {
|
|
689
|
+
setCart(e.detail);
|
|
690
|
+
} else {
|
|
691
|
+
fetchCart();
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
if (typeof window !== "undefined") {
|
|
695
|
+
window.addEventListener("huskel:cart_updated", handleCartUpdate);
|
|
696
|
+
return () => window.removeEventListener("huskel:cart_updated", handleCartUpdate);
|
|
697
|
+
}
|
|
698
|
+
}, [fetchCart, shopperId]);
|
|
699
|
+
return { cart, loading, fetchCart };
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// src/components/SearchBar.tsx
|
|
703
|
+
import { useState as useState5, useEffect as useEffect4, useRef as useRef6 } from "react";
|
|
704
|
+
|
|
705
|
+
// src/utils/cn.ts
|
|
706
|
+
import { clsx } from "clsx";
|
|
707
|
+
import { twMerge } from "tailwind-merge";
|
|
708
|
+
function cn(...inputs) {
|
|
709
|
+
return twMerge(clsx(inputs));
|
|
710
|
+
}
|
|
711
|
+
|
|
572
712
|
// src/components/SearchBar.tsx
|
|
573
|
-
import {
|
|
574
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
713
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
575
714
|
var SearchIcon = () => /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
576
715
|
/* @__PURE__ */ jsx2("circle", { cx: "8.5", cy: "8.5", r: "5.5" }),
|
|
577
716
|
/* @__PURE__ */ jsx2("line", { x1: "13", y1: "13", x2: "18", y2: "18" })
|
|
@@ -579,7 +718,7 @@ var SearchIcon = () => /* @__PURE__ */ jsxs("svg", { width: "15", height: "15",
|
|
|
579
718
|
function SearchBar({
|
|
580
719
|
placeholder = "Search products\u2026",
|
|
581
720
|
limit = 10,
|
|
582
|
-
debounceMs =
|
|
721
|
+
debounceMs = 300,
|
|
583
722
|
onSelect,
|
|
584
723
|
className,
|
|
585
724
|
inputClassName,
|
|
@@ -588,25 +727,35 @@ function SearchBar({
|
|
|
588
727
|
theme,
|
|
589
728
|
classNames = {}
|
|
590
729
|
}) {
|
|
591
|
-
const [query, setQuery] =
|
|
592
|
-
const [open, setOpen] =
|
|
730
|
+
const [query, setQuery] = useState5("");
|
|
731
|
+
const [open, setOpen] = useState5(false);
|
|
732
|
+
const [isDebouncing, setIsDebouncing] = useState5(false);
|
|
593
733
|
const { results, loading, search, clear } = useSearch();
|
|
734
|
+
const client = useHuskelContext();
|
|
594
735
|
const timer = useRef6();
|
|
595
736
|
const wrap = useRef6(null);
|
|
596
|
-
|
|
737
|
+
const ignoreNextQueryChange = useRef6(false);
|
|
738
|
+
useEffect4(() => {
|
|
739
|
+
if (ignoreNextQueryChange.current) {
|
|
740
|
+
ignoreNextQueryChange.current = false;
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
597
743
|
clearTimeout(timer.current);
|
|
598
744
|
if (!query.trim()) {
|
|
599
745
|
clear();
|
|
600
746
|
setOpen(false);
|
|
747
|
+
setIsDebouncing(false);
|
|
601
748
|
return;
|
|
602
749
|
}
|
|
603
750
|
setOpen(true);
|
|
751
|
+
setIsDebouncing(true);
|
|
604
752
|
timer.current = setTimeout(() => {
|
|
753
|
+
setIsDebouncing(false);
|
|
605
754
|
search(query, limit);
|
|
606
755
|
}, debounceMs);
|
|
607
756
|
return () => clearTimeout(timer.current);
|
|
608
757
|
}, [query]);
|
|
609
|
-
|
|
758
|
+
useEffect4(() => {
|
|
610
759
|
const h = (e) => {
|
|
611
760
|
if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
|
|
612
761
|
};
|
|
@@ -614,75 +763,220 @@ function SearchBar({
|
|
|
614
763
|
return () => document.removeEventListener("mousedown", h);
|
|
615
764
|
}, []);
|
|
616
765
|
const handleSelect = (r) => {
|
|
766
|
+
if (query.trim()) {
|
|
767
|
+
client.api.searchVector(query, 1).catch(() => {
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
ignoreNextQueryChange.current = true;
|
|
617
771
|
setOpen(false);
|
|
618
772
|
setQuery(r.product.name);
|
|
619
773
|
onSelect == null ? void 0 : onSelect(r);
|
|
620
774
|
};
|
|
775
|
+
const handleCommitSearch = () => {
|
|
776
|
+
if (!query.trim()) return;
|
|
777
|
+
client.api.searchVector(query, 1).catch(() => {
|
|
778
|
+
});
|
|
779
|
+
if (results.length > 0) {
|
|
780
|
+
handleSelect(results[0]);
|
|
781
|
+
}
|
|
782
|
+
};
|
|
621
783
|
const showDrop = open && query.trim().length > 0;
|
|
622
|
-
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily });
|
|
623
|
-
return /* @__PURE__ */ jsxs("div", { className:
|
|
784
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
785
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("hsk-sb-wrap", classNames.root, className), ref: wrap, style: customStyles, children: [
|
|
624
786
|
/* @__PURE__ */ jsx2("span", { className: "hsk-sb-icon", children: /* @__PURE__ */ jsx2(SearchIcon, {}) }),
|
|
625
787
|
/* @__PURE__ */ jsx2(
|
|
626
788
|
"input",
|
|
627
789
|
{
|
|
628
|
-
className:
|
|
790
|
+
className: cn("hsk-sb-input", classNames.input, inputClassName),
|
|
629
791
|
type: "text",
|
|
630
792
|
value: query,
|
|
631
793
|
placeholder,
|
|
632
794
|
onChange: (e) => setQuery(e.target.value),
|
|
633
795
|
onFocus: () => results.length > 0 && query.trim() && setOpen(true),
|
|
796
|
+
onKeyDown: (e) => {
|
|
797
|
+
if (e.key === "Enter") {
|
|
798
|
+
handleCommitSearch();
|
|
799
|
+
}
|
|
800
|
+
},
|
|
634
801
|
autoComplete: "off",
|
|
635
802
|
spellCheck: false
|
|
636
803
|
}
|
|
637
804
|
),
|
|
638
|
-
showDrop && /* @__PURE__ */ jsxs("div", { className:
|
|
639
|
-
loading && /* @__PURE__ */ jsx2("div", { className: "hsk-sb-loading-bar" }),
|
|
640
|
-
results.length === 0
|
|
641
|
-
"
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
{
|
|
650
|
-
|
|
651
|
-
className: "hsk-sb-
|
|
652
|
-
style: {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
"
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
805
|
+
showDrop && /* @__PURE__ */ jsxs("div", { className: cn("hsk-sb-drop", classNames.dropdown, dropdownClassName), style: { position: "absolute" }, children: [
|
|
806
|
+
(loading || isDebouncing) && /* @__PURE__ */ jsx2("div", { className: "hsk-sb-loading-bar" }),
|
|
807
|
+
(loading || isDebouncing) && results.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
808
|
+
/* @__PURE__ */ jsxs("div", { className: "hsk-sb-skeleton-row", children: [
|
|
809
|
+
/* @__PURE__ */ jsx2("span", { className: "hsk-sb-skeleton-icon" }),
|
|
810
|
+
/* @__PURE__ */ jsxs("div", { className: "hsk-sb-row-body", children: [
|
|
811
|
+
/* @__PURE__ */ jsx2("div", { className: "hsk-sb-skeleton-text1" }),
|
|
812
|
+
/* @__PURE__ */ jsx2("div", { className: "hsk-sb-skeleton-text2" })
|
|
813
|
+
] })
|
|
814
|
+
] }),
|
|
815
|
+
/* @__PURE__ */ jsxs("div", { className: "hsk-sb-skeleton-row", children: [
|
|
816
|
+
/* @__PURE__ */ jsx2("span", { className: "hsk-sb-skeleton-icon" }),
|
|
817
|
+
/* @__PURE__ */ jsxs("div", { className: "hsk-sb-row-body", children: [
|
|
818
|
+
/* @__PURE__ */ jsx2("div", { className: "hsk-sb-skeleton-text1", style: { width: "45%" } }),
|
|
819
|
+
/* @__PURE__ */ jsx2("div", { className: "hsk-sb-skeleton-text2", style: { width: "25%" } })
|
|
820
|
+
] })
|
|
821
|
+
] })
|
|
822
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
823
|
+
results.length === 0 && !loading && !isDebouncing && /* @__PURE__ */ jsxs("div", { className: "hsk-sb-empty", children: [
|
|
824
|
+
"No results for \u201C",
|
|
825
|
+
query,
|
|
826
|
+
"\u201D"
|
|
827
|
+
] }),
|
|
828
|
+
results.map((r, i) => {
|
|
829
|
+
var _a;
|
|
830
|
+
return renderResult ? /* @__PURE__ */ jsx2(
|
|
831
|
+
"div",
|
|
832
|
+
{
|
|
833
|
+
onClick: () => handleSelect(r),
|
|
834
|
+
className: "hsk-sb-fade",
|
|
835
|
+
style: { animationDelay: `${i * 18}ms` },
|
|
836
|
+
children: renderResult(r)
|
|
837
|
+
},
|
|
838
|
+
r.id
|
|
839
|
+
) : /* @__PURE__ */ jsxs(
|
|
840
|
+
"div",
|
|
841
|
+
{
|
|
842
|
+
className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
|
|
843
|
+
style: { animationDelay: `${i * 18}ms` },
|
|
844
|
+
onClick: () => handleSelect(r),
|
|
845
|
+
children: [
|
|
846
|
+
/* @__PURE__ */ jsx2("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ jsx2(SearchIcon, {}) }),
|
|
847
|
+
/* @__PURE__ */ jsxs("div", { className: "hsk-sb-row-body", children: [
|
|
848
|
+
/* @__PURE__ */ jsx2("div", { className: "hsk-sb-row-title", children: r.product.name }),
|
|
849
|
+
(r.product.category || r.product.brand) && /* @__PURE__ */ jsx2("div", { className: "hsk-sb-row-sub", children: (_a = r.product.category) != null ? _a : r.product.brand })
|
|
850
|
+
] })
|
|
851
|
+
]
|
|
852
|
+
},
|
|
853
|
+
r.id
|
|
854
|
+
);
|
|
855
|
+
})
|
|
856
|
+
] })
|
|
673
857
|
] })
|
|
674
858
|
] });
|
|
675
859
|
}
|
|
676
860
|
|
|
677
861
|
// src/components/Sparkle.tsx
|
|
678
|
-
import { useState as
|
|
862
|
+
import { useState as useState6, useEffect as useEffect5, useRef as useRef7 } from "react";
|
|
679
863
|
import { createPortal } from "react-dom";
|
|
680
|
-
|
|
681
|
-
|
|
864
|
+
|
|
865
|
+
// src/utils/markdown.tsx
|
|
866
|
+
import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime";
|
|
867
|
+
var parseInline = (text, keyPrefix) => {
|
|
868
|
+
const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
|
|
869
|
+
const parts = text.split(tokenRegex);
|
|
870
|
+
return parts.map((part, index) => {
|
|
871
|
+
if (!part) return null;
|
|
872
|
+
const key = `${keyPrefix}-inline-${index}`;
|
|
873
|
+
if (part.startsWith("`") && part.endsWith("`")) {
|
|
874
|
+
return /* @__PURE__ */ jsx3("code", { className: "hsk-markdown-code", children: part.slice(1, -1) }, key);
|
|
875
|
+
}
|
|
876
|
+
if (part.startsWith("**") && part.endsWith("**")) {
|
|
877
|
+
return /* @__PURE__ */ jsx3("strong", { children: parseInline(part.slice(2, -2), key) }, key);
|
|
878
|
+
}
|
|
879
|
+
const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
880
|
+
if (linkMatch) {
|
|
881
|
+
const url = linkMatch[2];
|
|
882
|
+
const isSafeUrl = /^(https?|mailto|tel):/i.test(url) || url.startsWith("/");
|
|
883
|
+
if (isSafeUrl) {
|
|
884
|
+
return /* @__PURE__ */ jsx3("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "hsk-markdown-link", children: parseInline(linkMatch[1], key) }, key);
|
|
885
|
+
}
|
|
886
|
+
return /* @__PURE__ */ jsx3("span", { children: parseInline(linkMatch[1], key) }, key);
|
|
887
|
+
}
|
|
888
|
+
return part;
|
|
889
|
+
});
|
|
890
|
+
};
|
|
891
|
+
function renderMarkdown(content) {
|
|
892
|
+
const lines = content.split("\n");
|
|
893
|
+
const elements = [];
|
|
894
|
+
let i = 0;
|
|
895
|
+
while (i < lines.length) {
|
|
896
|
+
const line = lines[i];
|
|
897
|
+
const key = `md-line-${i}`;
|
|
898
|
+
if (!line.trim()) {
|
|
899
|
+
i++;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const headerMatch = line.match(/^(#{1,3})\s+(.*)/);
|
|
903
|
+
if (headerMatch) {
|
|
904
|
+
const level = headerMatch[1].length;
|
|
905
|
+
const Tag = `h${level + 3}`;
|
|
906
|
+
elements.push(/* @__PURE__ */ jsx3(Tag, { className: `hsk-markdown-h${level}`, children: parseInline(headerMatch[2], key) }, key));
|
|
907
|
+
i++;
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
if (line.match(/^[-*]\s+/)) {
|
|
911
|
+
const listItems = [];
|
|
912
|
+
while (i < lines.length && lines[i].match(/^[-*]\s+/)) {
|
|
913
|
+
const itemText = lines[i].replace(/^[-*]\s+/, "");
|
|
914
|
+
listItems.push(/* @__PURE__ */ jsx3("li", { children: parseInline(itemText, `li-${i}`) }, `li-${i}`));
|
|
915
|
+
i++;
|
|
916
|
+
}
|
|
917
|
+
elements.push(/* @__PURE__ */ jsx3("ul", { className: "hsk-markdown-list", children: listItems }, `ul-${key}`));
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
if (line.trim().startsWith("|")) {
|
|
921
|
+
const tableRows = [];
|
|
922
|
+
let isHeader = true;
|
|
923
|
+
while (i < lines.length && lines[i].trim().startsWith("|")) {
|
|
924
|
+
const rowLine = lines[i].trim();
|
|
925
|
+
if (rowLine.match(/^\|[-:| ]+\|$/)) {
|
|
926
|
+
i++;
|
|
927
|
+
isHeader = false;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
const cells = rowLine.split("|").slice(1, -1).map((c) => c.trim());
|
|
931
|
+
const Tag = isHeader ? "th" : "td";
|
|
932
|
+
tableRows.push(
|
|
933
|
+
/* @__PURE__ */ jsx3("tr", { children: cells.map((cell, cIdx) => /* @__PURE__ */ jsx3(Tag, { children: parseInline(cell, `td-${i}-${cIdx}`) }, `td-${i}-${cIdx}`)) }, `tr-${i}`)
|
|
934
|
+
);
|
|
935
|
+
i++;
|
|
936
|
+
}
|
|
937
|
+
elements.push(
|
|
938
|
+
/* @__PURE__ */ jsx3("div", { className: "hsk-table-wrapper", children: /* @__PURE__ */ jsx3("table", { className: "hsk-markdown-table", children: /* @__PURE__ */ jsx3("tbody", { children: tableRows }) }) }, `table-wrapper-${key}`)
|
|
939
|
+
);
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
elements.push(
|
|
943
|
+
/* @__PURE__ */ jsx3("p", { className: "hsk-markdown-p", children: parseInline(line, key) }, key)
|
|
944
|
+
);
|
|
945
|
+
i++;
|
|
946
|
+
}
|
|
947
|
+
return /* @__PURE__ */ jsx3(Fragment2, { children: elements });
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/components/Sparkle.tsx
|
|
951
|
+
import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
952
|
+
var SparkleIcon = ({ className }) => /* @__PURE__ */ jsx4("svg", { className, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }) });
|
|
682
953
|
var CloseIcon = () => /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
683
|
-
/* @__PURE__ */
|
|
684
|
-
/* @__PURE__ */
|
|
954
|
+
/* @__PURE__ */ jsx4("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
955
|
+
/* @__PURE__ */ jsx4("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
956
|
+
] });
|
|
957
|
+
var ArrowUpIcon = () => /* @__PURE__ */ jsxs2("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
958
|
+
/* @__PURE__ */ jsx4("path", { d: "m5 12 7-7 7 7" }),
|
|
959
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 19V5" })
|
|
685
960
|
] });
|
|
961
|
+
var getFriendlyError = (err) => {
|
|
962
|
+
let str = "";
|
|
963
|
+
if (typeof err === "string") str = err;
|
|
964
|
+
else if (err && typeof err === "object" && err.message) str = err.message;
|
|
965
|
+
else try {
|
|
966
|
+
str = JSON.stringify(err);
|
|
967
|
+
} catch (e) {
|
|
968
|
+
str = String(err);
|
|
969
|
+
}
|
|
970
|
+
if (str.toLowerCase().includes("token limit")) {
|
|
971
|
+
return "You've reached your usage limit. Please update your billing limits in your dashboard to continue.";
|
|
972
|
+
}
|
|
973
|
+
try {
|
|
974
|
+
const parsed = JSON.parse(str);
|
|
975
|
+
return parsed.error || parsed.message || str;
|
|
976
|
+
} catch (e) {
|
|
977
|
+
return str;
|
|
978
|
+
}
|
|
979
|
+
};
|
|
686
980
|
function SparkleModal({
|
|
687
981
|
productName,
|
|
688
982
|
limit,
|
|
@@ -692,26 +986,42 @@ function SparkleModal({
|
|
|
692
986
|
onNavigate,
|
|
693
987
|
onResult,
|
|
694
988
|
theme,
|
|
695
|
-
classNames = {}
|
|
989
|
+
classNames = {},
|
|
990
|
+
product: initialProduct
|
|
696
991
|
}) {
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
992
|
+
var _a, _b, _c;
|
|
993
|
+
const client = useHuskelContext();
|
|
994
|
+
const [fetchedProduct, setFetchedProduct] = useState6(null);
|
|
995
|
+
const displayProduct = initialProduct || fetchedProduct;
|
|
996
|
+
const { results, loading: searchLoading, search } = useSearch();
|
|
997
|
+
const { messages, sources, loading: chatLoading, error: chatError, send } = useChat();
|
|
998
|
+
const [chatInput, setChatInput] = useState6("");
|
|
999
|
+
const chatBottomRef = useRef7(null);
|
|
1000
|
+
const chatTextareaRef = useRef7(null);
|
|
1001
|
+
useEffect5(() => {
|
|
1002
|
+
if (!initialProduct && !fetchedProduct) {
|
|
1003
|
+
client.api.searchVector(productName, 1).then((res) => {
|
|
1004
|
+
if (res.results && res.results.length > 0) {
|
|
1005
|
+
setFetchedProduct(res.results[0].product);
|
|
1006
|
+
}
|
|
1007
|
+
}).catch((err) => console.error("[Huskel] Failed to fetch product details", err));
|
|
703
1008
|
}
|
|
704
|
-
|
|
705
|
-
|
|
1009
|
+
search(productName, limit);
|
|
1010
|
+
}, [productName, initialProduct, fetchedProduct, client, limit, search]);
|
|
1011
|
+
useEffect5(() => {
|
|
706
1012
|
if (results.length > 0) onResult == null ? void 0 : onResult(results);
|
|
707
|
-
}, [results]);
|
|
708
|
-
|
|
1013
|
+
}, [results, onResult]);
|
|
1014
|
+
useEffect5(() => {
|
|
709
1015
|
const h = (e) => {
|
|
710
1016
|
if (e.key === "Escape") onClose();
|
|
711
1017
|
};
|
|
712
1018
|
document.addEventListener("keydown", h);
|
|
713
1019
|
return () => document.removeEventListener("keydown", h);
|
|
714
|
-
}, []);
|
|
1020
|
+
}, [onClose]);
|
|
1021
|
+
useEffect5(() => {
|
|
1022
|
+
var _a2;
|
|
1023
|
+
(_a2 = chatBottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
1024
|
+
}, [messages, chatLoading]);
|
|
715
1025
|
const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "16px";
|
|
716
1026
|
const bg = backdropColor != null ? backdropColor : void 0;
|
|
717
1027
|
const handleNav = (r) => {
|
|
@@ -721,83 +1031,209 @@ function SparkleModal({
|
|
|
721
1031
|
if (r.product.url) window.location.href = r.product.url;
|
|
722
1032
|
}
|
|
723
1033
|
};
|
|
724
|
-
const
|
|
725
|
-
|
|
1034
|
+
const handleSend = async (text) => {
|
|
1035
|
+
const q = (text != null ? text : chatInput).trim();
|
|
1036
|
+
if (!q || chatLoading) return;
|
|
1037
|
+
setChatInput("");
|
|
1038
|
+
if (chatTextareaRef.current) {
|
|
1039
|
+
chatTextareaRef.current.style.height = "auto";
|
|
1040
|
+
}
|
|
1041
|
+
if (messages.length === 0 && displayProduct) {
|
|
1042
|
+
const contextQuery = `[Context: Shopper is viewing "${displayProduct.name}". Price: ${displayProduct.price}. Description: ${displayProduct.description || ""}]
|
|
1043
|
+
|
|
1044
|
+
Question: ${q}`;
|
|
1045
|
+
await send(contextQuery, q);
|
|
1046
|
+
} else {
|
|
1047
|
+
await send(q);
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
const handleKeyDown = (e) => {
|
|
1051
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
1052
|
+
e.preventDefault();
|
|
1053
|
+
handleSend();
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
const handleInput = (e) => {
|
|
1057
|
+
setChatInput(e.target.value);
|
|
1058
|
+
const t = e.target;
|
|
1059
|
+
t.style.height = "auto";
|
|
1060
|
+
t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
|
|
1061
|
+
};
|
|
1062
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
1063
|
+
const displayMessages = messages.length === 0 && displayProduct ? [
|
|
1064
|
+
{
|
|
1065
|
+
role: "assistant",
|
|
1066
|
+
content: `Hi! I can help you with **${displayProduct.name}**. Ask me about its specifications, features, compare it with other options, or find alternatives!`
|
|
1067
|
+
}
|
|
1068
|
+
] : messages;
|
|
1069
|
+
return /* @__PURE__ */ jsx4(
|
|
726
1070
|
"div",
|
|
727
1071
|
{
|
|
728
|
-
className:
|
|
1072
|
+
className: cn("hsk-sp-backdrop", classNames.backdrop),
|
|
729
1073
|
onClick: onClose,
|
|
730
1074
|
style: __spreadValues({
|
|
731
1075
|
backdropFilter: `blur(${blurVal})`,
|
|
732
1076
|
WebkitBackdropFilter: `blur(${blurVal})`,
|
|
733
1077
|
background: bg != null ? bg : void 0
|
|
734
1078
|
}, customStyles),
|
|
735
|
-
children: /* @__PURE__ */ jsxs2("div", { className:
|
|
1079
|
+
children: /* @__PURE__ */ jsxs2("div", { className: cn("hsk-sp-card hsk-sp-fullscreen", classNames.card), onClick: (e) => e.stopPropagation(), children: [
|
|
736
1080
|
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-header", children: [
|
|
737
|
-
/* @__PURE__ */
|
|
1081
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-header-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx4(SparkleIcon, {}) }),
|
|
738
1082
|
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-header-body", children: [
|
|
739
|
-
/* @__PURE__ */
|
|
740
|
-
|
|
741
|
-
productName,
|
|
742
|
-
"\u201D"
|
|
743
|
-
] }),
|
|
744
|
-
/* @__PURE__ */ jsx3("div", { className: "hsk-sp-header-sub", children: "AI vector similarity \xB7 instant results" })
|
|
1083
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-header-title", children: (displayProduct == null ? void 0 : displayProduct.name) || productName }),
|
|
1084
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-header-sub", children: "Ask questions, compare specs, or check similar products" })
|
|
745
1085
|
] }),
|
|
746
|
-
/* @__PURE__ */
|
|
1086
|
+
/* @__PURE__ */ jsx4("button", { className: "hsk-sp-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx4(CloseIcon, {}) })
|
|
747
1087
|
] }),
|
|
748
|
-
|
|
749
|
-
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
1088
|
+
searchLoading && /* @__PURE__ */ jsx4("div", { className: "hsk-sp-bar" }),
|
|
1089
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-body", children: [
|
|
1090
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-details-pane", children: [
|
|
1091
|
+
displayProduct && /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-product-profile-container", children: [
|
|
1092
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-product-profile", children: [
|
|
1093
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-details-imgwrap", children: ((_a = displayProduct.images) == null ? void 0 : _a[0]) ? /* @__PURE__ */ jsx4("img", { src: displayProduct.images[0], alt: displayProduct.name }) : /* @__PURE__ */ jsx4("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
|
|
1094
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-details-meta", children: [
|
|
1095
|
+
displayProduct.brand && /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-brand", children: displayProduct.brand }),
|
|
1096
|
+
displayProduct.category && /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-cat", children: displayProduct.category }),
|
|
1097
|
+
/* @__PURE__ */ jsx4("h2", { className: "hsk-sp-details-name", children: displayProduct.name }),
|
|
1098
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-price-row", children: [
|
|
1099
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-currency", children: (_b = displayProduct.currency) != null ? _b : "KES" }),
|
|
1100
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-price", children: parseFloat(((_c = displayProduct.price) == null ? void 0 : _c.replace(/[^0-9.]/g, "")) || "0").toLocaleString() }),
|
|
1101
|
+
displayProduct.originalPrice && /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-original-price", children: parseFloat(displayProduct.originalPrice.replace(/[^0-9.]/g, "") || "0").toLocaleString() }),
|
|
1102
|
+
displayProduct.discount && /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-item-discount", children: [
|
|
1103
|
+
"(",
|
|
1104
|
+
displayProduct.discount,
|
|
1105
|
+
")"
|
|
1106
|
+
] })
|
|
1107
|
+
] }),
|
|
1108
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-meta-badges", children: [
|
|
1109
|
+
displayProduct.rating && /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-rating", children: [
|
|
1110
|
+
"\u2605 ",
|
|
1111
|
+
parseFloat(displayProduct.rating.toString()).toFixed(1),
|
|
1112
|
+
" ",
|
|
1113
|
+
displayProduct.reviewCount ? `(${displayProduct.reviewCount})` : ""
|
|
768
1114
|
] }),
|
|
769
|
-
/* @__PURE__ */
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
className: "hsk-sp-action hsk-sp-action-primary",
|
|
774
|
-
onClick: () => handleNav(r),
|
|
775
|
-
children: "View Product"
|
|
776
|
-
}
|
|
777
|
-
),
|
|
778
|
-
/* @__PURE__ */ jsx3(
|
|
779
|
-
"button",
|
|
780
|
-
{
|
|
781
|
-
className: "hsk-sp-action hsk-sp-action-secondary",
|
|
782
|
-
onClick: () => onClose(),
|
|
783
|
-
children: "Add to Cart"
|
|
784
|
-
}
|
|
785
|
-
)
|
|
1115
|
+
displayProduct.availability && /* @__PURE__ */ jsx4("span", { className: `hsk-sp-meta-badge hsk-sp-meta-badge-avail ${displayProduct.availability.toLowerCase().includes("in") ? "in-stock" : "out-stock"}`, children: displayProduct.availability }),
|
|
1116
|
+
displayProduct.stock && !displayProduct.availability && /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-stock", children: [
|
|
1117
|
+
"Stock: ",
|
|
1118
|
+
displayProduct.stock
|
|
786
1119
|
] })
|
|
787
1120
|
] })
|
|
788
|
-
]
|
|
789
|
-
},
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1121
|
+
] })
|
|
1122
|
+
] }),
|
|
1123
|
+
displayProduct.specs && Object.keys(displayProduct.specs).length > 0 && /* @__PURE__ */ jsx4("div", { className: "hsk-sp-specs-horizontal", children: Object.entries(displayProduct.specs).map(([key, val]) => /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-spec-item-horizontal", children: [
|
|
1124
|
+
/* @__PURE__ */ jsxs2("span", { className: "hsk-sp-spec-label-horizontal", children: [
|
|
1125
|
+
key,
|
|
1126
|
+
":"
|
|
1127
|
+
] }),
|
|
1128
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-spec-value-horizontal", title: val, children: val })
|
|
1129
|
+
] }, key)) }),
|
|
1130
|
+
displayProduct.description && /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-details-desc", children: [
|
|
1131
|
+
/* @__PURE__ */ jsx4("h4", { children: "Description" }),
|
|
1132
|
+
/* @__PURE__ */ jsx4("p", { children: displayProduct.description })
|
|
1133
|
+
] })
|
|
1134
|
+
] }),
|
|
1135
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-similar-section", children: [
|
|
1136
|
+
/* @__PURE__ */ jsx4("h3", { children: "Similar Products" }),
|
|
1137
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-results", children: (() => {
|
|
1138
|
+
const similarProducts = results.filter(
|
|
1139
|
+
(r) => {
|
|
1140
|
+
var _a2;
|
|
1141
|
+
const isSameName = r.product.name.toLowerCase() === ((_a2 = displayProduct == null ? void 0 : displayProduct.name) == null ? void 0 : _a2.toLowerCase());
|
|
1142
|
+
const isSameSlug = r.product.slug && (displayProduct == null ? void 0 : displayProduct.slug) && r.product.slug.toLowerCase() === displayProduct.slug.toLowerCase();
|
|
1143
|
+
return !isSameName && !isSameSlug;
|
|
1144
|
+
}
|
|
1145
|
+
);
|
|
1146
|
+
if (!searchLoading && similarProducts.length === 0) {
|
|
1147
|
+
return /* @__PURE__ */ jsx4("div", { className: "hsk-sp-empty", children: "No similar products found." });
|
|
1148
|
+
}
|
|
1149
|
+
return similarProducts.map((r, i) => {
|
|
1150
|
+
var _a2, _b2, _c2;
|
|
1151
|
+
const price = parseFloat(((_a2 = r.product.price) == null ? void 0 : _a2.replace(/[^0-9.]/g, "")) || "0");
|
|
1152
|
+
const currency = (_b2 = r.product.currency) != null ? _b2 : "KES";
|
|
1153
|
+
return /* @__PURE__ */ jsxs2(
|
|
1154
|
+
"div",
|
|
1155
|
+
{
|
|
1156
|
+
className: cn("hsk-sp-item", classNames.item),
|
|
1157
|
+
style: { animationDelay: `${i * 55}ms` },
|
|
1158
|
+
children: [
|
|
1159
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-img-wrap", children: ((_c2 = r.product.images) == null ? void 0 : _c2[0]) ? /* @__PURE__ */ jsx4("img", { src: r.product.images[0], alt: r.product.name }) : /* @__PURE__ */ jsx4("span", { className: "hsk-sp-img-placeholder", children: "\u{1F6CD}" }) }),
|
|
1160
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-body", children: [
|
|
1161
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
1162
|
+
r.product.category && /* @__PURE__ */ jsx4("div", { className: "hsk-sp-item-cat", children: r.product.category }),
|
|
1163
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-item-name", title: r.product.name, children: r.product.name })
|
|
1164
|
+
] }),
|
|
1165
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-price-row", children: [
|
|
1166
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-currency", children: currency }),
|
|
1167
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-price", children: price.toLocaleString() })
|
|
1168
|
+
] }),
|
|
1169
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-actions", children: /* @__PURE__ */ jsx4(
|
|
1170
|
+
"button",
|
|
1171
|
+
{
|
|
1172
|
+
className: "hsk-sp-action hsk-sp-action-primary",
|
|
1173
|
+
onClick: () => handleNav(r),
|
|
1174
|
+
children: "View"
|
|
1175
|
+
}
|
|
1176
|
+
) })
|
|
1177
|
+
] })
|
|
1178
|
+
]
|
|
1179
|
+
},
|
|
1180
|
+
r.id
|
|
1181
|
+
);
|
|
1182
|
+
});
|
|
1183
|
+
})() })
|
|
1184
|
+
] })
|
|
798
1185
|
] }),
|
|
799
|
-
/* @__PURE__ */
|
|
800
|
-
|
|
1186
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-chat-pane", children: [
|
|
1187
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-cb-msgs", children: [
|
|
1188
|
+
displayMessages.map((msg, idx) => {
|
|
1189
|
+
const isUser = msg.role === "user";
|
|
1190
|
+
return /* @__PURE__ */ jsx4("div", { className: "hsk-cb-msg-group", children: isUser ? /* @__PURE__ */ jsx4("div", { className: "hsk-cb-user-msg", children: /* @__PURE__ */ jsx4("div", { className: "hsk-cb-user-bubble", children: msg.content }) }) : /* @__PURE__ */ jsxs2("div", { className: "hsk-cb-ai-msg", children: [
|
|
1191
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx4(SparkleIcon, {}) }),
|
|
1192
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-text", children: renderMarkdown(msg.content) }) })
|
|
1193
|
+
] }) }, idx);
|
|
1194
|
+
}),
|
|
1195
|
+
chatLoading && /* @__PURE__ */ jsxs2("div", { className: "hsk-cb-typing-row", children: [
|
|
1196
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx4(SparkleIcon, {}) }),
|
|
1197
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-cb-typing", children: [
|
|
1198
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-dot" }),
|
|
1199
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-dot" }),
|
|
1200
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-dot" })
|
|
1201
|
+
] })
|
|
1202
|
+
] }),
|
|
1203
|
+
chatError && /* @__PURE__ */ jsx4("div", { className: "hsk-cb-error", children: getFriendlyError(chatError) }),
|
|
1204
|
+
/* @__PURE__ */ jsx4("div", { ref: chatBottomRef, style: { height: 1 } })
|
|
1205
|
+
] }),
|
|
1206
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-cb-input-wrap", children: [
|
|
1207
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-cb-input-box", children: [
|
|
1208
|
+
/* @__PURE__ */ jsx4(
|
|
1209
|
+
"textarea",
|
|
1210
|
+
{
|
|
1211
|
+
ref: chatTextareaRef,
|
|
1212
|
+
className: "hsk-cb-textarea",
|
|
1213
|
+
value: chatInput,
|
|
1214
|
+
onChange: handleInput,
|
|
1215
|
+
onKeyDown: handleKeyDown,
|
|
1216
|
+
placeholder: "Ask about this product, specs, or comparison...",
|
|
1217
|
+
rows: 1,
|
|
1218
|
+
disabled: chatLoading
|
|
1219
|
+
}
|
|
1220
|
+
),
|
|
1221
|
+
/* @__PURE__ */ jsx4(
|
|
1222
|
+
"button",
|
|
1223
|
+
{
|
|
1224
|
+
className: "hsk-cb-send",
|
|
1225
|
+
onClick: () => handleSend(),
|
|
1226
|
+
disabled: !chatInput.trim() || chatLoading,
|
|
1227
|
+
"aria-label": "Send message",
|
|
1228
|
+
children: /* @__PURE__ */ jsx4(ArrowUpIcon, {})
|
|
1229
|
+
}
|
|
1230
|
+
)
|
|
1231
|
+
] }),
|
|
1232
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-hint", children: "Huskel AI \xB7 instant product knowledge" })
|
|
1233
|
+
] })
|
|
1234
|
+
] })
|
|
1235
|
+
] }),
|
|
1236
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-footer", children: /* @__PURE__ */ jsx4("span", { className: "hsk-sp-esc", children: "Esc to close" }) })
|
|
801
1237
|
] })
|
|
802
1238
|
}
|
|
803
1239
|
);
|
|
@@ -811,39 +1247,41 @@ function Sparkle({
|
|
|
811
1247
|
className,
|
|
812
1248
|
onNavigate,
|
|
813
1249
|
theme,
|
|
814
|
-
classNames = {}
|
|
1250
|
+
classNames = {},
|
|
1251
|
+
product
|
|
815
1252
|
}) {
|
|
816
|
-
const [open, setOpen] =
|
|
817
|
-
const [mounted, setMounted] =
|
|
818
|
-
|
|
1253
|
+
const [open, setOpen] = useState6(false);
|
|
1254
|
+
const [mounted, setMounted] = useState6(false);
|
|
1255
|
+
useEffect5(() => {
|
|
819
1256
|
setMounted(true);
|
|
820
1257
|
}, []);
|
|
821
|
-
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily });
|
|
822
|
-
return /* @__PURE__ */ jsxs2(
|
|
823
|
-
/* @__PURE__ */
|
|
1258
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
1259
|
+
return /* @__PURE__ */ jsxs2(Fragment3, { children: [
|
|
1260
|
+
/* @__PURE__ */ jsx4(
|
|
824
1261
|
"button",
|
|
825
1262
|
{
|
|
826
|
-
className:
|
|
1263
|
+
className: cn("hsk-sp-btn", classNames.button, className),
|
|
827
1264
|
onClick: () => setOpen(true),
|
|
828
1265
|
style: customStyles,
|
|
829
1266
|
title: "Find similar products",
|
|
830
1267
|
"aria-label": "Find similar products",
|
|
831
|
-
children: /* @__PURE__ */
|
|
1268
|
+
children: /* @__PURE__ */ jsx4(SparkleIcon, {})
|
|
832
1269
|
}
|
|
833
1270
|
),
|
|
834
1271
|
open && mounted && createPortal(
|
|
835
|
-
/* @__PURE__ */
|
|
1272
|
+
/* @__PURE__ */ jsx4(
|
|
836
1273
|
SparkleModal,
|
|
837
1274
|
{
|
|
838
1275
|
productName,
|
|
839
1276
|
limit,
|
|
1277
|
+
onResult,
|
|
840
1278
|
backdropColor,
|
|
841
1279
|
backdropBlur,
|
|
842
1280
|
onClose: () => setOpen(false),
|
|
843
|
-
onResult,
|
|
844
1281
|
onNavigate,
|
|
845
1282
|
theme,
|
|
846
|
-
classNames
|
|
1283
|
+
classNames,
|
|
1284
|
+
product
|
|
847
1285
|
}
|
|
848
1286
|
),
|
|
849
1287
|
document.body
|
|
@@ -852,97 +1290,10 @@ function Sparkle({
|
|
|
852
1290
|
}
|
|
853
1291
|
|
|
854
1292
|
// src/components/ChatWidget.tsx
|
|
855
|
-
import { useState as
|
|
856
|
-
|
|
857
|
-
// src/utils/markdown.tsx
|
|
858
|
-
import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
|
|
859
|
-
var parseInline = (text, keyPrefix) => {
|
|
860
|
-
const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
|
|
861
|
-
const parts = text.split(tokenRegex);
|
|
862
|
-
return parts.map((part, index) => {
|
|
863
|
-
if (!part) return null;
|
|
864
|
-
const key = `${keyPrefix}-inline-${index}`;
|
|
865
|
-
if (part.startsWith("`") && part.endsWith("`")) {
|
|
866
|
-
return /* @__PURE__ */ jsx4("code", { className: "hsk-markdown-code", children: part.slice(1, -1) }, key);
|
|
867
|
-
}
|
|
868
|
-
if (part.startsWith("**") && part.endsWith("**")) {
|
|
869
|
-
return /* @__PURE__ */ jsx4("strong", { children: parseInline(part.slice(2, -2), key) }, key);
|
|
870
|
-
}
|
|
871
|
-
const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
872
|
-
if (linkMatch) {
|
|
873
|
-
const url = linkMatch[2];
|
|
874
|
-
const isSafeUrl = /^(https?|mailto|tel):/i.test(url) || url.startsWith("/");
|
|
875
|
-
if (isSafeUrl) {
|
|
876
|
-
return /* @__PURE__ */ jsx4("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "hsk-markdown-link", children: parseInline(linkMatch[1], key) }, key);
|
|
877
|
-
}
|
|
878
|
-
return /* @__PURE__ */ jsx4("span", { children: parseInline(linkMatch[1], key) }, key);
|
|
879
|
-
}
|
|
880
|
-
return part;
|
|
881
|
-
});
|
|
882
|
-
};
|
|
883
|
-
function renderMarkdown(content) {
|
|
884
|
-
const lines = content.split("\n");
|
|
885
|
-
const elements = [];
|
|
886
|
-
let i = 0;
|
|
887
|
-
while (i < lines.length) {
|
|
888
|
-
const line = lines[i];
|
|
889
|
-
const key = `md-line-${i}`;
|
|
890
|
-
if (!line.trim()) {
|
|
891
|
-
i++;
|
|
892
|
-
continue;
|
|
893
|
-
}
|
|
894
|
-
const headerMatch = line.match(/^(#{1,3})\s+(.*)/);
|
|
895
|
-
if (headerMatch) {
|
|
896
|
-
const level = headerMatch[1].length;
|
|
897
|
-
const Tag = `h${level + 3}`;
|
|
898
|
-
elements.push(/* @__PURE__ */ jsx4(Tag, { className: `hsk-markdown-h${level}`, children: parseInline(headerMatch[2], key) }, key));
|
|
899
|
-
i++;
|
|
900
|
-
continue;
|
|
901
|
-
}
|
|
902
|
-
if (line.match(/^[-*]\s+/)) {
|
|
903
|
-
const listItems = [];
|
|
904
|
-
while (i < lines.length && lines[i].match(/^[-*]\s+/)) {
|
|
905
|
-
const itemText = lines[i].replace(/^[-*]\s+/, "");
|
|
906
|
-
listItems.push(/* @__PURE__ */ jsx4("li", { children: parseInline(itemText, `li-${i}`) }, `li-${i}`));
|
|
907
|
-
i++;
|
|
908
|
-
}
|
|
909
|
-
elements.push(/* @__PURE__ */ jsx4("ul", { className: "hsk-markdown-list", children: listItems }, `ul-${key}`));
|
|
910
|
-
continue;
|
|
911
|
-
}
|
|
912
|
-
if (line.trim().startsWith("|")) {
|
|
913
|
-
const tableRows = [];
|
|
914
|
-
let isHeader = true;
|
|
915
|
-
while (i < lines.length && lines[i].trim().startsWith("|")) {
|
|
916
|
-
const rowLine = lines[i].trim();
|
|
917
|
-
if (rowLine.match(/^\|[-:| ]+\|$/)) {
|
|
918
|
-
i++;
|
|
919
|
-
isHeader = false;
|
|
920
|
-
continue;
|
|
921
|
-
}
|
|
922
|
-
const cells = rowLine.split("|").slice(1, -1).map((c) => c.trim());
|
|
923
|
-
const Tag = isHeader ? "th" : "td";
|
|
924
|
-
tableRows.push(
|
|
925
|
-
/* @__PURE__ */ jsx4("tr", { children: cells.map((cell, cIdx) => /* @__PURE__ */ jsx4(Tag, { children: parseInline(cell, `td-${i}-${cIdx}`) }, `td-${i}-${cIdx}`)) }, `tr-${i}`)
|
|
926
|
-
);
|
|
927
|
-
i++;
|
|
928
|
-
}
|
|
929
|
-
elements.push(
|
|
930
|
-
/* @__PURE__ */ jsx4("div", { className: "hsk-table-wrapper", children: /* @__PURE__ */ jsx4("table", { className: "hsk-markdown-table", children: /* @__PURE__ */ jsx4("tbody", { children: tableRows }) }) }, `table-wrapper-${key}`)
|
|
931
|
-
);
|
|
932
|
-
continue;
|
|
933
|
-
}
|
|
934
|
-
elements.push(
|
|
935
|
-
/* @__PURE__ */ jsx4("p", { className: "hsk-markdown-p", children: parseInline(line, key) }, key)
|
|
936
|
-
);
|
|
937
|
-
i++;
|
|
938
|
-
}
|
|
939
|
-
return /* @__PURE__ */ jsx4(Fragment2, { children: elements });
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// src/components/ChatWidget.tsx
|
|
1293
|
+
import { useState as useState7, useRef as useRef8, useEffect as useEffect6 } from "react";
|
|
943
1294
|
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
944
1295
|
var SparkleIcon2 = () => /* @__PURE__ */ jsx5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx5("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }) });
|
|
945
|
-
var
|
|
1296
|
+
var ArrowUpIcon2 = () => /* @__PURE__ */ jsxs3("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
946
1297
|
/* @__PURE__ */ jsx5("path", { d: "m5 12 7-7 7 7" }),
|
|
947
1298
|
/* @__PURE__ */ jsx5("path", { d: "M12 19V5" })
|
|
948
1299
|
] });
|
|
@@ -972,10 +1323,10 @@ function ChatWidget({
|
|
|
972
1323
|
onSelectSource
|
|
973
1324
|
}) {
|
|
974
1325
|
const { messages, sources, loading, error, send, reset } = useChat();
|
|
975
|
-
const [input, setInput] =
|
|
1326
|
+
const [input, setInput] = useState7("");
|
|
976
1327
|
const bottomRef = useRef8(null);
|
|
977
1328
|
const textareaRef = useRef8(null);
|
|
978
|
-
|
|
1329
|
+
useEffect6(() => {
|
|
979
1330
|
var _a;
|
|
980
1331
|
(_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
|
|
981
1332
|
}, [messages, loading]);
|
|
@@ -998,14 +1349,14 @@ function ChatWidget({
|
|
|
998
1349
|
t.style.height = "auto";
|
|
999
1350
|
t.style.height = Math.min(t.scrollHeight, 120) + "px";
|
|
1000
1351
|
};
|
|
1001
|
-
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily });
|
|
1352
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
1002
1353
|
return /* @__PURE__ */ jsxs3(
|
|
1003
1354
|
"div",
|
|
1004
1355
|
{
|
|
1005
|
-
className:
|
|
1356
|
+
className: cn("hsk-chat-widget", classNames.root, className),
|
|
1006
1357
|
style: customStyles,
|
|
1007
1358
|
children: [
|
|
1008
|
-
/* @__PURE__ */ jsxs3("div", { className:
|
|
1359
|
+
/* @__PURE__ */ jsxs3("div", { className: cn("hsk-chat-header", classNames.header), children: [
|
|
1009
1360
|
/* @__PURE__ */ jsx5("span", { className: "hsk-chat-header-icon", children: /* @__PURE__ */ jsx5(SparkleIcon2, {}) }),
|
|
1010
1361
|
/* @__PURE__ */ jsx5("span", { className: "hsk-chat-title", children: title }),
|
|
1011
1362
|
/* @__PURE__ */ jsx5("span", { className: "hsk-chat-badge", children: "AI" }),
|
|
@@ -1018,8 +1369,8 @@ function ChatWidget({
|
|
|
1018
1369
|
/* @__PURE__ */ jsx5("div", { className: "hsk-chat-empty-suggestions", children: emptyStateSuggestions })
|
|
1019
1370
|
] }) : messages.map((msg, idx) => /* @__PURE__ */ jsxs3("div", { children: [
|
|
1020
1371
|
/* @__PURE__ */ jsxs3("div", { className: `hsk-msg-row ${msg.role}`, children: [
|
|
1021
|
-
/* @__PURE__ */ jsx5("div", { className:
|
|
1022
|
-
/* @__PURE__ */ jsx5("div", { className:
|
|
1372
|
+
/* @__PURE__ */ jsx5("div", { className: cn("hsk-msg-avatar", msg.role === "assistant" ? "ai" : "user"), children: msg.role === "assistant" ? /* @__PURE__ */ jsx5(SparkleIcon2, {}) : "U" }),
|
|
1373
|
+
/* @__PURE__ */ jsx5("div", { className: cn("hsk-msg-bubble", msg.role, classNames.messageBubble), children: renderMarkdown(msg.content) })
|
|
1023
1374
|
] }),
|
|
1024
1375
|
msg.role === "assistant" && idx === messages.length - 1 && sources.length > 0 && /* @__PURE__ */ jsx5("div", { className: "hsk-sources-container", children: /* @__PURE__ */ jsx5("div", { className: "hsk-sources", children: sources.map((src, si) => /* @__PURE__ */ jsx5(SourceCard, { source: src, defaultCurrency, onSelect: onSelectSource }, si)) }) })
|
|
1025
1376
|
] }, idx)),
|
|
@@ -1031,7 +1382,14 @@ function ChatWidget({
|
|
|
1031
1382
|
/* @__PURE__ */ jsx5("div", { className: "hsk-typing-dot" })
|
|
1032
1383
|
] })
|
|
1033
1384
|
] }),
|
|
1034
|
-
error && /* @__PURE__ */ jsx5("div", { className: "hsk-chat-error", children:
|
|
1385
|
+
error && /* @__PURE__ */ jsx5("div", { className: "hsk-chat-error", children: (() => {
|
|
1386
|
+
try {
|
|
1387
|
+
const parsed = JSON.parse(error);
|
|
1388
|
+
return parsed.error || parsed.message || error;
|
|
1389
|
+
} catch (e) {
|
|
1390
|
+
return error;
|
|
1391
|
+
}
|
|
1392
|
+
})() }),
|
|
1035
1393
|
/* @__PURE__ */ jsx5("div", { ref: bottomRef })
|
|
1036
1394
|
] }),
|
|
1037
1395
|
/* @__PURE__ */ jsxs3("div", { className: "hsk-chat-input-area", children: [
|
|
@@ -1039,7 +1397,7 @@ function ChatWidget({
|
|
|
1039
1397
|
"textarea",
|
|
1040
1398
|
{
|
|
1041
1399
|
ref: textareaRef,
|
|
1042
|
-
className:
|
|
1400
|
+
className: cn("hsk-chat-input", classNames.input),
|
|
1043
1401
|
value: input,
|
|
1044
1402
|
onChange: handleInput,
|
|
1045
1403
|
onKeyDown: handleKey,
|
|
@@ -1055,7 +1413,7 @@ function ChatWidget({
|
|
|
1055
1413
|
onClick: handleSend,
|
|
1056
1414
|
disabled: !input.trim() || loading,
|
|
1057
1415
|
"aria-label": "Send message",
|
|
1058
|
-
children: /* @__PURE__ */ jsx5(
|
|
1416
|
+
children: /* @__PURE__ */ jsx5(ArrowUpIcon2, {})
|
|
1059
1417
|
}
|
|
1060
1418
|
)
|
|
1061
1419
|
] })
|
|
@@ -1065,11 +1423,11 @@ function ChatWidget({
|
|
|
1065
1423
|
}
|
|
1066
1424
|
|
|
1067
1425
|
// src/components/AIChatButton.tsx
|
|
1068
|
-
import { useState as
|
|
1426
|
+
import { useState as useState8, useEffect as useEffect7, useRef as useRef9, useCallback as useCallback5 } from "react";
|
|
1069
1427
|
import { createPortal as createPortal2 } from "react-dom";
|
|
1070
|
-
import { Fragment as
|
|
1428
|
+
import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1071
1429
|
var SparkleIcon3 = ({ className }) => /* @__PURE__ */ jsx6("svg", { className, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx6("path", { d: "m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" }) });
|
|
1072
|
-
var
|
|
1430
|
+
var ArrowUpIcon3 = () => /* @__PURE__ */ jsxs4("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1073
1431
|
/* @__PURE__ */ jsx6("path", { d: "m5 12 7-7 7 7" }),
|
|
1074
1432
|
/* @__PURE__ */ jsx6("path", { d: "M12 19V5" })
|
|
1075
1433
|
] });
|
|
@@ -1086,14 +1444,14 @@ var DEFAULT_CHIPS = [
|
|
|
1086
1444
|
];
|
|
1087
1445
|
function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
|
|
1088
1446
|
const railRef = useRef9(null);
|
|
1089
|
-
const [showNext, setShowNext] =
|
|
1090
|
-
const measure =
|
|
1447
|
+
const [showNext, setShowNext] = useState8(false);
|
|
1448
|
+
const measure = useCallback5(() => {
|
|
1091
1449
|
const el = railRef.current;
|
|
1092
1450
|
if (!el) return;
|
|
1093
1451
|
const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 8;
|
|
1094
1452
|
setShowNext(el.scrollWidth > el.clientWidth + 4 && !atEnd);
|
|
1095
1453
|
}, []);
|
|
1096
|
-
|
|
1454
|
+
useEffect7(() => {
|
|
1097
1455
|
measure();
|
|
1098
1456
|
const el = railRef.current;
|
|
1099
1457
|
if (!el) return;
|
|
@@ -1133,7 +1491,7 @@ function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
|
|
|
1133
1491
|
si
|
|
1134
1492
|
);
|
|
1135
1493
|
}) }),
|
|
1136
|
-
showNext && /* @__PURE__ */ jsxs4(
|
|
1494
|
+
showNext && /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1137
1495
|
/* @__PURE__ */ jsx6(
|
|
1138
1496
|
"div",
|
|
1139
1497
|
{
|
|
@@ -1146,7 +1504,7 @@ function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
|
|
|
1146
1504
|
] });
|
|
1147
1505
|
}
|
|
1148
1506
|
function ChatModal({
|
|
1149
|
-
title = "
|
|
1507
|
+
title = "Shopping Assistant",
|
|
1150
1508
|
placeholder = "Ask me anything \u2014 gifts, budget, use case\u2026",
|
|
1151
1509
|
backdropColor,
|
|
1152
1510
|
backdropBlur,
|
|
@@ -1159,15 +1517,15 @@ function ChatModal({
|
|
|
1159
1517
|
}) {
|
|
1160
1518
|
var _a, _b;
|
|
1161
1519
|
const { messages, sources, loading, error, send, reset } = useChat();
|
|
1162
|
-
const [input, setInput] =
|
|
1163
|
-
const [selectedProduct, setSelectedProduct] =
|
|
1520
|
+
const [input, setInput] = useState8("");
|
|
1521
|
+
const [selectedProduct, setSelectedProduct] = useState8(null);
|
|
1164
1522
|
const bottomRef = useRef9(null);
|
|
1165
1523
|
const textareaRef = useRef9(null);
|
|
1166
|
-
|
|
1524
|
+
useEffect7(() => {
|
|
1167
1525
|
var _a2;
|
|
1168
1526
|
(_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
1169
1527
|
}, [messages, loading, selectedProduct]);
|
|
1170
|
-
|
|
1528
|
+
useEffect7(() => {
|
|
1171
1529
|
const h = (e) => {
|
|
1172
1530
|
if (e.key === "Escape") onClose();
|
|
1173
1531
|
};
|
|
@@ -1204,24 +1562,21 @@ function ChatModal({
|
|
|
1204
1562
|
t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
|
|
1205
1563
|
};
|
|
1206
1564
|
const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "20px";
|
|
1207
|
-
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily });
|
|
1565
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
1208
1566
|
return /* @__PURE__ */ jsx6(
|
|
1209
1567
|
"div",
|
|
1210
1568
|
{
|
|
1211
|
-
className:
|
|
1569
|
+
className: cn("hsk-cb-overlay", classNames.overlay),
|
|
1212
1570
|
onClick: onClose,
|
|
1213
1571
|
style: __spreadValues(__spreadValues({
|
|
1214
1572
|
backdropFilter: `blur(${blurVal})`,
|
|
1215
1573
|
WebkitBackdropFilter: `blur(${blurVal})`
|
|
1216
1574
|
}, backdropColor ? { background: backdropColor } : {}), customStyles),
|
|
1217
|
-
children: /* @__PURE__ */ jsxs4("div", { className:
|
|
1575
|
+
children: /* @__PURE__ */ jsxs4("div", { className: cn("hsk-cb-panel", classNames.panel), onClick: (e) => e.stopPropagation(), children: [
|
|
1218
1576
|
/* @__PURE__ */ jsxs4("div", { className: "hsk-cb-topbar", children: [
|
|
1219
1577
|
/* @__PURE__ */ jsxs4("div", { className: "hsk-cb-topbar-left", children: [
|
|
1220
1578
|
/* @__PURE__ */ jsx6("span", { className: "hsk-cb-topbar-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx6(SparkleIcon3, {}) }),
|
|
1221
|
-
/* @__PURE__ */
|
|
1222
|
-
/* @__PURE__ */ jsx6("div", { className: "hsk-cb-topbar-title", children: title }),
|
|
1223
|
-
/* @__PURE__ */ jsx6("div", { className: "hsk-cb-topbar-sub", children: "Powered by Huskel AI \xB7 searches the whole catalogue" })
|
|
1224
|
-
] })
|
|
1579
|
+
/* @__PURE__ */ jsx6("div", { children: /* @__PURE__ */ jsx6("div", { className: "hsk-cb-topbar-title", children: title }) })
|
|
1225
1580
|
] }),
|
|
1226
1581
|
/* @__PURE__ */ jsxs4("div", { className: "hsk-cb-topbar-actions", children: [
|
|
1227
1582
|
messages.length > 0 && /* @__PURE__ */ jsx6("button", { className: "hsk-cb-topbar-btn", onClick: reset, children: "Clear chat" }),
|
|
@@ -1231,8 +1586,7 @@ function ChatModal({
|
|
|
1231
1586
|
/* @__PURE__ */ jsxs4("div", { className: "hsk-cb-msgs", children: [
|
|
1232
1587
|
messages.length === 0 ? /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-empty", children: [
|
|
1233
1588
|
/* @__PURE__ */ jsx6("div", { className: "hsk-cb-empty-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx6(SparkleIcon3, {}) }),
|
|
1234
|
-
/* @__PURE__ */ jsx6("div", { className: "hsk-cb-empty-title", children: "
|
|
1235
|
-
/* @__PURE__ */ jsx6("div", { className: "hsk-cb-empty-sub", children: "Ask about products, budgets, gift ideas, specs \u2014 I'll search the entire catalogue for you." }),
|
|
1589
|
+
/* @__PURE__ */ jsx6("div", { className: "hsk-cb-empty-title", children: "Find exactly what you need" }),
|
|
1236
1590
|
/* @__PURE__ */ jsx6("div", { className: "hsk-cb-chips", children: chips.map((chip) => /* @__PURE__ */ jsx6(
|
|
1237
1591
|
"button",
|
|
1238
1592
|
{
|
|
@@ -1295,7 +1649,7 @@ function ChatModal({
|
|
|
1295
1649
|
"textarea",
|
|
1296
1650
|
{
|
|
1297
1651
|
ref: textareaRef,
|
|
1298
|
-
className:
|
|
1652
|
+
className: cn("hsk-cb-textarea", classNames.input),
|
|
1299
1653
|
value: input,
|
|
1300
1654
|
onChange: handleInput,
|
|
1301
1655
|
onKeyDown: handleKeyDown,
|
|
@@ -1308,11 +1662,11 @@ function ChatModal({
|
|
|
1308
1662
|
/* @__PURE__ */ jsx6(
|
|
1309
1663
|
"button",
|
|
1310
1664
|
{
|
|
1311
|
-
className:
|
|
1665
|
+
className: cn("hsk-cb-send", classNames.sendButton),
|
|
1312
1666
|
onClick: () => handleSend(),
|
|
1313
1667
|
disabled: !input.trim() || loading,
|
|
1314
1668
|
"aria-label": "Send message",
|
|
1315
|
-
children: /* @__PURE__ */ jsx6(
|
|
1669
|
+
children: /* @__PURE__ */ jsx6(ArrowUpIcon3, {})
|
|
1316
1670
|
}
|
|
1317
1671
|
)
|
|
1318
1672
|
] }),
|
|
@@ -1335,17 +1689,17 @@ function AIChatButton({
|
|
|
1335
1689
|
theme,
|
|
1336
1690
|
classNames = {}
|
|
1337
1691
|
}) {
|
|
1338
|
-
const [open, setOpen] =
|
|
1339
|
-
const [mounted, setMounted] =
|
|
1340
|
-
|
|
1692
|
+
const [open, setOpen] = useState8(false);
|
|
1693
|
+
const [mounted, setMounted] = useState8(false);
|
|
1694
|
+
useEffect7(() => {
|
|
1341
1695
|
setMounted(true);
|
|
1342
1696
|
}, []);
|
|
1343
|
-
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily });
|
|
1344
|
-
return /* @__PURE__ */ jsxs4(
|
|
1697
|
+
const customStyles = __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius });
|
|
1698
|
+
return /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1345
1699
|
/* @__PURE__ */ jsxs4(
|
|
1346
1700
|
"button",
|
|
1347
1701
|
{
|
|
1348
|
-
className:
|
|
1702
|
+
className: cn("hsk-cb-btn", classNames.button, className),
|
|
1349
1703
|
onClick: () => setOpen(true),
|
|
1350
1704
|
style: customStyles,
|
|
1351
1705
|
"aria-label": "Open AI chat",
|
|
@@ -1375,8 +1729,260 @@ function AIChatButton({
|
|
|
1375
1729
|
)
|
|
1376
1730
|
] });
|
|
1377
1731
|
}
|
|
1732
|
+
|
|
1733
|
+
// src/components/CartBadge.tsx
|
|
1734
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1735
|
+
function CartBadge({ className }) {
|
|
1736
|
+
const { cart } = useCart();
|
|
1737
|
+
if (!cart || cart.item_count === 0) return null;
|
|
1738
|
+
return /* @__PURE__ */ jsx7("span", { className: cn("hsk-cart-badge", className), children: cart.item_count });
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// src/components/CartDrawer.tsx
|
|
1742
|
+
import { useState as useState10, useEffect as useEffect9 } from "react";
|
|
1743
|
+
import { createPortal as createPortal4 } from "react-dom";
|
|
1744
|
+
|
|
1745
|
+
// src/components/CheckoutModal.tsx
|
|
1746
|
+
import { useState as useState9, useEffect as useEffect8 } from "react";
|
|
1747
|
+
import { createPortal as createPortal3 } from "react-dom";
|
|
1748
|
+
import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1749
|
+
function CheckoutModal({
|
|
1750
|
+
onClose,
|
|
1751
|
+
theme,
|
|
1752
|
+
customStyles,
|
|
1753
|
+
hskThemeAttr
|
|
1754
|
+
}) {
|
|
1755
|
+
var _a, _b, _c, _d;
|
|
1756
|
+
const { cart, loading: cartLoading } = useCart();
|
|
1757
|
+
const client = useHuskelContext();
|
|
1758
|
+
const [config, setConfig] = useState9(null);
|
|
1759
|
+
const [loading, setLoading] = useState9(true);
|
|
1760
|
+
const [checkingOut, setCheckingOut] = useState9(false);
|
|
1761
|
+
const [paymentSuccess, setPaymentSuccess] = useState9(false);
|
|
1762
|
+
useEffect8(() => {
|
|
1763
|
+
client.api.getCheckoutConfig().then((res) => setConfig(res.payment_methods)).catch((e) => console.error("[Huskel] Failed to fetch checkout config", e)).finally(() => setLoading(false));
|
|
1764
|
+
}, [client]);
|
|
1765
|
+
const handlePay = async (method) => {
|
|
1766
|
+
setCheckingOut(true);
|
|
1767
|
+
setTimeout(async () => {
|
|
1768
|
+
try {
|
|
1769
|
+
const payload = await client.api.checkoutCart();
|
|
1770
|
+
if (client.onCheckout) {
|
|
1771
|
+
client.onCheckout(payload);
|
|
1772
|
+
}
|
|
1773
|
+
setPaymentSuccess(true);
|
|
1774
|
+
setTimeout(() => {
|
|
1775
|
+
onClose();
|
|
1776
|
+
}, 3e3);
|
|
1777
|
+
} catch (e) {
|
|
1778
|
+
console.error("[Huskel] Checkout failed", e);
|
|
1779
|
+
setCheckingOut(false);
|
|
1780
|
+
}
|
|
1781
|
+
}, 1500);
|
|
1782
|
+
};
|
|
1783
|
+
const hasPaymentMethods = config && Object.values(config).some((m) => m.enabled);
|
|
1784
|
+
return createPortal3(
|
|
1785
|
+
/* @__PURE__ */ jsx8(
|
|
1786
|
+
"div",
|
|
1787
|
+
{
|
|
1788
|
+
className: "hsk-cart-backdrop",
|
|
1789
|
+
style: __spreadProps(__spreadValues({}, customStyles), { zIndex: 999999 }),
|
|
1790
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1791
|
+
onClick: onClose,
|
|
1792
|
+
children: /* @__PURE__ */ jsxs5(
|
|
1793
|
+
"div",
|
|
1794
|
+
{
|
|
1795
|
+
className: "hsk-checkout-modal",
|
|
1796
|
+
style: customStyles,
|
|
1797
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1798
|
+
onClick: (e) => e.stopPropagation(),
|
|
1799
|
+
children: [
|
|
1800
|
+
/* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-header", children: [
|
|
1801
|
+
/* @__PURE__ */ jsx8("h2", { children: "Secure Checkout" }),
|
|
1802
|
+
/* @__PURE__ */ jsx8("button", { onClick: onClose, className: "hsk-close-btn", children: "\xD7" })
|
|
1803
|
+
] }),
|
|
1804
|
+
/* @__PURE__ */ jsx8("div", { className: "hsk-checkout-content", children: paymentSuccess ? /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-success", children: [
|
|
1805
|
+
/* @__PURE__ */ jsxs5("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "hsk-success-icon", children: [
|
|
1806
|
+
/* @__PURE__ */ jsx8("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
|
|
1807
|
+
/* @__PURE__ */ jsx8("polyline", { points: "22 4 12 14.01 9 11.01" })
|
|
1808
|
+
] }),
|
|
1809
|
+
/* @__PURE__ */ jsx8("h3", { children: "Payment Successful!" }),
|
|
1810
|
+
/* @__PURE__ */ jsx8("p", { children: "Thank you for your order." })
|
|
1811
|
+
] }) : /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-split", children: [
|
|
1812
|
+
/* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-summary", children: [
|
|
1813
|
+
/* @__PURE__ */ jsx8("h3", { children: "Order Summary" }),
|
|
1814
|
+
cartLoading || !cart ? /* @__PURE__ */ jsx8("p", { className: "hsk-cart-loading", children: "Loading order..." }) : /* @__PURE__ */ jsxs5(Fragment5, { children: [
|
|
1815
|
+
/* @__PURE__ */ jsx8("ul", { className: "hsk-checkout-items", children: cart.items.map((item) => /* @__PURE__ */ jsxs5("li", { children: [
|
|
1816
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
1817
|
+
item.quantity,
|
|
1818
|
+
"x ",
|
|
1819
|
+
item.name
|
|
1820
|
+
] }),
|
|
1821
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
1822
|
+
item.currency,
|
|
1823
|
+
" ",
|
|
1824
|
+
(item.price_numeric * item.quantity).toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1825
|
+
] })
|
|
1826
|
+
] }, item.id)) }),
|
|
1827
|
+
/* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-total", children: [
|
|
1828
|
+
/* @__PURE__ */ jsx8("span", { children: "Total" }),
|
|
1829
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
1830
|
+
cart.currency,
|
|
1831
|
+
" ",
|
|
1832
|
+
cart.total.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1833
|
+
] })
|
|
1834
|
+
] })
|
|
1835
|
+
] })
|
|
1836
|
+
] }),
|
|
1837
|
+
/* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-payment", children: [
|
|
1838
|
+
/* @__PURE__ */ jsx8("h3", { children: "Payment Method" }),
|
|
1839
|
+
loading ? /* @__PURE__ */ jsx8("p", { className: "hsk-cart-loading", children: "Loading secure payment methods..." }) : !hasPaymentMethods ? /* @__PURE__ */ jsx8("p", { className: "hsk-checkout-error", children: "No payment methods are currently available for this store." }) : /* @__PURE__ */ jsxs5("div", { className: "hsk-payment-options", children: [
|
|
1840
|
+
((_a = config == null ? void 0 : config.mpesa) == null ? void 0 : _a.enabled) && /* @__PURE__ */ jsx8("button", { onClick: () => handlePay("mpesa"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-mpesa", children: checkingOut ? "Processing..." : "Pay with M-Pesa" }),
|
|
1841
|
+
((_b = config == null ? void 0 : config.equity) == null ? void 0 : _b.enabled) && /* @__PURE__ */ jsx8("button", { onClick: () => handlePay("equity"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-equity", children: checkingOut ? "Processing..." : "Pay with Equity Bank" }),
|
|
1842
|
+
((_c = config == null ? void 0 : config.stripe) == null ? void 0 : _c.enabled) && /* @__PURE__ */ jsx8("button", { onClick: () => handlePay("stripe"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-stripe", children: checkingOut ? "Processing..." : "Pay with Card (Stripe)" }),
|
|
1843
|
+
((_d = config == null ? void 0 : config.paypal) == null ? void 0 : _d.enabled) && /* @__PURE__ */ jsx8("button", { onClick: () => handlePay("paypal"), disabled: checkingOut, className: "hsk-pay-btn hsk-pay-paypal", children: checkingOut ? "Processing..." : "Pay with PayPal" })
|
|
1844
|
+
] })
|
|
1845
|
+
] })
|
|
1846
|
+
] }) })
|
|
1847
|
+
]
|
|
1848
|
+
}
|
|
1849
|
+
)
|
|
1850
|
+
}
|
|
1851
|
+
),
|
|
1852
|
+
document.body
|
|
1853
|
+
);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
// src/components/CartDrawer.tsx
|
|
1857
|
+
import { Fragment as Fragment6, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1858
|
+
function CartDrawer({
|
|
1859
|
+
trigger,
|
|
1860
|
+
className,
|
|
1861
|
+
theme
|
|
1862
|
+
}) {
|
|
1863
|
+
const { cart, loading } = useCart();
|
|
1864
|
+
const [open, setOpen] = useState10(false);
|
|
1865
|
+
const [showCheckout, setShowCheckout] = useState10(false);
|
|
1866
|
+
const [mounted, setMounted] = useState10(false);
|
|
1867
|
+
const client = useHuskelContext();
|
|
1868
|
+
useEffect9(() => {
|
|
1869
|
+
setMounted(true);
|
|
1870
|
+
const handleTriggerCheckout = () => {
|
|
1871
|
+
setShowCheckout(true);
|
|
1872
|
+
setOpen(false);
|
|
1873
|
+
};
|
|
1874
|
+
window.addEventListener("huskel:trigger_checkout", handleTriggerCheckout);
|
|
1875
|
+
return () => {
|
|
1876
|
+
window.removeEventListener("huskel:trigger_checkout", handleTriggerCheckout);
|
|
1877
|
+
};
|
|
1878
|
+
}, []);
|
|
1879
|
+
useEffect9(() => {
|
|
1880
|
+
if (open) {
|
|
1881
|
+
document.body.style.overflow = "hidden";
|
|
1882
|
+
} else {
|
|
1883
|
+
document.body.style.overflow = "";
|
|
1884
|
+
}
|
|
1885
|
+
return () => {
|
|
1886
|
+
document.body.style.overflow = "";
|
|
1887
|
+
};
|
|
1888
|
+
}, [open]);
|
|
1889
|
+
const handleCheckout = async () => {
|
|
1890
|
+
if (!cart || cart.items.length === 0) return;
|
|
1891
|
+
setShowCheckout(true);
|
|
1892
|
+
};
|
|
1893
|
+
const isStringTheme = typeof theme === "string";
|
|
1894
|
+
const hskThemeAttr = isStringTheme ? theme : void 0;
|
|
1895
|
+
const customStyles = !isStringTheme && theme ? __spreadValues(__spreadValues(__spreadValues(__spreadValues(__spreadValues({}, (theme == null ? void 0 : theme.primaryColor) && { "--hsk-primary": theme.primaryColor, "--hsk-primary-color": theme.primaryColor }), (theme == null ? void 0 : theme.backgroundColor) && { "--hsk-bg": theme.backgroundColor }), (theme == null ? void 0 : theme.textColor) && { "--hsk-text": theme.textColor }), (theme == null ? void 0 : theme.fontFamily) && { "--hsk-font": theme.fontFamily }), (theme == null ? void 0 : theme.borderRadius) && { "--hsk-border-radius": theme.borderRadius }) : void 0;
|
|
1896
|
+
return /* @__PURE__ */ jsxs6(Fragment6, { children: [
|
|
1897
|
+
/* @__PURE__ */ jsx9("div", { onClick: () => setOpen(true), style: { display: "inline-block" }, children: trigger || /* @__PURE__ */ jsxs6(
|
|
1898
|
+
"button",
|
|
1899
|
+
{
|
|
1900
|
+
className: cn("hsk-cart-trigger", className),
|
|
1901
|
+
style: customStyles,
|
|
1902
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1903
|
+
"aria-label": "Open cart",
|
|
1904
|
+
children: [
|
|
1905
|
+
/* @__PURE__ */ jsxs6("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1906
|
+
/* @__PURE__ */ jsx9("circle", { cx: "9", cy: "21", r: "1" }),
|
|
1907
|
+
/* @__PURE__ */ jsx9("circle", { cx: "20", cy: "21", r: "1" }),
|
|
1908
|
+
/* @__PURE__ */ jsx9("path", { d: "M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" })
|
|
1909
|
+
] }),
|
|
1910
|
+
cart && cart.item_count > 0 ? /* @__PURE__ */ jsx9("span", { className: "hsk-cart-trigger-badge", children: cart.item_count }) : null
|
|
1911
|
+
]
|
|
1912
|
+
}
|
|
1913
|
+
) }),
|
|
1914
|
+
open && mounted && createPortal4(
|
|
1915
|
+
/* @__PURE__ */ jsx9(
|
|
1916
|
+
"div",
|
|
1917
|
+
{
|
|
1918
|
+
className: "hsk-cart-backdrop",
|
|
1919
|
+
style: customStyles,
|
|
1920
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1921
|
+
onClick: () => setOpen(false),
|
|
1922
|
+
children: /* @__PURE__ */ jsxs6(
|
|
1923
|
+
"div",
|
|
1924
|
+
{
|
|
1925
|
+
className: "hsk-cart-bottom-sheet",
|
|
1926
|
+
style: customStyles,
|
|
1927
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1928
|
+
onClick: (e) => e.stopPropagation(),
|
|
1929
|
+
children: [
|
|
1930
|
+
/* @__PURE__ */ jsx9("div", { className: "hsk-cart-sheet-handle" }),
|
|
1931
|
+
/* @__PURE__ */ jsxs6("div", { className: "hsk-cart-sheet-header", children: [
|
|
1932
|
+
/* @__PURE__ */ jsx9("h2", { children: "Your Cart" }),
|
|
1933
|
+
/* @__PURE__ */ jsx9("button", { onClick: () => setOpen(false), className: "hsk-close-btn", children: "\xD7" })
|
|
1934
|
+
] }),
|
|
1935
|
+
/* @__PURE__ */ jsx9("div", { className: "hsk-cart-sheet-content", children: loading && !cart ? /* @__PURE__ */ jsx9("div", { className: "hsk-cart-loading", children: "Loading cart..." }) : !cart || cart.items.length === 0 ? /* @__PURE__ */ jsx9("div", { className: "hsk-cart-empty", children: "Your cart is empty." }) : /* @__PURE__ */ jsx9("ul", { className: "hsk-cart-items", children: cart.items.map((item) => /* @__PURE__ */ jsxs6("li", { className: "hsk-cart-item", children: [
|
|
1936
|
+
item.image && /* @__PURE__ */ jsx9("img", { src: item.image, alt: item.name, className: "hsk-cart-item-img" }),
|
|
1937
|
+
/* @__PURE__ */ jsxs6("div", { className: "hsk-cart-item-info", children: [
|
|
1938
|
+
/* @__PURE__ */ jsx9("span", { className: "hsk-cart-item-name", children: item.name }),
|
|
1939
|
+
/* @__PURE__ */ jsxs6("span", { className: "hsk-cart-item-price", children: [
|
|
1940
|
+
item.currency,
|
|
1941
|
+
" ",
|
|
1942
|
+
item.price_numeric.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1943
|
+
] })
|
|
1944
|
+
] }),
|
|
1945
|
+
/* @__PURE__ */ jsxs6("div", { className: "hsk-cart-item-qty", children: [
|
|
1946
|
+
"x",
|
|
1947
|
+
item.quantity
|
|
1948
|
+
] })
|
|
1949
|
+
] }, item.id)) }) }),
|
|
1950
|
+
cart && cart.items.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "hsk-cart-sheet-footer", children: [
|
|
1951
|
+
/* @__PURE__ */ jsxs6("div", { className: "hsk-cart-total", children: [
|
|
1952
|
+
/* @__PURE__ */ jsx9("span", { children: "Total" }),
|
|
1953
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
1954
|
+
cart.currency,
|
|
1955
|
+
" ",
|
|
1956
|
+
cart.total.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1957
|
+
] })
|
|
1958
|
+
] }),
|
|
1959
|
+
/* @__PURE__ */ jsx9("button", { onClick: handleCheckout, className: "hsk-checkout-btn", children: "Checkout securely" })
|
|
1960
|
+
] })
|
|
1961
|
+
]
|
|
1962
|
+
}
|
|
1963
|
+
)
|
|
1964
|
+
}
|
|
1965
|
+
),
|
|
1966
|
+
document.body
|
|
1967
|
+
),
|
|
1968
|
+
showCheckout && mounted && /* @__PURE__ */ jsx9(
|
|
1969
|
+
CheckoutModal,
|
|
1970
|
+
{
|
|
1971
|
+
onClose: () => {
|
|
1972
|
+
setShowCheckout(false);
|
|
1973
|
+
setOpen(false);
|
|
1974
|
+
},
|
|
1975
|
+
theme: isStringTheme ? theme : void 0,
|
|
1976
|
+
customStyles,
|
|
1977
|
+
hskThemeAttr
|
|
1978
|
+
}
|
|
1979
|
+
)
|
|
1980
|
+
] });
|
|
1981
|
+
}
|
|
1378
1982
|
export {
|
|
1379
1983
|
AIChatButton,
|
|
1984
|
+
CartBadge,
|
|
1985
|
+
CartDrawer,
|
|
1380
1986
|
ChatWidget,
|
|
1381
1987
|
HuskelAPI,
|
|
1382
1988
|
HuskelClient,
|
|
@@ -1385,6 +1991,7 @@ export {
|
|
|
1385
1991
|
Sparkle,
|
|
1386
1992
|
getHuskelClient,
|
|
1387
1993
|
initHuskel,
|
|
1994
|
+
useCart,
|
|
1388
1995
|
useChat,
|
|
1389
1996
|
useHuskel,
|
|
1390
1997
|
useIngest,
|