@fairmint/ui 0.2.1 → 0.2.2

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.
@@ -0,0 +1,45 @@
1
+ import { ReactNode } from 'react';
2
+ export interface Wallet {
3
+ /** Ethereum wallet address. */
4
+ address: string;
5
+ /** Wallet type identifier (e.g. "fairmint", "privy", "manual"). */
6
+ type: string;
7
+ /** Optional display label. */
8
+ label?: string | null;
9
+ }
10
+ export interface WalletListProps {
11
+ /** Wallets to display. */
12
+ wallets: Wallet[];
13
+ /** Called when user adds a new wallet address. */
14
+ onAdd?: (address: string) => void | Promise<void>;
15
+ /** Called when user confirms removal of a wallet address. */
16
+ onRemove?: (address: string) => void | Promise<void>;
17
+ /** Whether add/remove actions are disabled (e.g. admin viewing another user). */
18
+ readOnly?: boolean;
19
+ /** Whether an add operation is in progress. */
20
+ addLoading?: boolean;
21
+ /** Whether a remove operation is in progress. */
22
+ removeLoading?: boolean;
23
+ /** Icon for the empty state. Passed as ReactNode. */
24
+ emptyIcon?: ReactNode;
25
+ /** Icon for the copy button. Passed as ReactNode. */
26
+ copyIcon?: ReactNode;
27
+ /** Icon for the "copied" state. Passed as ReactNode. */
28
+ copiedIcon?: ReactNode;
29
+ /** Icon for the remove button. Passed as ReactNode. */
30
+ removeIcon?: ReactNode;
31
+ /** Icon for the add button. Passed as ReactNode. */
32
+ addIcon?: ReactNode;
33
+ /**
34
+ * Map a wallet type string to a display label.
35
+ * @default Built-in: "fairmint"|"privy" → "Fairmint Wallet", "manual" → "Manual", else as-is
36
+ */
37
+ typeLabels?: Record<string, string>;
38
+ /**
39
+ * Which wallet types can be removed.
40
+ * @default ["manual"]
41
+ */
42
+ removableTypes?: string[];
43
+ }
44
+ export declare function WalletList({ wallets, onAdd, onRemove, readOnly, addLoading, removeLoading, emptyIcon, copyIcon, copiedIcon, removeIcon, addIcon, typeLabels, removableTypes, }: WalletListProps): import("react/jsx-runtime").JSX.Element;
45
+ //# sourceMappingURL=WalletList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WalletList.d.ts","sourceRoot":"","sources":["../../../../src/components/specific/WalletList/WalletList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAA+B,MAAM,OAAO,CAAC;AAYpE,MAAM,WAAW,MAAM;IACrB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,0BAA0B;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kDAAkD;IAClD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qDAAqD;IACrD,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,wDAAwD;IACxD,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,uDAAuD;IACvD,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,oDAAoD;IACpD,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAaD,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAgB,EAChB,UAAkB,EAClB,aAAqB,EACrB,SAAS,EACT,QAAQ,EACR,UAAU,EACV,UAAU,EACV,OAAO,EACP,UAAU,EACV,cAA2B,GAC5B,EAAE,eAAe,2CAmMjB"}
@@ -25533,6 +25533,211 @@ function InvestorProfileCard({
25533
25533
  children
25534
25534
  ] });
25535
25535
  }
25536
+ function truncateAddress(address, chars = 4) {
25537
+ if (address.length <= chars * 2 + 2) return address;
25538
+ return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
25539
+ }
25540
+ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
25541
+ const ETH_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
25542
+ function isEthereumAddress(value) {
25543
+ return ETH_ADDRESS_RE.test(value) && value.toLowerCase() !== ZERO_ADDRESS;
25544
+ }
25545
+ function validateEthereumAddress(value) {
25546
+ if (!ETH_ADDRESS_RE.test(value))
25547
+ return "Must be a valid Ethereum address (0x + 40 hex characters)";
25548
+ if (value.toLowerCase() === ZERO_ADDRESS) return "Cannot use the zero address";
25549
+ return null;
25550
+ }
25551
+ const ETHEREUM_ADDRESS_REGEX = ETH_ADDRESS_RE;
25552
+ const ZERO_ETHEREUM_ADDRESS = ZERO_ADDRESS;
25553
+ const DEFAULT_TYPE_LABELS = {
25554
+ fairmint: "Fairmint Wallet",
25555
+ privy: "Fairmint Wallet",
25556
+ manual: "Manual"
25557
+ };
25558
+ function getTypeLabel(type, custom2) {
25559
+ if (custom2?.[type]) return custom2[type];
25560
+ return DEFAULT_TYPE_LABELS[type] ?? type;
25561
+ }
25562
+ function WalletList({
25563
+ wallets,
25564
+ onAdd,
25565
+ onRemove,
25566
+ readOnly = false,
25567
+ addLoading = false,
25568
+ removeLoading = false,
25569
+ emptyIcon,
25570
+ copyIcon,
25571
+ copiedIcon,
25572
+ removeIcon,
25573
+ addIcon,
25574
+ typeLabels,
25575
+ removableTypes = ["manual"]
25576
+ }) {
25577
+ const [newAddress, setNewAddress] = React.useState("");
25578
+ const [addressError, setAddressError] = React.useState(null);
25579
+ const [showAddForm, setShowAddForm] = React.useState(false);
25580
+ const [walletToRemove, setWalletToRemove] = React.useState(null);
25581
+ const [copiedAddress, setCopiedAddress] = React.useState(null);
25582
+ const copyTimeoutRef = React.useRef(void 0);
25583
+ React.useEffect(() => () => clearTimeout(copyTimeoutRef.current), []);
25584
+ const copyAddress = async (address) => {
25585
+ try {
25586
+ await navigator.clipboard.writeText(address);
25587
+ clearTimeout(copyTimeoutRef.current);
25588
+ setCopiedAddress(address);
25589
+ copyTimeoutRef.current = setTimeout(() => setCopiedAddress(null), 2e3);
25590
+ } catch {
25591
+ }
25592
+ };
25593
+ const canAdd = !readOnly && !!onAdd;
25594
+ const canRemove = !readOnly && !!onRemove;
25595
+ const handleAdd = async () => {
25596
+ const trimmed = newAddress.trim();
25597
+ const error2 = validateEthereumAddress(trimmed);
25598
+ if (error2) {
25599
+ setAddressError(error2);
25600
+ return;
25601
+ }
25602
+ setAddressError(null);
25603
+ try {
25604
+ await onAdd?.(trimmed);
25605
+ setNewAddress("");
25606
+ setShowAddForm(false);
25607
+ } catch {
25608
+ setAddressError("Failed to add wallet");
25609
+ }
25610
+ };
25611
+ const [removeError, setRemoveError] = React.useState(null);
25612
+ const handleRemove = async () => {
25613
+ if (!walletToRemove) return;
25614
+ setRemoveError(null);
25615
+ try {
25616
+ await onRemove?.(walletToRemove);
25617
+ setWalletToRemove(null);
25618
+ } catch {
25619
+ setRemoveError("Failed to remove wallet. Please try again.");
25620
+ }
25621
+ };
25622
+ if (wallets.length === 0 && !showAddForm) {
25623
+ return /* @__PURE__ */ jsxRuntime.jsx(
25624
+ EmptyState,
25625
+ {
25626
+ icon: emptyIcon,
25627
+ title: "No wallets connected",
25628
+ description: "Add a wallet address to receive equity on-chain.",
25629
+ actionLabel: canAdd ? "Add Wallet" : void 0,
25630
+ onAction: canAdd ? () => setShowAddForm(true) : void 0
25631
+ }
25632
+ );
25633
+ }
25634
+ return /* @__PURE__ */ jsxRuntime.jsxs(Stack, { gap: "sm", children: [
25635
+ wallets.map((w) => /* @__PURE__ */ jsxRuntime.jsxs(
25636
+ Group,
25637
+ {
25638
+ justify: "space-between",
25639
+ p: "xs",
25640
+ style: {
25641
+ borderRadius: "var(--mantine-radius-sm)",
25642
+ border: "1px solid var(--mantine-color-default-border)"
25643
+ },
25644
+ children: [
25645
+ /* @__PURE__ */ jsxRuntime.jsxs(Group, { gap: "xs", children: [
25646
+ /* @__PURE__ */ jsxRuntime.jsx(Badge, { size: "xs", variant: "light", children: getTypeLabel(w.type, typeLabels) }),
25647
+ w.label && /* @__PURE__ */ jsxRuntime.jsx(Text, { size: "xs", c: `${colors.neutral}.5`, children: w.label }),
25648
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: "sm", ff: "monospace", children: truncateAddress(w.address) })
25649
+ ] }),
25650
+ /* @__PURE__ */ jsxRuntime.jsxs(Group, { gap: 4, children: [
25651
+ copyIcon && /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { label: copiedAddress === w.address ? "Copied!" : "Copy address", children: /* @__PURE__ */ jsxRuntime.jsx(
25652
+ ActionIcon,
25653
+ {
25654
+ variant: "subtle",
25655
+ size: "sm",
25656
+ onClick: () => copyAddress(w.address),
25657
+ "aria-label": "Copy wallet address",
25658
+ children: copiedAddress === w.address ? copiedIcon ?? copyIcon : copyIcon
25659
+ }
25660
+ ) }),
25661
+ canRemove && removeIcon && removableTypes.includes(w.type) && /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { label: "Remove wallet", children: /* @__PURE__ */ jsxRuntime.jsx(
25662
+ ActionIcon,
25663
+ {
25664
+ variant: "subtle",
25665
+ size: "sm",
25666
+ color: colors.error,
25667
+ onClick: () => setWalletToRemove(w.address),
25668
+ disabled: removeLoading,
25669
+ "aria-label": "Remove wallet",
25670
+ children: removeIcon
25671
+ }
25672
+ ) })
25673
+ ] })
25674
+ ]
25675
+ },
25676
+ w.address
25677
+ )),
25678
+ canAdd && (showAddForm ? /* @__PURE__ */ jsxRuntime.jsxs(Group, { gap: "xs", mt: "xs", children: [
25679
+ /* @__PURE__ */ jsxRuntime.jsx(
25680
+ TextInput,
25681
+ {
25682
+ placeholder: "0x...",
25683
+ "aria-label": "Wallet address",
25684
+ value: newAddress,
25685
+ onChange: (e) => {
25686
+ setNewAddress(e.currentTarget.value);
25687
+ setAddressError(null);
25688
+ },
25689
+ error: addressError,
25690
+ style: { flex: 1 }
25691
+ }
25692
+ ),
25693
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { size: "sm", onClick: handleAdd, loading: addLoading, children: "Add" }),
25694
+ /* @__PURE__ */ jsxRuntime.jsx(
25695
+ Button,
25696
+ {
25697
+ size: "sm",
25698
+ variant: "subtle",
25699
+ color: colors.neutral,
25700
+ onClick: () => {
25701
+ setShowAddForm(false);
25702
+ setNewAddress("");
25703
+ setAddressError(null);
25704
+ },
25705
+ children: "Cancel"
25706
+ }
25707
+ )
25708
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
25709
+ Button,
25710
+ {
25711
+ variant: "light",
25712
+ size: "xs",
25713
+ leftSection: addIcon,
25714
+ onClick: () => setShowAddForm(true),
25715
+ mt: "xs",
25716
+ children: "Add Wallet"
25717
+ }
25718
+ )),
25719
+ /* @__PURE__ */ jsxRuntime.jsx(
25720
+ ConfirmDialog,
25721
+ {
25722
+ title: "Remove Wallet",
25723
+ message: /* @__PURE__ */ jsxRuntime.jsxs(Stack, { gap: "sm", children: [
25724
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { size: "sm", children: "Are you sure you want to remove this wallet address? This action cannot be undone." }),
25725
+ walletToRemove && /* @__PURE__ */ jsxRuntime.jsx(Text, { size: "sm", ff: "monospace", c: `${colors.neutral}.5`, children: walletToRemove }),
25726
+ removeError && /* @__PURE__ */ jsxRuntime.jsx(Text, { size: "sm", c: colors.error, children: removeError })
25727
+ ] }),
25728
+ opened: walletToRemove !== null,
25729
+ confirmLabel: "Remove",
25730
+ confirmColor: colors.error,
25731
+ onConfirm: handleRemove,
25732
+ onCancel: () => {
25733
+ setWalletToRemove(null);
25734
+ setRemoveError(null);
25735
+ },
25736
+ loading: removeLoading
25737
+ }
25738
+ )
25739
+ ] });
25740
+ }
25536
25741
  function useDebouncedCallback(fn, delay) {
25537
25742
  const timer = React.useRef(null);
25538
25743
  const fnRef = React.useRef(fn);
@@ -25564,10 +25769,6 @@ const notify = {
25564
25769
  warning: ({ title, message, autoClose = 5e3 }) => notifications.show({ title, message, color: colors.warning, autoClose }),
25565
25770
  info: ({ title, message, autoClose = 4e3 }) => notifications.show({ title, message, color: colors.info, autoClose })
25566
25771
  };
25567
- function truncateAddress(address, chars = 4) {
25568
- if (address.length <= chars * 2 + 2) return address;
25569
- return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
25570
- }
25571
25772
  exports.AVATAR_PALETTE = AVATAR_PALETTE;
25572
25773
  exports.ActionIcon = ActionIcon;
25573
25774
  exports.Alert = Alert;
@@ -25591,6 +25792,7 @@ exports.DetailDrawer = DetailDrawer;
25591
25792
  exports.Divider = Divider;
25592
25793
  exports.DocumentRow = DocumentRow;
25593
25794
  exports.Drawer = Drawer;
25795
+ exports.ETHEREUM_ADDRESS_REGEX = ETHEREUM_ADDRESS_REGEX;
25594
25796
  exports.EmailField = EmailField;
25595
25797
  exports.EmptyState = EmptyState;
25596
25798
  exports.EquityGrantCard = EquityGrantCard;
@@ -25634,6 +25836,8 @@ exports.TextInput = TextInput;
25634
25836
  exports.Title = Title;
25635
25837
  exports.Tooltip = Tooltip;
25636
25838
  exports.UserMenu = UserMenu;
25839
+ exports.WalletList = WalletList;
25840
+ exports.ZERO_ETHEREUM_ADDRESS = ZERO_ETHEREUM_ADDRESS;
25637
25841
  exports.brandColors = brandColors;
25638
25842
  exports.colors = colors;
25639
25843
  exports.emailSchema = emailSchema;
@@ -25644,6 +25848,7 @@ exports.formatNumber = formatNumber;
25644
25848
  exports.formatPercent = formatPercent;
25645
25849
  exports.formatPreciseWithCurrency = formatPreciseWithCurrency;
25646
25850
  exports.getAvatarColor = getAvatarColor;
25851
+ exports.isEthereumAddress = isEthereumAddress;
25647
25852
  exports.neutralColors = neutralColors;
25648
25853
  exports.notify = notify;
25649
25854
  exports.passwordSchema = passwordSchema;
@@ -25664,5 +25869,6 @@ exports.useIsMobile = useIsMobile;
25664
25869
  exports.useMediaQuery = useMediaQuery;
25665
25870
  exports.useScrollIntoView = useScrollIntoView;
25666
25871
  exports.useViewportSize = useViewportSize;
25872
+ exports.validateEthereumAddress = validateEthereumAddress;
25667
25873
  exports.z = z;
25668
25874
  //# sourceMappingURL=fairmint-ui.cjs.map