@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.js CHANGED
@@ -52,8 +52,10 @@ __export(index_exports, {
52
52
  useCart: () => useCart,
53
53
  useChat: () => useChat,
54
54
  useHuskel: () => useHuskel,
55
+ useHuskelContext: () => useHuskelContext,
55
56
  useIngest: () => useIngest,
56
57
  usePageIngest: () => usePageIngest,
58
+ usePaymentPolling: () => usePaymentPolling,
57
59
  useSearch: () => useSearch
58
60
  });
59
61
  module.exports = __toCommonJS(index_exports);
@@ -207,6 +209,32 @@ var HuskelAPI = class {
207
209
  if (!res.ok) throw new Error("Failed to fetch checkout config");
208
210
  return res.json();
209
211
  }
212
+ async initiatePayment(phoneNumber, email, firstName, lastName) {
213
+ const res = await fetch(`${this.apiUrl}/payment/initiate`, {
214
+ method: "POST",
215
+ headers: this.buildHeaders(),
216
+ body: JSON.stringify({
217
+ siteId: this.siteId,
218
+ phoneNumber,
219
+ email,
220
+ firstName,
221
+ lastName
222
+ })
223
+ });
224
+ if (!res.ok) {
225
+ const errText = await res.text();
226
+ throw new Error("Failed to initiate payment: " + errText);
227
+ }
228
+ return res.json();
229
+ }
230
+ async getPaymentStatus(ref) {
231
+ const res = await fetch(`${this.apiUrl}/payment/status?ref=${ref}`, {
232
+ method: "GET",
233
+ headers: this.buildHeaders()
234
+ });
235
+ if (!res.ok) throw new Error("Failed to get payment status");
236
+ return res.json();
237
+ }
210
238
  };
211
239
 
212
240
  // src/client.ts
@@ -358,6 +386,9 @@ var _HuskelClient = class _HuskelClient {
358
386
  } catch (e) {
359
387
  }
360
388
  }
389
+ reRegister() {
390
+ instance = this;
391
+ }
361
392
  setShopperId(id) {
362
393
  this.shopperId = id;
363
394
  }
@@ -491,11 +522,17 @@ function HuskelProvider({ siteId, apiUrl, apiToken, shopperId, children }) {
491
522
  const clientRef = (0, import_react2.useRef)(null);
492
523
  if (!clientRef.current) {
493
524
  clientRef.current = new HuskelClient({ siteId, apiUrl, apiToken, shopperId });
525
+ } else {
526
+ clientRef.current.reRegister();
494
527
  }
495
528
  (0, import_react2.useEffect)(() => {
496
529
  var _a;
497
530
  (_a = clientRef.current) == null ? void 0 : _a.setShopperId(shopperId);
498
531
  }, [shopperId]);
532
+ (0, import_react2.useEffect)(() => {
533
+ var _a;
534
+ (_a = clientRef.current) == null ? void 0 : _a.reRegister();
535
+ }, []);
499
536
  (0, import_react2.useEffect)(() => {
500
537
  return () => {
501
538
  var _a;
@@ -527,6 +564,7 @@ function useSearch() {
527
564
  return;
528
565
  }
529
566
  const gen = ++genRef.current;
567
+ setLoading(true);
530
568
  setError(null);
531
569
  try {
532
570
  const res = await client.api.searchAutocomplete(query, limit);
@@ -617,9 +655,10 @@ function useChat() {
617
655
  const [sources, setSources] = (0, import_react6.useState)([]);
618
656
  const [loading, setLoading] = (0, import_react6.useState)(false);
619
657
  const [error, setError] = (0, import_react6.useState)(null);
658
+ const [lastAction, setLastAction] = (0, import_react6.useState)(null);
620
659
  const abortRef = (0, import_react6.useRef)(null);
621
660
  const send = (0, import_react6.useCallback)(async (query, displayQuery) => {
622
- var _a, _b, _c, _d, _e;
661
+ var _a, _b, _c, _d, _e, _f;
623
662
  if (!query.trim() || loading) return;
624
663
  (_a = abortRef.current) == null ? void 0 : _a.abort();
625
664
  abortRef.current = new AbortController();
@@ -650,6 +689,7 @@ function useChat() {
650
689
  }
651
690
  if (signal.aborted) return;
652
691
  setSources((_b = res.sources) != null ? _b : []);
692
+ if (res.action) setLastAction(res.action);
653
693
  if (((_c = res.action) == null ? void 0 : _c.type) === "add_to_cart" || res.checkout) {
654
694
  if (typeof window !== "undefined") {
655
695
  window.dispatchEvent(new CustomEvent("huskel:cart_updated", { detail: res.checkout }));
@@ -660,12 +700,17 @@ function useChat() {
660
700
  window.dispatchEvent(new CustomEvent("huskel:trigger_checkout", { detail: res.checkout }));
661
701
  }
662
702
  }
703
+ if (((_e = res.action) == null ? void 0 : _e.type) === "awaiting_payment") {
704
+ if (typeof window !== "undefined") {
705
+ window.dispatchEvent(new CustomEvent("huskel:awaiting_payment", { detail: res.action }));
706
+ }
707
+ }
663
708
  if (res.checkout && client.onCheckout) {
664
709
  client.onCheckout(res.checkout);
665
710
  }
666
711
  } catch (e) {
667
712
  if (signal.aborted) return;
668
- let msg = (_e = e == null ? void 0 : e.message) != null ? _e : "Chat request failed";
713
+ let msg = (_f = e == null ? void 0 : e.message) != null ? _f : "Chat request failed";
669
714
  try {
670
715
  const parsed = JSON.parse(msg);
671
716
  if (parsed && parsed.error) {
@@ -688,8 +733,9 @@ function useChat() {
688
733
  setSources([]);
689
734
  setError(null);
690
735
  setLoading(false);
736
+ setLastAction(null);
691
737
  }, []);
692
- return { messages, sources, loading, error, send, reset };
738
+ return { messages, sources, loading, error, lastAction, send, reset };
693
739
  }
694
740
 
695
741
  // src/hooks/useCart.ts
@@ -728,8 +774,71 @@ function useCart() {
728
774
  return { cart, loading, fetchCart };
729
775
  }
730
776
 
731
- // src/components/SearchBar.tsx
777
+ // src/hooks/usePaymentPolling.ts
732
778
  var import_react8 = require("react");
779
+ function usePaymentPolling({
780
+ client,
781
+ merchantReference,
782
+ onSuccess,
783
+ onFailure,
784
+ intervalMs = 3e3,
785
+ timeoutMs = 3e5
786
+ // 5 minutes default
787
+ }) {
788
+ const [status, setStatus] = (0, import_react8.useState)("IDLE");
789
+ const [error, setError] = (0, import_react8.useState)(null);
790
+ const onSuccessRef = (0, import_react8.useRef)(onSuccess);
791
+ const onFailureRef = (0, import_react8.useRef)(onFailure);
792
+ (0, import_react8.useEffect)(() => {
793
+ onSuccessRef.current = onSuccess;
794
+ onFailureRef.current = onFailure;
795
+ }, [onSuccess, onFailure]);
796
+ (0, import_react8.useEffect)(() => {
797
+ if (!merchantReference) {
798
+ setStatus("IDLE");
799
+ setError(null);
800
+ return;
801
+ }
802
+ setStatus("PENDING");
803
+ setError(null);
804
+ const startTime = Date.now();
805
+ let timerId = null;
806
+ async function checkStatus() {
807
+ try {
808
+ if (Date.now() - startTime >= timeoutMs) {
809
+ setStatus("FAILED");
810
+ setError("Payment session timed out");
811
+ if (onFailureRef.current) onFailureRef.current("Payment session timed out");
812
+ return;
813
+ }
814
+ const res = await client.getPaymentStatus(merchantReference);
815
+ if (res.status === "COMPLETED") {
816
+ setStatus("COMPLETED");
817
+ if (onSuccessRef.current) onSuccessRef.current();
818
+ } else if (res.status === "FAILED") {
819
+ setStatus("FAILED");
820
+ setError("Payment failed");
821
+ if (onFailureRef.current) onFailureRef.current("Payment failed");
822
+ } else {
823
+ timerId = setTimeout(checkStatus, intervalMs);
824
+ }
825
+ } catch (err) {
826
+ console.error("[Huskel Polling Error]", err);
827
+ timerId = setTimeout(checkStatus, intervalMs);
828
+ }
829
+ }
830
+ timerId = setTimeout(checkStatus, intervalMs);
831
+ return () => {
832
+ if (timerId) {
833
+ clearTimeout(timerId);
834
+ }
835
+ };
836
+ }, [client, merchantReference, intervalMs, timeoutMs]);
837
+ return { status, error };
838
+ }
839
+
840
+ // src/components/SearchBar.tsx
841
+ var import_react9 = require("react");
733
842
 
734
843
  // src/utils/cn.ts
735
844
  var import_clsx = require("clsx");
@@ -747,7 +856,7 @@ var SearchIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { wi
747
856
  function SearchBar({
748
857
  placeholder = "Search products\u2026",
749
858
  limit = 10,
750
- debounceMs = 80,
859
+ debounceMs = 300,
751
860
  onSelect,
752
861
  className,
753
862
  inputClassName,
@@ -756,25 +865,35 @@ function SearchBar({
756
865
  theme,
757
866
  classNames = {}
758
867
  }) {
759
- const [query, setQuery] = (0, import_react8.useState)("");
760
- const [open, setOpen] = (0, import_react8.useState)(false);
868
+ const [query, setQuery] = (0, import_react9.useState)("");
869
+ const [open, setOpen] = (0, import_react9.useState)(false);
870
+ const [isDebouncing, setIsDebouncing] = (0, import_react9.useState)(false);
761
871
  const { results, loading, search, clear } = useSearch();
762
- const timer = (0, import_react8.useRef)();
763
- const wrap = (0, import_react8.useRef)(null);
764
- (0, import_react8.useEffect)(() => {
872
+ const client = useHuskelContext();
873
+ const timer = (0, import_react9.useRef)();
874
+ const wrap = (0, import_react9.useRef)(null);
875
+ const ignoreNextQueryChange = (0, import_react9.useRef)(false);
876
+ (0, import_react9.useEffect)(() => {
877
+ if (ignoreNextQueryChange.current) {
878
+ ignoreNextQueryChange.current = false;
879
+ return;
880
+ }
765
881
  clearTimeout(timer.current);
766
882
  if (!query.trim()) {
767
883
  clear();
768
884
  setOpen(false);
885
+ setIsDebouncing(false);
769
886
  return;
770
887
  }
771
888
  setOpen(true);
889
+ setIsDebouncing(true);
772
890
  timer.current = setTimeout(() => {
891
+ setIsDebouncing(false);
773
892
  search(query, limit);
774
893
  }, debounceMs);
775
894
  return () => clearTimeout(timer.current);
776
895
  }, [query]);
777
- (0, import_react8.useEffect)(() => {
896
+ (0, import_react9.useEffect)(() => {
778
897
  const h = (e) => {
779
898
  if (wrap.current && !wrap.current.contains(e.target)) setOpen(false);
780
899
  };
@@ -782,10 +901,23 @@ function SearchBar({
782
901
  return () => document.removeEventListener("mousedown", h);
783
902
  }, []);
784
903
  const handleSelect = (r) => {
904
+ if (query.trim()) {
905
+ client.api.searchVector(query, 1).catch(() => {
906
+ });
907
+ }
908
+ ignoreNextQueryChange.current = true;
785
909
  setOpen(false);
786
910
  setQuery(r.product.name);
787
911
  onSelect == null ? void 0 : onSelect(r);
788
912
  };
913
+ const handleCommitSearch = () => {
914
+ if (!query.trim()) return;
915
+ client.api.searchVector(query, 1).catch(() => {
916
+ });
917
+ if (results.length > 0) {
918
+ handleSelect(results[0]);
919
+ }
920
+ };
789
921
  const showDrop = open && query.trim().length > 0;
790
922
  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 });
791
923
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("hsk-sb-wrap", classNames.root, className), ref: wrap, style: customStyles, children: [
@@ -799,51 +931,73 @@ function SearchBar({
799
931
  placeholder,
800
932
  onChange: (e) => setQuery(e.target.value),
801
933
  onFocus: () => results.length > 0 && query.trim() && setOpen(true),
934
+ onKeyDown: (e) => {
935
+ if (e.key === "Enter") {
936
+ handleCommitSearch();
937
+ }
938
+ },
802
939
  autoComplete: "off",
803
940
  spellCheck: false
804
941
  }
805
942
  ),
806
943
  showDrop && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("hsk-sb-drop", classNames.dropdown, dropdownClassName), style: { position: "absolute" }, children: [
807
- loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-loading-bar" }),
808
- results.length === 0 && !loading && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-empty", children: [
809
- "No results for \u201C",
810
- query,
811
- "\u201D"
812
- ] }),
813
- results.map((r, i) => {
814
- var _a;
815
- return renderResult ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
816
- "div",
817
- {
818
- onClick: () => handleSelect(r),
819
- className: "hsk-sb-fade",
820
- style: { animationDelay: `${i * 18}ms` },
821
- children: renderResult(r)
822
- },
823
- r.id
824
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
825
- "div",
826
- {
827
- className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
828
- style: { animationDelay: `${i * 18}ms` },
829
- onClick: () => handleSelect(r),
830
- children: [
831
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchIcon, {}) }),
832
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-row-body", children: [
833
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-title", children: r.product.name }),
834
- (r.product.category || r.product.brand) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-sub", children: (_a = r.product.category) != null ? _a : r.product.brand })
835
- ] })
836
- ]
837
- },
838
- r.id
839
- );
840
- })
944
+ (loading || isDebouncing) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-loading-bar" }),
945
+ (loading || isDebouncing) && results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
946
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-skeleton-row", children: [
947
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-skeleton-icon" }),
948
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-row-body", children: [
949
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-skeleton-text1" }),
950
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-skeleton-text2" })
951
+ ] })
952
+ ] }),
953
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-skeleton-row", children: [
954
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-skeleton-icon" }),
955
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-row-body", children: [
956
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-skeleton-text1", style: { width: "45%" } }),
957
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-skeleton-text2", style: { width: "25%" } })
958
+ ] })
959
+ ] })
960
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
961
+ results.length === 0 && !loading && !isDebouncing && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-empty", children: [
962
+ "No results for \u201C",
963
+ query,
964
+ "\u201D"
965
+ ] }),
966
+ results.map((r, i) => {
967
+ var _a;
968
+ return renderResult ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
969
+ "div",
970
+ {
971
+ onClick: () => handleSelect(r),
972
+ className: "hsk-sb-fade",
973
+ style: { animationDelay: `${i * 18}ms` },
974
+ children: renderResult(r)
975
+ },
976
+ r.id
977
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
978
+ "div",
979
+ {
980
+ className: cn("hsk-sb-row hsk-sb-fade", classNames.row),
981
+ style: { animationDelay: `${i * 18}ms` },
982
+ onClick: () => handleSelect(r),
983
+ children: [
984
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "hsk-sb-row-icon", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SearchIcon, {}) }),
985
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hsk-sb-row-body", children: [
986
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-title", children: r.product.name }),
987
+ (r.product.category || r.product.brand) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hsk-sb-row-sub", children: (_a = r.product.category) != null ? _a : r.product.brand })
988
+ ] })
989
+ ]
990
+ },
991
+ r.id
992
+ );
993
+ })
994
+ ] })
841
995
  ] })
842
996
  ] });
843
997
  }
844
998
 
845
999
  // src/components/Sparkle.tsx
846
- var import_react9 = require("react");
1000
+ var import_react10 = require("react");
847
1001
  var import_react_dom = require("react-dom");
848
1002
 
849
1003
  // src/utils/markdown.tsx
@@ -975,14 +1129,14 @@ function SparkleModal({
975
1129
  }) {
976
1130
  var _a, _b, _c;
977
1131
  const client = useHuskelContext();
978
- const [fetchedProduct, setFetchedProduct] = (0, import_react9.useState)(null);
1132
+ const [fetchedProduct, setFetchedProduct] = (0, import_react10.useState)(null);
979
1133
  const displayProduct = initialProduct || fetchedProduct;
980
1134
  const { results, loading: searchLoading, search } = useSearch();
981
1135
  const { messages, sources, loading: chatLoading, error: chatError, send } = useChat();
982
- const [chatInput, setChatInput] = (0, import_react9.useState)("");
983
- const chatBottomRef = (0, import_react9.useRef)(null);
984
- const chatTextareaRef = (0, import_react9.useRef)(null);
985
- (0, import_react9.useEffect)(() => {
1136
+ const [chatInput, setChatInput] = (0, import_react10.useState)("");
1137
+ const chatBottomRef = (0, import_react10.useRef)(null);
1138
+ const chatTextareaRef = (0, import_react10.useRef)(null);
1139
+ (0, import_react10.useEffect)(() => {
986
1140
  if (!initialProduct && !fetchedProduct) {
987
1141
  client.api.searchVector(productName, 1).then((res) => {
988
1142
  if (res.results && res.results.length > 0) {
@@ -992,17 +1146,17 @@ function SparkleModal({
992
1146
  }
993
1147
  search(productName, limit);
994
1148
  }, [productName, initialProduct, fetchedProduct, client, limit, search]);
995
- (0, import_react9.useEffect)(() => {
1149
+ (0, import_react10.useEffect)(() => {
996
1150
  if (results.length > 0) onResult == null ? void 0 : onResult(results);
997
1151
  }, [results, onResult]);
998
- (0, import_react9.useEffect)(() => {
1152
+ (0, import_react10.useEffect)(() => {
999
1153
  const h = (e) => {
1000
1154
  if (e.key === "Escape") onClose();
1001
1155
  };
1002
1156
  document.addEventListener("keydown", h);
1003
1157
  return () => document.removeEventListener("keydown", h);
1004
1158
  }, [onClose]);
1005
- (0, import_react9.useEffect)(() => {
1159
+ (0, import_react10.useEffect)(() => {
1006
1160
  var _a2;
1007
1161
  (_a2 = chatBottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
1008
1162
  }, [messages, chatLoading]);
@@ -1234,9 +1388,9 @@ function Sparkle({
1234
1388
  classNames = {},
1235
1389
  product
1236
1390
  }) {
1237
- const [open, setOpen] = (0, import_react9.useState)(false);
1238
- const [mounted, setMounted] = (0, import_react9.useState)(false);
1239
- (0, import_react9.useEffect)(() => {
1391
+ const [open, setOpen] = (0, import_react10.useState)(false);
1392
+ const [mounted, setMounted] = (0, import_react10.useState)(false);
1393
+ (0, import_react10.useEffect)(() => {
1240
1394
  setMounted(true);
1241
1395
  }, []);
1242
1396
  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 });
@@ -1274,7 +1428,7 @@ function Sparkle({
1274
1428
  }
1275
1429
 
1276
1430
  // src/components/ChatWidget.tsx
1277
- var import_react10 = require("react");
1431
+ var import_react11 = require("react");
1278
1432
  var import_jsx_runtime5 = require("react/jsx-runtime");
1279
1433
  var SparkleIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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" }) });
1280
1434
  var ArrowUpIcon2 = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
@@ -1307,10 +1461,10 @@ function ChatWidget({
1307
1461
  onSelectSource
1308
1462
  }) {
1309
1463
  const { messages, sources, loading, error, send, reset } = useChat();
1310
- const [input, setInput] = (0, import_react10.useState)("");
1311
- const bottomRef = (0, import_react10.useRef)(null);
1312
- const textareaRef = (0, import_react10.useRef)(null);
1313
- (0, import_react10.useEffect)(() => {
1464
+ const [input, setInput] = (0, import_react11.useState)("");
1465
+ const bottomRef = (0, import_react11.useRef)(null);
1466
+ const textareaRef = (0, import_react11.useRef)(null);
1467
+ (0, import_react11.useEffect)(() => {
1314
1468
  var _a;
1315
1469
  (_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
1316
1470
  }, [messages, loading]);
@@ -1407,7 +1561,7 @@ function ChatWidget({
1407
1561
  }
1408
1562
 
1409
1563
  // src/components/AIChatButton.tsx
1410
- var import_react11 = require("react");
1564
+ var import_react12 = require("react");
1411
1565
  var import_react_dom2 = require("react-dom");
1412
1566
  var import_jsx_runtime6 = require("react/jsx-runtime");
1413
1567
  var SparkleIcon3 = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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" }) });
@@ -1427,15 +1581,15 @@ var DEFAULT_CHIPS = [
1427
1581
  "Best laptop for students"
1428
1582
  ];
1429
1583
  function SourcesCarousel({ sources, defaultCurrency, onSelectSource }) {
1430
- const railRef = (0, import_react11.useRef)(null);
1431
- const [showNext, setShowNext] = (0, import_react11.useState)(false);
1432
- const measure = (0, import_react11.useCallback)(() => {
1584
+ const railRef = (0, import_react12.useRef)(null);
1585
+ const [showNext, setShowNext] = (0, import_react12.useState)(false);
1586
+ const measure = (0, import_react12.useCallback)(() => {
1433
1587
  const el = railRef.current;
1434
1588
  if (!el) return;
1435
1589
  const atEnd = el.scrollLeft + el.clientWidth >= el.scrollWidth - 8;
1436
1590
  setShowNext(el.scrollWidth > el.clientWidth + 4 && !atEnd);
1437
1591
  }, []);
1438
- (0, import_react11.useEffect)(() => {
1592
+ (0, import_react12.useEffect)(() => {
1439
1593
  measure();
1440
1594
  const el = railRef.current;
1441
1595
  if (!el) return;
@@ -1500,16 +1654,53 @@ function ChatModal({
1500
1654
  classNames = {}
1501
1655
  }) {
1502
1656
  var _a, _b;
1503
- const { messages, sources, loading, error, send, reset } = useChat();
1504
- const [input, setInput] = (0, import_react11.useState)("");
1505
- const [selectedProduct, setSelectedProduct] = (0, import_react11.useState)(null);
1506
- const bottomRef = (0, import_react11.useRef)(null);
1507
- const textareaRef = (0, import_react11.useRef)(null);
1508
- (0, import_react11.useEffect)(() => {
1657
+ const client = useHuskelContext();
1658
+ const { messages, sources, loading, error, lastAction, send, reset } = useChat();
1659
+ const [input, setInput] = (0, import_react12.useState)("");
1660
+ const [selectedProduct, setSelectedProduct] = (0, import_react12.useState)(null);
1661
+ const bottomRef = (0, import_react12.useRef)(null);
1662
+ const textareaRef = (0, import_react12.useRef)(null);
1663
+ const [phoneInput, setPhoneInput] = (0, import_react12.useState)("");
1664
+ const [merchantRef, setMerchantRef] = (0, import_react12.useState)(null);
1665
+ const [paymentPhase, setPaymentPhase] = (0, import_react12.useState)("idle");
1666
+ const { status: pollStatus } = usePaymentPolling({
1667
+ client: client.api,
1668
+ merchantReference: merchantRef,
1669
+ onSuccess: () => {
1670
+ setPaymentPhase("done");
1671
+ setMerchantRef(null);
1672
+ },
1673
+ onFailure: () => {
1674
+ setPaymentPhase("failed");
1675
+ setMerchantRef(null);
1676
+ }
1677
+ });
1678
+ (0, import_react12.useEffect)(() => {
1679
+ var _a2;
1680
+ if (!lastAction) return;
1681
+ if (lastAction.type === "request_phone") {
1682
+ setPaymentPhase("prompt_phone");
1683
+ } else if (lastAction.type === "awaiting_payment") {
1684
+ setMerchantRef((_a2 = lastAction.merchantReference) != null ? _a2 : null);
1685
+ setPaymentPhase("awaiting");
1686
+ }
1687
+ }, [lastAction]);
1688
+ const handlePhoneSubmit = async () => {
1689
+ if (!phoneInput.trim()) return;
1690
+ try {
1691
+ const res = await client.api.initiatePayment(phoneInput);
1692
+ setMerchantRef(res.merchantReference);
1693
+ setPaymentPhase("awaiting");
1694
+ } catch (e) {
1695
+ console.error("[Huskel] initiatePayment error", e);
1696
+ setPaymentPhase("failed");
1697
+ }
1698
+ };
1699
+ (0, import_react12.useEffect)(() => {
1509
1700
  var _a2;
1510
1701
  (_a2 = bottomRef.current) == null ? void 0 : _a2.scrollIntoView({ behavior: "smooth" });
1511
1702
  }, [messages, loading, selectedProduct]);
1512
- (0, import_react11.useEffect)(() => {
1703
+ (0, import_react12.useEffect)(() => {
1513
1704
  const h = (e) => {
1514
1705
  if (e.key === "Escape") onClose();
1515
1706
  };
@@ -1625,6 +1816,39 @@ function ChatModal({
1625
1816
  ] })
1626
1817
  ] }),
1627
1818
  error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-error", children: error }),
1819
+ paymentPhase === "prompt_phone" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-payment-prompt", children: [
1820
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-payment-icon", children: "\u{1F4F1}" }),
1821
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "hsk-cb-payment-label", children: "Enter your M-Pesa number to pay" }),
1822
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1823
+ "input",
1824
+ {
1825
+ type: "tel",
1826
+ className: "hsk-cb-phone-input",
1827
+ placeholder: "e.g. 0712 345 678",
1828
+ value: phoneInput,
1829
+ onChange: (e) => setPhoneInput(e.target.value),
1830
+ onKeyDown: (e) => e.key === "Enter" && handlePhoneSubmit()
1831
+ }
1832
+ ),
1833
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "hsk-cb-pay-submit", onClick: handlePhoneSubmit, children: "Send STK Push \u2192" })
1834
+ ] }),
1835
+ paymentPhase === "awaiting" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-payment-prompt", children: [
1836
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-payment-icon", style: { fontSize: "2rem" }, children: "\u23F3" }),
1837
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "hsk-cb-payment-label", style: { fontWeight: 600 }, children: "Check your phone" }),
1838
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: { fontSize: "0.8rem", opacity: 0.6 }, children: "An M-Pesa STK push has been sent. Enter your PIN to complete payment." })
1839
+ ] }),
1840
+ paymentPhase === "done" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-payment-prompt", children: [
1841
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-payment-icon", style: { color: "#22c55e", fontSize: "2rem" }, children: "\u2705" }),
1842
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "hsk-cb-payment-label", children: "Payment complete! Thank you." })
1843
+ ] }),
1844
+ paymentPhase === "failed" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-payment-prompt", children: [
1845
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hsk-cb-payment-icon", style: { color: "#ef4444", fontSize: "2rem" }, children: "\u274C" }),
1846
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "hsk-cb-payment-label", children: "Payment failed or timed out." }),
1847
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "hsk-cb-pay-submit", onClick: () => {
1848
+ setPaymentPhase("idle");
1849
+ setMerchantRef(null);
1850
+ }, children: "Try again" })
1851
+ ] }),
1628
1852
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { ref: bottomRef, style: { height: 1 } })
1629
1853
  ] }),
1630
1854
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hsk-cb-input-wrap", children: [
@@ -1673,9 +1897,9 @@ function AIChatButton({
1673
1897
  theme,
1674
1898
  classNames = {}
1675
1899
  }) {
1676
- const [open, setOpen] = (0, import_react11.useState)(false);
1677
- const [mounted, setMounted] = (0, import_react11.useState)(false);
1678
- (0, import_react11.useEffect)(() => {
1900
+ const [open, setOpen] = (0, import_react12.useState)(false);
1901
+ const [mounted, setMounted] = (0, import_react12.useState)(false);
1902
+ (0, import_react12.useEffect)(() => {
1679
1903
  setMounted(true);
1680
1904
  }, []);
1681
1905
  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 });
@@ -1723,11 +1947,11 @@ function CartBadge({ className }) {
1723
1947
  }
1724
1948
 
1725
1949
  // src/components/CartDrawer.tsx
1726
- var import_react13 = require("react");
1950
+ var import_react14 = require("react");
1727
1951
  var import_react_dom4 = require("react-dom");
1728
1952
 
1729
1953
  // src/components/CheckoutModal.tsx
1730
- var import_react12 = require("react");
1954
+ var import_react13 = require("react");
1731
1955
  var import_react_dom3 = require("react-dom");
1732
1956
  var import_jsx_runtime8 = require("react/jsx-runtime");
1733
1957
  function CheckoutModal({
@@ -1739,11 +1963,11 @@ function CheckoutModal({
1739
1963
  var _a, _b, _c, _d;
1740
1964
  const { cart, loading: cartLoading } = useCart();
1741
1965
  const client = useHuskelContext();
1742
- const [config, setConfig] = (0, import_react12.useState)(null);
1743
- const [loading, setLoading] = (0, import_react12.useState)(true);
1744
- const [checkingOut, setCheckingOut] = (0, import_react12.useState)(false);
1745
- const [paymentSuccess, setPaymentSuccess] = (0, import_react12.useState)(false);
1746
- (0, import_react12.useEffect)(() => {
1966
+ const [config, setConfig] = (0, import_react13.useState)(null);
1967
+ const [loading, setLoading] = (0, import_react13.useState)(true);
1968
+ const [checkingOut, setCheckingOut] = (0, import_react13.useState)(false);
1969
+ const [paymentSuccess, setPaymentSuccess] = (0, import_react13.useState)(false);
1970
+ (0, import_react13.useEffect)(() => {
1747
1971
  client.api.getCheckoutConfig().then((res) => setConfig(res.payment_methods)).catch((e) => console.error("[Huskel] Failed to fetch checkout config", e)).finally(() => setLoading(false));
1748
1972
  }, [client]);
1749
1973
  const handlePay = async (method) => {
@@ -1845,11 +2069,11 @@ function CartDrawer({
1845
2069
  theme
1846
2070
  }) {
1847
2071
  const { cart, loading } = useCart();
1848
- const [open, setOpen] = (0, import_react13.useState)(false);
1849
- const [showCheckout, setShowCheckout] = (0, import_react13.useState)(false);
1850
- const [mounted, setMounted] = (0, import_react13.useState)(false);
2072
+ const [open, setOpen] = (0, import_react14.useState)(false);
2073
+ const [showCheckout, setShowCheckout] = (0, import_react14.useState)(false);
2074
+ const [mounted, setMounted] = (0, import_react14.useState)(false);
1851
2075
  const client = useHuskelContext();
1852
- (0, import_react13.useEffect)(() => {
2076
+ (0, import_react14.useEffect)(() => {
1853
2077
  setMounted(true);
1854
2078
  const handleTriggerCheckout = () => {
1855
2079
  setShowCheckout(true);
@@ -1860,7 +2084,7 @@ function CartDrawer({
1860
2084
  window.removeEventListener("huskel:trigger_checkout", handleTriggerCheckout);
1861
2085
  };
1862
2086
  }, []);
1863
- (0, import_react13.useEffect)(() => {
2087
+ (0, import_react14.useEffect)(() => {
1864
2088
  if (open) {
1865
2089
  document.body.style.overflow = "hidden";
1866
2090
  } else {
@@ -1979,8 +2203,10 @@ function CartDrawer({
1979
2203
  useCart,
1980
2204
  useChat,
1981
2205
  useHuskel,
2206
+ useHuskelContext,
1982
2207
  useIngest,
1983
2208
  usePageIngest,
2209
+ usePaymentPolling,
1984
2210
  useSearch
1985
2211
  });
1986
2212
  //# sourceMappingURL=index.js.map