@fairmint/ui 0.2.1 → 0.2.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.
@@ -24822,6 +24822,29 @@ function PhoneField({ error: error2, ...rest }) {
24822
24822
  }
24823
24823
  );
24824
24824
  }
24825
+ const US_ZIP_REGEX = /^\d{5}(-\d{4})?$/;
24826
+ const INTL_POSTAL_REGEX = /^[A-Z0-9]+(?:[ -][A-Z0-9]+)*$/i;
24827
+ function createZipSchema(options) {
24828
+ if (options?.international) {
24829
+ return stringType().trim().min(3, "Please enter a valid postal code").max(10, "Please enter a valid postal code").regex(INTL_POSTAL_REGEX, "Please enter a valid postal code");
24830
+ }
24831
+ return stringType().trim().regex(US_ZIP_REGEX, "Please enter a valid ZIP code (e.g. 94105 or 94105-1234)");
24832
+ }
24833
+ const zipSchema = createZipSchema();
24834
+ function ZipField({ error: error2, international = false, ...rest }) {
24835
+ return /* @__PURE__ */ jsx(
24836
+ TextInput,
24837
+ {
24838
+ type: "text",
24839
+ label: rest.label ?? "ZIP / Postal Code",
24840
+ placeholder: rest.placeholder ?? (international ? "SW1A 2AA" : "94105"),
24841
+ autoComplete: "postal-code",
24842
+ inputMode: "text",
24843
+ error: error2,
24844
+ ...rest
24845
+ }
24846
+ );
24847
+ }
24825
24848
  const ANIMATION_MAP = {
24826
24849
  up: "fadeUp",
24827
24850
  down: "fadeDown",
@@ -25515,6 +25538,211 @@ function InvestorProfileCard({
25515
25538
  children
25516
25539
  ] });
25517
25540
  }
25541
+ function truncateAddress(address, chars = 4) {
25542
+ if (address.length <= chars * 2 + 2) return address;
25543
+ return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
25544
+ }
25545
+ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
25546
+ const ETH_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
25547
+ function isEthereumAddress(value) {
25548
+ return ETH_ADDRESS_RE.test(value) && value.toLowerCase() !== ZERO_ADDRESS;
25549
+ }
25550
+ function validateEthereumAddress(value) {
25551
+ if (!ETH_ADDRESS_RE.test(value))
25552
+ return "Must be a valid Ethereum address (0x + 40 hex characters)";
25553
+ if (value.toLowerCase() === ZERO_ADDRESS) return "Cannot use the zero address";
25554
+ return null;
25555
+ }
25556
+ const ETHEREUM_ADDRESS_REGEX = ETH_ADDRESS_RE;
25557
+ const ZERO_ETHEREUM_ADDRESS = ZERO_ADDRESS;
25558
+ const DEFAULT_TYPE_LABELS = {
25559
+ fairmint: "Fairmint Wallet",
25560
+ privy: "Fairmint Wallet",
25561
+ manual: "Manual"
25562
+ };
25563
+ function getTypeLabel(type, custom2) {
25564
+ if (custom2?.[type]) return custom2[type];
25565
+ return DEFAULT_TYPE_LABELS[type] ?? type;
25566
+ }
25567
+ function WalletList({
25568
+ wallets,
25569
+ onAdd,
25570
+ onRemove,
25571
+ readOnly = false,
25572
+ addLoading = false,
25573
+ removeLoading = false,
25574
+ emptyIcon,
25575
+ copyIcon,
25576
+ copiedIcon,
25577
+ removeIcon,
25578
+ addIcon,
25579
+ typeLabels,
25580
+ removableTypes = ["manual"]
25581
+ }) {
25582
+ const [newAddress, setNewAddress] = useState("");
25583
+ const [addressError, setAddressError] = useState(null);
25584
+ const [showAddForm, setShowAddForm] = useState(false);
25585
+ const [walletToRemove, setWalletToRemove] = useState(null);
25586
+ const [copiedAddress, setCopiedAddress] = useState(null);
25587
+ const copyTimeoutRef = useRef(void 0);
25588
+ useEffect(() => () => clearTimeout(copyTimeoutRef.current), []);
25589
+ const copyAddress = async (address) => {
25590
+ try {
25591
+ await navigator.clipboard.writeText(address);
25592
+ clearTimeout(copyTimeoutRef.current);
25593
+ setCopiedAddress(address);
25594
+ copyTimeoutRef.current = setTimeout(() => setCopiedAddress(null), 2e3);
25595
+ } catch {
25596
+ }
25597
+ };
25598
+ const canAdd = !readOnly && !!onAdd;
25599
+ const canRemove = !readOnly && !!onRemove;
25600
+ const handleAdd = async () => {
25601
+ const trimmed = newAddress.trim();
25602
+ const error2 = validateEthereumAddress(trimmed);
25603
+ if (error2) {
25604
+ setAddressError(error2);
25605
+ return;
25606
+ }
25607
+ setAddressError(null);
25608
+ try {
25609
+ await onAdd?.(trimmed);
25610
+ setNewAddress("");
25611
+ setShowAddForm(false);
25612
+ } catch {
25613
+ setAddressError("Failed to add wallet");
25614
+ }
25615
+ };
25616
+ const [removeError, setRemoveError] = useState(null);
25617
+ const handleRemove = async () => {
25618
+ if (!walletToRemove) return;
25619
+ setRemoveError(null);
25620
+ try {
25621
+ await onRemove?.(walletToRemove);
25622
+ setWalletToRemove(null);
25623
+ } catch {
25624
+ setRemoveError("Failed to remove wallet. Please try again.");
25625
+ }
25626
+ };
25627
+ if (wallets.length === 0 && !showAddForm) {
25628
+ return /* @__PURE__ */ jsx(
25629
+ EmptyState,
25630
+ {
25631
+ icon: emptyIcon,
25632
+ title: "No wallets connected",
25633
+ description: "Add a wallet address to receive equity on-chain.",
25634
+ actionLabel: canAdd ? "Add Wallet" : void 0,
25635
+ onAction: canAdd ? () => setShowAddForm(true) : void 0
25636
+ }
25637
+ );
25638
+ }
25639
+ return /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
25640
+ wallets.map((w) => /* @__PURE__ */ jsxs(
25641
+ Group,
25642
+ {
25643
+ justify: "space-between",
25644
+ p: "xs",
25645
+ style: {
25646
+ borderRadius: "var(--mantine-radius-sm)",
25647
+ border: "1px solid var(--mantine-color-default-border)"
25648
+ },
25649
+ children: [
25650
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", children: [
25651
+ /* @__PURE__ */ jsx(Badge, { size: "xs", variant: "light", children: getTypeLabel(w.type, typeLabels) }),
25652
+ w.label && /* @__PURE__ */ jsx(Text, { size: "xs", c: `${colors.neutral}.5`, children: w.label }),
25653
+ /* @__PURE__ */ jsx(Text, { size: "sm", ff: "monospace", children: truncateAddress(w.address) })
25654
+ ] }),
25655
+ /* @__PURE__ */ jsxs(Group, { gap: 4, children: [
25656
+ copyIcon && /* @__PURE__ */ jsx(Tooltip, { label: copiedAddress === w.address ? "Copied!" : "Copy address", children: /* @__PURE__ */ jsx(
25657
+ ActionIcon,
25658
+ {
25659
+ variant: "subtle",
25660
+ size: "sm",
25661
+ onClick: () => copyAddress(w.address),
25662
+ "aria-label": "Copy wallet address",
25663
+ children: copiedAddress === w.address ? copiedIcon ?? copyIcon : copyIcon
25664
+ }
25665
+ ) }),
25666
+ canRemove && removeIcon && removableTypes.includes(w.type) && /* @__PURE__ */ jsx(Tooltip, { label: "Remove wallet", children: /* @__PURE__ */ jsx(
25667
+ ActionIcon,
25668
+ {
25669
+ variant: "subtle",
25670
+ size: "sm",
25671
+ color: colors.error,
25672
+ onClick: () => setWalletToRemove(w.address),
25673
+ disabled: removeLoading,
25674
+ "aria-label": "Remove wallet",
25675
+ children: removeIcon
25676
+ }
25677
+ ) })
25678
+ ] })
25679
+ ]
25680
+ },
25681
+ w.address
25682
+ )),
25683
+ canAdd && (showAddForm ? /* @__PURE__ */ jsxs(Group, { gap: "xs", mt: "xs", children: [
25684
+ /* @__PURE__ */ jsx(
25685
+ TextInput,
25686
+ {
25687
+ placeholder: "0x...",
25688
+ "aria-label": "Wallet address",
25689
+ value: newAddress,
25690
+ onChange: (e) => {
25691
+ setNewAddress(e.currentTarget.value);
25692
+ setAddressError(null);
25693
+ },
25694
+ error: addressError,
25695
+ style: { flex: 1 }
25696
+ }
25697
+ ),
25698
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: handleAdd, loading: addLoading, children: "Add" }),
25699
+ /* @__PURE__ */ jsx(
25700
+ Button,
25701
+ {
25702
+ size: "sm",
25703
+ variant: "subtle",
25704
+ color: colors.neutral,
25705
+ onClick: () => {
25706
+ setShowAddForm(false);
25707
+ setNewAddress("");
25708
+ setAddressError(null);
25709
+ },
25710
+ children: "Cancel"
25711
+ }
25712
+ )
25713
+ ] }) : /* @__PURE__ */ jsx(
25714
+ Button,
25715
+ {
25716
+ variant: "light",
25717
+ size: "xs",
25718
+ leftSection: addIcon,
25719
+ onClick: () => setShowAddForm(true),
25720
+ mt: "xs",
25721
+ children: "Add Wallet"
25722
+ }
25723
+ )),
25724
+ /* @__PURE__ */ jsx(
25725
+ ConfirmDialog,
25726
+ {
25727
+ title: "Remove Wallet",
25728
+ message: /* @__PURE__ */ jsxs(Stack, { gap: "sm", children: [
25729
+ /* @__PURE__ */ jsx(Text, { size: "sm", children: "Are you sure you want to remove this wallet address? This action cannot be undone." }),
25730
+ walletToRemove && /* @__PURE__ */ jsx(Text, { size: "sm", ff: "monospace", c: `${colors.neutral}.5`, children: walletToRemove }),
25731
+ removeError && /* @__PURE__ */ jsx(Text, { size: "sm", c: colors.error, children: removeError })
25732
+ ] }),
25733
+ opened: walletToRemove !== null,
25734
+ confirmLabel: "Remove",
25735
+ confirmColor: colors.error,
25736
+ onConfirm: handleRemove,
25737
+ onCancel: () => {
25738
+ setWalletToRemove(null);
25739
+ setRemoveError(null);
25740
+ },
25741
+ loading: removeLoading
25742
+ }
25743
+ )
25744
+ ] });
25745
+ }
25518
25746
  function useDebouncedCallback(fn, delay) {
25519
25747
  const timer = useRef(null);
25520
25748
  const fnRef = useRef(fn);
@@ -25546,10 +25774,6 @@ const notify = {
25546
25774
  warning: ({ title, message, autoClose = 5e3 }) => notifications.show({ title, message, color: colors.warning, autoClose }),
25547
25775
  info: ({ title, message, autoClose = 4e3 }) => notifications.show({ title, message, color: colors.info, autoClose })
25548
25776
  };
25549
- function truncateAddress(address, chars = 4) {
25550
- if (address.length <= chars * 2 + 2) return address;
25551
- return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
25552
- }
25553
25777
  export {
25554
25778
  AVATAR_PALETTE,
25555
25779
  ActionIcon,
@@ -25574,6 +25798,7 @@ export {
25574
25798
  Divider,
25575
25799
  DocumentRow,
25576
25800
  Drawer,
25801
+ ETHEREUM_ADDRESS_REGEX,
25577
25802
  EmailField,
25578
25803
  EmptyState,
25579
25804
  EquityGrantCard,
@@ -25617,8 +25842,12 @@ export {
25617
25842
  Title,
25618
25843
  Tooltip,
25619
25844
  UserMenu,
25845
+ WalletList,
25846
+ ZERO_ETHEREUM_ADDRESS,
25847
+ ZipField,
25620
25848
  brandColors,
25621
25849
  colors,
25850
+ createZipSchema,
25622
25851
  emailSchema,
25623
25852
  formatCurrency,
25624
25853
  formatCurrencyPrecise,
@@ -25627,6 +25856,7 @@ export {
25627
25856
  formatPercent,
25628
25857
  formatPreciseWithCurrency,
25629
25858
  getAvatarColor,
25859
+ isEthereumAddress,
25630
25860
  neutralColors,
25631
25861
  notify,
25632
25862
  passwordSchema,
@@ -25647,6 +25877,8 @@ export {
25647
25877
  useMediaQuery,
25648
25878
  useScrollIntoView,
25649
25879
  useViewportSize,
25650
- z
25880
+ validateEthereumAddress,
25881
+ z,
25882
+ zipSchema
25651
25883
  };
25652
25884
  //# sourceMappingURL=fairmint-ui.js.map