@huskel/sdk 0.4.3 → 0.4.7

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.mjs CHANGED
@@ -168,6 +168,32 @@ var HuskelAPI = class {
168
168
  if (!res.ok) throw new Error("Failed to fetch checkout config");
169
169
  return res.json();
170
170
  }
171
+ async initiatePayment(phoneNumber, email, firstName, lastName) {
172
+ const res = await fetch(`${this.apiUrl}/payment/initiate`, {
173
+ method: "POST",
174
+ headers: this.buildHeaders(),
175
+ body: JSON.stringify({
176
+ siteId: this.siteId,
177
+ phoneNumber,
178
+ email,
179
+ firstName,
180
+ lastName
181
+ })
182
+ });
183
+ if (!res.ok) {
184
+ const errText = await res.text();
185
+ throw new Error("Failed to initiate payment: " + errText);
186
+ }
187
+ return res.json();
188
+ }
189
+ async getPaymentStatus(ref) {
190
+ const res = await fetch(`${this.apiUrl}/payment/status?ref=${ref}`, {
191
+ method: "GET",
192
+ headers: this.buildHeaders()
193
+ });
194
+ if (!res.ok) throw new Error("Failed to get payment status");
195
+ return res.json();
196
+ }
171
197
  };
172
198
 
173
199
  // src/client.ts
@@ -319,6 +345,9 @@ var _HuskelClient = class _HuskelClient {
319
345
  } catch (e) {
320
346
  }
321
347
  }
348
+ reRegister() {
349
+ instance = this;
350
+ }
322
351
  setShopperId(id) {
323
352
  this.shopperId = id;
324
353
  }
@@ -452,11 +481,17 @@ function HuskelProvider({ siteId, apiUrl, apiToken, shopperId, children }) {
452
481
  const clientRef = useRef2(null);
453
482
  if (!clientRef.current) {
454
483
  clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken, shopperId });
484
+ } else {
485
+ clientRef.current.reRegister();
455
486
  }
456
487
  useEffect(() => {
457
488
  var _a;
458
489
  (_a = clientRef.current) == null ? void 0 : _a.setShopperId(shopperId);
459
490
  }, [shopperId]);
491
+ useEffect(() => {
492
+ var _a;
493
+ (_a = clientRef.current) == null ? void 0 : _a.reRegister();
494
+ }, []);
460
495
  useEffect(() => {
461
496
  return () => {
462
497
  var _a;
@@ -488,6 +523,7 @@ function useSearch() {
488
523
  return;
489
524
  }
490
525
  const gen = ++genRef.current;
526
+ setLoading(true);
491
527
  setError(null);
492
528
  try {
493
529
  const res = await client.api.searchAutocomplete(query, limit);
@@ -578,9 +614,10 @@ function useChat() {
578
614
  const [sources, setSources] = useState3([]);
579
615
  const [loading, setLoading] = useState3(false);
580
616
  const [error, setError] = useState3(null);
617
+ const [lastAction, setLastAction] = useState3(null);
581
618
  const abortRef = useRef5(null);
582
619
  const send = useCallback3(async (query, displayQuery) => {
583
- var _a, _b, _c, _d, _e;
620
+ var _a, _b, _c, _d, _e, _f;
584
621
  if (!query.trim() || loading) return;
585
622
  (_a = abortRef.current) == null ? void 0 : _a.abort();
586
623
  abortRef.current = new AbortController();
@@ -611,6 +648,7 @@ function useChat() {
611
648
  }
612
649
  if (signal.aborted) return;
613
650
  setSources((_b = res.sources) != null ? _b : []);
651
+ if (res.action) setLastAction(res.action);
614
652
  if (((_c = res.action) == null ? void 0 : _c.type) === "add_to_cart" || res.checkout) {
615
653
  if (typeof window !== "undefined") {
616
654
  window.dispatchEvent(new CustomEvent("huskel:cart_updated", { detail: res.checkout }));
@@ -621,12 +659,17 @@ function useChat() {
621
659
  window.dispatchEvent(new CustomEvent("huskel:trigger_checkout", { detail: res.checkout }));
622
660
  }
623
661
  }
662
+ if (((_e = res.action) == null ? void 0 : _e.type) === "awaiting_payment") {
663
+ if (typeof window !== "undefined") {
664
+ window.dispatchEvent(new CustomEvent("huskel:awaiting_payment", { detail: res.action }));
665
+ }
666
+ }
624
667
  if (res.checkout && client.onCheckout) {
625
668
  client.onCheckout(res.checkout);
626
669
  }
627
670
  } catch (e) {
628
671
  if (signal.aborted) return;
629
- let msg = (_e = e == null ? void 0 : e.message) != null ? _e : "Chat request failed";
672
+ let msg = (_f = e == null ? void 0 : e.message) != null ? _f : "Chat request failed";
630
673
  try {
631
674
  const parsed = JSON.parse(msg);
632
675
  if (parsed && parsed.error) {
@@ -649,8 +692,9 @@ function useChat() {
649
692
  setSources([]);
650
693
  setError(null);
651
694
  setLoading(false);
695
+ setLastAction(null);
652
696
  }, []);
653
- return { messages, sources, loading, error, send, reset };
697
+ return { messages, sources, loading, error, lastAction, send, reset };
654
698
  }
655
699
 
656
700
  // src/hooks/useCart.ts
@@ -689,8 +733,71 @@ function useCart() {
689
733
  return { cart, loading, fetchCart };
690
734
  }
691
735
 
692
- // src/components/SearchBar.tsx
736
+ // src/hooks/usePaymentPolling.ts
693
737
  import { useState as useState5, useEffect as useEffect4, useRef as useRef6 } from "react";
738
+ function usePaymentPolling({
739
+ client,
740
+ merchantReference,
741
+ onSuccess,
742
+ onFailure,
743
+ intervalMs = 3e3,
744
+ timeoutMs = 3e5
745
+ // 5 minutes default
746
+ }) {
747
+ const [status, setStatus] = useState5("IDLE");
748
+ const [error, setError] = useState5(null);
749
+ const onSuccessRef = useRef6(onSuccess);
750
+ const onFailureRef = useRef6(onFailure);
751
+ useEffect4(() => {
752
+ onSuccessRef.current = onSuccess;
753
+ onFailureRef.current = onFailure;
754
+ }, [onSuccess, onFailure]);
755
+ useEffect4(() => {
756
+ if (!merchantReference) {
757
+ setStatus("IDLE");
758
+ setError(null);
759
+ return;
760
+ }
761
+ setStatus("PENDING");
762
+ setError(null);
763
+ const startTime = Date.now();
764
+ let timerId = null;
765
+ async function checkStatus() {
766
+ try {
767
+ if (Date.now() - startTime >= timeoutMs) {
768
+ setStatus("FAILED");
769
+ setError("Payment session timed out");
770
+ if (onFailureRef.current) onFailureRef.current("Payment session timed out");
771
+ return;
772
+ }
773
+ const res = await client.getPaymentStatus(merchantReference);
774
+ if (res.status === "COMPLETED") {
775
+ setStatus("COMPLETED");
776
+ if (onSuccessRef.current) onSuccessRef.current();
777
+ } else if (res.status === "FAILED") {
778
+ setStatus("FAILED");
779
+ setError("Payment failed");
780
+ if (onFailureRef.current) onFailureRef.current("Payment failed");
781
+ } else {
782
+ timerId = setTimeout(checkStatus, intervalMs);
783
+ }
784
+ } catch (err) {
785
+ console.error("[Huskel Polling Error]", err);
786
+ timerId = setTimeout(checkStatus, intervalMs);
787
+ }
788
+ }
789
+ timerId = setTimeout(checkStatus, intervalMs);
790
+ return () => {
791
+ if (timerId) {
792
+ clearTimeout(timerId);
793
+ }
794
+ };
795
+ }, [client, merchantReference, intervalMs, timeoutMs]);
796
+ return { status, error };
797
+ }
798
+
799
+ // src/components/SearchBar.tsx
800
+ import { useState as useState6, useEffect as useEffect5, useRef as useRef7 } from "react";
694
801
 
695
802
  // src/utils/cn.ts
696
803
  import { clsx } from "clsx";
@@ -700,7 +807,7 @@ function cn(...inputs) {
700
807
  }
701
808
 
702
809
  // src/components/SearchBar.tsx
703
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
810
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
704
811
  var SearchIcon = () => /* @__PURE__ */ jsxs("svg", { width: "15", height: "15", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
705
812
  /* @__PURE__ */ jsx2("circle", { cx: "8.5", cy: "8.5", r: "5.5" }),
706
813
  /* @__PURE__ */ jsx2("line", { x1: "13", y1: "13", x2: "18", y2: "18" })
@@ -708,7 +815,7 @@ var SearchIcon = () => /* @__PURE__ */ jsxs("svg", { width: "15", height: "15",
708
815
  function SearchBar({
709
816
  placeholder = "Search products\u2026",
710
817
  limit = 10,
711
- debounceMs = 80,
818
+ debounceMs = 300,
712
819
  onSelect,
713
820
  className,
714
821
  inputClassName,
@@ -717,25 +824,35 @@ function SearchBar({
717
824
  theme,
718
825
  classNames = {}
719
826
  }) {
720
- const [query, setQuery] = useState5("");
721
- const [open, setOpen] = useState5(false);
827
+ const [query, setQuery] = useState6("");
828
+ const [open, setOpen] = useState6(false);
829
+ const [isDebouncing, setIsDebouncing] = useState6(false);
722
830
  const { results, loading, search, clear } = useSearch();
723
- const timer = useRef6();
724
- const wrap = useRef6(null);
725
- useEffect4(() => {
831
+ const client = useHuskelContext();
832
+ const timer = useRef7();
833
+ const wrap = useRef7(null);
834
+ const ignoreNextQueryChange = useRef7(false);
835
+ useEffect5(() => {
836
+ if (ignoreNextQueryChange.current) {
837
+ ignoreNextQueryChange.current = false;
838
+ return;
839
+ }
726
840
  clearTimeout(timer.current);
727
841
  if (!query.trim()) {
728
842
  clear();
729
843
  setOpen(false);
844
+ setIsDebouncing(false);
730
845
  return;
731
846
  }
732
847
  setOpen(true);
848
+ setIsDebouncing(true);
733
849
  timer.current = setTimeout(() => {
850
+ setIsDebouncing(false);
734
851
  search(query, limit);
735
852
  }, debounceMs);
736
853
  return () => clearTimeout(timer.current);
737
854
  }, [query]);
738
- useEffect4(() => {
855
+ useEffect5(() => {
739
856
  const h = (e) => {
740
857
  if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
741
858
  };
@@ -743,10 +860,23 @@ function SearchBar({
743
860
  return () => document.removeEventListener("mousedown", h);
744
861
  }, []);
745
862
  const handleSelect = (r) => {
863
+ if (query.trim()) {
864
+ client.api.searchVector(query, 1).catch(() => {
865
+ });
866
+ }
867
+ ignoreNextQueryChange.current = true;
746
868
  setOpen(false);
747
869
  setQuery(r.product.name);
748
870
  onSelect == null ? void 0 : onSelect(r);
749
871
  };
872
+ const handleCommitSearch = () => {
873
+ if (!query.trim()) return;
874
+ client.api.searchVector(query, 1).catch(() => {
875
+ });
876
+ if (results.length > 0) {
877
+ handleSelect(results[0]);
878
+ }
879
+ };
750
880
  const showDrop = open && query.trim().length > 0;
751
881
  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
882
  return /* @__PURE__ */ jsxs("div", { className: cn("hsk-sb-wrap", classNames.root, className), ref: wrap, style: customStyles, children: [
@@ -760,55 +890,77 @@ function SearchBar({
760
890
  placeholder,
761
891
  onChange: (e) => setQuery(e.target.value),
762
892
  onFocus: () => results.length > 0 && query.trim() && setOpen(true),
893
+ onKeyDown: (e) => {
894
+ if (e.key === "Enter") {
895
+ handleCommitSearch();
896
+ }
897
+ },
763
898
  autoComplete: "off",
764
899
  spellCheck: false
765
900
  }
766
901
  ),
767
902
  showDrop && /* @__PURE__ */ jsxs("div", { className: cn("hsk-sb-drop", classNames.dropdown, dropdownClassName), style: { position: "absolute" }, children: [
768
- loading && /* @__PURE__ */ jsx2("div", { className: "hsk-sb-loading-bar" }),
769
- results.length === 0 && !loading && /* @__PURE__ */ jsxs("div", { className: "hsk-sb-empty", children: [
770
- "No results for \u201C",
771
- query,
772
- "\u201D"
773
- ] }),
774
- results.map((r, i) => {
775
- var _a;
776
- return renderResult ? /* @__PURE__ */ jsx2(
777
- "div",
778
- {
779
- onClick: () => handleSelect(r),
780
- className: "hsk-sb-fade",
781
- style: { animationDelay: `${i * 18}ms` },
782
- children: renderResult(r)
783
- },
784
- r.id
785
- ) : /* @__PURE__ */ jsxs(
786
- "div",
787
- {
788
- className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
789
- style: { animationDelay: `${i * 18}ms` },
790
- onClick: () => handleSelect(r),
791
- children: [
792
- /* @__PURE__ */ jsx2("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ jsx2(SearchIcon, {}) }),
793
- /* @__PURE__ */ jsxs("div", { className: "hsk-sb-row-body", children: [
794
- /* @__PURE__ */ jsx2("div", { className: "hsk-sb-row-title", children: r.product.name }),
795
- (r.product.category || r.product.brand) && /* @__PURE__ */ jsx2("div", { className: "hsk-sb-row-sub", children: (_a = r.product.category) != null ? _a : r.product.brand })
796
- ] })
797
- ]
798
- },
799
- r.id
800
- );
801
- })
903
+ (loading || isDebouncing) && /* @__PURE__ */ jsx2("div", { className: "hsk-sb-loading-bar" }),
904
+ (loading || isDebouncing) && results.length === 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
905
+ /* @__PURE__ */ jsxs("div", { className: "hsk-sb-skeleton-row", children: [
906
+ /* @__PURE__ */ jsx2("span", { className: "hsk-sb-skeleton-icon" }),
907
+ /* @__PURE__ */ jsxs("div", { className: "hsk-sb-row-body", children: [
908
+ /* @__PURE__ */ jsx2("div", { className: "hsk-sb-skeleton-text1" }),
909
+ /* @__PURE__ */ jsx2("div", { className: "hsk-sb-skeleton-text2" })
910
+ ] })
911
+ ] }),
912
+ /* @__PURE__ */ jsxs("div", { className: "hsk-sb-skeleton-row", children: [
913
+ /* @__PURE__ */ jsx2("span", { className: "hsk-sb-skeleton-icon" }),
914
+ /* @__PURE__ */ jsxs("div", { className: "hsk-sb-row-body", children: [
915
+ /* @__PURE__ */ jsx2("div", { className: "hsk-sb-skeleton-text1", style: { width: "45%" } }),
916
+ /* @__PURE__ */ jsx2("div", { className: "hsk-sb-skeleton-text2", style: { width: "25%" } })
917
+ ] })
918
+ ] })
919
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
920
+ results.length === 0 && !loading && !isDebouncing && /* @__PURE__ */ jsxs("div", { className: "hsk-sb-empty", children: [
921
+ "No results for \u201C",
922
+ query,
923
+ "\u201D"
924
+ ] }),
925
+ results.map((r, i) => {
926
+ var _a;
927
+ return renderResult ? /* @__PURE__ */ jsx2(
928
+ "div",
929
+ {
930
+ onClick: () => handleSelect(r),
931
+ className: "hsk-sb-fade",
932
+ style: { animationDelay: `${i * 18}ms` },
933
+ children: renderResult(r)
934
+ },
935
+ r.id
936
+ ) : /* @__PURE__ */ jsxs(
937
+ "div",
938
+ {
939
+ className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
940
+ style: { animationDelay: `${i * 18}ms` },
941
+ onClick: () => handleSelect(r),
942
+ children: [
943
+ /* @__PURE__ */ jsx2("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ jsx2(SearchIcon, {}) }),
944
+ /* @__PURE__ */ jsxs("div", { className: "hsk-sb-row-body", children: [
945
+ /* @__PURE__ */ jsx2("div", { className: "hsk-sb-row-title", children: r.product.name }),
946
+ (r.product.category || r.product.brand) && /* @__PURE__ */ jsx2("div", { className: "hsk-sb-row-sub", children: (_a = r.product.category) != null ? _a : r.product.brand })
947
+ ] })
948
+ ]
949
+ },
950
+ r.id
951
+ );
952
+ })
953
+ ] })
802
954
  ] })
803
955
  ] });
804
956
  }
805
957
 
806
958
  // src/components/Sparkle.tsx
807
- import { useState as useState6, useEffect as useEffect5, useRef as useRef7 } from "react";
959
+ import { useState as useState7, useEffect as useEffect6, useRef as useRef8 } from "react";
808
960
  import { createPortal } from "react-dom";
809
961
 
810
962
  // src/utils/markdown.tsx
811
- import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
963
+ import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime";
812
964
  var parseInline = (text, keyPrefix) => {
813
965
  const tokenRegex = /(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`)/g;
814
966
  const parts = text.split(tokenRegex);
@@ -889,11 +1041,11 @@ function renderMarkdown(content) {
889
1041
  );
890
1042
  i++;
891
1043
  }
892
- return /* @__PURE__ */ jsx3(Fragment, { children: elements });
1044
+ return /* @__PURE__ */ jsx3(Fragment2, { children: elements });
893
1045
  }
894
1046
 
895
1047
  // src/components/Sparkle.tsx
896
- import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1048
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
897
1049
  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" }) });
898
1050
  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: [
899
1051
  /* @__PURE__ */ jsx4("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
@@ -936,14 +1088,14 @@ function SparkleModal({
936
1088
  }) {
937
1089
  var _a, _b, _c;
938
1090
  const client = useHuskelContext();
939
- const [fetchedProduct, setFetchedProduct] = useState6(null);
1091
+ const [fetchedProduct, setFetchedProduct] = useState7(null);
940
1092
  const displayProduct = initialProduct || fetchedProduct;
941
1093
  const { results, loading: searchLoading, search } = useSearch();
942
1094
  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(() => {
1095
+ const [chatInput, setChatInput] = useState7("");
1096
+ const chatBottomRef = useRef8(null);
1097
+ const chatTextareaRef = useRef8(null);
1098
+ useEffect6(() => {
947
1099
  if (!initialProduct && !fetchedProduct) {
948
1100
  client.api.searchVector(productName, 1).then((res) => {
949
1101
  if (res.results && res.results.length > 0) {
@@ -953,17 +1105,17 @@ function SparkleModal({
953
1105
  }
954
1106
  search(productName, limit);
955
1107
  }, [productName, initialProduct, fetchedProduct, client, limit, search]);
956
- useEffect5(() => {
1108
+ useEffect6(() => {
957
1109
  if (results.length > 0) onResult == null ? void 0 : onResult(results);
958
1110
  }, [results, onResult]);
959
- useEffect5(() => {
1111
+ useEffect6(() => {
960
1112
  const h = (e) => {
961
1113
  if (e.key === "Escape") onClose();
962
1114
  };
963
1115
  document.addEventListener("keydown", h);
964
1116
  return () => document.removeEventListener("keydown", h);
965
1117
  }, [onClose]);
966
- useEffect5(() => {
1118
+ useEffect6(() => {
967
1119
  var _a2;
968
1120
  (_a2 = chatBottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
969
1121
  }, [messages, chatLoading]);
@@ -1195,13 +1347,13 @@ function Sparkle({
1195
1347
  classNames = {},
1196
1348
  product
1197
1349
  }) {
1198
- const [open, setOpen] = useState6(false);
1199
- const [mounted, setMounted] = useState6(false);
1200
- useEffect5(() => {
1350
+ const [open, setOpen] = useState7(false);
1351
+ const [mounted, setMounted] = useState7(false);
1352
+ useEffect6(() => {
1201
1353
  setMounted(true);
1202
1354
  }, []);
1203
1355
  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: [
1356
+ return /* @__PURE__ */ jsxs2(Fragment3, { children: [
1205
1357
  /* @__PURE__ */ jsx4(
1206
1358
  "button",
1207
1359
  {
@@ -1235,7 +1387,7 @@ function Sparkle({
1235
1387
  }
1236
1388
 
1237
1389
  // src/components/ChatWidget.tsx
1238
- import { useState as useState7, useRef as useRef8, useEffect as useEffect6 } from "react";
1390
+ import { useState as useState8, useRef as useRef9, useEffect as useEffect7 } from "react";
1239
1391
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1240
1392
  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" }) });
1241
1393
  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: [
@@ -1268,10 +1420,10 @@ function ChatWidget({
1268
1420
  onSelectSource
1269
1421
  }) {
1270
1422
  const { messages, sources, loading, error, send, reset } = useChat();
1271
- const [input, setInput] = useState7("");
1272
- const bottomRef = useRef8(null);
1273
- const textareaRef = useRef8(null);
1274
- useEffect6(() => {
1423
+ const [input, setInput] = useState8("");
1424
+ const bottomRef = useRef9(null);
1425
+ const textareaRef = useRef9(null);
1426
+ useEffect7(() => {
1275
1427
  var _a;
1276
1428
  (_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
1277
1429
  }, [messages, loading]);
@@ -1368,9 +1520,9 @@ function ChatWidget({
1368
1520
  }
1369
1521
 
1370
1522
  // src/components/AIChatButton.tsx
1371
- import { useState as useState8, useEffect as useEffect7, useRef as useRef9, useCallback as useCallback5 } from "react";
1523
+ import { useState as useState9, useEffect as useEffect8, useRef as useRef10, useCallback as useCallback5 } from "react";
1372
1524
  import { createPortal as createPortal2 } from "react-dom";
1373
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1525
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1374
1526
  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" }) });
1375
1527
  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: [
1376
1528
  /* @__PURE__ */ jsx6("path", { d: "m5 12 7-7 7 7" }),
@@ -1388,15 +1540,15 @@ var DEFAULT_CHIPS = [
1388
1540
  "Best laptop for students"
1389
1541
  ];
1390
1542
  function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
1391
- const railRef = useRef9(null);
1392
- const [showNext, setShowNext] = useState8(false);
1543
+ const railRef = useRef10(null);
1544
+ const [showNext, setShowNext] = useState9(false);
1393
1545
  const measure = useCallback5(() => {
1394
1546
  const el = railRef.current;
1395
1547
  if (!el) return;
1396
1548
  const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 8;
1397
1549
  setShowNext(el.scrollWidth > el.clientWidth + 4 && !atEnd);
1398
1550
  }, []);
1399
- useEffect7(() => {
1551
+ useEffect8(() => {
1400
1552
  measure();
1401
1553
  const el = railRef.current;
1402
1554
  if (!el) return;
@@ -1436,7 +1588,7 @@ function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
1436
1588
  si
1437
1589
  );
1438
1590
  }) }),
1439
- showNext && /* @__PURE__ */ jsxs4(Fragment3, { children: [
1591
+ showNext && /* @__PURE__ */ jsxs4(Fragment4, { children: [
1440
1592
  /* @__PURE__ */ jsx6(
1441
1593
  "div",
1442
1594
  {
@@ -1461,16 +1613,53 @@ function ChatModal({
1461
1613
  classNames = {}
1462
1614
  }) {
1463
1615
  var _a, _b;
1464
- const { messages, sources, loading, error, send, reset } = useChat();
1465
- const [input, setInput] = useState8("");
1466
- const [selectedProduct, setSelectedProduct] = useState8(null);
1467
- const bottomRef = useRef9(null);
1468
- const textareaRef = useRef9(null);
1469
- useEffect7(() => {
1616
+ const client = useHuskelContext();
1617
+ const { messages, sources, loading, error, lastAction, send, reset } = useChat();
1618
+ const [input, setInput] = useState9("");
1619
+ const [selectedProduct, setSelectedProduct] = useState9(null);
1620
+ const bottomRef = useRef10(null);
1621
+ const textareaRef = useRef10(null);
1622
+ const [phoneInput, setPhoneInput] = useState9("");
1623
+ const [merchantRef, setMerchantRef] = useState9(null);
1624
+ const [paymentPhase, setPaymentPhase] = useState9("idle");
1625
+ const { status: pollStatus } = usePaymentPolling({
1626
+ client: client.api,
1627
+ merchantReference: merchantRef,
1628
+ onSuccess: () => {
1629
+ setPaymentPhase("done");
1630
+ setMerchantRef(null);
1631
+ },
1632
+ onFailure: () => {
1633
+ setPaymentPhase("failed");
1634
+ setMerchantRef(null);
1635
+ }
1636
+ });
1637
+ useEffect8(() => {
1638
+ var _a2;
1639
+ if (!lastAction) return;
1640
+ if (lastAction.type === "request_phone") {
1641
+ setPaymentPhase("prompt_phone");
1642
+ } else if (lastAction.type === "awaiting_payment") {
1643
+ setMerchantRef((_a2 = lastAction.merchantReference) != null ? _a2 : null);
1644
+ setPaymentPhase("awaiting");
1645
+ }
1646
+ }, [lastAction]);
1647
+ const handlePhoneSubmit = async () => {
1648
+ if (!phoneInput.trim()) return;
1649
+ try {
1650
+ const res = await client.api.initiatePayment(phoneInput);
1651
+ setMerchantRef(res.merchantReference);
1652
+ setPaymentPhase("awaiting");
1653
+ } catch (e) {
1654
+ console.error("[Huskel] initiatePayment error", e);
1655
+ setPaymentPhase("failed");
1656
+ }
1657
+ };
1658
+ useEffect8(() => {
1470
1659
  var _a2;
1471
1660
  (_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
1472
1661
  }, [messages, loading, selectedProduct]);
1473
- useEffect7(() => {
1662
+ useEffect8(() => {
1474
1663
  const h = (e) => {
1475
1664
  if (e.key === "Escape") onClose();
1476
1665
  };
@@ -1586,6 +1775,39 @@ function ChatModal({
1586
1775
  ] })
1587
1776
  ] }),
1588
1777
  error && /* @__PURE__ */ jsx6("div", { className: "hsk-cb-error", children: error }),
1778
+ paymentPhase === "prompt_phone" && /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-payment-prompt", children: [
1779
+ /* @__PURE__ */ jsx6("div", { className: "hsk-cb-payment-icon", children: "\u{1F4F1}" }),
1780
+ /* @__PURE__ */ jsx6("p", { className: "hsk-cb-payment-label", children: "Enter your M-Pesa number to pay" }),
1781
+ /* @__PURE__ */ jsx6(
1782
+ "input",
1783
+ {
1784
+ type: "tel",
1785
+ className: "hsk-cb-phone-input",
1786
+ placeholder: "e.g. 0712 345 678",
1787
+ value: phoneInput,
1788
+ onChange: (e) => setPhoneInput(e.target.value),
1789
+ onKeyDown: (e) => e.key === "Enter" && handlePhoneSubmit()
1790
+ }
1791
+ ),
1792
+ /* @__PURE__ */ jsx6("button", { className: "hsk-cb-pay-submit", onClick: handlePhoneSubmit, children: "Send STK Push \u2192" })
1793
+ ] }),
1794
+ paymentPhase === "awaiting" && /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-payment-prompt", children: [
1795
+ /* @__PURE__ */ jsx6("div", { className: "hsk-cb-payment-icon", style: { fontSize: "2rem" }, children: "\u23F3" }),
1796
+ /* @__PURE__ */ jsx6("p", { className: "hsk-cb-payment-label", style: { fontWeight: 600 }, children: "Check your phone" }),
1797
+ /* @__PURE__ */ jsx6("p", { style: { fontSize: "0.8rem", opacity: 0.6 }, children: "An M-Pesa STK push has been sent. Enter your PIN to complete payment." })
1798
+ ] }),
1799
+ paymentPhase === "done" && /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-payment-prompt", children: [
1800
+ /* @__PURE__ */ jsx6("div", { className: "hsk-cb-payment-icon", style: { color: "#22c55e", fontSize: "2rem" }, children: "\u2705" }),
1801
+ /* @__PURE__ */ jsx6("p", { className: "hsk-cb-payment-label", children: "Payment complete! Thank you." })
1802
+ ] }),
1803
+ paymentPhase === "failed" && /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-payment-prompt", children: [
1804
+ /* @__PURE__ */ jsx6("div", { className: "hsk-cb-payment-icon", style: { color: "#ef4444", fontSize: "2rem" }, children: "\u274C" }),
1805
+ /* @__PURE__ */ jsx6("p", { className: "hsk-cb-payment-label", children: "Payment failed or timed out." }),
1806
+ /* @__PURE__ */ jsx6("button", { className: "hsk-cb-pay-submit", onClick: () => {
1807
+ setPaymentPhase("idle");
1808
+ setMerchantRef(null);
1809
+ }, children: "Try again" })
1810
+ ] }),
1589
1811
  /* @__PURE__ */ jsx6("div", { ref: bottomRef, style: { height: 1 } })
1590
1812
  ] }),
1591
1813
  /* @__PURE__ */ jsxs4("div", { className: "hsk-cb-input-wrap", children: [
@@ -1634,13 +1856,13 @@ function AIChatButton({
1634
1856
  theme,
1635
1857
  classNames = {}
1636
1858
  }) {
1637
- const [open, setOpen] = useState8(false);
1638
- const [mounted, setMounted] = useState8(false);
1639
- useEffect7(() => {
1859
+ const [open, setOpen] = useState9(false);
1860
+ const [mounted, setMounted] = useState9(false);
1861
+ useEffect8(() => {
1640
1862
  setMounted(true);
1641
1863
  }, []);
1642
1864
  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 });
1643
- return /* @__PURE__ */ jsxs4(Fragment3, { children: [
1865
+ return /* @__PURE__ */ jsxs4(Fragment4, { children: [
1644
1866
  /* @__PURE__ */ jsxs4(
1645
1867
  "button",
1646
1868
  {
@@ -1684,13 +1906,13 @@ function CartBadge({ className }) {
1684
1906
  }
1685
1907
 
1686
1908
  // src/components/CartDrawer.tsx
1687
- import { useState as useState10, useEffect as useEffect9 } from "react";
1909
+ import { useState as useState11, useEffect as useEffect10 } from "react";
1688
1910
  import { createPortal as createPortal4 } from "react-dom";
1689
1911
 
1690
1912
  // src/components/CheckoutModal.tsx
1691
- import { useState as useState9, useEffect as useEffect8 } from "react";
1913
+ import { useState as useState10, useEffect as useEffect9 } from "react";
1692
1914
  import { createPortal as createPortal3 } from "react-dom";
1693
- import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1915
+ import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1694
1916
  function CheckoutModal({
1695
1917
  onClose,
1696
1918
  theme,
@@ -1700,11 +1922,11 @@ function CheckoutModal({
1700
1922
  var _a, _b, _c, _d;
1701
1923
  const { cart, loading: cartLoading } = useCart();
1702
1924
  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(() => {
1925
+ const [config, setConfig] = useState10(null);
1926
+ const [loading, setLoading] = useState10(true);
1927
+ const [checkingOut, setCheckingOut] = useState10(false);
1928
+ const [paymentSuccess, setPaymentSuccess] = useState10(false);
1929
+ useEffect9(() => {
1708
1930
  client.api.getCheckoutConfig().then((res) => setConfig(res.payment_methods)).catch((e) => console.error("[Huskel] Failed to fetch checkout config", e)).finally(() => setLoading(false));
1709
1931
  }, [client]);
1710
1932
  const handlePay = async (method) => {
@@ -1756,7 +1978,7 @@ function CheckoutModal({
1756
1978
  ] }) : /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-split", children: [
1757
1979
  /* @__PURE__ */ jsxs5("div", { className: "hsk-checkout-summary", children: [
1758
1980
  /* @__PURE__ */ jsx8("h3", { children: "Order Summary" }),
1759
- cartLoading || !cart ? /* @__PURE__ */ jsx8("p", { className: "hsk-cart-loading", children: "Loading order..." }) : /* @__PURE__ */ jsxs5(Fragment4, { children: [
1981
+ cartLoading || !cart ? /* @__PURE__ */ jsx8("p", { className: "hsk-cart-loading", children: "Loading order..." }) : /* @__PURE__ */ jsxs5(Fragment5, { children: [
1760
1982
  /* @__PURE__ */ jsx8("ul", { className: "hsk-checkout-items", children: cart.items.map((item) => /* @__PURE__ */ jsxs5("li", { children: [
1761
1983
  /* @__PURE__ */ jsxs5("span", { children: [
1762
1984
  item.quantity,
@@ -1799,18 +2021,18 @@ function CheckoutModal({
1799
2021
  }
1800
2022
 
1801
2023
  // src/components/CartDrawer.tsx
1802
- import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
2024
+ import { Fragment as Fragment6, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
1803
2025
  function CartDrawer({
1804
2026
  trigger,
1805
2027
  className,
1806
2028
  theme
1807
2029
  }) {
1808
2030
  const { cart, loading } = useCart();
1809
- const [open, setOpen] = useState10(false);
1810
- const [showCheckout, setShowCheckout] = useState10(false);
1811
- const [mounted, setMounted] = useState10(false);
2031
+ const [open, setOpen] = useState11(false);
2032
+ const [showCheckout, setShowCheckout] = useState11(false);
2033
+ const [mounted, setMounted] = useState11(false);
1812
2034
  const client = useHuskelContext();
1813
- useEffect9(() => {
2035
+ useEffect10(() => {
1814
2036
  setMounted(true);
1815
2037
  const handleTriggerCheckout = () => {
1816
2038
  setShowCheckout(true);
@@ -1821,7 +2043,7 @@ function CartDrawer({
1821
2043
  window.removeEventListener("huskel:trigger_checkout", handleTriggerCheckout);
1822
2044
  };
1823
2045
  }, []);
1824
- useEffect9(() => {
2046
+ useEffect10(() => {
1825
2047
  if (open) {
1826
2048
  document.body.style.overflow = "hidden";
1827
2049
  } else {
@@ -1838,7 +2060,7 @@ function CartDrawer({
1838
2060
  const isStringTheme = typeof theme === "string";
1839
2061
  const hskThemeAttr = isStringTheme ? theme : void 0;
1840
2062
  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: [
2063
+ return /* @__PURE__ */ jsxs6(Fragment6, { children: [
1842
2064
  /* @__PURE__ */ jsx9("div", { onClick: () => setOpen(true), style: { display: "inline-block" }, children: trigger || /* @__PURE__ */ jsxs6(
1843
2065
  "button",
1844
2066
  {
@@ -1939,8 +2161,10 @@ export {
1939
2161
  useCart,
1940
2162
  useChat,
1941
2163
  useHuskel,
2164
+ useHuskelContext,
1942
2165
  useIngest,
1943
2166
  usePageIngest,
2167
+ usePaymentPolling,
1944
2168
  useSearch
1945
2169
  };
1946
2170
  //# sourceMappingURL=index.mjs.map