@pollar/react 0.10.1-rc.0 → 0.10.1-rc.1

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.mjs CHANGED
@@ -1033,7 +1033,7 @@ var PollarModalFooter = () => {
1033
1033
  /* @__PURE__ */ jsx("span", { className: "pollar-footer-name", children: "Pollar" }),
1034
1034
  /* @__PURE__ */ jsxs("span", { className: "pollar-footer-version", children: [
1035
1035
  "v",
1036
- "0.10.1-rc.0"
1036
+ "0.10.1-rc.1"
1037
1037
  ] })
1038
1038
  ] })
1039
1039
  ] });
@@ -2457,14 +2457,12 @@ function RouteDisplay({ quote, onSelect }) {
2457
2457
  );
2458
2458
  }
2459
2459
  var LOADING_STEPS = ["Detecting your country\u2026", "Consulting providers\u2026", "Route found!"];
2460
- var COUNTRY_CURRENCIES = {
2461
- MX: "MXN",
2462
- BR: "BRL",
2463
- CO: "COP",
2464
- CL: "CLP",
2465
- PE: "PEN",
2466
- AR: "ARS"
2467
- };
2460
+ function flagEmoji(code) {
2461
+ const cc = code.trim().toUpperCase();
2462
+ if (!/^[A-Z]{2}$/.test(cc)) return "";
2463
+ const base = 127462;
2464
+ return String.fromCodePoint(base + (cc.charCodeAt(0) - 65), base + (cc.charCodeAt(1) - 65));
2465
+ }
2468
2466
  var STATUS_LABEL = {
2469
2467
  pending: "Pending",
2470
2468
  processing: "Processing",
@@ -2479,6 +2477,9 @@ function RampWidgetTemplate({
2479
2477
  amount,
2480
2478
  currency,
2481
2479
  country,
2480
+ countries,
2481
+ countriesLoading,
2482
+ refreshing,
2482
2483
  quotes,
2483
2484
  isLoading,
2484
2485
  provider,
@@ -2497,6 +2498,7 @@ function RampWidgetTemplate({
2497
2498
  onOpenKyc,
2498
2499
  onCompleteWithdraw,
2499
2500
  onRetry,
2501
+ onRefresh,
2500
2502
  onClose
2501
2503
  }) {
2502
2504
  const isDark = theme === "dark";
@@ -2535,9 +2537,35 @@ function RampWidgetTemplate({
2535
2537
  error: "Please try again"
2536
2538
  };
2537
2539
  return /* @__PURE__ */ jsxs("div", { className: "pollar-modal-card pollar-ramp-modal", style: cssVars, onClick: (e) => e.stopPropagation(), children: [
2538
- /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-header", children: [
2539
- /* @__PURE__ */ jsx("h2", { className: "pollar-ramp-title", children: stepTitle[step] }),
2540
- /* @__PURE__ */ jsx("p", { className: "pollar-ramp-subtitle", children: stepSubtitle[step] })
2540
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header", children: [
2541
+ /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-header-text", children: [
2542
+ /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: stepTitle[step] }),
2543
+ /* @__PURE__ */ jsx("p", { className: "pollar-ramp-subtitle", children: stepSubtitle[step] })
2544
+ ] }),
2545
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header-actions", children: [
2546
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: onRefresh, disabled: refreshing, "aria-label": "Refresh", children: /* @__PURE__ */ jsx(
2547
+ "svg",
2548
+ {
2549
+ className: refreshing ? "pollar-modal-refresh-icon spinning" : "pollar-modal-refresh-icon",
2550
+ width: "16",
2551
+ height: "16",
2552
+ viewBox: "0 0 16 16",
2553
+ fill: "none",
2554
+ "aria-hidden": true,
2555
+ children: /* @__PURE__ */ jsx(
2556
+ "path",
2557
+ {
2558
+ d: "M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2v3h-3",
2559
+ stroke: "currentColor",
2560
+ strokeWidth: "1.6",
2561
+ strokeLinecap: "round",
2562
+ strokeLinejoin: "round"
2563
+ }
2564
+ )
2565
+ }
2566
+ ) }),
2567
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M2 2l12 12M14 2L2 14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) })
2568
+ ] })
2541
2569
  ] }),
2542
2570
  step === "input" && /* @__PURE__ */ jsxs(Fragment, { children: [
2543
2571
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-tabs", children: [
@@ -2594,30 +2622,25 @@ function RampWidgetTemplate({
2594
2622
  ] }),
2595
2623
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
2596
2624
  /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", children: "Country" }),
2597
- /* @__PURE__ */ jsxs(
2598
- "select",
2599
- {
2600
- className: "pollar-ramp-input",
2601
- value: country,
2602
- onChange: (e) => {
2603
- const c = e.target.value;
2604
- onCountryChange(c);
2605
- if (COUNTRY_CURRENCIES[c]) onCurrencyChange(COUNTRY_CURRENCIES[c]);
2606
- },
2607
- children: [
2608
- /* @__PURE__ */ jsx("option", { value: "MX", children: "\u{1F1F2}\u{1F1FD} Mexico" }),
2609
- /* @__PURE__ */ jsx("option", { value: "BR", children: "\u{1F1E7}\u{1F1F7} Brazil" }),
2610
- /* @__PURE__ */ jsx("option", { value: "CO", children: "\u{1F1E8}\u{1F1F4} Colombia" }),
2611
- /* @__PURE__ */ jsx("option", { value: "CL", children: "\u{1F1E8}\u{1F1F1} Chile" }),
2612
- /* @__PURE__ */ jsx("option", { value: "PE", children: "\u{1F1F5}\u{1F1EA} Peru" }),
2613
- /* @__PURE__ */ jsx("option", { value: "AR", children: "\u{1F1E6}\u{1F1F7} Argentina" })
2614
- ]
2615
- }
2616
- )
2625
+ countriesLoading ? /* @__PURE__ */ jsx("div", { className: "pollar-ramp-input pollar-ramp-input-loading", children: "Loading countries\u2026" }) : countries.length === 0 ? /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: "No ramp providers available on this network yet." }) : /* @__PURE__ */ jsx("select", { className: "pollar-ramp-input", value: country, onChange: (e) => onCountryChange(e.target.value), children: countries.map((c) => /* @__PURE__ */ jsxs("option", { value: c.code, children: [
2626
+ flagEmoji(c.code),
2627
+ " ",
2628
+ c.code,
2629
+ c.currency ? ` \u2014 ${c.currency}` : ""
2630
+ ] }, c.code)) })
2617
2631
  ] }),
2618
2632
  /* @__PURE__ */ jsxs("div", { className: "pollar-modal-actions", children: [
2619
2633
  /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onClose, children: "Cancel" }),
2620
- /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-primary", disabled: !amount || isLoading, onClick: onFindRoute, children: "Find best route" })
2634
+ /* @__PURE__ */ jsx(
2635
+ "button",
2636
+ {
2637
+ type: "button",
2638
+ className: "pollar-btn-primary",
2639
+ disabled: !amount || isLoading || countriesLoading || countries.length === 0,
2640
+ onClick: onFindRoute,
2641
+ children: "Find best route"
2642
+ }
2643
+ )
2621
2644
  ] })
2622
2645
  ] }),
2623
2646
  step === "loading_quote" && /* @__PURE__ */ jsx("div", { className: "pollar-ramp-loading", children: LOADING_STEPS.map((text, i) => /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-loading-row", children: [
@@ -2635,13 +2658,7 @@ function RampWidgetTemplate({
2635
2658
  ] }),
2636
2659
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
2637
2660
  /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: "Status" }),
2638
- /* @__PURE__ */ jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsx(
2639
- "code",
2640
- {
2641
- style: { color: txStatus === "completed" ? "var(--pollar-success-text)" : void 0 },
2642
- children: txStatus ? STATUS_LABEL[txStatus] : "Processing"
2643
- }
2644
- ) })
2661
+ /* @__PURE__ */ jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsx("code", { style: { color: txStatus === "completed" ? "var(--pollar-success-text)" : void 0 }, children: txStatus ? STATUS_LABEL[txStatus] : "Processing" }) })
2645
2662
  ] }),
2646
2663
  stellarTxHash && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
2647
2664
  /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: "Stellar tx" }),
@@ -2677,8 +2694,11 @@ function RampWidget({ onClose }) {
2677
2694
  const [step, setStep] = useState("input");
2678
2695
  const [direction, setDirection] = useState("onramp");
2679
2696
  const [amount, setAmount] = useState("");
2680
- const [currency, setCurrency] = useState("ARS");
2681
- const [country, setCountry] = useState("AR");
2697
+ const [currency, setCurrency] = useState("");
2698
+ const [country, setCountry] = useState("");
2699
+ const [countries, setCountries] = useState([]);
2700
+ const [countriesLoading, setCountriesLoading] = useState(true);
2701
+ const [refreshing, setRefreshing] = useState(false);
2682
2702
  const [quotes, setQuotes] = useState([]);
2683
2703
  const [isLoading, setIsLoading] = useState(false);
2684
2704
  const [txId, setTxId] = useState(null);
@@ -2710,6 +2730,50 @@ function RampWidget({ onClose }) {
2710
2730
  clearInterval(id);
2711
2731
  };
2712
2732
  }, [step, txId, txStatus, client]);
2733
+ async function loadCountries(resetSelection) {
2734
+ setCountriesLoading(true);
2735
+ try {
2736
+ const { countries: list } = await client.getRampCountries();
2737
+ setCountries(list);
2738
+ const first = list[0];
2739
+ const stillValid = list.some((c) => c.code === country);
2740
+ if (first && (resetSelection || !stillValid)) {
2741
+ setCountry(first.code);
2742
+ if (first.currency) setCurrency(first.currency);
2743
+ }
2744
+ } catch {
2745
+ setCountries([]);
2746
+ } finally {
2747
+ setCountriesLoading(false);
2748
+ }
2749
+ }
2750
+ useEffect(() => {
2751
+ loadCountries(true);
2752
+ }, []);
2753
+ function handleCountryChange(code) {
2754
+ setCountry(code);
2755
+ const match = countries.find((c) => c.code === code);
2756
+ if (match?.currency) setCurrency(match.currency);
2757
+ }
2758
+ async function handleRefresh() {
2759
+ if (refreshing) return;
2760
+ setRefreshing(true);
2761
+ try {
2762
+ await loadCountries(false);
2763
+ if (step === "select_route") {
2764
+ const result = await client.getRampsQuote({ country, amount: Number(amount), currency, direction });
2765
+ setQuotes(result.quotes ?? []);
2766
+ } else if (step === "status" && txId) {
2767
+ const tx = await client.getRampTransaction(txId);
2768
+ setTxStatus(tx.status);
2769
+ if (tx.stellarTxHash) setStellarTxHash(tx.stellarTxHash);
2770
+ if (tx.kycUrl) setKycUrl(tx.kycUrl);
2771
+ }
2772
+ } catch {
2773
+ } finally {
2774
+ setRefreshing(false);
2775
+ }
2776
+ }
2713
2777
  function resetToInput() {
2714
2778
  setStep("input");
2715
2779
  setQuotes([]);
@@ -2815,6 +2879,9 @@ function RampWidget({ onClose }) {
2815
2879
  amount,
2816
2880
  currency,
2817
2881
  country,
2882
+ countries,
2883
+ countriesLoading,
2884
+ refreshing,
2818
2885
  quotes,
2819
2886
  isLoading,
2820
2887
  provider,
@@ -2827,12 +2894,13 @@ function RampWidget({ onClose }) {
2827
2894
  onDirectionChange: setDirection,
2828
2895
  onAmountChange: setAmount,
2829
2896
  onCurrencyChange: setCurrency,
2830
- onCountryChange: setCountry,
2897
+ onCountryChange: handleCountryChange,
2831
2898
  onFindRoute: handleFindRoute,
2832
2899
  onSelectQuote: handleSelectQuote,
2833
2900
  onOpenKyc: handleOpenKyc,
2834
2901
  onCompleteWithdraw: handleCompleteWithdraw,
2835
2902
  onRetry: resetToInput,
2903
+ onRefresh: handleRefresh,
2836
2904
  onClose
2837
2905
  }
2838
2906
  ) });
@@ -3542,6 +3610,7 @@ function SwapModalTemplate({
3542
3610
  onRefresh,
3543
3611
  onSelectSell,
3544
3612
  onSelectBuy,
3613
+ onAddCustomToken,
3545
3614
  onAmountChange,
3546
3615
  onProviderChange,
3547
3616
  onSwap,
@@ -3551,6 +3620,21 @@ function SwapModalTemplate({
3551
3620
  onDone
3552
3621
  }) {
3553
3622
  const isDark = theme === "dark";
3623
+ const [customOpen, setCustomOpen] = useState(false);
3624
+ const [customCode, setCustomCode] = useState("");
3625
+ const [customIssuer, setCustomIssuer] = useState("");
3626
+ const [customError, setCustomError] = useState("");
3627
+ const submitCustomToken = () => {
3628
+ const err = onAddCustomToken(customCode, customIssuer);
3629
+ if (err) {
3630
+ setCustomError(err);
3631
+ return;
3632
+ }
3633
+ setCustomCode("");
3634
+ setCustomIssuer("");
3635
+ setCustomError("");
3636
+ setCustomOpen(false);
3637
+ };
3554
3638
  const cssVars = {
3555
3639
  "--pollar-accent": accentColor,
3556
3640
  "--pollar-bg": isDark ? "#1a1a1a" : "#ffffff",
@@ -3669,7 +3753,45 @@ function SwapModalTemplate({
3669
3753
  ] }, assetOptionKey(o)))
3670
3754
  ]
3671
3755
  }
3672
- )
3756
+ ),
3757
+ !isLoadingData && (customOpen ? /* @__PURE__ */ jsxs("div", { className: "pollar-swap-custom", children: [
3758
+ /* @__PURE__ */ jsx(
3759
+ "input",
3760
+ {
3761
+ className: "pollar-input",
3762
+ type: "text",
3763
+ placeholder: "Code (e.g. USDC)",
3764
+ value: customCode,
3765
+ onChange: (e) => setCustomCode(e.target.value)
3766
+ }
3767
+ ),
3768
+ /* @__PURE__ */ jsx(
3769
+ "input",
3770
+ {
3771
+ className: "pollar-input",
3772
+ type: "text",
3773
+ placeholder: "Issuer (G...)",
3774
+ value: customIssuer,
3775
+ onChange: (e) => setCustomIssuer(e.target.value)
3776
+ }
3777
+ ),
3778
+ customError && /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: customError }),
3779
+ /* @__PURE__ */ jsxs("div", { className: "pollar-swap-custom-actions", children: [
3780
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-primary", onClick: submitCustomToken, children: "Add token" }),
3781
+ /* @__PURE__ */ jsx(
3782
+ "button",
3783
+ {
3784
+ type: "button",
3785
+ className: "pollar-swap-custom-cancel",
3786
+ onClick: () => {
3787
+ setCustomOpen(false);
3788
+ setCustomError("");
3789
+ },
3790
+ children: "Cancel"
3791
+ }
3792
+ )
3793
+ ] })
3794
+ ] }) : /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-swap-custom-toggle", onClick: () => setCustomOpen(true), children: "+ Add a token by code / issuer" }))
3673
3795
  ] }),
3674
3796
  /* @__PURE__ */ jsxs("div", { className: "pollar-send-field", children: [
3675
3797
  /* @__PURE__ */ jsx("label", { className: "pollar-send-label", children: "Route" }),
@@ -3777,6 +3899,7 @@ function SwapModal({ onClose }) {
3777
3899
  const [provider, setProvider] = useState("auto");
3778
3900
  const [venues, setVenues] = useState(null);
3779
3901
  const [catalogTokens, setCatalogTokens] = useState([]);
3902
+ const [customTokens, setCustomTokens] = useState([]);
3780
3903
  const [quotes, setQuotes] = useState([]);
3781
3904
  const [quoteLoading, setQuoteLoading] = useState(false);
3782
3905
  const [quoteError, setQuoteError] = useState("");
@@ -3834,7 +3957,21 @@ function SwapModal({ onClose }) {
3834
3957
  issuer: tk.issuer,
3835
3958
  enabledInApp: false
3836
3959
  })).filter((o) => !enabledKeys.has(optKey(o)));
3837
- const buyOptions = [...enabledBuy, ...catalogBuy].filter((o) => optKey(o) !== buyKeyOfSell);
3960
+ const knownKeys = /* @__PURE__ */ new Set([...enabledKeys, ...catalogBuy.map(optKey)]);
3961
+ const customBuy = customTokens.filter((o) => !knownKeys.has(optKey(o)));
3962
+ const buyOptions = [...enabledBuy, ...catalogBuy, ...customBuy].filter(
3963
+ (o) => optKey(o) !== buyKeyOfSell
3964
+ );
3965
+ function addCustomToken(code, issuer) {
3966
+ const c = code.trim().toUpperCase();
3967
+ const i = issuer.trim();
3968
+ if (c.length < 1 || c.length > 12) return "Code must be 1-12 characters";
3969
+ if (i.length !== 56 || !i.startsWith("G")) return "Issuer must be a Stellar address (G..., 56 chars)";
3970
+ const opt = { ref: catalogRef(c, i), code: c, issuer: i, enabledInApp: false };
3971
+ setCustomTokens((prev) => prev.some((o) => optKey(o) === optKey(opt)) ? prev : [...prev, opt]);
3972
+ setSelectedBuy(opt);
3973
+ return null;
3974
+ }
3838
3975
  const buyNeedsTrustline = (() => {
3839
3976
  if (!selectedBuy || smartUnsupported) return false;
3840
3977
  if (selectedBuy.ref.type === "native") return false;
@@ -3974,6 +4111,7 @@ function SwapModal({ onClose }) {
3974
4111
  onRefresh: handleRefresh,
3975
4112
  onSelectSell: setSelectedSell,
3976
4113
  onSelectBuy: setSelectedBuy,
4114
+ onAddCustomToken: addCustomToken,
3977
4115
  onAmountChange: setAmount,
3978
4116
  onProviderChange: setProvider,
3979
4117
  onSwap: () => void handleSwap(),