@b3dotfun/sdk 0.0.80 → 0.0.81-alpha.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.
Files changed (25) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.js +23 -10
  2. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +22 -6
  3. package/dist/cjs/anyspend/react/components/common/FiatPaymentMethod.d.ts +4 -0
  4. package/dist/cjs/anyspend/react/components/common/FiatPaymentMethod.js +14 -7
  5. package/dist/cjs/anyspend/react/components/common/PanelOnramp.js +7 -1
  6. package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +1 -1
  7. package/dist/cjs/global-account/react/hooks/useUserQuery.d.ts +2 -2
  8. package/dist/cjs/global-account/react/hooks/useUserQuery.js +76 -55
  9. package/dist/esm/anyspend/react/components/AnySpend.js +23 -10
  10. package/dist/esm/anyspend/react/components/AnySpendCustom.js +23 -7
  11. package/dist/esm/anyspend/react/components/common/FiatPaymentMethod.d.ts +4 -0
  12. package/dist/esm/anyspend/react/components/common/FiatPaymentMethod.js +13 -6
  13. package/dist/esm/anyspend/react/components/common/PanelOnramp.js +8 -2
  14. package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +1 -1
  15. package/dist/esm/global-account/react/hooks/useUserQuery.d.ts +2 -2
  16. package/dist/esm/global-account/react/hooks/useUserQuery.js +76 -55
  17. package/dist/types/anyspend/react/components/common/FiatPaymentMethod.d.ts +4 -0
  18. package/dist/types/global-account/react/hooks/useUserQuery.d.ts +2 -2
  19. package/package.json +1 -1
  20. package/src/anyspend/react/components/AnySpend.tsx +24 -10
  21. package/src/anyspend/react/components/AnySpendCustom.tsx +42 -33
  22. package/src/anyspend/react/components/common/FiatPaymentMethod.tsx +14 -6
  23. package/src/anyspend/react/components/common/PanelOnramp.tsx +23 -27
  24. package/src/global-account/react/components/SignInWithB3/SignInWithB3Flow.tsx +1 -1
  25. package/src/global-account/react/hooks/useUserQuery.ts +82 -54
@@ -6,7 +6,7 @@ import { cn, formatUsername } from "../../../../shared/utils/index.js";
6
6
  import { formatAddress } from "../../../../shared/utils/formatAddress.js";
7
7
  import { ChevronRight, Info, Wallet } from "lucide-react";
8
8
  import { useRef } from "react";
9
- import { FiatPaymentMethod } from "./FiatPaymentMethod.js";
9
+ import { FIAT_PAYMENT_METHOD_DISPLAY, FiatPaymentMethod } from "./FiatPaymentMethod.js";
10
10
  import { OrderTokenAmountFiat } from "./OrderTokenAmountFiat.js";
11
11
  import { PointsBadge } from "./PointsBadge.js";
12
12
  const ONE_CHAR_WIDTH = 30;
@@ -75,7 +75,13 @@ export function PanelOnramp({ srcAmountOnRamp, setSrcAmountOnRamp, selectedPayme
75
75
  const handleQuickAmount = (value) => {
76
76
  setSrcAmountOnRamp(value);
77
77
  };
78
- return (_jsxs("div", { className: "panel-onramp bg-as-surface-primary flex w-full flex-col", children: [_jsxs("div", { className: "border-as-border-secondary bg-as-surface-secondary relative flex w-full flex-col rounded-2xl border p-4", children: [_jsxs("div", { className: "flex h-7 w-full items-center justify-between", children: [_jsx("span", { className: "text-as-tertiarry flex items-center text-sm font-bold", children: "Pay" }), _jsx("button", { className: "text-as-tertiarry flex h-7 items-center gap-1 text-sm", onClick: () => setActivePanel(fiatPaymentMethodIndex), children: selectedPaymentMethod === FiatPaymentMethod.COINBASE_PAY ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-blue-600", children: _jsx("span", { className: "text-xs font-bold text-white", children: "C" }) }), "Coinbase Pay"] }), _jsx(ChevronRight, { className: "h-4 w-4" })] })) : selectedPaymentMethod === FiatPaymentMethod.STRIPE ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-blue-600", children: _jsx("span", { className: "text-xs font-bold text-white", children: "S" }) }), "Stripe"] }), _jsx(ChevronRight, { className: "h-4 w-4" })] })) : (_jsxs(_Fragment, { children: ["Select payment method", _jsx(ChevronRight, { className: "h-4 w-4" })] })) })] }), _jsx("div", { className: "flex items-center justify-center pb-2 pt-8", children: _jsxs("div", { className: "flex gap-1", children: [_jsx("span", { className: "text-as-tertiarry text-2xl font-bold", children: "$" }), _jsx(Input, { ref: amountInputRef, type: "text", value: srcAmountOnRamp, onChange: handleAmountChange, placeholder: "5", className: "text-as-primary placeholder:text-as-primary/50 h-auto border-0 bg-transparent p-0 px-1 pt-1 text-4xl font-bold focus-visible:ring-0 focus-visible:ring-offset-0", style: {
78
+ return (_jsxs("div", { className: "panel-onramp bg-as-surface-primary flex w-full flex-col", children: [_jsxs("div", { className: "border-as-border-secondary bg-as-surface-secondary relative flex w-full flex-col rounded-2xl border p-4", children: [_jsxs("div", { className: "flex h-7 w-full items-center justify-between", children: [_jsx("span", { className: "text-as-tertiarry flex items-center text-sm font-bold", children: "Pay" }), _jsx("button", { className: "text-as-tertiarry flex h-7 items-center gap-1 text-sm", onClick: () => setActivePanel(fiatPaymentMethodIndex), children: (() => {
79
+ const config = selectedPaymentMethod ? FIAT_PAYMENT_METHOD_DISPLAY[selectedPaymentMethod] : null;
80
+ if (config) {
81
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-5 w-5 items-center justify-center rounded-full bg-blue-600", children: _jsx("span", { className: "text-xs font-bold text-white", children: config.icon }) }), config.label] }), _jsx(ChevronRight, { className: "h-4 w-4" })] }));
82
+ }
83
+ return (_jsxs(_Fragment, { children: ["Select payment method", _jsx(ChevronRight, { className: "h-4 w-4" })] }));
84
+ })() })] }), _jsx("div", { className: "flex items-center justify-center pb-2 pt-8", children: _jsxs("div", { className: "flex gap-1", children: [_jsx("span", { className: "text-as-tertiarry text-2xl font-bold", children: "$" }), _jsx(Input, { ref: amountInputRef, type: "text", value: srcAmountOnRamp, onChange: handleAmountChange, placeholder: "5", className: "text-as-primary placeholder:text-as-primary/50 h-auto border-0 bg-transparent p-0 px-1 pt-1 text-4xl font-bold focus-visible:ring-0 focus-visible:ring-offset-0", style: {
79
85
  width: `${Math.max(ONE_CHAR_WIDTH, srcAmountOnRamp.length * ONE_CHAR_WIDTH)}px`,
80
86
  } })] }) }), _jsx("div", { className: cn("mx-auto mb-6 flex justify-center gap-2", hideDstToken && "mb-0"), children: customUsdInputValues
81
87
  .filter(v => !isNaN(Number(v)))
@@ -162,7 +162,7 @@ export function SignInWithB3Flow({ strategies, onLoginSuccess, onSessionKeySucce
162
162
  isOpen,
163
163
  source,
164
164
  });
165
- if (isConnected && isAuthenticated) {
165
+ if (isConnected && isAuthenticated && user) {
166
166
  // Mark that login just completed BEFORE opening manage account or closing modal
167
167
  // This allows Turnkey modal to show (if enableTurnkey is true)
168
168
  if (closeAfterLogin) {
@@ -2,7 +2,7 @@ import { Users } from "@b3dotfun/b3-api";
2
2
  /**
3
3
  * NOTE: THIS IS ONLY MEANT FOR INTERNAL USE, from useOnConnect
4
4
  *
5
- * Custom hook to manage user state with react-query
5
+ * Custom hook to manage user state with Zustand
6
6
  * This allows for invalidation and refetching of user data
7
7
  */
8
8
  export declare function useUserQuery(): {
@@ -59,7 +59,7 @@ export declare function useUserQuery(): {
59
59
  };
60
60
  } | undefined;
61
61
  setUser: (newUser?: Users) => void;
62
- refetchUser: () => Promise<void>;
62
+ refetchUser: () => Promise<any>;
63
63
  clearUser: () => void;
64
64
  queryKey: string[];
65
65
  };
@@ -1,77 +1,98 @@
1
1
  import { debugB3React } from "../../../shared/utils/debug.js";
2
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { useEffect } from "react";
3
+ import { create } from "zustand";
4
+ import { persist } from "zustand/middleware";
3
5
  const debug = debugB3React("useUserQuery");
4
6
  const USER_QUERY_KEY = ["b3-user"];
5
7
  /**
6
- * Retrieves the user from localStorage
8
+ * Zustand store for managing user state
9
+ * Persists user data to localStorage
7
10
  */
8
- function getUserFromStorage() {
9
- if (typeof window === "undefined") {
10
- return null;
11
- }
12
- try {
13
- const storedUser = localStorage.getItem("b3-user");
14
- return storedUser ? JSON.parse(storedUser) : null;
15
- }
16
- catch (error) {
17
- console.warn("Failed to restore user from localStorage:", error);
18
- return null;
19
- }
20
- }
21
- /**
22
- * Saves user to localStorage
23
- */
24
- function saveUserToStorage(user) {
25
- if (typeof window === "undefined") {
26
- return;
27
- }
28
- if (user) {
29
- localStorage.setItem("b3-user", JSON.stringify(user));
30
- }
31
- else {
32
- localStorage.removeItem("b3-user");
33
- }
34
- }
11
+ const useUserStore = create()(persist(set => ({
12
+ user: null,
13
+ setUser: (newUser) => {
14
+ const userToSave = newUser ?? null;
15
+ set({ user: userToSave });
16
+ debug("User updated", userToSave);
17
+ },
18
+ clearUser: () => {
19
+ set({ user: null });
20
+ debug("User cleared");
21
+ },
22
+ }), {
23
+ name: "b3-user",
24
+ onRehydrateStorage: () => (_, error) => {
25
+ if (error) {
26
+ console.warn("Failed to rehydrate user store:", error);
27
+ }
28
+ },
29
+ }));
35
30
  /**
36
31
  * NOTE: THIS IS ONLY MEANT FOR INTERNAL USE, from useOnConnect
37
32
  *
38
- * Custom hook to manage user state with react-query
33
+ * Custom hook to manage user state with Zustand
39
34
  * This allows for invalidation and refetching of user data
40
35
  */
41
36
  export function useUserQuery() {
42
- const queryClient = useQueryClient();
43
- // Query to get user data (primarily from cache/localStorage)
44
- const { data: user } = useQuery({
45
- queryKey: USER_QUERY_KEY,
46
- queryFn: getUserFromStorage,
47
- staleTime: Infinity, // User data doesn't go stale automatically
48
- gcTime: Infinity, // Keep in cache indefinitely
49
- initialData: getUserFromStorage,
50
- });
51
- // Mutation to update user
52
- const setUserMutation = useMutation({
53
- mutationFn: async (newUser) => {
54
- const userToSave = newUser ?? null;
55
- saveUserToStorage(userToSave);
56
- return userToSave;
57
- },
58
- onSuccess: data => {
59
- queryClient.setQueryData(USER_QUERY_KEY, data);
60
- debug("User updated", data);
61
- },
62
- });
37
+ const user = useUserStore(state => state.user);
38
+ const setUserStore = useUserStore(state => state.setUser);
39
+ const clearUserStore = useUserStore(state => state.clearUser);
40
+ // Listen for storage events from other tabs/windows
41
+ useEffect(() => {
42
+ const handleStorageChange = (e) => {
43
+ if (e.key === "b3-user") {
44
+ // Sync with changes from other tabs/windows
45
+ const stored = e.newValue;
46
+ if (stored) {
47
+ try {
48
+ const parsed = JSON.parse(stored);
49
+ // Zustand persist format: { state: { user: ... }, version: ... }
50
+ const userData = parsed?.state?.user ?? parsed?.user ?? null;
51
+ useUserStore.setState({ user: userData });
52
+ }
53
+ catch (error) {
54
+ console.warn("Failed to parse user from storage event:", error);
55
+ }
56
+ }
57
+ else {
58
+ useUserStore.setState({ user: null });
59
+ }
60
+ }
61
+ };
62
+ window.addEventListener("storage", handleStorageChange);
63
+ return () => {
64
+ window.removeEventListener("storage", handleStorageChange);
65
+ };
66
+ }, []);
63
67
  // Helper function to set user (maintains backward compatibility)
64
68
  const setUser = (newUser) => {
65
- setUserMutation.mutate(newUser);
69
+ setUserStore(newUser);
66
70
  };
67
71
  // Helper function to invalidate and refetch user
68
72
  const refetchUser = async () => {
69
- await queryClient.invalidateQueries({ queryKey: USER_QUERY_KEY });
70
- return queryClient.refetchQueries({ queryKey: USER_QUERY_KEY });
73
+ // Re-read from localStorage and update store
74
+ // Zustand persist stores data as { state: { user: ... }, version: ... }
75
+ const stored = localStorage.getItem("b3-user");
76
+ if (stored) {
77
+ try {
78
+ const parsed = JSON.parse(stored);
79
+ // Zustand persist format: { state: { user: ... }, version: ... }
80
+ const userData = parsed?.state?.user ?? parsed?.user ?? null;
81
+ useUserStore.setState({ user: userData });
82
+ return userData ?? undefined;
83
+ }
84
+ catch (error) {
85
+ console.warn("Failed to refetch user from localStorage:", error);
86
+ // Fallback to current store state
87
+ return useUserStore.getState().user ?? undefined;
88
+ }
89
+ }
90
+ useUserStore.setState({ user: null });
91
+ return undefined;
71
92
  };
72
93
  // Helper function to clear user
73
94
  const clearUser = () => {
74
- setUser(undefined);
95
+ clearUserStore();
75
96
  };
76
97
  return {
77
98
  user: user ?? undefined,
@@ -4,6 +4,10 @@ export declare enum FiatPaymentMethod {
4
4
  STRIPE = "stripe",// Stripe redirect (one-click buy URL)
5
5
  STRIPE_WEB2 = "stripe_web2"
6
6
  }
7
+ export declare const FIAT_PAYMENT_METHOD_DISPLAY: Record<FiatPaymentMethod, {
8
+ icon: string;
9
+ label: string;
10
+ } | null>;
7
11
  interface FiatPaymentMethodProps {
8
12
  selectedPaymentMethod: FiatPaymentMethod;
9
13
  setSelectedPaymentMethod: (method: FiatPaymentMethod) => void;
@@ -2,7 +2,7 @@ import { Users } from "@b3dotfun/b3-api";
2
2
  /**
3
3
  * NOTE: THIS IS ONLY MEANT FOR INTERNAL USE, from useOnConnect
4
4
  *
5
- * Custom hook to manage user state with react-query
5
+ * Custom hook to manage user state with Zustand
6
6
  * This allows for invalidation and refetching of user data
7
7
  */
8
8
  export declare function useUserQuery(): {
@@ -59,7 +59,7 @@ export declare function useUserQuery(): {
59
59
  };
60
60
  } | undefined;
61
61
  setUser: (newUser?: Users) => void;
62
- refetchUser: () => Promise<void>;
62
+ refetchUser: () => Promise<any>;
63
63
  clearUser: () => void;
64
64
  queryKey: string[];
65
65
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.0.80",
3
+ "version": "0.0.81-alpha.1",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -510,7 +510,8 @@ function AnySpendInner({
510
510
  });
511
511
 
512
512
  // Get geo-based onramp options for fiat payments
513
- const { geoData, coinbaseAvailablePaymentMethods, stripeWeb2Support } = useGeoOnrampOptions(srcAmountOnRamp);
513
+ const { geoData, coinbaseAvailablePaymentMethods, stripeOnrampSupport, stripeWeb2Support } =
514
+ useGeoOnrampOptions(srcAmountOnRamp);
514
515
 
515
516
  // Helper function to map payment method to onramp vendor
516
517
  const getOnrampVendor = (paymentMethod: FiatPaymentMethod): "coinbase" | "stripe" | "stripe-web2" | undefined => {
@@ -518,11 +519,11 @@ function AnySpendInner({
518
519
  case FiatPaymentMethod.COINBASE_PAY:
519
520
  return "coinbase";
520
521
  case FiatPaymentMethod.STRIPE:
521
- // Determine if it's stripe onramp or stripe-web2 based on support
522
- if (stripeWeb2Support?.isSupport) {
523
- return "stripe-web2";
524
- }
525
- return undefined;
522
+ // Stripe redirect flow (one-click URL)
523
+ return stripeOnrampSupport ? "stripe" : undefined;
524
+ case FiatPaymentMethod.STRIPE_WEB2:
525
+ // Stripe embedded payment form
526
+ return stripeWeb2Support?.isSupport ? "stripe-web2" : undefined;
526
527
  default:
527
528
  return undefined;
528
529
  }
@@ -683,7 +684,10 @@ function AnySpendInner({
683
684
 
684
685
  // Determine button state and text
685
686
  const btnInfo: { text: string; disable: boolean; error: boolean; loading: boolean } = useMemo(() => {
686
- if (activeInputAmountInWei === "0") return { text: "Enter an amount", disable: true, error: false, loading: false };
687
+ // For fiat tab, check srcAmountOnRamp; for crypto tab, check activeInputAmountInWei
688
+ const hasAmount =
689
+ activeTab === "fiat" ? srcAmountOnRamp && parseFloat(srcAmountOnRamp) > 0 : activeInputAmountInWei !== "0";
690
+ if (!hasAmount) return { text: "Enter an amount", disable: true, error: false, loading: false };
687
691
  if (isSameChainSameToken)
688
692
  return { text: "Select a different token or chain", disable: true, error: false, loading: false };
689
693
  if (isLoadingAnyspendQuote) return { text: "Loading quote...", disable: true, error: false, loading: true };
@@ -739,6 +743,7 @@ function AnySpendInner({
739
743
  activeTab,
740
744
  effectiveCryptoPaymentMethod,
741
745
  selectedFiatPaymentMethod,
746
+ srcAmountOnRamp,
742
747
  ]);
743
748
 
744
749
  // Handle main button click
@@ -870,11 +875,20 @@ function AnySpendInner({
870
875
  vendor = "coinbase";
871
876
  paymentMethodString = coinbaseAvailablePaymentMethods[0]?.id || ""; // Use first available payment method ID
872
877
  } else if (paymentMethod === FiatPaymentMethod.STRIPE) {
873
- if (!stripeWeb2Support || !stripeWeb2Support.isSupport) {
874
- toast.error("Stripe not available");
878
+ // Stripe redirect flow (one-click URL)
879
+ if (!stripeOnrampSupport) {
880
+ toast.error("Credit/Debit Card not available");
881
+ return;
882
+ }
883
+ vendor = "stripe";
884
+ paymentMethodString = "";
885
+ } else if (paymentMethod === FiatPaymentMethod.STRIPE_WEB2) {
886
+ // Stripe embedded payment form
887
+ if (!stripeWeb2Support.isSupport) {
888
+ toast.error("Pay with Card not available");
875
889
  return;
876
890
  }
877
- vendor = stripeWeb2Support && stripeWeb2Support.isSupport ? "stripe-web2" : "stripe";
891
+ vendor = "stripe-web2";
878
892
  paymentMethodString = "";
879
893
  } else {
880
894
  toast.error("Please select a payment method");
@@ -50,7 +50,7 @@ import { useRecipientAddressState } from "../hooks/useRecipientAddressState";
50
50
  import { AnySpendFingerprintWrapper, getFingerprintConfig } from "./AnySpendFingerprintWrapper";
51
51
  import { CryptoPaymentMethod, CryptoPaymentMethodType } from "./common/CryptoPaymentMethod";
52
52
  import { FeeBreakDown } from "./common/FeeBreakDown";
53
- import { FiatPaymentMethod, FiatPaymentMethodComponent } from "./common/FiatPaymentMethod";
53
+ import { FIAT_PAYMENT_METHOD_DISPLAY, FiatPaymentMethod, FiatPaymentMethodComponent } from "./common/FiatPaymentMethod";
54
54
  import { OrderDetails } from "./common/OrderDetails";
55
55
  import { OrderHistory } from "./common/OrderHistory";
56
56
  import { OrderToken } from "./common/OrderToken";
@@ -355,7 +355,12 @@ function AnySpendCustomInner({
355
355
  contractType: orderType === "mint_nft" ? metadata?.nftContract?.type : undefined,
356
356
  encodedData: encodedData,
357
357
  spenderAddress: spenderAddress,
358
- onrampVendor: selectedFiatPaymentMethod === FiatPaymentMethod.STRIPE ? "stripe-web2" : undefined,
358
+ onrampVendor:
359
+ selectedFiatPaymentMethod === FiatPaymentMethod.STRIPE
360
+ ? "stripe"
361
+ : selectedFiatPaymentMethod === FiatPaymentMethod.STRIPE_WEB2
362
+ ? "stripe-web2"
363
+ : undefined,
359
364
  });
360
365
  }, [
361
366
  activeTab,
@@ -1127,32 +1132,28 @@ function AnySpendCustomInner({
1127
1132
  className="text-as-tertiarry flex flex-wrap items-center justify-end gap-2 text-sm transition-colors hover:text-blue-700"
1128
1133
  onClick={() => setActivePanel(PanelView.FIAT_PAYMENT_METHOD)}
1129
1134
  >
1130
- {selectedFiatPaymentMethod === FiatPaymentMethod.COINBASE_PAY ? (
1131
- <>
1132
- <div className="flex items-center gap-2 whitespace-nowrap">
1133
- <div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-600">
1134
- <span className="text-xs font-bold text-white">C</span>
1135
- </div>
1136
- Coinbase Pay
1137
- </div>
1138
- <ChevronRight className="h-4 w-4 shrink-0" />
1139
- </>
1140
- ) : selectedFiatPaymentMethod === FiatPaymentMethod.STRIPE ? (
1141
- <>
1142
- <div className="flex items-center gap-2 whitespace-nowrap">
1143
- <div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-600">
1144
- <span className="text-xs font-bold text-white">S</span>
1145
- </div>
1146
- Credit/Debit Card
1147
- </div>
1148
- <ChevronRight className="h-4 w-4 shrink-0" />
1149
- </>
1150
- ) : (
1151
- <>
1152
- <span className="whitespace-nowrap">Select payment method</span>
1153
- <ChevronRight className="h-4 w-4 shrink-0" />
1154
- </>
1155
- )}
1135
+ {(() => {
1136
+ const config = FIAT_PAYMENT_METHOD_DISPLAY[selectedFiatPaymentMethod];
1137
+ if (config) {
1138
+ return (
1139
+ <>
1140
+ <div className="flex items-center gap-2 whitespace-nowrap">
1141
+ <div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-600">
1142
+ <span className="text-xs font-bold text-white">{config.icon}</span>
1143
+ </div>
1144
+ {config.label}
1145
+ </div>
1146
+ <ChevronRight className="h-4 w-4 shrink-0" />
1147
+ </>
1148
+ );
1149
+ }
1150
+ return (
1151
+ <>
1152
+ <span className="whitespace-nowrap">Select payment method</span>
1153
+ <ChevronRight className="h-4 w-4 shrink-0" />
1154
+ </>
1155
+ );
1156
+ })()}
1156
1157
  </button>
1157
1158
  </motion.div>
1158
1159
 
@@ -1318,17 +1319,25 @@ function AnySpendCustomInner({
1318
1319
  </div>
1319
1320
  );
1320
1321
 
1322
+ // Stable callback for fiat payment method selection
1323
+ const handleFiatPaymentMethodSelect = useCallback((method: FiatPaymentMethod) => {
1324
+ setSelectedFiatPaymentMethod(method);
1325
+ setActivePanel(PanelView.CONFIRM_ORDER);
1326
+ }, []);
1327
+
1328
+ // Stable callback for navigating back to confirm order
1329
+ const handleBackToConfirmOrder = useCallback(() => {
1330
+ setActivePanel(PanelView.CONFIRM_ORDER);
1331
+ }, []);
1332
+
1321
1333
  // Fiat payment method view
1322
1334
  const fiatPaymentMethodView = (
1323
1335
  <div className={cn("bg-as-surface-primary mx-auto w-[460px] max-w-full rounded-xl p-4")}>
1324
1336
  <FiatPaymentMethodComponent
1325
1337
  selectedPaymentMethod={selectedFiatPaymentMethod}
1326
1338
  setSelectedPaymentMethod={setSelectedFiatPaymentMethod}
1327
- onBack={() => setActivePanel(PanelView.CONFIRM_ORDER)}
1328
- onSelectPaymentMethod={(method: FiatPaymentMethod) => {
1329
- setSelectedFiatPaymentMethod(method);
1330
- setActivePanel(PanelView.CONFIRM_ORDER);
1331
- }}
1339
+ onBack={handleBackToConfirmOrder}
1340
+ onSelectPaymentMethod={handleFiatPaymentMethodSelect}
1332
1341
  srcAmountOnRamp={srcFiatAmount}
1333
1342
  />
1334
1343
  </div>
@@ -11,6 +11,14 @@ export enum FiatPaymentMethod {
11
11
  STRIPE_WEB2 = "stripe_web2", // Stripe embedded payment
12
12
  }
13
13
 
14
+ // Shared display config for fiat payment methods
15
+ export const FIAT_PAYMENT_METHOD_DISPLAY: Record<FiatPaymentMethod, { icon: string; label: string } | null> = {
16
+ [FiatPaymentMethod.COINBASE_PAY]: { icon: "C", label: "Coinbase Pay" },
17
+ [FiatPaymentMethod.STRIPE]: { icon: "S", label: "Pay via Stripe" },
18
+ [FiatPaymentMethod.STRIPE_WEB2]: { icon: "S", label: "Pay with Card" },
19
+ [FiatPaymentMethod.NONE]: null,
20
+ };
21
+
14
22
  interface FiatPaymentMethodProps {
15
23
  selectedPaymentMethod: FiatPaymentMethod;
16
24
  setSelectedPaymentMethod: (method: FiatPaymentMethod) => void;
@@ -70,26 +78,26 @@ export function FiatPaymentMethodComponent({
70
78
  });
71
79
  }
72
80
 
73
- // Add Stripe redirect (one-click) if available - primary option
81
+ // Add Stripe redirect (one-click) if available - redirects to Stripe checkout
74
82
  if (stripeOnrampSupport) {
75
83
  const stripeFee = getFeeFromApi(FiatPaymentMethod.STRIPE_WEB2); // Use same fee estimate
76
84
  availablePaymentMethods.push({
77
85
  id: FiatPaymentMethod.STRIPE,
78
- name: "Credit/Debit Card",
79
- description: "Pay via Stripe checkout",
86
+ name: "Pay via Stripe",
87
+ description: "Redirects to Stripe checkout",
80
88
  badge: stripeFee ? `$${Number(stripeFee).toFixed(2)} fee` : undefined,
81
89
  badgeColor: "bg-gray-100 text-gray-800",
82
90
  available: true,
83
91
  });
84
92
  }
85
93
 
86
- // Add Stripe Web2 (embedded) if available - secondary option
94
+ // Add Stripe Web2 (embedded) if available - embedded card form
87
95
  if (stripeWeb2Support && stripeWeb2Support.isSupport) {
88
96
  const stripeFee = getFeeFromApi(FiatPaymentMethod.STRIPE_WEB2);
89
97
  availablePaymentMethods.push({
90
98
  id: FiatPaymentMethod.STRIPE_WEB2,
91
- name: "Quick Pay",
92
- description: "Credit or debit card",
99
+ name: "Pay with Card",
100
+ description: "Fast checkout",
93
101
  badge: stripeFee ? `$${Number(stripeFee).toFixed(2)} fee` : undefined,
94
102
  badgeColor: "bg-gray-100 text-gray-800",
95
103
  available: true,
@@ -8,7 +8,7 @@ import { formatAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
8
8
  import { ChevronRight, Info, Wallet } from "lucide-react";
9
9
  import { useRef } from "react";
10
10
 
11
- import { FiatPaymentMethod } from "./FiatPaymentMethod";
11
+ import { FIAT_PAYMENT_METHOD_DISPLAY, FiatPaymentMethod } from "./FiatPaymentMethod";
12
12
  import { OrderTokenAmountFiat } from "./OrderTokenAmountFiat";
13
13
  import { PointsBadge } from "./PointsBadge";
14
14
 
@@ -141,32 +141,28 @@ export function PanelOnramp({
141
141
  className="text-as-tertiarry flex h-7 items-center gap-1 text-sm"
142
142
  onClick={() => setActivePanel(fiatPaymentMethodIndex)} // PanelView.FIAT_PAYMENT_METHOD
143
143
  >
144
- {selectedPaymentMethod === FiatPaymentMethod.COINBASE_PAY ? (
145
- <>
146
- <div className="flex items-center gap-2">
147
- <div className="flex h-5 w-5 items-center justify-center rounded-full bg-blue-600">
148
- <span className="text-xs font-bold text-white">C</span>
149
- </div>
150
- Coinbase Pay
151
- </div>
152
- <ChevronRight className="h-4 w-4" />
153
- </>
154
- ) : selectedPaymentMethod === FiatPaymentMethod.STRIPE ? (
155
- <>
156
- <div className="flex items-center gap-2">
157
- <div className="flex h-5 w-5 items-center justify-center rounded-full bg-blue-600">
158
- <span className="text-xs font-bold text-white">S</span>
159
- </div>
160
- Stripe
161
- </div>
162
- <ChevronRight className="h-4 w-4" />
163
- </>
164
- ) : (
165
- <>
166
- Select payment method
167
- <ChevronRight className="h-4 w-4" />
168
- </>
169
- )}
144
+ {(() => {
145
+ const config = selectedPaymentMethod ? FIAT_PAYMENT_METHOD_DISPLAY[selectedPaymentMethod] : null;
146
+ if (config) {
147
+ return (
148
+ <>
149
+ <div className="flex items-center gap-2">
150
+ <div className="flex h-5 w-5 items-center justify-center rounded-full bg-blue-600">
151
+ <span className="text-xs font-bold text-white">{config.icon}</span>
152
+ </div>
153
+ {config.label}
154
+ </div>
155
+ <ChevronRight className="h-4 w-4" />
156
+ </>
157
+ );
158
+ }
159
+ return (
160
+ <>
161
+ Select payment method
162
+ <ChevronRight className="h-4 w-4" />
163
+ </>
164
+ );
165
+ })()}
170
166
  </button>
171
167
  </div>
172
168
 
@@ -198,7 +198,7 @@ export function SignInWithB3Flow({
198
198
  source,
199
199
  });
200
200
 
201
- if (isConnected && isAuthenticated) {
201
+ if (isConnected && isAuthenticated && user) {
202
202
  // Mark that login just completed BEFORE opening manage account or closing modal
203
203
  // This allows Turnkey modal to show (if enableTurnkey is true)
204
204
  if (closeAfterLogin) {