@pollar/react 0.6.0 → 0.7.0

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.js CHANGED
@@ -1030,7 +1030,7 @@ var PollarModalFooter = () => {
1030
1030
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-footer-name", children: "Pollar" }),
1031
1031
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-footer-version", children: [
1032
1032
  "v",
1033
- "0.6.0"
1033
+ "0.7.0"
1034
1034
  ] })
1035
1035
  ] })
1036
1036
  ] });
@@ -2501,6 +2501,254 @@ function SendModal({ onClose }) {
2501
2501
  }
2502
2502
  ) });
2503
2503
  }
2504
+ function describeDevice(s) {
2505
+ if (s.deviceLabel) return s.deviceLabel;
2506
+ if (!s.userAgent) return "Unknown device";
2507
+ return parseUserAgent(s.userAgent);
2508
+ }
2509
+ function detectBrowser(ua) {
2510
+ if (/Edg\//.test(ua)) return "Edge";
2511
+ if (/OPR\//.test(ua)) return "Opera";
2512
+ if (/(Chrome|CriOS)\//.test(ua)) return "Chrome";
2513
+ if (/(Firefox|FxiOS)\//.test(ua)) return "Firefox";
2514
+ if (/Safari\//.test(ua)) return "Safari";
2515
+ return null;
2516
+ }
2517
+ function detectOS(ua) {
2518
+ if (/iPhone|iPad|iPod/.test(ua)) return "iOS";
2519
+ if (/Android/.test(ua)) return "Android";
2520
+ if (/Mac OS X/.test(ua)) return "macOS";
2521
+ if (/Windows NT/.test(ua)) return "Windows";
2522
+ if (/Linux/.test(ua)) return "Linux";
2523
+ return null;
2524
+ }
2525
+ function parseUserAgent(ua) {
2526
+ const browser = detectBrowser(ua);
2527
+ const os = detectOS(ua);
2528
+ if (browser && os) return `${os} \u2014 ${browser}`;
2529
+ if (os) return os;
2530
+ if (browser) return browser;
2531
+ return ua.slice(0, 48);
2532
+ }
2533
+ function formatRelative(iso) {
2534
+ if (!iso) return "\u2014";
2535
+ const ts = new Date(iso).getTime();
2536
+ if (!Number.isFinite(ts)) return "\u2014";
2537
+ const diffSec = Math.round((Date.now() - ts) / 1e3);
2538
+ if (diffSec < 0) return "just now";
2539
+ if (diffSec < 60) return `${diffSec}s ago`;
2540
+ const diffMin = Math.round(diffSec / 60);
2541
+ if (diffMin < 60) return `${diffMin}m ago`;
2542
+ const diffHr = Math.round(diffMin / 60);
2543
+ if (diffHr < 24) return `${diffHr}h ago`;
2544
+ const diffDay = Math.round(diffHr / 24);
2545
+ if (diffDay < 30) return `${diffDay}d ago`;
2546
+ return new Date(iso).toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
2547
+ }
2548
+ function shortIp(hash) {
2549
+ if (!hash) return "";
2550
+ return hash.slice(0, 8);
2551
+ }
2552
+ function SessionsModalTemplate({
2553
+ theme,
2554
+ accentColor,
2555
+ state,
2556
+ revokingFamilyId,
2557
+ signingOutEverywhere,
2558
+ onRefresh,
2559
+ onRevoke,
2560
+ onLogoutEverywhere,
2561
+ onClose
2562
+ }) {
2563
+ const isDark = theme === "dark";
2564
+ const cssVars = {
2565
+ "--pollar-accent": accentColor,
2566
+ "--pollar-bg": isDark ? "#1a1a1a" : "#ffffff",
2567
+ "--pollar-border": isDark ? "#374151" : "#e5e7eb",
2568
+ "--pollar-text": isDark ? "#ffffff" : "#111827",
2569
+ "--pollar-muted": isDark ? "#9ca3af" : "#6b7280",
2570
+ "--pollar-input-bg": isDark ? "#374151" : "#f9fafb",
2571
+ "--pollar-error-bg": isDark ? "#2a1515" : "#fef2f2",
2572
+ "--pollar-error-border": isDark ? "#7f1d1d" : "#fecaca",
2573
+ "--pollar-error-text": isDark ? "#f87171" : "#dc2626",
2574
+ "--pollar-success-text": isDark ? "#4ade80" : "#16a34a",
2575
+ "--pollar-buttons-border-radius": "6px",
2576
+ "--pollar-buttons-height": "44px",
2577
+ "--pollar-input-height": "44px",
2578
+ "--pollar-input-border-radius": "0.5rem",
2579
+ "--pollar-card-border-radius": "10px"
2580
+ };
2581
+ const isLoading = state.step === "loading";
2582
+ const sessions = state.step === "loaded" ? state.sessions : [];
2583
+ const otherCount = sessions.filter((s) => !s.current).length;
2584
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2585
+ "div",
2586
+ {
2587
+ className: "pollar-modal-card pollar-sessions-modal",
2588
+ "data-theme": theme,
2589
+ style: cssVars,
2590
+ onClick: (e) => e.stopPropagation(),
2591
+ children: [
2592
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-header", children: [
2593
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "pollar-modal-title", children: "Active sessions" }),
2594
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-header-actions", children: [
2595
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-modal-refresh-btn", onClick: onRefresh, disabled: isLoading, children: [
2596
+ /* @__PURE__ */ jsxRuntime.jsxs(
2597
+ "svg",
2598
+ {
2599
+ className: `pollar-modal-refresh-icon${isLoading ? " spinning" : ""}`,
2600
+ width: "13",
2601
+ height: "13",
2602
+ viewBox: "0 0 13 13",
2603
+ fill: "none",
2604
+ "aria-hidden": true,
2605
+ children: [
2606
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M11.5 6.5a5 5 0 11-1.5-3.536", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }),
2607
+ /* @__PURE__ */ jsxRuntime.jsx(
2608
+ "path",
2609
+ {
2610
+ d: "M10 1v3h-3",
2611
+ stroke: "currentColor",
2612
+ strokeWidth: "1.5",
2613
+ strokeLinecap: "round",
2614
+ strokeLinejoin: "round"
2615
+ }
2616
+ )
2617
+ ]
2618
+ }
2619
+ ),
2620
+ "Refresh"
2621
+ ] }),
2622
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-modal-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 2l12 12M14 2L2 14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) })
2623
+ ] })
2624
+ ] }),
2625
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-sessions-list", children: [
2626
+ state.step === "idle" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-empty", children: "Loading\u2026" }),
2627
+ isLoading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-empty", children: "Loading\u2026" }),
2628
+ state.step === "error" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-empty", children: state.message }),
2629
+ state.step === "loaded" && sessions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-empty", children: "No active sessions." }),
2630
+ sessions.map((s) => {
2631
+ const isRevoking = revokingFamilyId === s.familyId;
2632
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-sessions-item", "data-current": s.current || void 0, children: [
2633
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-sessions-item-main", children: [
2634
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-sessions-item-device", children: describeDevice(s) }),
2635
+ s.current && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-sessions-item-badge", children: "This device" })
2636
+ ] }),
2637
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-sessions-item-meta", children: [
2638
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2639
+ "Last used ",
2640
+ formatRelative(s.lastUsedAt ?? s.createdAt)
2641
+ ] }),
2642
+ s.ipHash && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2643
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "\xB7" }),
2644
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { title: `ip-hash ${s.ipHash}`, children: [
2645
+ "ip ",
2646
+ shortIp(s.ipHash)
2647
+ ] })
2648
+ ] })
2649
+ ] }),
2650
+ !s.current && /* @__PURE__ */ jsxRuntime.jsx(
2651
+ "button",
2652
+ {
2653
+ className: "pollar-sessions-item-revoke",
2654
+ onClick: () => onRevoke(s.familyId),
2655
+ disabled: isRevoking || signingOutEverywhere,
2656
+ children: isRevoking ? "Revoking\u2026" : "Revoke"
2657
+ }
2658
+ )
2659
+ ] }, s.familyId);
2660
+ })
2661
+ ] }),
2662
+ state.step === "loaded" && sessions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-sessions-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
2663
+ "button",
2664
+ {
2665
+ className: "pollar-sessions-logout-all",
2666
+ onClick: onLogoutEverywhere,
2667
+ disabled: signingOutEverywhere || otherCount === 0,
2668
+ title: otherCount === 0 ? "No other devices to sign out" : void 0,
2669
+ children: signingOutEverywhere ? "Signing out\u2026" : "Sign out everywhere"
2670
+ }
2671
+ ) }),
2672
+ /* @__PURE__ */ jsxRuntime.jsx(PollarModalFooter, {})
2673
+ ]
2674
+ }
2675
+ );
2676
+ }
2677
+ function SessionsModal({ onClose }) {
2678
+ const { getClient, styles } = usePollar();
2679
+ const { theme = "light", accentColor = "#005DB4" } = styles;
2680
+ const [state, setState] = react.useState({ step: "idle" });
2681
+ const [revokingFamilyId, setRevokingFamilyId] = react.useState(null);
2682
+ const [signingOutEverywhere, setSigningOutEverywhere] = react.useState(false);
2683
+ const mountedRef = react.useRef(true);
2684
+ react.useEffect(() => () => {
2685
+ mountedRef.current = false;
2686
+ }, []);
2687
+ const onCloseRef = react.useRef(onClose);
2688
+ onCloseRef.current = onClose;
2689
+ react.useEffect(() => {
2690
+ return getClient().onAuthStateChange((authState) => {
2691
+ if (authState.step === "idle") onCloseRef.current();
2692
+ });
2693
+ }, [getClient]);
2694
+ const load = react.useCallback(async () => {
2695
+ setState({ step: "loading" });
2696
+ try {
2697
+ const sessions = await getClient().listSessions();
2698
+ if (!mountedRef.current) return;
2699
+ setState({ step: "loaded", sessions });
2700
+ } catch (err) {
2701
+ if (!mountedRef.current) return;
2702
+ const message = err instanceof Error ? err.message : "Failed to load sessions";
2703
+ setState({ step: "error", message });
2704
+ }
2705
+ }, [getClient]);
2706
+ react.useEffect(() => {
2707
+ void load();
2708
+ }, [load]);
2709
+ const handleRevoke = react.useCallback(
2710
+ async (familyId) => {
2711
+ setRevokingFamilyId(familyId);
2712
+ try {
2713
+ await getClient().revokeSession(familyId);
2714
+ if (!mountedRef.current) return;
2715
+ await load();
2716
+ } catch {
2717
+ if (!mountedRef.current) return;
2718
+ setState(
2719
+ (prev) => prev.step === "loaded" ? { step: "error", message: "Failed to revoke session" } : prev
2720
+ );
2721
+ } finally {
2722
+ if (mountedRef.current) setRevokingFamilyId(null);
2723
+ }
2724
+ },
2725
+ [getClient, load]
2726
+ );
2727
+ const handleLogoutEverywhere = react.useCallback(async () => {
2728
+ setSigningOutEverywhere(true);
2729
+ try {
2730
+ await getClient().logoutEverywhere();
2731
+ onClose();
2732
+ } catch {
2733
+ if (!mountedRef.current) return;
2734
+ setSigningOutEverywhere(false);
2735
+ }
2736
+ }, [getClient, onClose]);
2737
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-overlay", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
2738
+ SessionsModalTemplate,
2739
+ {
2740
+ theme,
2741
+ accentColor,
2742
+ state,
2743
+ revokingFamilyId,
2744
+ signingOutEverywhere,
2745
+ onRefresh: () => void load(),
2746
+ onRevoke: (familyId) => void handleRevoke(familyId),
2747
+ onLogoutEverywhere: () => void handleLogoutEverywhere(),
2748
+ onClose
2749
+ }
2750
+ ) });
2751
+ }
2504
2752
  function TransactionModalTemplate({
2505
2753
  theme,
2506
2754
  accentColor,
@@ -2942,9 +3190,10 @@ function PollarProvider({ config, styles: propStyles, adapters, children }) {
2942
3190
  const [walletBalanceModalOpen, setWalletBalanceModalOpen] = react.useState(false);
2943
3191
  const [sendModalOpen, setSendModalOpen] = react.useState(false);
2944
3192
  const [receiveModalOpen, setReceiveModalOpen] = react.useState(false);
3193
+ const [sessionsModalOpen, setSessionsModalOpen] = react.useState(false);
2945
3194
  const adaptersRef = react.useRef(adapters);
2946
3195
  adaptersRef.current = adapters;
2947
- const walletAddress = sessionState?.data?.providers?.wallet?.address || sessionState?.wallet?.publicKey || "";
3196
+ const walletAddress = sessionState?.wallet?.publicKey || "";
2948
3197
  const getClient = react.useCallback(() => pollarClient, [pollarClient]);
2949
3198
  const refreshWalletBalance = react.useCallback(() => pollarClient.refreshBalance(walletAddress), [pollarClient, walletAddress]);
2950
3199
  const contextValue = react.useMemo(
@@ -2974,6 +3223,8 @@ function PollarProvider({ config, styles: propStyles, adapters, children }) {
2974
3223
  // send / receive
2975
3224
  openSendModal: () => setSendModalOpen(true),
2976
3225
  openReceiveModal: () => setReceiveModalOpen(true),
3226
+ // sessions
3227
+ openSessionsModal: () => setSessionsModalOpen(true),
2977
3228
  // network
2978
3229
  network: networkState.step === "connected" ? networkState.network : "testnet",
2979
3230
  setNetwork: (network) => pollarClient.setNetwork(network),
@@ -3008,7 +3259,8 @@ function PollarProvider({ config, styles: propStyles, adapters, children }) {
3008
3259
  txHistoryModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setTxHistoryModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(TxHistoryModal, { onClose: () => setTxHistoryModalOpen(false) }) }),
3009
3260
  walletBalanceModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setWalletBalanceModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(WalletBalanceModal, { onClose: () => setWalletBalanceModalOpen(false) }) }),
3010
3261
  sendModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setSendModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(SendModal, { onClose: () => setSendModalOpen(false) }) }),
3011
- receiveModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setReceiveModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(ReceiveModal, { onClose: () => setReceiveModalOpen(false) }) })
3262
+ receiveModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setReceiveModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(ReceiveModal, { onClose: () => setReceiveModalOpen(false) }) }),
3263
+ sessionsModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setSessionsModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(SessionsModal, { onClose: () => setSessionsModalOpen(false) }) })
3012
3264
  ] });
3013
3265
  }
3014
3266
  function usePollar() {
@@ -3271,6 +3523,8 @@ exports.ReceiveModalTemplate = ReceiveModalTemplate;
3271
3523
  exports.RouteDisplay = RouteDisplay;
3272
3524
  exports.SendModal = SendModal;
3273
3525
  exports.SendModalTemplate = SendModalTemplate;
3526
+ exports.SessionsModal = SessionsModal;
3527
+ exports.SessionsModalTemplate = SessionsModalTemplate;
3274
3528
  exports.TransactionModalTemplate = TransactionModalTemplate;
3275
3529
  exports.TxHistoryModalTemplate = TxHistoryModalTemplate;
3276
3530
  exports.TxStatusView = TxStatusView;