@proveanything/smartlinks-utils-ui 1.14.1 → 1.14.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.
@@ -1,8 +1,9 @@
1
1
  import { useFacets } from './chunk-DH5HG5DW.js';
2
2
  import { cn } from './chunk-L7FQ52F5.js';
3
3
  import { useState, useCallback, useRef, useMemo, useEffect } from 'react';
4
- import { GitBranch, Plus, Filter, ChevronRight, X, RefreshCw, Trash2, ChevronDown, Layers, ToggleLeft, Package, MapPin, Tag, Monitor, Calendar, User, Hash, Globe, Tags } from 'lucide-react';
4
+ import { GitBranch, Plus, Filter, ChevronRight, X, RefreshCw, Trash2, ChevronDown, Layers, ToggleLeft, Package, MapPin, Tag, Monitor, Calendar, User, Hash, Globe, Tags, Search, Loader2 } from 'lucide-react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+ import * as SL from '@proveanything/smartlinks';
6
7
 
7
8
  var CONDITION_TYPES = [
8
9
  { value: "version", title: "Version", description: "Match specific versions", icon: "tags", color: "#7c3aed", helpText: "Limit by specific versions of your application or product." },
@@ -14,7 +15,7 @@ var CONDITION_TYPES = [
14
15
  { value: "tag", title: "Label", description: "Match by tags or labels", icon: "tag", color: "#db2777", helpText: "Limit by assigned labels or tags." },
15
16
  { value: "facet", title: "Facet", description: "Match product facet values", icon: "layers", color: "#9333ea", helpText: "Limit by one or more facet values assigned to products in the collection." },
16
17
  { value: "geofence", title: "Geofence", description: "Geographic boundary", icon: "map-pin", color: "#0d9488", helpText: "Limit by geographic boundaries (rectangular area)." },
17
- { value: "product", title: "Product ID", description: "Match specific items", icon: "package", color: "#d97706", helpText: "Limit by specific product IDs." },
18
+ { value: "product", title: "Product", description: "Match specific products", icon: "package", color: "#d97706", helpText: "Limit by one or more products in the collection. Search by name, SKU, or ID." },
18
19
  { value: "itemStatus", title: "Item Status", description: "Filter by item state", icon: "toggle-left", color: "#ea580c", helpText: "Limit by item status such as proof, claimable, owner, or virtual." },
19
20
  { value: "condition", title: "Condition", description: "Reference another condition", icon: "git-branch", color: "#4f46e5", helpText: "Reference another saved condition and check if it passes or fails." }
20
21
  ];
@@ -109,7 +110,10 @@ function getConditionSummary(cond, options) {
109
110
  case "product":
110
111
  if (cond.productIds?.length) {
111
112
  const verb = cond.contains ? "is" : "is not";
112
- summary += ` ${verb} ${cond.productIds.length} item(s)`;
113
+ const names = cond.productIds.map((id) => cond.productNames?.[id] || id);
114
+ const preview = names.slice(0, 2).join(", ");
115
+ const rest = names.length > 2 ? ` +${names.length - 2}` : "";
116
+ summary += ` ${verb} ${preview}${rest}`;
113
117
  }
114
118
  break;
115
119
  case "condition":
@@ -603,6 +607,253 @@ var CountryPicker = ({
603
607
  }) })
604
608
  ] });
605
609
  };
610
+ function toHit(p) {
611
+ return {
612
+ id: p?.id ?? p?.productId ?? "",
613
+ name: p?.name ?? p?.id ?? "Untitled",
614
+ sku: p?.sku ?? null
615
+ };
616
+ }
617
+ async function queryProducts(collectionId, search) {
618
+ const body = {
619
+ page: { limit: 25 },
620
+ sort: [{ field: "name", direction: "asc" }]
621
+ };
622
+ if (search.trim()) body.query = { search: search.trim() };
623
+ const products = SL?.products ?? SL?.product;
624
+ if (!products) return [];
625
+ try {
626
+ if (products.query) {
627
+ const res = await products.query(collectionId, body, true);
628
+ return (res?.items ?? []).map(toHit);
629
+ }
630
+ if (products.list) {
631
+ const list = await products.list(collectionId, true);
632
+ const term = search.trim().toLowerCase();
633
+ const filtered = term ? list.filter((p) => `${p?.name ?? ""} ${p?.sku ?? ""} ${p?.id ?? ""}`.toLowerCase().includes(term)) : list;
634
+ return filtered.slice(0, 25).map(toHit);
635
+ }
636
+ } catch (err) {
637
+ console.warn("[ConditionsEditor] product search failed", err);
638
+ }
639
+ return [];
640
+ }
641
+ var ProductPicker = ({
642
+ collectionId,
643
+ selectedIds,
644
+ names,
645
+ onChange,
646
+ disabled
647
+ }) => {
648
+ const [search, setSearch] = useState("");
649
+ const [results, setResults] = useState([]);
650
+ const [loading, setLoading] = useState(false);
651
+ const [open, setOpen] = useState(false);
652
+ const containerRef = useRef(null);
653
+ const cachedNames = useMemo(() => names ?? {}, [names]);
654
+ const refreshedRef = useRef("");
655
+ useEffect(() => {
656
+ if (!collectionId || selectedIds.length === 0) return;
657
+ const key = selectedIds.slice().sort().join("|");
658
+ if (refreshedRef.current === key) return;
659
+ refreshedRef.current = key;
660
+ const products = SL?.products ?? SL?.product;
661
+ if (!products?.get) return;
662
+ let cancelled = false;
663
+ (async () => {
664
+ const updates = {};
665
+ await Promise.all(selectedIds.map(async (id) => {
666
+ try {
667
+ const p = await products.get(collectionId, id, true);
668
+ const name = p?.name;
669
+ if (name && name !== cachedNames[id]) updates[id] = name;
670
+ } catch {
671
+ }
672
+ }));
673
+ if (cancelled || Object.keys(updates).length === 0) return;
674
+ onChange(selectedIds, { ...cachedNames, ...updates });
675
+ })();
676
+ return () => {
677
+ cancelled = true;
678
+ };
679
+ }, [collectionId, selectedIds.join("|")]);
680
+ useEffect(() => {
681
+ if (!open) return;
682
+ let cancelled = false;
683
+ setLoading(true);
684
+ const t = window.setTimeout(async () => {
685
+ const hits = await queryProducts(collectionId, search);
686
+ if (!cancelled) {
687
+ setResults(hits);
688
+ setLoading(false);
689
+ }
690
+ }, 200);
691
+ return () => {
692
+ cancelled = true;
693
+ window.clearTimeout(t);
694
+ };
695
+ }, [search, open, collectionId]);
696
+ useEffect(() => {
697
+ if (!open) return;
698
+ const onDoc = (e) => {
699
+ if (!containerRef.current?.contains(e.target)) setOpen(false);
700
+ };
701
+ document.addEventListener("mousedown", onDoc);
702
+ return () => document.removeEventListener("mousedown", onDoc);
703
+ }, [open]);
704
+ const addProduct = (hit) => {
705
+ if (selectedIds.includes(hit.id)) return;
706
+ const nextNames = { ...cachedNames, [hit.id]: hit.name };
707
+ onChange([...selectedIds, hit.id], nextNames);
708
+ };
709
+ const removeProduct = (id) => {
710
+ const nextNames = { ...cachedNames };
711
+ delete nextNames[id];
712
+ onChange(selectedIds.filter((i) => i !== id), nextNames);
713
+ };
714
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
715
+ selectedIds.length > 0 && /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: selectedIds.map((id) => {
716
+ const name = cachedNames[id];
717
+ return /* @__PURE__ */ jsxs(
718
+ "span",
719
+ {
720
+ title: id,
721
+ style: {
722
+ display: "inline-flex",
723
+ alignItems: "center",
724
+ gap: 6,
725
+ padding: "4px 4px 4px 10px",
726
+ fontSize: 12,
727
+ borderRadius: 9999,
728
+ border: "1px solid #93c5fd",
729
+ backgroundColor: "#dbeafe",
730
+ color: "#1d4ed8"
731
+ },
732
+ children: [
733
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: name || id }),
734
+ name && /* @__PURE__ */ jsxs("span", { style: { opacity: 0.6, fontSize: 10, fontFamily: "ui-monospace, monospace" }, children: [
735
+ "(",
736
+ id.length > 12 ? `${id.slice(0, 6)}\u2026${id.slice(-4)}` : id,
737
+ ")"
738
+ ] }),
739
+ !disabled && /* @__PURE__ */ jsx(
740
+ "button",
741
+ {
742
+ type: "button",
743
+ onClick: () => removeProduct(id),
744
+ style: {
745
+ display: "inline-flex",
746
+ alignItems: "center",
747
+ justifyContent: "center",
748
+ width: 18,
749
+ height: 18,
750
+ borderRadius: 9999,
751
+ border: "none",
752
+ backgroundColor: "rgba(29,78,216,0.15)",
753
+ cursor: "pointer",
754
+ color: "#1d4ed8"
755
+ },
756
+ "aria-label": `Remove ${name || id}`,
757
+ children: /* @__PURE__ */ jsx(X, { size: 11 })
758
+ }
759
+ )
760
+ ]
761
+ },
762
+ id
763
+ );
764
+ }) }),
765
+ !disabled && /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, children: [
766
+ /* @__PURE__ */ jsxs("div", { style: {
767
+ display: "flex",
768
+ alignItems: "center",
769
+ gap: 6,
770
+ padding: "6px 10px",
771
+ fontSize: 14,
772
+ borderRadius: 6,
773
+ border: "1px solid #d1d5db",
774
+ backgroundColor: "transparent"
775
+ }, children: [
776
+ /* @__PURE__ */ jsx(Search, { size: 14, style: { opacity: 0.5 } }),
777
+ /* @__PURE__ */ jsx(
778
+ "input",
779
+ {
780
+ type: "text",
781
+ value: search,
782
+ onChange: (e) => {
783
+ setSearch(e.target.value);
784
+ setOpen(true);
785
+ },
786
+ onFocus: () => setOpen(true),
787
+ placeholder: "Search products by name, SKU, or ID\u2026",
788
+ style: {
789
+ flex: 1,
790
+ border: "none",
791
+ outline: "none",
792
+ backgroundColor: "transparent",
793
+ fontSize: 14
794
+ }
795
+ }
796
+ ),
797
+ loading && /* @__PURE__ */ jsx(Loader2, { size: 14, className: "animate-spin", style: { opacity: 0.5 } })
798
+ ] }),
799
+ open && /* @__PURE__ */ jsxs("div", { style: {
800
+ position: "absolute",
801
+ top: "calc(100% + 4px)",
802
+ left: 0,
803
+ right: 0,
804
+ zIndex: 20,
805
+ maxHeight: 280,
806
+ overflowY: "auto",
807
+ backgroundColor: "#ffffff",
808
+ border: "1px solid #e5e7eb",
809
+ borderRadius: 8,
810
+ boxShadow: "0 8px 20px rgba(0,0,0,0.08)"
811
+ }, children: [
812
+ results.length === 0 && !loading && /* @__PURE__ */ jsx("div", { style: { padding: 12, fontSize: 12, color: "#9ca3af", textAlign: "center" }, children: search ? "No products found" : "Start typing to search products" }),
813
+ results.map((hit) => {
814
+ const isSelected = selectedIds.includes(hit.id);
815
+ return /* @__PURE__ */ jsxs(
816
+ "button",
817
+ {
818
+ type: "button",
819
+ disabled: isSelected,
820
+ onClick: () => {
821
+ addProduct(hit);
822
+ setSearch("");
823
+ },
824
+ style: {
825
+ display: "flex",
826
+ alignItems: "center",
827
+ justifyContent: "space-between",
828
+ width: "100%",
829
+ padding: "8px 12px",
830
+ border: "none",
831
+ borderBottom: "1px solid #f3f4f6",
832
+ backgroundColor: isSelected ? "#f9fafb" : "transparent",
833
+ cursor: isSelected ? "default" : "pointer",
834
+ textAlign: "left",
835
+ color: "#111827",
836
+ fontSize: 13,
837
+ opacity: isSelected ? 0.5 : 1
838
+ },
839
+ children: [
840
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }, children: [
841
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: hit.name }),
842
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 10, color: "#9ca3af", fontFamily: "ui-monospace, monospace" }, children: [
843
+ hit.sku ? `${hit.sku} \xB7 ` : "",
844
+ hit.id
845
+ ] })
846
+ ] }),
847
+ isSelected && /* @__PURE__ */ jsx("span", { style: { fontSize: 10, color: "#9ca3af", marginLeft: 8 }, children: "Added" })
848
+ ]
849
+ },
850
+ hit.id
851
+ );
852
+ })
853
+ ] })
854
+ ] })
855
+ ] });
856
+ };
606
857
  var ContainsToggle = ({ value = true, onChange, trueLabel = "Is One Of", falseLabel = "Is Not One Of", disabled }) => /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4, backgroundColor: "#f3f4f6", borderRadius: 6, padding: 4, width: "fit-content" }, children: [{ v: true, label: trueLabel }, { v: false, label: falseLabel }].map((opt) => /* @__PURE__ */ jsx(
607
858
  "button",
608
859
  {
@@ -950,7 +1201,16 @@ var ConditionConfig = ({
950
1201
  case "product":
951
1202
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
952
1203
  /* @__PURE__ */ jsx(ContainsToggle, { value: cond.contains, onChange: (v) => update({ contains: v }), disabled: readOnly }),
953
- editorProps.products?.length ? /* @__PURE__ */ jsx(
1204
+ editorProps.collectionId ? /* @__PURE__ */ jsx(
1205
+ ProductPicker,
1206
+ {
1207
+ collectionId: editorProps.collectionId,
1208
+ selectedIds: cond.productIds || [],
1209
+ names: cond.productNames,
1210
+ onChange: (ids, names) => update({ productIds: ids, productNames: names }),
1211
+ disabled: readOnly
1212
+ }
1213
+ ) : editorProps.products?.length ? /* @__PURE__ */ jsx(
954
1214
  ChipSelect,
955
1215
  {
956
1216
  options: editorProps.products,
@@ -1159,7 +1419,7 @@ var ConditionsEditorContent = ({
1159
1419
  className
1160
1420
  }) => {
1161
1421
  const resolvedFacets = useFacets(collectionId, facets, getFacets);
1162
- const editorProps = { versions, tags, products, savedConditions, userGroups, facets: resolvedFacets };
1422
+ const editorProps = { versions, tags, products, savedConditions, userGroups, facets: resolvedFacets, collectionId };
1163
1423
  const updateCondition = useCallback((index, updated) => {
1164
1424
  const next = { ...value, conditions: [...value.conditions] };
1165
1425
  next.conditions[index] = updated;
@@ -1314,5 +1574,5 @@ var ConditionsEditor = (props) => {
1314
1574
  };
1315
1575
 
1316
1576
  export { ConditionsEditor };
1317
- //# sourceMappingURL=chunk-JNCRSL2H.js.map
1318
- //# sourceMappingURL=chunk-JNCRSL2H.js.map
1577
+ //# sourceMappingURL=chunk-WJTD5HT7.js.map
1578
+ //# sourceMappingURL=chunk-WJTD5HT7.js.map