@huskel/sdk 0.4.2 → 0.4.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/dist/index.css +1008 -159
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +64 -27
- package/dist/index.d.ts +64 -27
- package/dist/index.js +798 -243
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +786 -234
- 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;
|
|
@@ -268,7 +323,7 @@ var _HuskelClient = class _HuskelClient {
|
|
|
268
323
|
this.shopperId = id;
|
|
269
324
|
}
|
|
270
325
|
getShopperId() {
|
|
271
|
-
return this.shopperId;
|
|
326
|
+
return this.shopperId || "guest_" + this.sessionId;
|
|
272
327
|
}
|
|
273
328
|
getSessionId() {
|
|
274
329
|
return this.sessionId;
|
|
@@ -441,7 +496,15 @@ function useSearch() {
|
|
|
441
496
|
}
|
|
442
497
|
} catch (e) {
|
|
443
498
|
if (gen === genRef.current) {
|
|
444
|
-
|
|
499
|
+
let msg = (_b = e == null ? void 0 : e.message) != null ? _b : "Search failed";
|
|
500
|
+
try {
|
|
501
|
+
const parsed = JSON.parse(msg);
|
|
502
|
+
if (parsed && parsed.error) {
|
|
503
|
+
msg = parsed.error;
|
|
504
|
+
}
|
|
505
|
+
} catch (e2) {
|
|
506
|
+
}
|
|
507
|
+
setError(msg);
|
|
445
508
|
}
|
|
446
509
|
} finally {
|
|
447
510
|
if (gen === genRef.current) setLoading(false);
|
|
@@ -516,13 +579,13 @@ function useChat() {
|
|
|
516
579
|
const [loading, setLoading] = useState3(false);
|
|
517
580
|
const [error, setError] = useState3(null);
|
|
518
581
|
const abortRef = useRef5(null);
|
|
519
|
-
const send = useCallback3(async (query) => {
|
|
520
|
-
var _a, _b, _c;
|
|
582
|
+
const send = useCallback3(async (query, displayQuery) => {
|
|
583
|
+
var _a, _b, _c, _d, _e;
|
|
521
584
|
if (!query.trim() || loading) return;
|
|
522
585
|
(_a = abortRef.current) == null ? void 0 : _a.abort();
|
|
523
586
|
abortRef.current = new AbortController();
|
|
524
587
|
const signal = abortRef.current.signal;
|
|
525
|
-
const userMsg = { role: "user", content: query };
|
|
588
|
+
const userMsg = { role: "user", content: displayQuery != null ? displayQuery : query };
|
|
526
589
|
setMessages((prev) => [...prev, userMsg]);
|
|
527
590
|
setLoading(true);
|
|
528
591
|
setError(null);
|
|
@@ -548,9 +611,30 @@ function useChat() {
|
|
|
548
611
|
}
|
|
549
612
|
if (signal.aborted) return;
|
|
550
613
|
setSources((_b = res.sources) != null ? _b : []);
|
|
614
|
+
if (((_c = res.action) == null ? void 0 : _c.type) === "add_to_cart" || res.checkout) {
|
|
615
|
+
if (typeof window !== "undefined") {
|
|
616
|
+
window.dispatchEvent(new CustomEvent("huskel:cart_updated", { detail: res.checkout }));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (((_d = res.action) == null ? void 0 : _d.type) === "checkout") {
|
|
620
|
+
if (typeof window !== "undefined") {
|
|
621
|
+
window.dispatchEvent(new CustomEvent("huskel:trigger_checkout", { detail: res.checkout }));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (res.checkout && client.onCheckout) {
|
|
625
|
+
client.onCheckout(res.checkout);
|
|
626
|
+
}
|
|
551
627
|
} catch (e) {
|
|
552
628
|
if (signal.aborted) return;
|
|
553
|
-
|
|
629
|
+
let msg = (_e = e == null ? void 0 : e.message) != null ? _e : "Chat request failed";
|
|
630
|
+
try {
|
|
631
|
+
const parsed = JSON.parse(msg);
|
|
632
|
+
if (parsed && parsed.error) {
|
|
633
|
+
msg = parsed.error;
|
|
634
|
+
}
|
|
635
|
+
} catch (e2) {
|
|
636
|
+
}
|
|
637
|
+
setError(msg);
|
|
554
638
|
setMessages((prev) => prev.slice(0, -1));
|
|
555
639
|
} finally {
|
|
556
640
|
if (!signal.aborted) {
|
|
@@ -569,8 +653,53 @@ function useChat() {
|
|
|
569
653
|
return { messages, sources, loading, error, send, reset };
|
|
570
654
|
}
|
|
571
655
|
|
|
656
|
+
// src/hooks/useCart.ts
|
|
657
|
+
import { useState as useState4, useEffect as useEffect3, useCallback as useCallback4 } from "react";
|
|
658
|
+
function useCart() {
|
|
659
|
+
const client = useHuskelContext();
|
|
660
|
+
const [cart, setCart] = useState4(null);
|
|
661
|
+
const [loading, setLoading] = useState4(false);
|
|
662
|
+
const shopperId = client.getShopperId();
|
|
663
|
+
const fetchCart = useCallback4(async () => {
|
|
664
|
+
if (!shopperId) return;
|
|
665
|
+
setLoading(true);
|
|
666
|
+
try {
|
|
667
|
+
const res = await client.api.getCart();
|
|
668
|
+
setCart(res);
|
|
669
|
+
} catch (e) {
|
|
670
|
+
console.error("[Huskel] Failed to fetch cart", e);
|
|
671
|
+
} finally {
|
|
672
|
+
setLoading(false);
|
|
673
|
+
}
|
|
674
|
+
}, [client, shopperId]);
|
|
675
|
+
useEffect3(() => {
|
|
676
|
+
fetchCart();
|
|
677
|
+
const handleCartUpdate = (e) => {
|
|
678
|
+
if (e.detail) {
|
|
679
|
+
setCart(e.detail);
|
|
680
|
+
} else {
|
|
681
|
+
fetchCart();
|
|
682
|
+
}
|
|
683
|
+
};
|
|
684
|
+
if (typeof window !== "undefined") {
|
|
685
|
+
window.addEventListener("huskel:cart_updated", handleCartUpdate);
|
|
686
|
+
return () => window.removeEventListener("huskel:cart_updated", handleCartUpdate);
|
|
687
|
+
}
|
|
688
|
+
}, [fetchCart, shopperId]);
|
|
689
|
+
return { cart, loading, fetchCart };
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/components/SearchBar.tsx
|
|
693
|
+
import { useState as useState5, useEffect as useEffect4, useRef as useRef6 } from "react";
|
|
694
|
+
|
|
695
|
+
// src/utils/cn.ts
|
|
696
|
+
import { clsx } from "clsx";
|
|
697
|
+
import { twMerge } from "tailwind-merge";
|
|
698
|
+
function cn(...inputs) {
|
|
699
|
+
return twMerge(clsx(inputs));
|
|
700
|
+
}
|
|
701
|
+
|
|
572
702
|
// src/components/SearchBar.tsx
|
|
573
|
-
import { useState as useState4, useEffect as useEffect3, useRef as useRef6 } from "react";
|
|
574
703
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
575
704
|
var SearchIcon = () => /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
|
|
576
705
|
/* @__PURE__ */ jsx2("circle", { cx: "8.5", cy: "8.5", r: "5.5" }),
|
|
@@ -588,12 +717,12 @@ function SearchBar({
|
|
|
588
717
|
theme,
|
|
589
718
|
classNames = {}
|
|
590
719
|
}) {
|
|
591
|
-
const [query, setQuery] =
|
|
592
|
-
const [open, setOpen] =
|
|
720
|
+
const [query, setQuery] = useState5("");
|
|
721
|
+
const [open, setOpen] = useState5(false);
|
|
593
722
|
const { results, loading, search, clear } = useSearch();
|
|
594
723
|
const timer = useRef6();
|
|
595
724
|
const wrap = useRef6(null);
|
|
596
|
-
|
|
725
|
+
useEffect4(() => {
|
|
597
726
|
clearTimeout(timer.current);
|
|
598
727
|
if (!query.trim()) {
|
|
599
728
|
clear();
|
|
@@ -606,7 +735,7 @@ function SearchBar({
|
|
|
606
735
|
}, debounceMs);
|
|
607
736
|
return () => clearTimeout(timer.current);
|
|
608
737
|
}, [query]);
|
|
609
|
-
|
|
738
|
+
useEffect4(() => {
|
|
610
739
|
const h = (e) => {
|
|
611
740
|
if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
|
|
612
741
|
};
|
|
@@ -619,13 +748,13 @@ function SearchBar({
|
|
|
619
748
|
onSelect == null ? void 0 : onSelect(r);
|
|
620
749
|
};
|
|
621
750
|
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:
|
|
751
|
+
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 });
|
|
752
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("hsk-sb-wrap", classNames.root, className), ref: wrap, style: customStyles, children: [
|
|
624
753
|
/* @__PURE__ */ jsx2("span", { className: "hsk-sb-icon", children: /* @__PURE__ */ jsx2(SearchIcon, {}) }),
|
|
625
754
|
/* @__PURE__ */ jsx2(
|
|
626
755
|
"input",
|
|
627
756
|
{
|
|
628
|
-
className:
|
|
757
|
+
className: cn("hsk-sb-input", classNames.input, inputClassName),
|
|
629
758
|
type: "text",
|
|
630
759
|
value: query,
|
|
631
760
|
placeholder,
|
|
@@ -635,7 +764,7 @@ function SearchBar({
|
|
|
635
764
|
spellCheck: false
|
|
636
765
|
}
|
|
637
766
|
),
|
|
638
|
-
showDrop && /* @__PURE__ */ jsxs("div", { className:
|
|
767
|
+
showDrop && /* @__PURE__ */ jsxs("div", { className: cn("hsk-sb-drop", classNames.dropdown, dropdownClassName), style: { position: "absolute" }, children: [
|
|
639
768
|
loading && /* @__PURE__ */ jsx2("div", { className: "hsk-sb-loading-bar" }),
|
|
640
769
|
results.length === 0 && !loading && /* @__PURE__ */ jsxs("div", { className: "hsk-sb-empty", children: [
|
|
641
770
|
"No results for \u201C",
|
|
@@ -656,7 +785,7 @@ function SearchBar({
|
|
|
656
785
|
) : /* @__PURE__ */ jsxs(
|
|
657
786
|
"div",
|
|
658
787
|
{
|
|
659
|
-
className:
|
|
788
|
+
className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
|
|
660
789
|
style: { animationDelay: `${i * 18}ms` },
|
|
661
790
|
onClick: () => handleSelect(r),
|
|
662
791
|
children: [
|
|
@@ -675,14 +804,124 @@ function SearchBar({
|
|
|
675
804
|
}
|
|
676
805
|
|
|
677
806
|
// src/components/Sparkle.tsx
|
|
678
|
-
import { useState as
|
|
807
|
+
import { useState as useState6, useEffect as useEffect5, useRef as useRef7 } from "react";
|
|
679
808
|
import { createPortal } from "react-dom";
|
|
680
|
-
|
|
681
|
-
|
|
809
|
+
|
|
810
|
+
// src/utils/markdown.tsx
|
|
811
|
+
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
812
|
+
var parseInline = (text, keyPrefix) => {
|
|
813
|
+
const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
|
|
814
|
+
const parts = text.split(tokenRegex);
|
|
815
|
+
return parts.map((part, index) => {
|
|
816
|
+
if (!part) return null;
|
|
817
|
+
const key = `${keyPrefix}-inline-${index}`;
|
|
818
|
+
if (part.startsWith("`") && part.endsWith("`")) {
|
|
819
|
+
return /* @__PURE__ */ jsx3("code", { className: "hsk-markdown-code", children: part.slice(1, -1) }, key);
|
|
820
|
+
}
|
|
821
|
+
if (part.startsWith("**") && part.endsWith("**")) {
|
|
822
|
+
return /* @__PURE__ */ jsx3("strong", { children: parseInline(part.slice(2, -2), key) }, key);
|
|
823
|
+
}
|
|
824
|
+
const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
825
|
+
if (linkMatch) {
|
|
826
|
+
const url = linkMatch[2];
|
|
827
|
+
const isSafeUrl = /^(https?|mailto|tel):/i.test(url) || url.startsWith("/");
|
|
828
|
+
if (isSafeUrl) {
|
|
829
|
+
return /* @__PURE__ */ jsx3("a", { href: url, target: "_blank", rel: "noopener noreferrer", className: "hsk-markdown-link", children: parseInline(linkMatch[1], key) }, key);
|
|
830
|
+
}
|
|
831
|
+
return /* @__PURE__ */ jsx3("span", { children: parseInline(linkMatch[1], key) }, key);
|
|
832
|
+
}
|
|
833
|
+
return part;
|
|
834
|
+
});
|
|
835
|
+
};
|
|
836
|
+
function renderMarkdown(content) {
|
|
837
|
+
const lines = content.split("\n");
|
|
838
|
+
const elements = [];
|
|
839
|
+
let i = 0;
|
|
840
|
+
while (i < lines.length) {
|
|
841
|
+
const line = lines[i];
|
|
842
|
+
const key = `md-line-${i}`;
|
|
843
|
+
if (!line.trim()) {
|
|
844
|
+
i++;
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
const headerMatch = line.match(/^(#{1,3})\s+(.*)/);
|
|
848
|
+
if (headerMatch) {
|
|
849
|
+
const level = headerMatch[1].length;
|
|
850
|
+
const Tag = `h${level + 3}`;
|
|
851
|
+
elements.push(/* @__PURE__ */ jsx3(Tag, { className: `hsk-markdown-h${level}`, children: parseInline(headerMatch[2], key) }, key));
|
|
852
|
+
i++;
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
if (line.match(/^[-*]\s+/)) {
|
|
856
|
+
const listItems = [];
|
|
857
|
+
while (i < lines.length && lines[i].match(/^[-*]\s+/)) {
|
|
858
|
+
const itemText = lines[i].replace(/^[-*]\s+/, "");
|
|
859
|
+
listItems.push(/* @__PURE__ */ jsx3("li", { children: parseInline(itemText, `li-${i}`) }, `li-${i}`));
|
|
860
|
+
i++;
|
|
861
|
+
}
|
|
862
|
+
elements.push(/* @__PURE__ */ jsx3("ul", { className: "hsk-markdown-list", children: listItems }, `ul-${key}`));
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
if (line.trim().startsWith("|")) {
|
|
866
|
+
const tableRows = [];
|
|
867
|
+
let isHeader = true;
|
|
868
|
+
while (i < lines.length && lines[i].trim().startsWith("|")) {
|
|
869
|
+
const rowLine = lines[i].trim();
|
|
870
|
+
if (rowLine.match(/^\|[-:| ]+\|$/)) {
|
|
871
|
+
i++;
|
|
872
|
+
isHeader = false;
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
const cells = rowLine.split("|").slice(1, -1).map((c) => c.trim());
|
|
876
|
+
const Tag = isHeader ? "th" : "td";
|
|
877
|
+
tableRows.push(
|
|
878
|
+
/* @__PURE__ */ jsx3("tr", { children: cells.map((cell, cIdx) => /* @__PURE__ */ jsx3(Tag, { children: parseInline(cell, `td-${i}-${cIdx}`) }, `td-${i}-${cIdx}`)) }, `tr-${i}`)
|
|
879
|
+
);
|
|
880
|
+
i++;
|
|
881
|
+
}
|
|
882
|
+
elements.push(
|
|
883
|
+
/* @__PURE__ */ jsx3("div", { className: "hsk-table-wrapper", children: /* @__PURE__ */ jsx3("table", { className: "hsk-markdown-table", children: /* @__PURE__ */ jsx3("tbody", { children: tableRows }) }) }, `table-wrapper-${key}`)
|
|
884
|
+
);
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
elements.push(
|
|
888
|
+
/* @__PURE__ */ jsx3("p", { className: "hsk-markdown-p", children: parseInline(line, key) }, key)
|
|
889
|
+
);
|
|
890
|
+
i++;
|
|
891
|
+
}
|
|
892
|
+
return /* @__PURE__ */ jsx3(Fragment, { children: elements });
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/components/Sparkle.tsx
|
|
896
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
897
|
+
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
898
|
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__ */
|
|
899
|
+
/* @__PURE__ */ jsx4("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
900
|
+
/* @__PURE__ */ jsx4("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
901
|
+
] });
|
|
902
|
+
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: [
|
|
903
|
+
/* @__PURE__ */ jsx4("path", { d: "m5 12 7-7 7 7" }),
|
|
904
|
+
/* @__PURE__ */ jsx4("path", { d: "M12 19V5" })
|
|
685
905
|
] });
|
|
906
|
+
var getFriendlyError = (err) => {
|
|
907
|
+
let str = "";
|
|
908
|
+
if (typeof err === "string") str = err;
|
|
909
|
+
else if (err && typeof err === "object" && err.message) str = err.message;
|
|
910
|
+
else try {
|
|
911
|
+
str = JSON.stringify(err);
|
|
912
|
+
} catch (e) {
|
|
913
|
+
str = String(err);
|
|
914
|
+
}
|
|
915
|
+
if (str.toLowerCase().includes("token limit")) {
|
|
916
|
+
return "You've reached your usage limit. Please update your billing limits in your dashboard to continue.";
|
|
917
|
+
}
|
|
918
|
+
try {
|
|
919
|
+
const parsed = JSON.parse(str);
|
|
920
|
+
return parsed.error || parsed.message || str;
|
|
921
|
+
} catch (e) {
|
|
922
|
+
return str;
|
|
923
|
+
}
|
|
924
|
+
};
|
|
686
925
|
function SparkleModal({
|
|
687
926
|
productName,
|
|
688
927
|
limit,
|
|
@@ -692,26 +931,42 @@ function SparkleModal({
|
|
|
692
931
|
onNavigate,
|
|
693
932
|
onResult,
|
|
694
933
|
theme,
|
|
695
|
-
classNames = {}
|
|
934
|
+
classNames = {},
|
|
935
|
+
product: initialProduct
|
|
696
936
|
}) {
|
|
697
|
-
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
937
|
+
var _a, _b, _c;
|
|
938
|
+
const client = useHuskelContext();
|
|
939
|
+
const [fetchedProduct, setFetchedProduct] = useState6(null);
|
|
940
|
+
const displayProduct = initialProduct || fetchedProduct;
|
|
941
|
+
const { results, loading: searchLoading, search } = useSearch();
|
|
942
|
+
const { messages, sources, loading: chatLoading, error: chatError, send } = useChat();
|
|
943
|
+
const [chatInput, setChatInput] = useState6("");
|
|
944
|
+
const chatBottomRef = useRef7(null);
|
|
945
|
+
const chatTextareaRef = useRef7(null);
|
|
946
|
+
useEffect5(() => {
|
|
947
|
+
if (!initialProduct && !fetchedProduct) {
|
|
948
|
+
client.api.searchVector(productName, 1).then((res) => {
|
|
949
|
+
if (res.results && res.results.length > 0) {
|
|
950
|
+
setFetchedProduct(res.results[0].product);
|
|
951
|
+
}
|
|
952
|
+
}).catch((err) => console.error("[Huskel] Failed to fetch product details", err));
|
|
703
953
|
}
|
|
704
|
-
|
|
705
|
-
|
|
954
|
+
search(productName, limit);
|
|
955
|
+
}, [productName, initialProduct, fetchedProduct, client, limit, search]);
|
|
956
|
+
useEffect5(() => {
|
|
706
957
|
if (results.length > 0) onResult == null ? void 0 : onResult(results);
|
|
707
|
-
}, [results]);
|
|
708
|
-
|
|
958
|
+
}, [results, onResult]);
|
|
959
|
+
useEffect5(() => {
|
|
709
960
|
const h = (e) => {
|
|
710
961
|
if (e.key === "Escape") onClose();
|
|
711
962
|
};
|
|
712
963
|
document.addEventListener("keydown", h);
|
|
713
964
|
return () => document.removeEventListener("keydown", h);
|
|
714
|
-
}, []);
|
|
965
|
+
}, [onClose]);
|
|
966
|
+
useEffect5(() => {
|
|
967
|
+
var _a2;
|
|
968
|
+
(_a2 = chatBottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
969
|
+
}, [messages, chatLoading]);
|
|
715
970
|
const blurVal = typeof backdropBlur === "number" ? `${backdropBlur}px` : backdropBlur != null ? backdropBlur : "16px";
|
|
716
971
|
const bg = backdropColor != null ? backdropColor : void 0;
|
|
717
972
|
const handleNav = (r) => {
|
|
@@ -721,83 +976,209 @@ function SparkleModal({
|
|
|
721
976
|
if (r.product.url) window.location.href = r.product.url;
|
|
722
977
|
}
|
|
723
978
|
};
|
|
724
|
-
const
|
|
725
|
-
|
|
979
|
+
const handleSend = async (text) => {
|
|
980
|
+
const q = (text != null ? text : chatInput).trim();
|
|
981
|
+
if (!q || chatLoading) return;
|
|
982
|
+
setChatInput("");
|
|
983
|
+
if (chatTextareaRef.current) {
|
|
984
|
+
chatTextareaRef.current.style.height = "auto";
|
|
985
|
+
}
|
|
986
|
+
if (messages.length === 0 && displayProduct) {
|
|
987
|
+
const contextQuery = `[Context: Shopper is viewing "${displayProduct.name}". Price: ${displayProduct.price}. Description: ${displayProduct.description || ""}]
|
|
988
|
+
|
|
989
|
+
Question: ${q}`;
|
|
990
|
+
await send(contextQuery, q);
|
|
991
|
+
} else {
|
|
992
|
+
await send(q);
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
const handleKeyDown = (e) => {
|
|
996
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
997
|
+
e.preventDefault();
|
|
998
|
+
handleSend();
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
const handleInput = (e) => {
|
|
1002
|
+
setChatInput(e.target.value);
|
|
1003
|
+
const t = e.target;
|
|
1004
|
+
t.style.height = "auto";
|
|
1005
|
+
t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
|
|
1006
|
+
};
|
|
1007
|
+
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 });
|
|
1008
|
+
const displayMessages = messages.length === 0 && displayProduct ? [
|
|
1009
|
+
{
|
|
1010
|
+
role: "assistant",
|
|
1011
|
+
content: `Hi! I can help you with **${displayProduct.name}**. Ask me about its specifications, features, compare it with other options, or find alternatives!`
|
|
1012
|
+
}
|
|
1013
|
+
] : messages;
|
|
1014
|
+
return /* @__PURE__ */ jsx4(
|
|
726
1015
|
"div",
|
|
727
1016
|
{
|
|
728
|
-
className:
|
|
1017
|
+
className: cn("hsk-sp-backdrop", classNames.backdrop),
|
|
729
1018
|
onClick: onClose,
|
|
730
1019
|
style: __spreadValues({
|
|
731
1020
|
backdropFilter: `blur(${blurVal})`,
|
|
732
1021
|
WebkitBackdropFilter: `blur(${blurVal})`,
|
|
733
1022
|
background: bg != null ? bg : void 0
|
|
734
1023
|
}, customStyles),
|
|
735
|
-
children: /* @__PURE__ */ jsxs2("div", { className:
|
|
1024
|
+
children: /* @__PURE__ */ jsxs2("div", { className: cn("hsk-sp-card hsk-sp-fullscreen", classNames.card), onClick: (e) => e.stopPropagation(), children: [
|
|
736
1025
|
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-header", children: [
|
|
737
|
-
/* @__PURE__ */
|
|
1026
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-header-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx4(SparkleIcon, {}) }),
|
|
738
1027
|
/* @__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" })
|
|
1028
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-header-title", children: (displayProduct == null ? void 0 : displayProduct.name) || productName }),
|
|
1029
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-header-sub", children: "Ask questions, compare specs, or check similar products" })
|
|
745
1030
|
] }),
|
|
746
|
-
/* @__PURE__ */
|
|
1031
|
+
/* @__PURE__ */ jsx4("button", { className: "hsk-sp-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx4(CloseIcon, {}) })
|
|
747
1032
|
] }),
|
|
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
|
-
|
|
1033
|
+
searchLoading && /* @__PURE__ */ jsx4("div", { className: "hsk-sp-bar" }),
|
|
1034
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-body", children: [
|
|
1035
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-details-pane", children: [
|
|
1036
|
+
displayProduct && /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-product-profile-container", children: [
|
|
1037
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-product-profile", children: [
|
|
1038
|
+
/* @__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}" }) }),
|
|
1039
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-details-meta", children: [
|
|
1040
|
+
displayProduct.brand && /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-brand", children: displayProduct.brand }),
|
|
1041
|
+
displayProduct.category && /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-cat", children: displayProduct.category }),
|
|
1042
|
+
/* @__PURE__ */ jsx4("h2", { className: "hsk-sp-details-name", children: displayProduct.name }),
|
|
1043
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-price-row", children: [
|
|
1044
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-currency", children: (_b = displayProduct.currency) != null ? _b : "KES" }),
|
|
1045
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-price", children: parseFloat(((_c = displayProduct.price) == null ? void 0 : _c.replace(/[^0-9.]/g, "")) || "0").toLocaleString() }),
|
|
1046
|
+
displayProduct.originalPrice && /* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-original-price", children: parseFloat(displayProduct.originalPrice.replace(/[^0-9.]/g, "") || "0").toLocaleString() }),
|
|
1047
|
+
displayProduct.discount && /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-item-discount", children: [
|
|
1048
|
+
"(",
|
|
1049
|
+
displayProduct.discount,
|
|
1050
|
+
")"
|
|
1051
|
+
] })
|
|
1052
|
+
] }),
|
|
1053
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-meta-badges", children: [
|
|
1054
|
+
displayProduct.rating && /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-rating", children: [
|
|
1055
|
+
"\u2605 ",
|
|
1056
|
+
parseFloat(displayProduct.rating.toString()).toFixed(1),
|
|
1057
|
+
" ",
|
|
1058
|
+
displayProduct.reviewCount ? `(${displayProduct.reviewCount})` : ""
|
|
768
1059
|
] }),
|
|
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
|
-
)
|
|
1060
|
+
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 }),
|
|
1061
|
+
displayProduct.stock && !displayProduct.availability && /* @__PURE__ */ jsxs2("span", { className: "hsk-sp-meta-badge hsk-sp-meta-badge-stock", children: [
|
|
1062
|
+
"Stock: ",
|
|
1063
|
+
displayProduct.stock
|
|
786
1064
|
] })
|
|
787
1065
|
] })
|
|
788
|
-
]
|
|
789
|
-
},
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1066
|
+
] })
|
|
1067
|
+
] }),
|
|
1068
|
+
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: [
|
|
1069
|
+
/* @__PURE__ */ jsxs2("span", { className: "hsk-sp-spec-label-horizontal", children: [
|
|
1070
|
+
key,
|
|
1071
|
+
":"
|
|
1072
|
+
] }),
|
|
1073
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-spec-value-horizontal", title: val, children: val })
|
|
1074
|
+
] }, key)) }),
|
|
1075
|
+
displayProduct.description && /* @__PURE__ */ jsxs2("div", { className: "hsk-sp-details-desc", children: [
|
|
1076
|
+
/* @__PURE__ */ jsx4("h4", { children: "Description" }),
|
|
1077
|
+
/* @__PURE__ */ jsx4("p", { children: displayProduct.description })
|
|
1078
|
+
] })
|
|
1079
|
+
] }),
|
|
1080
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-similar-section", children: [
|
|
1081
|
+
/* @__PURE__ */ jsx4("h3", { children: "Similar Products" }),
|
|
1082
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-results", children: (() => {
|
|
1083
|
+
const similarProducts = results.filter(
|
|
1084
|
+
(r) => {
|
|
1085
|
+
var _a2;
|
|
1086
|
+
const isSameName = r.product.name.toLowerCase() === ((_a2 = displayProduct == null ? void 0 : displayProduct.name) == null ? void 0 : _a2.toLowerCase());
|
|
1087
|
+
const isSameSlug = r.product.slug && (displayProduct == null ? void 0 : displayProduct.slug) && r.product.slug.toLowerCase() === displayProduct.slug.toLowerCase();
|
|
1088
|
+
return !isSameName && !isSameSlug;
|
|
1089
|
+
}
|
|
1090
|
+
);
|
|
1091
|
+
if (!searchLoading && similarProducts.length === 0) {
|
|
1092
|
+
return /* @__PURE__ */ jsx4("div", { className: "hsk-sp-empty", children: "No similar products found." });
|
|
1093
|
+
}
|
|
1094
|
+
return similarProducts.map((r, i) => {
|
|
1095
|
+
var _a2, _b2, _c2;
|
|
1096
|
+
const price = parseFloat(((_a2 = r.product.price) == null ? void 0 : _a2.replace(/[^0-9.]/g, "")) || "0");
|
|
1097
|
+
const currency = (_b2 = r.product.currency) != null ? _b2 : "KES";
|
|
1098
|
+
return /* @__PURE__ */ jsxs2(
|
|
1099
|
+
"div",
|
|
1100
|
+
{
|
|
1101
|
+
className: cn("hsk-sp-item", classNames.item),
|
|
1102
|
+
style: { animationDelay: `${i * 55}ms` },
|
|
1103
|
+
children: [
|
|
1104
|
+
/* @__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}" }) }),
|
|
1105
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-body", children: [
|
|
1106
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
1107
|
+
r.product.category && /* @__PURE__ */ jsx4("div", { className: "hsk-sp-item-cat", children: r.product.category }),
|
|
1108
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-item-name", title: r.product.name, children: r.product.name })
|
|
1109
|
+
] }),
|
|
1110
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-item-price-row", children: [
|
|
1111
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-currency", children: currency }),
|
|
1112
|
+
/* @__PURE__ */ jsx4("span", { className: "hsk-sp-item-price", children: price.toLocaleString() })
|
|
1113
|
+
] }),
|
|
1114
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-actions", children: /* @__PURE__ */ jsx4(
|
|
1115
|
+
"button",
|
|
1116
|
+
{
|
|
1117
|
+
className: "hsk-sp-action hsk-sp-action-primary",
|
|
1118
|
+
onClick: () => handleNav(r),
|
|
1119
|
+
children: "View"
|
|
1120
|
+
}
|
|
1121
|
+
) })
|
|
1122
|
+
] })
|
|
1123
|
+
]
|
|
1124
|
+
},
|
|
1125
|
+
r.id
|
|
1126
|
+
);
|
|
1127
|
+
});
|
|
1128
|
+
})() })
|
|
1129
|
+
] })
|
|
798
1130
|
] }),
|
|
799
|
-
/* @__PURE__ */
|
|
800
|
-
|
|
1131
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-sp-chat-pane", children: [
|
|
1132
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-cb-msgs", children: [
|
|
1133
|
+
displayMessages.map((msg, idx) => {
|
|
1134
|
+
const isUser = msg.role === "user";
|
|
1135
|
+
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: [
|
|
1136
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx4(SparkleIcon, {}) }),
|
|
1137
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-body", children: /* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-text", children: renderMarkdown(msg.content) }) })
|
|
1138
|
+
] }) }, idx);
|
|
1139
|
+
}),
|
|
1140
|
+
chatLoading && /* @__PURE__ */ jsxs2("div", { className: "hsk-cb-typing-row", children: [
|
|
1141
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-ai-icon", style: { display: "flex", alignItems: "center" }, children: /* @__PURE__ */ jsx4(SparkleIcon, {}) }),
|
|
1142
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-cb-typing", children: [
|
|
1143
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-dot" }),
|
|
1144
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-dot" }),
|
|
1145
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-dot" })
|
|
1146
|
+
] })
|
|
1147
|
+
] }),
|
|
1148
|
+
chatError && /* @__PURE__ */ jsx4("div", { className: "hsk-cb-error", children: getFriendlyError(chatError) }),
|
|
1149
|
+
/* @__PURE__ */ jsx4("div", { ref: chatBottomRef, style: { height: 1 } })
|
|
1150
|
+
] }),
|
|
1151
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-cb-input-wrap", children: [
|
|
1152
|
+
/* @__PURE__ */ jsxs2("div", { className: "hsk-cb-input-box", children: [
|
|
1153
|
+
/* @__PURE__ */ jsx4(
|
|
1154
|
+
"textarea",
|
|
1155
|
+
{
|
|
1156
|
+
ref: chatTextareaRef,
|
|
1157
|
+
className: "hsk-cb-textarea",
|
|
1158
|
+
value: chatInput,
|
|
1159
|
+
onChange: handleInput,
|
|
1160
|
+
onKeyDown: handleKeyDown,
|
|
1161
|
+
placeholder: "Ask about this product, specs, or comparison...",
|
|
1162
|
+
rows: 1,
|
|
1163
|
+
disabled: chatLoading
|
|
1164
|
+
}
|
|
1165
|
+
),
|
|
1166
|
+
/* @__PURE__ */ jsx4(
|
|
1167
|
+
"button",
|
|
1168
|
+
{
|
|
1169
|
+
className: "hsk-cb-send",
|
|
1170
|
+
onClick: () => handleSend(),
|
|
1171
|
+
disabled: !chatInput.trim() || chatLoading,
|
|
1172
|
+
"aria-label": "Send message",
|
|
1173
|
+
children: /* @__PURE__ */ jsx4(ArrowUpIcon, {})
|
|
1174
|
+
}
|
|
1175
|
+
)
|
|
1176
|
+
] }),
|
|
1177
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-cb-hint", children: "Huskel AI \xB7 instant product knowledge" })
|
|
1178
|
+
] })
|
|
1179
|
+
] })
|
|
1180
|
+
] }),
|
|
1181
|
+
/* @__PURE__ */ jsx4("div", { className: "hsk-sp-footer", children: /* @__PURE__ */ jsx4("span", { className: "hsk-sp-esc", children: "Esc to close" }) })
|
|
801
1182
|
] })
|
|
802
1183
|
}
|
|
803
1184
|
);
|
|
@@ -811,39 +1192,41 @@ function Sparkle({
|
|
|
811
1192
|
className,
|
|
812
1193
|
onNavigate,
|
|
813
1194
|
theme,
|
|
814
|
-
classNames = {}
|
|
1195
|
+
classNames = {},
|
|
1196
|
+
product
|
|
815
1197
|
}) {
|
|
816
|
-
const [open, setOpen] =
|
|
817
|
-
const [mounted, setMounted] =
|
|
818
|
-
|
|
1198
|
+
const [open, setOpen] = useState6(false);
|
|
1199
|
+
const [mounted, setMounted] = useState6(false);
|
|
1200
|
+
useEffect5(() => {
|
|
819
1201
|
setMounted(true);
|
|
820
1202
|
}, []);
|
|
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__ */
|
|
1203
|
+
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 });
|
|
1204
|
+
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1205
|
+
/* @__PURE__ */ jsx4(
|
|
824
1206
|
"button",
|
|
825
1207
|
{
|
|
826
|
-
className:
|
|
1208
|
+
className: cn("hsk-sp-btn", classNames.button, className),
|
|
827
1209
|
onClick: () => setOpen(true),
|
|
828
1210
|
style: customStyles,
|
|
829
1211
|
title: "Find similar products",
|
|
830
1212
|
"aria-label": "Find similar products",
|
|
831
|
-
children: /* @__PURE__ */
|
|
1213
|
+
children: /* @__PURE__ */ jsx4(SparkleIcon, {})
|
|
832
1214
|
}
|
|
833
1215
|
),
|
|
834
1216
|
open && mounted && createPortal(
|
|
835
|
-
/* @__PURE__ */
|
|
1217
|
+
/* @__PURE__ */ jsx4(
|
|
836
1218
|
SparkleModal,
|
|
837
1219
|
{
|
|
838
1220
|
productName,
|
|
839
1221
|
limit,
|
|
1222
|
+
onResult,
|
|
840
1223
|
backdropColor,
|
|
841
1224
|
backdropBlur,
|
|
842
1225
|
onClose: () => setOpen(false),
|
|
843
|
-
onResult,
|
|
844
1226
|
onNavigate,
|
|
845
1227
|
theme,
|
|
846
|
-
classNames
|
|
1228
|
+
classNames,
|
|
1229
|
+
product
|
|
847
1230
|
}
|
|
848
1231
|
),
|
|
849
1232
|
document.body
|
|
@@ -852,97 +1235,10 @@ function Sparkle({
|
|
|
852
1235
|
}
|
|
853
1236
|
|
|
854
1237
|
// 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
|
|
1238
|
+
import { useState as useState7, useRef as useRef8, useEffect as useEffect6 } from "react";
|
|
943
1239
|
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
944
1240
|
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
|
|
1241
|
+
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
1242
|
/* @__PURE__ */ jsx5("path", { d: "m5 12 7-7 7 7" }),
|
|
947
1243
|
/* @__PURE__ */ jsx5("path", { d: "M12 19V5" })
|
|
948
1244
|
] });
|
|
@@ -972,10 +1268,10 @@ function ChatWidget({
|
|
|
972
1268
|
onSelectSource
|
|
973
1269
|
}) {
|
|
974
1270
|
const { messages, sources, loading, error, send, reset } = useChat();
|
|
975
|
-
const [input, setInput] =
|
|
1271
|
+
const [input, setInput] = useState7("");
|
|
976
1272
|
const bottomRef = useRef8(null);
|
|
977
1273
|
const textareaRef = useRef8(null);
|
|
978
|
-
|
|
1274
|
+
useEffect6(() => {
|
|
979
1275
|
var _a;
|
|
980
1276
|
(_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
|
|
981
1277
|
}, [messages, loading]);
|
|
@@ -998,14 +1294,14 @@ function ChatWidget({
|
|
|
998
1294
|
t.style.height = "auto";
|
|
999
1295
|
t.style.height = Math.min(t.scrollHeight, 120) + "px";
|
|
1000
1296
|
};
|
|
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 });
|
|
1297
|
+
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
1298
|
return /* @__PURE__ */ jsxs3(
|
|
1003
1299
|
"div",
|
|
1004
1300
|
{
|
|
1005
|
-
className:
|
|
1301
|
+
className: cn("hsk-chat-widget", classNames.root, className),
|
|
1006
1302
|
style: customStyles,
|
|
1007
1303
|
children: [
|
|
1008
|
-
/* @__PURE__ */ jsxs3("div", { className:
|
|
1304
|
+
/* @__PURE__ */ jsxs3("div", { className: cn("hsk-chat-header", classNames.header), children: [
|
|
1009
1305
|
/* @__PURE__ */ jsx5("span", { className: "hsk-chat-header-icon", children: /* @__PURE__ */ jsx5(SparkleIcon2, {}) }),
|
|
1010
1306
|
/* @__PURE__ */ jsx5("span", { className: "hsk-chat-title", children: title }),
|
|
1011
1307
|
/* @__PURE__ */ jsx5("span", { className: "hsk-chat-badge", children: "AI" }),
|
|
@@ -1018,8 +1314,8 @@ function ChatWidget({
|
|
|
1018
1314
|
/* @__PURE__ */ jsx5("div", { className: "hsk-chat-empty-suggestions", children: emptyStateSuggestions })
|
|
1019
1315
|
] }) : messages.map((msg, idx) => /* @__PURE__ */ jsxs3("div", { children: [
|
|
1020
1316
|
/* @__PURE__ */ jsxs3("div", { className: `hsk-msg-row ${msg.role}`, children: [
|
|
1021
|
-
/* @__PURE__ */ jsx5("div", { className:
|
|
1022
|
-
/* @__PURE__ */ jsx5("div", { className:
|
|
1317
|
+
/* @__PURE__ */ jsx5("div", { className: cn("hsk-msg-avatar", msg.role === "assistant" ? "ai" : "user"), children: msg.role === "assistant" ? /* @__PURE__ */ jsx5(SparkleIcon2, {}) : "U" }),
|
|
1318
|
+
/* @__PURE__ */ jsx5("div", { className: cn("hsk-msg-bubble", msg.role, classNames.messageBubble), children: renderMarkdown(msg.content) })
|
|
1023
1319
|
] }),
|
|
1024
1320
|
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
1321
|
] }, idx)),
|
|
@@ -1031,7 +1327,14 @@ function ChatWidget({
|
|
|
1031
1327
|
/* @__PURE__ */ jsx5("div", { className: "hsk-typing-dot" })
|
|
1032
1328
|
] })
|
|
1033
1329
|
] }),
|
|
1034
|
-
error && /* @__PURE__ */ jsx5("div", { className: "hsk-chat-error", children:
|
|
1330
|
+
error && /* @__PURE__ */ jsx5("div", { className: "hsk-chat-error", children: (() => {
|
|
1331
|
+
try {
|
|
1332
|
+
const parsed = JSON.parse(error);
|
|
1333
|
+
return parsed.error || parsed.message || error;
|
|
1334
|
+
} catch (e) {
|
|
1335
|
+
return error;
|
|
1336
|
+
}
|
|
1337
|
+
})() }),
|
|
1035
1338
|
/* @__PURE__ */ jsx5("div", { ref: bottomRef })
|
|
1036
1339
|
] }),
|
|
1037
1340
|
/* @__PURE__ */ jsxs3("div", { className: "hsk-chat-input-area", children: [
|
|
@@ -1039,7 +1342,7 @@ function ChatWidget({
|
|
|
1039
1342
|
"textarea",
|
|
1040
1343
|
{
|
|
1041
1344
|
ref: textareaRef,
|
|
1042
|
-
className:
|
|
1345
|
+
className: cn("hsk-chat-input", classNames.input),
|
|
1043
1346
|
value: input,
|
|
1044
1347
|
onChange: handleInput,
|
|
1045
1348
|
onKeyDown: handleKey,
|
|
@@ -1055,7 +1358,7 @@ function ChatWidget({
|
|
|
1055
1358
|
onClick: handleSend,
|
|
1056
1359
|
disabled: !input.trim() || loading,
|
|
1057
1360
|
"aria-label": "Send message",
|
|
1058
|
-
children: /* @__PURE__ */ jsx5(
|
|
1361
|
+
children: /* @__PURE__ */ jsx5(ArrowUpIcon2, {})
|
|
1059
1362
|
}
|
|
1060
1363
|
)
|
|
1061
1364
|
] })
|
|
@@ -1065,11 +1368,11 @@ function ChatWidget({
|
|
|
1065
1368
|
}
|
|
1066
1369
|
|
|
1067
1370
|
// src/components/AIChatButton.tsx
|
|
1068
|
-
import { useState as
|
|
1371
|
+
import { useState as useState8, useEffect as useEffect7, useRef as useRef9, useCallback as useCallback5 } from "react";
|
|
1069
1372
|
import { createPortal as createPortal2 } from "react-dom";
|
|
1070
1373
|
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1071
1374
|
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
|
|
1375
|
+
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
1376
|
/* @__PURE__ */ jsx6("path", { d: "m5 12 7-7 7 7" }),
|
|
1074
1377
|
/* @__PURE__ */ jsx6("path", { d: "M12 19V5" })
|
|
1075
1378
|
] });
|
|
@@ -1086,14 +1389,14 @@ var DEFAULT_CHIPS = [
|
|
|
1086
1389
|
];
|
|
1087
1390
|
function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
|
|
1088
1391
|
const railRef = useRef9(null);
|
|
1089
|
-
const [showNext, setShowNext] =
|
|
1090
|
-
const measure =
|
|
1392
|
+
const [showNext, setShowNext] = useState8(false);
|
|
1393
|
+
const measure = useCallback5(() => {
|
|
1091
1394
|
const el = railRef.current;
|
|
1092
1395
|
if (!el) return;
|
|
1093
1396
|
const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 8;
|
|
1094
1397
|
setShowNext(el.scrollWidth > el.clientWidth + 4 && !atEnd);
|
|
1095
1398
|
}, []);
|
|
1096
|
-
|
|
1399
|
+
useEffect7(() => {
|
|
1097
1400
|
measure();
|
|
1098
1401
|
const el = railRef.current;
|
|
1099
1402
|
if (!el) return;
|
|
@@ -1146,7 +1449,7 @@ function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
|
|
|
1146
1449
|
] });
|
|
1147
1450
|
}
|
|
1148
1451
|
function ChatModal({
|
|
1149
|
-
title = "
|
|
1452
|
+
title = "Shopping Assistant",
|
|
1150
1453
|
placeholder = "Ask me anything \u2014 gifts, budget, use case\u2026",
|
|
1151
1454
|
backdropColor,
|
|
1152
1455
|
backdropBlur,
|
|
@@ -1159,15 +1462,15 @@ function ChatModal({
|
|
|
1159
1462
|
}) {
|
|
1160
1463
|
var _a, _b;
|
|
1161
1464
|
const { messages, sources, loading, error, send, reset } = useChat();
|
|
1162
|
-
const [input, setInput] =
|
|
1163
|
-
const [selectedProduct, setSelectedProduct] =
|
|
1465
|
+
const [input, setInput] = useState8("");
|
|
1466
|
+
const [selectedProduct, setSelectedProduct] = useState8(null);
|
|
1164
1467
|
const bottomRef = useRef9(null);
|
|
1165
1468
|
const textareaRef = useRef9(null);
|
|
1166
|
-
|
|
1469
|
+
useEffect7(() => {
|
|
1167
1470
|
var _a2;
|
|
1168
1471
|
(_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
|
|
1169
1472
|
}, [messages, loading, selectedProduct]);
|
|
1170
|
-
|
|
1473
|
+
useEffect7(() => {
|
|
1171
1474
|
const h = (e) => {
|
|
1172
1475
|
if (e.key === "Escape") onClose();
|
|
1173
1476
|
};
|
|
@@ -1204,24 +1507,21 @@ function ChatModal({
|
|
|
1204
1507
|
t.style.height = `${Math.min(t.scrollHeight, 140)}px`;
|
|
1205
1508
|
};
|
|
1206
1509
|
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 });
|
|
1510
|
+
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
1511
|
return /* @__PURE__ */ jsx6(
|
|
1209
1512
|
"div",
|
|
1210
1513
|
{
|
|
1211
|
-
className:
|
|
1514
|
+
className: cn("hsk-cb-overlay", classNames.overlay),
|
|
1212
1515
|
onClick: onClose,
|
|
1213
1516
|
style: __spreadValues(__spreadValues({
|
|
1214
1517
|
backdropFilter: `blur(${blurVal})`,
|
|
1215
1518
|
WebkitBackdropFilter: `blur(${blurVal})`
|
|
1216
1519
|
}, backdropColor ? { background: backdropColor } : {}), customStyles),
|
|
1217
|
-
children: /* @__PURE__ */ jsxs4("div", { className:
|
|
1520
|
+
children: /* @__PURE__ */ jsxs4("div", { className: cn("hsk-cb-panel", classNames.panel), onClick: (e) => e.stopPropagation(), children: [
|
|
1218
1521
|
/* @__PURE__ */ jsxs4("div", { className: "hsk-cb-topbar", children: [
|
|
1219
1522
|
/* @__PURE__ */ jsxs4("div", { className: "hsk-cb-topbar-left", children: [
|
|
1220
1523
|
/* @__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
|
-
] })
|
|
1524
|
+
/* @__PURE__ */ jsx6("div", { children: /* @__PURE__ */ jsx6("div", { className: "hsk-cb-topbar-title", children: title }) })
|
|
1225
1525
|
] }),
|
|
1226
1526
|
/* @__PURE__ */ jsxs4("div", { className: "hsk-cb-topbar-actions", children: [
|
|
1227
1527
|
messages.length > 0 && /* @__PURE__ */ jsx6("button", { className: "hsk-cb-topbar-btn", onClick: reset, children: "Clear chat" }),
|
|
@@ -1231,8 +1531,7 @@ function ChatModal({
|
|
|
1231
1531
|
/* @__PURE__ */ jsxs4("div", { className: "hsk-cb-msgs", children: [
|
|
1232
1532
|
messages.length === 0 ? /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-empty", children: [
|
|
1233
1533
|
/* @__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." }),
|
|
1534
|
+
/* @__PURE__ */ jsx6("div", { className: "hsk-cb-empty-title", children: "Find exactly what you need" }),
|
|
1236
1535
|
/* @__PURE__ */ jsx6("div", { className: "hsk-cb-chips", children: chips.map((chip) => /* @__PURE__ */ jsx6(
|
|
1237
1536
|
"button",
|
|
1238
1537
|
{
|
|
@@ -1295,7 +1594,7 @@ function ChatModal({
|
|
|
1295
1594
|
"textarea",
|
|
1296
1595
|
{
|
|
1297
1596
|
ref: textareaRef,
|
|
1298
|
-
className:
|
|
1597
|
+
className: cn("hsk-cb-textarea", classNames.input),
|
|
1299
1598
|
value: input,
|
|
1300
1599
|
onChange: handleInput,
|
|
1301
1600
|
onKeyDown: handleKeyDown,
|
|
@@ -1308,11 +1607,11 @@ function ChatModal({
|
|
|
1308
1607
|
/* @__PURE__ */ jsx6(
|
|
1309
1608
|
"button",
|
|
1310
1609
|
{
|
|
1311
|
-
className:
|
|
1610
|
+
className: cn("hsk-cb-send", classNames.sendButton),
|
|
1312
1611
|
onClick: () => handleSend(),
|
|
1313
1612
|
disabled: !input.trim() || loading,
|
|
1314
1613
|
"aria-label": "Send message",
|
|
1315
|
-
children: /* @__PURE__ */ jsx6(
|
|
1614
|
+
children: /* @__PURE__ */ jsx6(ArrowUpIcon3, {})
|
|
1316
1615
|
}
|
|
1317
1616
|
)
|
|
1318
1617
|
] }),
|
|
@@ -1335,17 +1634,17 @@ function AIChatButton({
|
|
|
1335
1634
|
theme,
|
|
1336
1635
|
classNames = {}
|
|
1337
1636
|
}) {
|
|
1338
|
-
const [open, setOpen] =
|
|
1339
|
-
const [mounted, setMounted] =
|
|
1340
|
-
|
|
1637
|
+
const [open, setOpen] = useState8(false);
|
|
1638
|
+
const [mounted, setMounted] = useState8(false);
|
|
1639
|
+
useEffect7(() => {
|
|
1341
1640
|
setMounted(true);
|
|
1342
1641
|
}, []);
|
|
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 });
|
|
1642
|
+
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 });
|
|
1344
1643
|
return /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1345
1644
|
/* @__PURE__ */ jsxs4(
|
|
1346
1645
|
"button",
|
|
1347
1646
|
{
|
|
1348
|
-
className:
|
|
1647
|
+
className: cn("hsk-cb-btn", classNames.button, className),
|
|
1349
1648
|
onClick: () => setOpen(true),
|
|
1350
1649
|
style: customStyles,
|
|
1351
1650
|
"aria-label": "Open AI chat",
|
|
@@ -1375,8 +1674,260 @@ function AIChatButton({
|
|
|
1375
1674
|
)
|
|
1376
1675
|
] });
|
|
1377
1676
|
}
|
|
1677
|
+
|
|
1678
|
+
// src/components/CartBadge.tsx
|
|
1679
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1680
|
+
function CartBadge({ className }) {
|
|
1681
|
+
const { cart } = useCart();
|
|
1682
|
+
if (!cart || cart.item_count === 0) return null;
|
|
1683
|
+
return /* @__PURE__ */ jsx7("span", { className: cn("hsk-cart-badge", className), children: cart.item_count });
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// src/components/CartDrawer.tsx
|
|
1687
|
+
import { useState as useState10, useEffect as useEffect9 } from "react";
|
|
1688
|
+
import { createPortal as createPortal4 } from "react-dom";
|
|
1689
|
+
|
|
1690
|
+
// src/components/CheckoutModal.tsx
|
|
1691
|
+
import { useState as useState9, useEffect as useEffect8 } from "react";
|
|
1692
|
+
import { createPortal as createPortal3 } from "react-dom";
|
|
1693
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1694
|
+
function CheckoutModal({
|
|
1695
|
+
onClose,
|
|
1696
|
+
theme,
|
|
1697
|
+
customStyles,
|
|
1698
|
+
hskThemeAttr
|
|
1699
|
+
}) {
|
|
1700
|
+
var _a, _b, _c, _d;
|
|
1701
|
+
const { cart, loading: cartLoading } = useCart();
|
|
1702
|
+
const client = useHuskelContext();
|
|
1703
|
+
const [config, setConfig] = useState9(null);
|
|
1704
|
+
const [loading, setLoading] = useState9(true);
|
|
1705
|
+
const [checkingOut, setCheckingOut] = useState9(false);
|
|
1706
|
+
const [paymentSuccess, setPaymentSuccess] = useState9(false);
|
|
1707
|
+
useEffect8(() => {
|
|
1708
|
+
client.api.getCheckoutConfig().then((res) => setConfig(res.payment_methods)).catch((e) => console.error("[Huskel] Failed to fetch checkout config", e)).finally(() => setLoading(false));
|
|
1709
|
+
}, [client]);
|
|
1710
|
+
const handlePay = async (method) => {
|
|
1711
|
+
setCheckingOut(true);
|
|
1712
|
+
setTimeout(async () => {
|
|
1713
|
+
try {
|
|
1714
|
+
const payload = await client.api.checkoutCart();
|
|
1715
|
+
if (client.onCheckout) {
|
|
1716
|
+
client.onCheckout(payload);
|
|
1717
|
+
}
|
|
1718
|
+
setPaymentSuccess(true);
|
|
1719
|
+
setTimeout(() => {
|
|
1720
|
+
onClose();
|
|
1721
|
+
}, 3e3);
|
|
1722
|
+
} catch (e) {
|
|
1723
|
+
console.error("[Huskel] Checkout failed", e);
|
|
1724
|
+
setCheckingOut(false);
|
|
1725
|
+
}
|
|
1726
|
+
}, 1500);
|
|
1727
|
+
};
|
|
1728
|
+
const hasPaymentMethods = config && Object.values(config).some((m) => m.enabled);
|
|
1729
|
+
return createPortal3(
|
|
1730
|
+
/* @__PURE__ */ jsx8(
|
|
1731
|
+
"div",
|
|
1732
|
+
{
|
|
1733
|
+
className: "hsk-cart-backdrop",
|
|
1734
|
+
style: __spreadProps(__spreadValues({}, customStyles), { zIndex: 999999 }),
|
|
1735
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1736
|
+
onClick: onClose,
|
|
1737
|
+
children: /* @__PURE__ */ jsxs5(
|
|
1738
|
+
"div",
|
|
1739
|
+
{
|
|
1740
|
+
className: "hsk-checkout-modal",
|
|
1741
|
+
style: customStyles,
|
|
1742
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1743
|
+
onClick: (e) => e.stopPropagation(),
|
|
1744
|
+
children: [
|
|
1745
|
+
/* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-header", children: [
|
|
1746
|
+
/* @__PURE__ */ jsx8("h2", { children: "Secure Checkout" }),
|
|
1747
|
+
/* @__PURE__ */ jsx8("button", { onClick: onClose, className: "hsk-close-btn", children: "\xD7" })
|
|
1748
|
+
] }),
|
|
1749
|
+
/* @__PURE__ */ jsx8("div", { className: "hsk-checkout-content", children: paymentSuccess ? /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-success", children: [
|
|
1750
|
+
/* @__PURE__ */ jsxs5("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "hsk-success-icon", children: [
|
|
1751
|
+
/* @__PURE__ */ jsx8("path", { d: "M22 11.08V12a10 10 0 1 1-5.93-9.14" }),
|
|
1752
|
+
/* @__PURE__ */ jsx8("polyline", { points: "22 4 12 14.01 9 11.01" })
|
|
1753
|
+
] }),
|
|
1754
|
+
/* @__PURE__ */ jsx8("h3", { children: "Payment Successful!" }),
|
|
1755
|
+
/* @__PURE__ */ jsx8("p", { children: "Thank you for your order." })
|
|
1756
|
+
] }) : /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-split", children: [
|
|
1757
|
+
/* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-summary", children: [
|
|
1758
|
+
/* @__PURE__ */ jsx8("h3", { children: "Order Summary" }),
|
|
1759
|
+
cartLoading || !cart ? /* @__PURE__ */ jsx8("p", { className: "hsk-cart-loading", children: "Loading order..." }) : /* @__PURE__ */ jsxs5(Fragment4, { children: [
|
|
1760
|
+
/* @__PURE__ */ jsx8("ul", { className: "hsk-checkout-items", children: cart.items.map((item) => /* @__PURE__ */ jsxs5("li", { children: [
|
|
1761
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
1762
|
+
item.quantity,
|
|
1763
|
+
"x ",
|
|
1764
|
+
item.name
|
|
1765
|
+
] }),
|
|
1766
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
1767
|
+
item.currency,
|
|
1768
|
+
" ",
|
|
1769
|
+
(item.price_numeric * item.quantity).toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1770
|
+
] })
|
|
1771
|
+
] }, item.id)) }),
|
|
1772
|
+
/* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-total", children: [
|
|
1773
|
+
/* @__PURE__ */ jsx8("span", { children: "Total" }),
|
|
1774
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
1775
|
+
cart.currency,
|
|
1776
|
+
" ",
|
|
1777
|
+
cart.total.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1778
|
+
] })
|
|
1779
|
+
] })
|
|
1780
|
+
] })
|
|
1781
|
+
] }),
|
|
1782
|
+
/* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-payment", children: [
|
|
1783
|
+
/* @__PURE__ */ jsx8("h3", { children: "Payment Method" }),
|
|
1784
|
+
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: [
|
|
1785
|
+
((_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" }),
|
|
1786
|
+
((_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" }),
|
|
1787
|
+
((_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)" }),
|
|
1788
|
+
((_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" })
|
|
1789
|
+
] })
|
|
1790
|
+
] })
|
|
1791
|
+
] }) })
|
|
1792
|
+
]
|
|
1793
|
+
}
|
|
1794
|
+
)
|
|
1795
|
+
}
|
|
1796
|
+
),
|
|
1797
|
+
document.body
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// src/components/CartDrawer.tsx
|
|
1802
|
+
import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1803
|
+
function CartDrawer({
|
|
1804
|
+
trigger,
|
|
1805
|
+
className,
|
|
1806
|
+
theme
|
|
1807
|
+
}) {
|
|
1808
|
+
const { cart, loading } = useCart();
|
|
1809
|
+
const [open, setOpen] = useState10(false);
|
|
1810
|
+
const [showCheckout, setShowCheckout] = useState10(false);
|
|
1811
|
+
const [mounted, setMounted] = useState10(false);
|
|
1812
|
+
const client = useHuskelContext();
|
|
1813
|
+
useEffect9(() => {
|
|
1814
|
+
setMounted(true);
|
|
1815
|
+
const handleTriggerCheckout = () => {
|
|
1816
|
+
setShowCheckout(true);
|
|
1817
|
+
setOpen(false);
|
|
1818
|
+
};
|
|
1819
|
+
window.addEventListener("huskel:trigger_checkout", handleTriggerCheckout);
|
|
1820
|
+
return () => {
|
|
1821
|
+
window.removeEventListener("huskel:trigger_checkout", handleTriggerCheckout);
|
|
1822
|
+
};
|
|
1823
|
+
}, []);
|
|
1824
|
+
useEffect9(() => {
|
|
1825
|
+
if (open) {
|
|
1826
|
+
document.body.style.overflow = "hidden";
|
|
1827
|
+
} else {
|
|
1828
|
+
document.body.style.overflow = "";
|
|
1829
|
+
}
|
|
1830
|
+
return () => {
|
|
1831
|
+
document.body.style.overflow = "";
|
|
1832
|
+
};
|
|
1833
|
+
}, [open]);
|
|
1834
|
+
const handleCheckout = async () => {
|
|
1835
|
+
if (!cart || cart.items.length === 0) return;
|
|
1836
|
+
setShowCheckout(true);
|
|
1837
|
+
};
|
|
1838
|
+
const isStringTheme = typeof theme === "string";
|
|
1839
|
+
const hskThemeAttr = isStringTheme ? theme : void 0;
|
|
1840
|
+
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;
|
|
1841
|
+
return /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
1842
|
+
/* @__PURE__ */ jsx9("div", { onClick: () => setOpen(true), style: { display: "inline-block" }, children: trigger || /* @__PURE__ */ jsxs6(
|
|
1843
|
+
"button",
|
|
1844
|
+
{
|
|
1845
|
+
className: cn("hsk-cart-trigger", className),
|
|
1846
|
+
style: customStyles,
|
|
1847
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1848
|
+
"aria-label": "Open cart",
|
|
1849
|
+
children: [
|
|
1850
|
+
/* @__PURE__ */ jsxs6("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1851
|
+
/* @__PURE__ */ jsx9("circle", { cx: "9", cy: "21", r: "1" }),
|
|
1852
|
+
/* @__PURE__ */ jsx9("circle", { cx: "20", cy: "21", r: "1" }),
|
|
1853
|
+
/* @__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" })
|
|
1854
|
+
] }),
|
|
1855
|
+
cart && cart.item_count > 0 ? /* @__PURE__ */ jsx9("span", { className: "hsk-cart-trigger-badge", children: cart.item_count }) : null
|
|
1856
|
+
]
|
|
1857
|
+
}
|
|
1858
|
+
) }),
|
|
1859
|
+
open && mounted && createPortal4(
|
|
1860
|
+
/* @__PURE__ */ jsx9(
|
|
1861
|
+
"div",
|
|
1862
|
+
{
|
|
1863
|
+
className: "hsk-cart-backdrop",
|
|
1864
|
+
style: customStyles,
|
|
1865
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1866
|
+
onClick: () => setOpen(false),
|
|
1867
|
+
children: /* @__PURE__ */ jsxs6(
|
|
1868
|
+
"div",
|
|
1869
|
+
{
|
|
1870
|
+
className: "hsk-cart-bottom-sheet",
|
|
1871
|
+
style: customStyles,
|
|
1872
|
+
"data-hsk-theme": hskThemeAttr,
|
|
1873
|
+
onClick: (e) => e.stopPropagation(),
|
|
1874
|
+
children: [
|
|
1875
|
+
/* @__PURE__ */ jsx9("div", { className: "hsk-cart-sheet-handle" }),
|
|
1876
|
+
/* @__PURE__ */ jsxs6("div", { className: "hsk-cart-sheet-header", children: [
|
|
1877
|
+
/* @__PURE__ */ jsx9("h2", { children: "Your Cart" }),
|
|
1878
|
+
/* @__PURE__ */ jsx9("button", { onClick: () => setOpen(false), className: "hsk-close-btn", children: "\xD7" })
|
|
1879
|
+
] }),
|
|
1880
|
+
/* @__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: [
|
|
1881
|
+
item.image && /* @__PURE__ */ jsx9("img", { src: item.image, alt: item.name, className: "hsk-cart-item-img" }),
|
|
1882
|
+
/* @__PURE__ */ jsxs6("div", { className: "hsk-cart-item-info", children: [
|
|
1883
|
+
/* @__PURE__ */ jsx9("span", { className: "hsk-cart-item-name", children: item.name }),
|
|
1884
|
+
/* @__PURE__ */ jsxs6("span", { className: "hsk-cart-item-price", children: [
|
|
1885
|
+
item.currency,
|
|
1886
|
+
" ",
|
|
1887
|
+
item.price_numeric.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1888
|
+
] })
|
|
1889
|
+
] }),
|
|
1890
|
+
/* @__PURE__ */ jsxs6("div", { className: "hsk-cart-item-qty", children: [
|
|
1891
|
+
"x",
|
|
1892
|
+
item.quantity
|
|
1893
|
+
] })
|
|
1894
|
+
] }, item.id)) }) }),
|
|
1895
|
+
cart && cart.items.length > 0 && /* @__PURE__ */ jsxs6("div", { className: "hsk-cart-sheet-footer", children: [
|
|
1896
|
+
/* @__PURE__ */ jsxs6("div", { className: "hsk-cart-total", children: [
|
|
1897
|
+
/* @__PURE__ */ jsx9("span", { children: "Total" }),
|
|
1898
|
+
/* @__PURE__ */ jsxs6("span", { children: [
|
|
1899
|
+
cart.currency,
|
|
1900
|
+
" ",
|
|
1901
|
+
cart.total.toLocaleString(void 0, { minimumFractionDigits: 2 })
|
|
1902
|
+
] })
|
|
1903
|
+
] }),
|
|
1904
|
+
/* @__PURE__ */ jsx9("button", { onClick: handleCheckout, className: "hsk-checkout-btn", children: "Checkout securely" })
|
|
1905
|
+
] })
|
|
1906
|
+
]
|
|
1907
|
+
}
|
|
1908
|
+
)
|
|
1909
|
+
}
|
|
1910
|
+
),
|
|
1911
|
+
document.body
|
|
1912
|
+
),
|
|
1913
|
+
showCheckout && mounted && /* @__PURE__ */ jsx9(
|
|
1914
|
+
CheckoutModal,
|
|
1915
|
+
{
|
|
1916
|
+
onClose: () => {
|
|
1917
|
+
setShowCheckout(false);
|
|
1918
|
+
setOpen(false);
|
|
1919
|
+
},
|
|
1920
|
+
theme: isStringTheme ? theme : void 0,
|
|
1921
|
+
customStyles,
|
|
1922
|
+
hskThemeAttr
|
|
1923
|
+
}
|
|
1924
|
+
)
|
|
1925
|
+
] });
|
|
1926
|
+
}
|
|
1378
1927
|
export {
|
|
1379
1928
|
AIChatButton,
|
|
1929
|
+
CartBadge,
|
|
1930
|
+
CartDrawer,
|
|
1380
1931
|
ChatWidget,
|
|
1381
1932
|
HuskelAPI,
|
|
1382
1933
|
HuskelClient,
|
|
@@ -1385,6 +1936,7 @@ export {
|
|
|
1385
1936
|
Sparkle,
|
|
1386
1937
|
getHuskelClient,
|
|
1387
1938
|
initHuskel,
|
|
1939
|
+
useCart,
|
|
1388
1940
|
useChat,
|
|
1389
1941
|
useHuskel,
|
|
1390
1942
|
useIngest,
|