@fairmint/ui 0.2.14 → 0.2.15

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.
@@ -30688,38 +30688,84 @@ function InvestorProfileCard({
30688
30688
  children
30689
30689
  ] });
30690
30690
  }
30691
- const DEFAULT_TYPE_LABELS = {
30691
+ const SYSTEM_TYPES = /* @__PURE__ */ new Set(["fairmint", "privy"]);
30692
+ const TYPE_LABELS = {
30692
30693
  fairmint: "Fairmint Wallet",
30693
30694
  privy: "Fairmint Wallet",
30694
30695
  manual: "Manual"
30695
30696
  };
30696
- function getTypeLabel(type, custom2) {
30697
- if (custom2?.[type]) return custom2[type];
30698
- return DEFAULT_TYPE_LABELS[type] ?? type;
30697
+ function normalizeWalletType(type) {
30698
+ return String(type ?? "").trim().toLowerCase();
30699
+ }
30700
+ function getWalletLabel(type, custom2) {
30701
+ const key = normalizeWalletType(type);
30702
+ const rawType = typeof type === "string" ? type : String(type ?? "");
30703
+ if (custom2?.[key]) return custom2[key];
30704
+ if (custom2?.[rawType]) return custom2[rawType];
30705
+ return TYPE_LABELS[key] ?? rawType;
30706
+ }
30707
+ function getWalletDisplayName(wallet, customLabels) {
30708
+ const label = wallet.label?.trim();
30709
+ if (label) return label;
30710
+ return getWalletLabel(wallet.type, customLabels);
30711
+ }
30712
+ function isSystemWalletType(type) {
30713
+ return SYSTEM_TYPES.has(normalizeWalletType(type));
30714
+ }
30715
+ function isRemovableWalletType(type) {
30716
+ return !isSystemWalletType(type);
30717
+ }
30718
+ function walletKey(w) {
30719
+ return `${normalizeWalletType(w.type)}:${w.address.toLowerCase()}`;
30699
30720
  }
30700
30721
  function WalletList({
30701
30722
  wallets,
30702
30723
  onAdd,
30703
30724
  onRemove,
30725
+ onSetDefault,
30726
+ onLabel,
30727
+ onConnect,
30728
+ getExplorerUrl,
30704
30729
  readOnly = false,
30705
30730
  addLoading = false,
30706
30731
  removeLoading = false,
30732
+ setDefaultLoading = false,
30733
+ setDefaultAddress,
30734
+ labelLoading = false,
30735
+ labelAddress,
30736
+ connectLoading = false,
30707
30737
  emptyIcon,
30708
30738
  copyIcon,
30709
30739
  copiedIcon,
30710
30740
  removeIcon,
30711
30741
  addIcon,
30742
+ defaultIcon,
30743
+ renameIcon,
30744
+ explorerIcon,
30745
+ connectIcon,
30712
30746
  typeLabels,
30713
- removableTypes = ["manual"]
30747
+ removableTypes,
30748
+ children
30714
30749
  }) {
30750
+ const normalizeWalletAddress = useCallback(
30751
+ (address) => address?.trim().toLowerCase() ?? "",
30752
+ []
30753
+ );
30715
30754
  const [newAddress, setNewAddress] = useState("");
30716
30755
  const [addressError, setAddressError] = useState(null);
30717
30756
  const [showAddForm, setShowAddForm] = useState(false);
30718
30757
  const [walletToRemove, setWalletToRemove] = useState(null);
30719
30758
  const [copiedAddress, setCopiedAddress] = useState(null);
30759
+ const [renamingAddress, setRenamingAddress] = useState(null);
30760
+ const [renameValue, setRenameValue] = useState("");
30761
+ const [labelError, setLabelError] = useState(null);
30762
+ const [setDefaultError, setSetDefaultError] = useState(null);
30720
30763
  const copyTimeoutRef = useRef(void 0);
30721
30764
  useEffect(() => () => clearTimeout(copyTimeoutRef.current), []);
30722
- const copyAddress = async (address) => {
30765
+ useEffect(() => {
30766
+ if (readOnly) setRenamingAddress(null);
30767
+ }, [readOnly]);
30768
+ const copyAddress = useCallback(async (address) => {
30723
30769
  try {
30724
30770
  await navigator.clipboard.writeText(address);
30725
30771
  clearTimeout(copyTimeoutRef.current);
@@ -30727,9 +30773,27 @@ function WalletList({
30727
30773
  copyTimeoutRef.current = setTimeout(() => setCopiedAddress(null), 2e3);
30728
30774
  } catch {
30729
30775
  }
30730
- };
30776
+ }, []);
30731
30777
  const canAdd = !readOnly && !!onAdd;
30732
30778
  const canRemove = !readOnly && !!onRemove;
30779
+ const canSetDefault = !readOnly && !!onSetDefault;
30780
+ const canLabel = !readOnly && !!onLabel;
30781
+ const canConnect = !readOnly && !!onConnect;
30782
+ const getSafeExplorerUrl = useCallback(
30783
+ (address) => {
30784
+ if (!getExplorerUrl) return null;
30785
+ try {
30786
+ const url = getExplorerUrl(address);
30787
+ if (!url) return null;
30788
+ const parsed = new URL(url);
30789
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
30790
+ return parsed.toString();
30791
+ } catch {
30792
+ return null;
30793
+ }
30794
+ },
30795
+ [getExplorerUrl]
30796
+ );
30733
30797
  const handleAdd = async () => {
30734
30798
  const trimmed = newAddress.trim();
30735
30799
  const error2 = validateEthereumAddress(trimmed);
@@ -30757,8 +30821,191 @@ function WalletList({
30757
30821
  setRemoveError("Failed to remove wallet. Please try again.");
30758
30822
  }
30759
30823
  };
30760
- if (wallets.length === 0 && !showAddForm) {
30761
- return /* @__PURE__ */ jsx(
30824
+ const handleSetDefault = async (address) => {
30825
+ setSetDefaultError(null);
30826
+ try {
30827
+ await onSetDefault?.(address);
30828
+ } catch {
30829
+ setSetDefaultError("Failed to set default wallet. Please try again.");
30830
+ }
30831
+ };
30832
+ const handleLabelSave = async (address) => {
30833
+ setLabelError(null);
30834
+ try {
30835
+ await onLabel?.(address, renameValue.trim() || null);
30836
+ setRenamingAddress((current) => current === address ? null : current);
30837
+ } catch {
30838
+ setLabelError("Failed to update label. Please try again.");
30839
+ }
30840
+ };
30841
+ const hasWallets = wallets.length > 0;
30842
+ if (!hasWallets && !showAddForm && !canConnect) {
30843
+ return /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
30844
+ /* @__PURE__ */ jsx(
30845
+ EmptyState,
30846
+ {
30847
+ icon: emptyIcon,
30848
+ title: "No wallets connected",
30849
+ description: "Add a wallet address to receive equity on-chain.",
30850
+ actionLabel: canAdd ? "Add Wallet" : void 0,
30851
+ onAction: canAdd ? () => setShowAddForm(true) : void 0
30852
+ }
30853
+ ),
30854
+ children
30855
+ ] });
30856
+ }
30857
+ return /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
30858
+ wallets.map((w) => {
30859
+ const normalizedWalletAddress = normalizeWalletAddress(w.address);
30860
+ const setDefaultActionLoading = setDefaultLoading && (!setDefaultAddress || normalizeWalletAddress(setDefaultAddress) === normalizedWalletAddress);
30861
+ const labelActionLoading = labelLoading && (!labelAddress || normalizeWalletAddress(labelAddress) === normalizedWalletAddress);
30862
+ const explorerUrl = getSafeExplorerUrl(w.address);
30863
+ return /* @__PURE__ */ jsxs(Stack, { gap: 0, children: [
30864
+ /* @__PURE__ */ jsxs(
30865
+ Group,
30866
+ {
30867
+ justify: "space-between",
30868
+ p: "xs",
30869
+ style: {
30870
+ borderRadius: radii.sm,
30871
+ border: `1px solid ${neutralColors[200]}`
30872
+ },
30873
+ children: [
30874
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
30875
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", children: getWalletLabel(w.type, typeLabels) }),
30876
+ w.label && /* @__PURE__ */ jsx(Text, { size: "xs", c: fairmintVars.colorTextSecondary, children: w.label }),
30877
+ w.defaultRecipient && /* @__PURE__ */ jsx(Badge, { size: "xs", color: colors.success, variant: "light", children: "Default" }),
30878
+ /* @__PURE__ */ jsx(Text, { size: "sm", ff: "monospace", children: truncateAddress(w.address) })
30879
+ ] }),
30880
+ /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
30881
+ copyIcon && /* @__PURE__ */ jsx(Tooltip, { label: copiedAddress === w.address ? "Copied!" : "Copy address", children: /* @__PURE__ */ jsx(
30882
+ ActionIcon,
30883
+ {
30884
+ variant: "subtle",
30885
+ size: "sm",
30886
+ onClick: () => copyAddress(w.address),
30887
+ "aria-label": "Copy wallet address",
30888
+ children: copiedAddress === w.address ? copiedIcon ?? copyIcon : copyIcon
30889
+ }
30890
+ ) }),
30891
+ canSetDefault && defaultIcon && !w.defaultRecipient && /* @__PURE__ */ jsx(Tooltip, { label: "Set as default recipient", children: /* @__PURE__ */ jsx(
30892
+ ActionIcon,
30893
+ {
30894
+ variant: "subtle",
30895
+ size: "sm",
30896
+ onClick: () => handleSetDefault(w.address),
30897
+ loading: setDefaultActionLoading,
30898
+ disabled: setDefaultActionLoading,
30899
+ "aria-label": "Set as default wallet",
30900
+ children: defaultIcon
30901
+ }
30902
+ ) }),
30903
+ canLabel && renameIcon && /* @__PURE__ */ jsx(
30904
+ Tooltip,
30905
+ {
30906
+ label: renamingAddress === w.address ? "Cancel rename" : "Rename wallet",
30907
+ children: /* @__PURE__ */ jsx(
30908
+ ActionIcon,
30909
+ {
30910
+ variant: "subtle",
30911
+ size: "sm",
30912
+ onClick: () => {
30913
+ setLabelError(null);
30914
+ if (renamingAddress === w.address) {
30915
+ setRenamingAddress(null);
30916
+ } else {
30917
+ setRenamingAddress(w.address);
30918
+ setRenameValue(w.label ?? "");
30919
+ }
30920
+ },
30921
+ "aria-label": "Rename wallet",
30922
+ children: renameIcon
30923
+ }
30924
+ )
30925
+ }
30926
+ ),
30927
+ explorerUrl && explorerIcon && /* @__PURE__ */ jsx(Tooltip, { label: "View on block explorer", children: /* @__PURE__ */ jsx(
30928
+ Anchor,
30929
+ {
30930
+ href: explorerUrl,
30931
+ target: "_blank",
30932
+ rel: "noopener noreferrer",
30933
+ "aria-label": "View on block explorer",
30934
+ c: colors.primary,
30935
+ style: {
30936
+ display: "inline-flex",
30937
+ alignItems: "center",
30938
+ justifyContent: "center",
30939
+ width: "1.625rem",
30940
+ height: "1.625rem",
30941
+ borderRadius: radii.sm
30942
+ },
30943
+ children: explorerIcon
30944
+ }
30945
+ ) }),
30946
+ canRemove && removeIcon && (removableTypes ? removableTypes.some(
30947
+ (t) => normalizeWalletType(t) === normalizeWalletType(w.type)
30948
+ ) : isRemovableWalletType(w.type)) && /* @__PURE__ */ jsx(Tooltip, { label: "Remove wallet", children: /* @__PURE__ */ jsx(
30949
+ ActionIcon,
30950
+ {
30951
+ variant: "subtle",
30952
+ size: "sm",
30953
+ color: colors.error,
30954
+ onClick: () => setWalletToRemove(w.address),
30955
+ disabled: removeLoading,
30956
+ "aria-label": "Remove wallet",
30957
+ children: removeIcon
30958
+ }
30959
+ ) })
30960
+ ] })
30961
+ ]
30962
+ }
30963
+ ),
30964
+ renamingAddress === w.address && /* @__PURE__ */ jsxs(Group, { gap: "xs", mt: "xs", align: "flex-start", children: [
30965
+ /* @__PURE__ */ jsx(
30966
+ TextInput,
30967
+ {
30968
+ value: renameValue,
30969
+ onChange: (e) => {
30970
+ setRenameValue(e.currentTarget.value);
30971
+ setLabelError(null);
30972
+ },
30973
+ placeholder: getWalletLabel(w.type, typeLabels),
30974
+ size: "xs",
30975
+ style: { flex: 1 },
30976
+ "aria-label": "Wallet label",
30977
+ maxLength: 50,
30978
+ error: labelError
30979
+ }
30980
+ ),
30981
+ /* @__PURE__ */ jsx(
30982
+ Button,
30983
+ {
30984
+ size: "xs",
30985
+ loading: labelActionLoading,
30986
+ disabled: labelActionLoading,
30987
+ onClick: () => handleLabelSave(w.address),
30988
+ children: "Save"
30989
+ }
30990
+ ),
30991
+ /* @__PURE__ */ jsx(
30992
+ Button,
30993
+ {
30994
+ size: "xs",
30995
+ variant: "subtle",
30996
+ color: colors.neutral,
30997
+ onClick: () => {
30998
+ setRenamingAddress(null);
30999
+ setLabelError(null);
31000
+ },
31001
+ children: "Cancel"
31002
+ }
31003
+ )
31004
+ ] })
31005
+ ] }, walletKey(w));
31006
+ }),
31007
+ setDefaultError && /* @__PURE__ */ jsx(Text, { size: "sm", c: colors.error, children: setDefaultError }),
31008
+ !hasWallets && !showAddForm && /* @__PURE__ */ jsx(
30762
31009
  EmptyState,
30763
31010
  {
30764
31011
  icon: emptyIcon,
@@ -30767,52 +31014,19 @@ function WalletList({
30767
31014
  actionLabel: canAdd ? "Add Wallet" : void 0,
30768
31015
  onAction: canAdd ? () => setShowAddForm(true) : void 0
30769
31016
  }
30770
- );
30771
- }
30772
- return /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
30773
- wallets.map((w) => /* @__PURE__ */ jsxs(
30774
- Group,
31017
+ ),
31018
+ canConnect && /* @__PURE__ */ jsx(
31019
+ Button,
30775
31020
  {
30776
- justify: "space-between",
30777
- p: "xs",
30778
- style: {
30779
- borderRadius: radii.sm,
30780
- border: `1px solid ${neutralColors[200]}`
30781
- },
30782
- children: [
30783
- /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
30784
- /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", children: getTypeLabel(w.type, typeLabels) }),
30785
- w.label && /* @__PURE__ */ jsx(Text, { size: "xs", c: fairmintVars.colorTextSecondary, children: w.label }),
30786
- /* @__PURE__ */ jsx(Text, { size: "sm", ff: "monospace", children: truncateAddress(w.address) })
30787
- ] }),
30788
- /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
30789
- copyIcon && /* @__PURE__ */ jsx(Tooltip, { label: copiedAddress === w.address ? "Copied!" : "Copy address", children: /* @__PURE__ */ jsx(
30790
- ActionIcon,
30791
- {
30792
- variant: "subtle",
30793
- size: "sm",
30794
- onClick: () => copyAddress(w.address),
30795
- "aria-label": "Copy wallet address",
30796
- children: copiedAddress === w.address ? copiedIcon ?? copyIcon : copyIcon
30797
- }
30798
- ) }),
30799
- canRemove && removeIcon && removableTypes.includes(w.type) && /* @__PURE__ */ jsx(Tooltip, { label: "Remove wallet", children: /* @__PURE__ */ jsx(
30800
- ActionIcon,
30801
- {
30802
- variant: "subtle",
30803
- size: "sm",
30804
- color: colors.error,
30805
- onClick: () => setWalletToRemove(w.address),
30806
- disabled: removeLoading,
30807
- "aria-label": "Remove wallet",
30808
- children: removeIcon
30809
- }
30810
- ) })
30811
- ] })
30812
- ]
30813
- },
30814
- w.address
30815
- )),
31021
+ variant: "light",
31022
+ size: "xs",
31023
+ leftSection: connectIcon,
31024
+ onClick: onConnect,
31025
+ loading: connectLoading,
31026
+ mt: "xs",
31027
+ children: "Connect Wallet"
31028
+ }
31029
+ ),
30816
31030
  canAdd && (showAddForm ? /* @__PURE__ */ jsxs(Group, { gap: "xs", mt: "xs", children: [
30817
31031
  /* @__PURE__ */ jsx(
30818
31032
  TextInput,
@@ -30843,7 +31057,7 @@ function WalletList({
30843
31057
  children: "Cancel"
30844
31058
  }
30845
31059
  )
30846
- ] }) : /* @__PURE__ */ jsx(
31060
+ ] }) : hasWallets ? /* @__PURE__ */ jsx(
30847
31061
  Button,
30848
31062
  {
30849
31063
  variant: "light",
@@ -30853,7 +31067,8 @@ function WalletList({
30853
31067
  mt: "xs",
30854
31068
  children: "Add Wallet"
30855
31069
  }
30856
- )),
31070
+ ) : null),
31071
+ children,
30857
31072
  /* @__PURE__ */ jsx(
30858
31073
  ConfirmDialog,
30859
31074
  {
@@ -31013,9 +31228,14 @@ export {
31013
31228
  formatPhoneDisplay,
31014
31229
  formatPreciseWithCurrency,
31015
31230
  getAvatarColor,
31231
+ getWalletDisplayName,
31232
+ getWalletLabel,
31016
31233
  isEthereumAddress,
31234
+ isRemovableWalletType,
31235
+ isSystemWalletType,
31017
31236
  neutralColors,
31018
31237
  normalizePhone,
31238
+ normalizeWalletType,
31019
31239
  notify,
31020
31240
  passwordSchema,
31021
31241
  phoneSchema,
@@ -31038,6 +31258,7 @@ export {
31038
31258
  useScrollIntoView,
31039
31259
  useViewportSize,
31040
31260
  validateEthereumAddress,
31261
+ walletKey,
31041
31262
  z,
31042
31263
  zipSchema
31043
31264
  };