@b3dotfun/sdk 0.0.10 → 0.0.11
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/cjs/anyspend/react/components/AnySpendCustom.js +1 -1
- package/dist/cjs/anyspend/react/components/common/PaymentStripeWeb2.js +62 -25
- package/dist/cjs/anyspend/react/components/webview/WebviewOnrampPayment.js +29 -6
- package/dist/cjs/anyspend/react/providers/AnyspendProvider.d.ts +1 -0
- package/dist/cjs/anyspend/react/providers/AnyspendProvider.js +3 -1
- package/dist/cjs/anyspend/react/providers/StripeRedirectHandler.d.ts +8 -0
- package/dist/cjs/anyspend/react/providers/StripeRedirectHandler.js +41 -0
- package/dist/cjs/global-account/react/components/B3DynamicModal.js +2 -2
- package/dist/cjs/global-account/react/components/ui/dialog.js +1 -1
- package/dist/cjs/global-account/react/hooks/index.d.ts +1 -0
- package/dist/cjs/global-account/react/hooks/index.js +4 -1
- package/dist/cjs/global-account/react/hooks/useAccountWallet.js +0 -4
- package/dist/cjs/global-account/react/hooks/useProfile.d.ts +38 -0
- package/dist/cjs/global-account/react/hooks/useProfile.js +72 -0
- package/dist/esm/anyspend/react/components/AnySpendCustom.js +1 -1
- package/dist/esm/anyspend/react/components/common/PaymentStripeWeb2.js +64 -27
- package/dist/esm/anyspend/react/components/webview/WebviewOnrampPayment.js +30 -7
- package/dist/esm/anyspend/react/providers/AnyspendProvider.d.ts +1 -0
- package/dist/esm/anyspend/react/providers/AnyspendProvider.js +4 -2
- package/dist/esm/anyspend/react/providers/StripeRedirectHandler.d.ts +8 -0
- package/dist/esm/anyspend/react/providers/StripeRedirectHandler.js +38 -0
- package/dist/esm/global-account/react/components/B3DynamicModal.js +2 -2
- package/dist/esm/global-account/react/components/ui/dialog.js +1 -1
- package/dist/esm/global-account/react/hooks/index.d.ts +1 -0
- package/dist/esm/global-account/react/hooks/index.js +1 -0
- package/dist/esm/global-account/react/hooks/useAccountWallet.js +0 -4
- package/dist/esm/global-account/react/hooks/useProfile.d.ts +38 -0
- package/dist/esm/global-account/react/hooks/useProfile.js +68 -0
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/react/providers/AnyspendProvider.d.ts +1 -0
- package/dist/types/anyspend/react/providers/StripeRedirectHandler.d.ts +8 -0
- package/dist/types/global-account/react/hooks/index.d.ts +1 -0
- package/dist/types/global-account/react/hooks/useProfile.d.ts +38 -0
- package/package.json +24 -23
- package/src/anyspend/react/components/AnySpendCustom.tsx +1 -1
- package/src/anyspend/react/components/common/PaymentStripeWeb2.tsx +86 -35
- package/src/anyspend/react/components/webview/WebviewOnrampPayment.tsx +39 -7
- package/src/anyspend/react/providers/AnyspendProvider.tsx +6 -1
- package/src/anyspend/react/providers/StripeRedirectHandler.tsx +45 -0
- package/src/global-account/react/components/B3DynamicModal.tsx +2 -2
- package/src/global-account/react/components/ui/dialog.tsx +3 -4
- package/src/global-account/react/components/ui/drawer.tsx +0 -2
- package/src/global-account/react/hooks/index.ts +7 -0
- package/src/global-account/react/hooks/useAccountWallet.tsx +0 -5
- package/src/global-account/react/hooks/useProfile.ts +141 -0
- package/src/styles/index.css +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { USDC_BASE } from "../../../../anyspend/index.js";
|
|
3
|
-
import { useStripeClientSecret } from "../../../../anyspend/react/index.js";
|
|
4
3
|
import { STRIPE_CONFIG } from "../../../../anyspend/constants/index.js";
|
|
5
|
-
import {
|
|
4
|
+
import { useStripeClientSecret } from "../../../../anyspend/react/index.js";
|
|
5
|
+
import { ShinyButton, useB3, useModalStore } from "../../../../global-account/react/index.js";
|
|
6
6
|
import { formatStripeAmount } from "../../../../shared/utils/payment.utils.js";
|
|
7
|
-
import { Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
7
|
+
import { AddressElement, Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
8
8
|
import { loadStripe } from "@stripe/stripe-js";
|
|
9
9
|
import { HelpCircle, Info, X } from "lucide-react";
|
|
10
10
|
import { useEffect, useState } from "react";
|
|
@@ -34,11 +34,13 @@ function StripeErrorState({ error }) {
|
|
|
34
34
|
function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
|
|
35
35
|
const stripe = useStripe();
|
|
36
36
|
const elements = useElements();
|
|
37
|
+
const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
|
|
37
38
|
const [loading, setLoading] = useState(false);
|
|
38
39
|
const [message, setMessage] = useState(null);
|
|
39
40
|
const [amount, setAmount] = useState(null);
|
|
40
41
|
const [stripeReady, setStripeReady] = useState(false);
|
|
41
42
|
const [showHowItWorks, setShowHowItWorks] = useState(false);
|
|
43
|
+
const [showAddressElement, setShowAddressElement] = useState(false);
|
|
42
44
|
useEffect(() => {
|
|
43
45
|
if (stripe && elements) {
|
|
44
46
|
setStripeReady(true);
|
|
@@ -62,6 +64,12 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
|
|
|
62
64
|
};
|
|
63
65
|
fetchPaymentIntent();
|
|
64
66
|
}, [clientSecret, stripe]);
|
|
67
|
+
// Handle payment element changes
|
|
68
|
+
const handlePaymentElementChange = (event) => {
|
|
69
|
+
// Show address element only for card payments
|
|
70
|
+
console.log("@@stripe-web2-payment:payment-element-change:", JSON.stringify(event, null, 2));
|
|
71
|
+
setShowAddressElement(event.value.type === "card");
|
|
72
|
+
};
|
|
65
73
|
const handleSubmit = async (e) => {
|
|
66
74
|
e.preventDefault();
|
|
67
75
|
if (!stripe || !elements) {
|
|
@@ -72,25 +80,29 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
|
|
|
72
80
|
setMessage(null);
|
|
73
81
|
try {
|
|
74
82
|
console.log("@@stripe-web2-payment:confirming-payment:", JSON.stringify({ orderId: order.id }, null, 2));
|
|
75
|
-
const
|
|
83
|
+
const result = (await stripe.confirmPayment({
|
|
76
84
|
elements,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
console.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// Add waitingForDeposit=true to query params
|
|
88
|
-
const currentUrl = new URL(window.location.href);
|
|
89
|
-
currentUrl.searchParams.set("waitingForDeposit", "true");
|
|
90
|
-
window.history.replaceState(null, "", currentUrl.toString());
|
|
91
|
-
// Call the success callback if provided
|
|
92
|
-
onPaymentSuccess?.(paymentIntent);
|
|
85
|
+
confirmParams: {
|
|
86
|
+
return_url: `${window.location.origin}/?orderId=${order.id}&waitingForDeposit=true&fromStripe=true`,
|
|
87
|
+
},
|
|
88
|
+
}));
|
|
89
|
+
if (result.error) {
|
|
90
|
+
// This point will only be reached if there is an immediate error.
|
|
91
|
+
// Otherwise, the customer will be redirected to the `return_url`.
|
|
92
|
+
console.error("@@stripe-web2-payment:error:", JSON.stringify(result.error, null, 2));
|
|
93
|
+
setMessage(result.error.message || "An unexpected error occurred.");
|
|
94
|
+
return;
|
|
93
95
|
}
|
|
96
|
+
// At this point TypeScript knows result.paymentIntent exists and error is undefined
|
|
97
|
+
console.log("@@stripe-web2-payment:success:", JSON.stringify({ orderId: order.id, paymentIntentId: result.paymentIntent.id }, null, 2));
|
|
98
|
+
// Payment succeeded without redirect - handle success in the modal
|
|
99
|
+
setMessage(null);
|
|
100
|
+
// Add waitingForDeposit=true to query params
|
|
101
|
+
const currentUrl = new URL(window.location.href);
|
|
102
|
+
currentUrl.searchParams.set("waitingForDeposit", "true");
|
|
103
|
+
window.history.replaceState(null, "", currentUrl.toString());
|
|
104
|
+
// Call the success callback if provided
|
|
105
|
+
onPaymentSuccess?.(result.paymentIntent);
|
|
94
106
|
}
|
|
95
107
|
catch (error) {
|
|
96
108
|
console.error("@@stripe-web2-payment:confirmation-error:", JSON.stringify(error, null, 2));
|
|
@@ -100,17 +112,27 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
|
|
|
100
112
|
setLoading(false);
|
|
101
113
|
}
|
|
102
114
|
};
|
|
115
|
+
// Handle 3DS redirect
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
// Check if we're returning from Stripe
|
|
118
|
+
const url = new URL(window.location.href);
|
|
119
|
+
const fromStripe = url.searchParams.get("fromStripe");
|
|
120
|
+
const paymentIntent = url.searchParams.get("payment_intent");
|
|
121
|
+
console.log("@@stripe-web2-payment:fromStripe:", fromStripe);
|
|
122
|
+
console.log("@@stripe-web2-payment:paymentIntent:", paymentIntent);
|
|
123
|
+
if (fromStripe && paymentIntent) {
|
|
124
|
+
// Close the modal as we're returning from 3DS
|
|
125
|
+
setB3ModalOpen(true);
|
|
126
|
+
// Clean up URL params
|
|
127
|
+
url.searchParams.delete("fromStripe");
|
|
128
|
+
window.history.replaceState({}, "", url.toString());
|
|
129
|
+
}
|
|
130
|
+
}, [setB3ModalOpen]);
|
|
103
131
|
if (!stripeReady) {
|
|
104
132
|
return _jsx(StripeLoadingState, {});
|
|
105
133
|
}
|
|
106
134
|
const stripeElementOptions = {
|
|
107
135
|
layout: "tabs",
|
|
108
|
-
defaultValues: {
|
|
109
|
-
billingDetails: {
|
|
110
|
-
name: "",
|
|
111
|
-
email: "",
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
136
|
fields: {
|
|
115
137
|
billingDetails: "auto",
|
|
116
138
|
},
|
|
@@ -138,7 +160,22 @@ function StripePaymentForm({ order, clientSecret, onPaymentSuccess, }) {
|
|
|
138
160
|
const finalAmount = Number(amount);
|
|
139
161
|
const calculatedFee = finalAmount - originalAmount;
|
|
140
162
|
return (_jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center justify-between text-sm", children: [_jsx("span", { className: "text-as-primary/60", children: "Amount" }), _jsxs("span", { className: "text-as-primary", children: ["$", originalAmount.toFixed(2)] })] }), _jsxs("div", { className: "flex items-center justify-between text-sm", children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "text-as-primary/60", children: "Processing fee" }), _jsx(Tooltip, { content: `Credit card companies charge a processing fee of 5.4% + $0.30 for all transactions.\n\nThis fee covers secure payment processing and fraud protection.`, children: _jsx(Info, { className: "text-as-primary/40 hover:text-as-primary/60 h-3 w-3 transition-colors" }) })] }), _jsxs("span", { className: "text-as-primary", children: ["$", calculatedFee.toFixed(2)] })] }), _jsx("div", { className: "border-as-stroke border-t pt-2", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "text-as-primary font-semibold", children: "Total Amount" }), _jsxs("span", { className: "text-as-primary text-2xl font-bold", children: ["$", finalAmount.toFixed(2), " USD"] })] }) })] }));
|
|
141
|
-
})()] }))] }), _jsxs("div", { className: "bg-as-on-surface-1 w-full rounded-2xl p-6", children: [_jsx("div", { className: "text-as-primary mb-4 text-lg font-semibold", children: "Payment Details" }), _jsx(PaymentElement, {
|
|
163
|
+
})()] }))] }), _jsxs("div", { className: "bg-as-on-surface-1 w-full rounded-2xl p-6", children: [_jsx("div", { className: "text-as-primary mb-4 text-lg font-semibold", children: "Payment Details" }), _jsx(PaymentElement, { onChange: handlePaymentElementChange, options: stripeElementOptions }), showAddressElement && (_jsx(AddressElement, { options: {
|
|
164
|
+
mode: "billing",
|
|
165
|
+
fields: {
|
|
166
|
+
phone: "always",
|
|
167
|
+
},
|
|
168
|
+
// More granular control
|
|
169
|
+
display: {
|
|
170
|
+
name: "split", // or 'split' for first/last name separately
|
|
171
|
+
},
|
|
172
|
+
// Validation
|
|
173
|
+
validation: {
|
|
174
|
+
phone: {
|
|
175
|
+
required: "auto", // or 'always', 'never'
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
} }))] }), message && (_jsxs("div", { className: "bg-as-red/10 border-as-red/20 flex w-full items-center gap-3 rounded-2xl border p-4", children: [_jsx("div", { className: "bg-as-red flex h-6 w-6 shrink-0 items-center justify-center rounded-full", children: _jsx("svg", { className: "h-4 w-4 text-white", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) }), _jsx("div", { className: "text-as-red text-sm font-medium", children: message })] })), _jsx(ShinyButton, { type: "submit", accentColor: "hsl(var(--as-brand))", disabled: !stripe || !elements || loading, className: "relative w-full py-4 text-lg font-semibold", children: loading ? (_jsxs("div", { className: "flex items-center justify-center gap-3", children: [_jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-current border-t-transparent" }), _jsx("span", { className: "text-white", children: "Processing Payment..." })] })) : (_jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsx("span", { className: "text-white", children: "Complete Payment" }), amount && _jsxs("span", { className: "text-white/90", children: ["$", Number(amount).toFixed(2)] })] })) })] }), showHowItWorks && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: _jsxs("div", { className: "bg-as-on-surface-1 relative max-h-[80vh] w-full max-w-2xl overflow-y-auto rounded-2xl p-6", children: [_jsxs("div", { className: "mb-6 flex items-center justify-between", children: [_jsx("h2", { className: "text-as-primary text-xl font-semibold", children: "How it works" }), _jsx("button", { onClick: () => setShowHowItWorks(false), className: "text-as-primary/60 hover:text-as-primary transition-colors", children: _jsx(X, { className: "h-6 w-6" }) })] }), _jsxs("div", { className: "space-y-6", children: [_jsx(PaymentMethodIcons, {}), _jsx(HowItWorks, { steps: howItWorksSteps })] })] }) }))] }));
|
|
142
179
|
}
|
|
143
180
|
// Add tooltip component
|
|
144
181
|
function Tooltip({ children, content }) {
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { getChainName, STRIPE_CONFIG } from "../../../../anyspend/index.js";
|
|
3
3
|
import { useAnyspendCreateOnrampOrder, useGeoOnrampOptions, useStripeClientSecret } from "../../../../anyspend/react/index.js";
|
|
4
4
|
import centerTruncate from "../../../../shared/utils/centerTruncate.js";
|
|
5
|
-
import { Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
5
|
+
import { AddressElement, Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
6
6
|
import { loadStripe } from "@stripe/stripe-js";
|
|
7
7
|
import { motion } from "framer-motion";
|
|
8
8
|
import { Loader2 } from "lucide-react";
|
|
@@ -16,6 +16,7 @@ function StripePaymentForm({ order, onPaymentSuccess, }) {
|
|
|
16
16
|
const elements = useElements();
|
|
17
17
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
18
18
|
const [error, setError] = useState(null);
|
|
19
|
+
const [showAddressElement, setShowAddressElement] = useState(false);
|
|
19
20
|
const handleSubmit = async (e) => {
|
|
20
21
|
e.preventDefault();
|
|
21
22
|
if (!stripe || !elements) {
|
|
@@ -45,16 +46,38 @@ function StripePaymentForm({ order, onPaymentSuccess, }) {
|
|
|
45
46
|
setIsProcessing(false);
|
|
46
47
|
}
|
|
47
48
|
};
|
|
49
|
+
// Handle payment element changes
|
|
50
|
+
const handlePaymentElementChange = (event) => {
|
|
51
|
+
// Show address element only for card payments
|
|
52
|
+
console.log("@@stripe-web2-payment:payment-element-change:", JSON.stringify(event, null, 2));
|
|
53
|
+
setShowAddressElement(event.value.type === "card");
|
|
54
|
+
};
|
|
48
55
|
const stripeElementOptions = {
|
|
49
56
|
layout: "tabs",
|
|
50
|
-
|
|
51
|
-
billingDetails:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
fields: {
|
|
58
|
+
billingDetails: "auto",
|
|
59
|
+
},
|
|
60
|
+
wallets: {
|
|
61
|
+
applePay: "auto",
|
|
62
|
+
googlePay: "auto",
|
|
55
63
|
},
|
|
56
64
|
};
|
|
57
|
-
return (_jsx("form", { onSubmit: handleSubmit, className: "w-full", children: _jsx("div", { className: "overflow-hidden rounded-xl bg-white", children: _jsxs("div", { className: "px-6 py-4", children: [_jsx("h2", { className: "mb-4 text-lg font-semibold", children: "Payment Details" }), _jsx(PaymentElement, { options: stripeElementOptions }),
|
|
65
|
+
return (_jsx("form", { onSubmit: handleSubmit, className: "w-full", children: _jsx("div", { className: "overflow-hidden rounded-xl bg-white", children: _jsxs("div", { className: "px-6 py-4", children: [_jsx("h2", { className: "mb-4 text-lg font-semibold", children: "Payment Details" }), _jsx(PaymentElement, { options: stripeElementOptions, onChange: handlePaymentElementChange }), showAddressElement && (_jsx("div", { className: "mt-4", children: _jsx(AddressElement, { options: {
|
|
66
|
+
mode: "billing",
|
|
67
|
+
fields: {
|
|
68
|
+
phone: "always",
|
|
69
|
+
},
|
|
70
|
+
// More granular control
|
|
71
|
+
display: {
|
|
72
|
+
name: "split", // or 'split' for first/last name separately
|
|
73
|
+
},
|
|
74
|
+
// Validation
|
|
75
|
+
validation: {
|
|
76
|
+
phone: {
|
|
77
|
+
required: "auto", // or 'always', 'never'
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
} }) })), error && (_jsx("div", { className: "mt-4 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-600", children: error })), _jsx("button", { type: "submit", disabled: !stripe || isProcessing, className: "mt-6 w-full rounded-xl bg-blue-600 px-4 py-3 font-medium text-white hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50", children: isProcessing ? (_jsxs("div", { className: "flex items-center justify-center gap-2", children: [_jsx(Loader2, { className: "h-5 w-5 animate-spin" }), _jsx("span", { children: "Processing..." })] })) : (_jsx("span", { children: "Complete Payment" })) })] }) }) }));
|
|
58
81
|
}
|
|
59
82
|
export function WebviewOnrampPayment({ srcAmountOnRamp, recipientAddress, destinationToken, anyspendQuote, onPaymentSuccess, userId, partnerId, }) {
|
|
60
83
|
const [stableAmountForGeo, setStableAmountForGeo] = useState(srcAmountOnRamp);
|
|
@@ -11,6 +11,7 @@ interface AnyspendProviderProps {
|
|
|
11
11
|
* - Optimized for performance with React.memo
|
|
12
12
|
* - Safe to use at the application root
|
|
13
13
|
* - Configures sensible defaults for query caching
|
|
14
|
+
* - Handles Stripe payment redirects and modal state
|
|
14
15
|
*
|
|
15
16
|
* @example
|
|
16
17
|
* ```tsx
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { TooltipProvider } from "../../../global-account/react/index.js";
|
|
4
4
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
5
|
import { useState } from "react";
|
|
6
|
+
import { StripeRedirectHandler } from "./StripeRedirectHandler.js";
|
|
6
7
|
const defaultQueryClientConfig = {
|
|
7
8
|
defaultOptions: {
|
|
8
9
|
queries: {
|
|
@@ -21,6 +22,7 @@ const defaultQueryClientConfig = {
|
|
|
21
22
|
* - Optimized for performance with React.memo
|
|
22
23
|
* - Safe to use at the application root
|
|
23
24
|
* - Configures sensible defaults for query caching
|
|
25
|
+
* - Handles Stripe payment redirects and modal state
|
|
24
26
|
*
|
|
25
27
|
* @example
|
|
26
28
|
* ```tsx
|
|
@@ -35,5 +37,5 @@ const defaultQueryClientConfig = {
|
|
|
35
37
|
*/
|
|
36
38
|
export const AnyspendProvider = function AnyspendProvider({ children }) {
|
|
37
39
|
const [queryClient] = useState(() => new QueryClient(defaultQueryClientConfig));
|
|
38
|
-
return (_jsx(QueryClientProvider, { client: queryClient, children:
|
|
40
|
+
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsxs(TooltipProvider, { children: [_jsx(StripeRedirectHandler, {}), children] }) }));
|
|
39
41
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles Stripe payment redirects by showing the order details modal
|
|
3
|
+
* when returning from Stripe's payment flow.
|
|
4
|
+
*
|
|
5
|
+
* This component should be mounted inside AnyspendProvider to ensure
|
|
6
|
+
* proper modal state management.
|
|
7
|
+
*/
|
|
8
|
+
export declare function StripeRedirectHandler(): null;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useModalStore } from "../../../global-account/react/index.js";
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* Handles Stripe payment redirects by showing the order details modal
|
|
6
|
+
* when returning from Stripe's payment flow.
|
|
7
|
+
*
|
|
8
|
+
* This component should be mounted inside AnyspendProvider to ensure
|
|
9
|
+
* proper modal state management.
|
|
10
|
+
*/
|
|
11
|
+
export function StripeRedirectHandler() {
|
|
12
|
+
const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
|
|
13
|
+
const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const url = new URL(window.location.href);
|
|
16
|
+
const fromStripe = url.searchParams.get("fromStripe");
|
|
17
|
+
const paymentIntent = url.searchParams.get("payment_intent");
|
|
18
|
+
const orderId = url.searchParams.get("orderId");
|
|
19
|
+
console.log("@@stripe-web2-payment:fromStripe:", fromStripe);
|
|
20
|
+
console.log("@@stripe-web2-payment:paymentIntent:", paymentIntent);
|
|
21
|
+
console.log("@@stripe-web2-payment:orderId:", orderId);
|
|
22
|
+
if (fromStripe && paymentIntent && orderId) {
|
|
23
|
+
// Re-open the modal with the order details
|
|
24
|
+
setB3ModalOpen(true);
|
|
25
|
+
setB3ModalContentType({
|
|
26
|
+
type: "anyspendOrderDetails",
|
|
27
|
+
orderId,
|
|
28
|
+
});
|
|
29
|
+
// Clean up URL params
|
|
30
|
+
url.searchParams.delete("fromStripe");
|
|
31
|
+
url.searchParams.delete("payment_intent");
|
|
32
|
+
url.searchParams.delete("payment_intent_client_secret");
|
|
33
|
+
url.searchParams.delete("redirect_status");
|
|
34
|
+
window.history.replaceState({}, "", url.toString());
|
|
35
|
+
}
|
|
36
|
+
}, [setB3ModalOpen, setB3ModalContentType]);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
@@ -28,7 +28,7 @@ export function B3DynamicModal() {
|
|
|
28
28
|
"anySpendSignatureMint",
|
|
29
29
|
"anySpendBondKit",
|
|
30
30
|
].find(type => contentType?.type === type)) {
|
|
31
|
-
contentClass += "
|
|
31
|
+
contentClass += " w-full";
|
|
32
32
|
}
|
|
33
33
|
if ([
|
|
34
34
|
"anySpendNft",
|
|
@@ -90,5 +90,5 @@ export function B3DynamicModal() {
|
|
|
90
90
|
const ModalContent = isMobile ? DrawerContent : DialogContent;
|
|
91
91
|
const ModalTitle = isMobile ? DrawerTitle : DialogTitle;
|
|
92
92
|
const ModalDescription = isMobile ? DrawerDescription : DialogDescription;
|
|
93
|
-
return (_jsx(ModalComponent, { open: isOpen, onOpenChange: setB3ModalOpen, children: _jsxs(ModalContent, { className: contentClass, hideCloseButton: hideCloseButton, children: [_jsx(ModalTitle, { className: "sr-only hidden", children: contentType?.type || "Modal" }), _jsx(ModalDescription, { className: "sr-only hidden", children: contentType?.type || "Modal Body" }), _jsxs("div", { className: "overflow-auto", children: [history.length > 0 && contentType?.showBackButton && (_jsxs("button", { onClick: navigateBack, className: "b3-modal-back-button mb-4 flex items-center gap-2 transition-colors hover:text-white", children: [_jsx("span", { children: "\u2190" }), _jsx("span", { className: "text-sm", children: "Back" })] })), renderContent()] })] }) }));
|
|
93
|
+
return (_jsx(ModalComponent, { open: isOpen, onOpenChange: setB3ModalOpen, children: _jsxs(ModalContent, { className: contentClass, hideCloseButton: hideCloseButton, children: [_jsx(ModalTitle, { className: "sr-only hidden", children: contentType?.type || "Modal" }), _jsx(ModalDescription, { className: "sr-only hidden", children: contentType?.type || "Modal Body" }), _jsxs("div", { className: "no-scrollbar max-h-[90dvh] overflow-auto sm:max-h-[80dvh]", children: [history.length > 0 && contentType?.showBackButton && (_jsxs("button", { onClick: navigateBack, className: "b3-modal-back-button mb-4 flex items-center gap-2 transition-colors hover:text-white", children: [_jsx("span", { children: "\u2190" }), _jsx("span", { className: "text-sm", children: "Back" })] })), renderContent()] })] }) }));
|
|
94
94
|
}
|
|
@@ -12,7 +12,7 @@ const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (_jsx(D
|
|
|
12
12
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
13
13
|
const DialogContent = React.forwardRef(({ className, children, hideCloseButton = false, closeBtnClassName, ...props }, ref) => {
|
|
14
14
|
const container = typeof window !== "undefined" ? document.getElementById("b3-root") : null;
|
|
15
|
-
return (_jsxs(DialogPortal, { container: container, children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn("bg-b3-react-background fixed left-
|
|
15
|
+
return (_jsxs(DialogPortal, { container: container, children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn("bg-b3-react-background fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border p-6 shadow-lg !outline-none", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 duration-500", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]", "[perspective:1200px] [transform-style:preserve-3d] sm:rounded-xl", "transition-all ease-out", className), ...props, children: [children, !hideCloseButton && (_jsxs(DialogPrimitive.Close, { className: cn("data-[state=open]:bg-b3-react-background data-[state=open]:text-b3-react-muted-foreground absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none dark:data-[state=open]:bg-gray-800 dark:data-[state=open]:text-gray-400", closeBtnClassName), children: [_jsx(X, { className: "h-5 w-5" }), _jsx("span", { className: "sr-only", children: "Close" })] }))] })] }));
|
|
16
16
|
});
|
|
17
17
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
18
18
|
const DialogHeader = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className), ...props }));
|
|
@@ -20,6 +20,7 @@ export { useMediaQuery } from "./useMediaQuery";
|
|
|
20
20
|
export { useNativeBalance, useNativeBalanceFromRPC } from "./useNativeBalance";
|
|
21
21
|
export { useOnchainName } from "./useOnchainName";
|
|
22
22
|
export { useOneBalance } from "./useOneBalance";
|
|
23
|
+
export { useProfile, useProfilePreference, type Profile, type CombinedProfile, type PreferenceRequestBody, } from "./useProfile";
|
|
23
24
|
export { useQueryB3 } from "./useQueryB3";
|
|
24
25
|
export { useQueryBSMNT } from "./useQueryBSMNT";
|
|
25
26
|
export { useRemoveSessionKey } from "./useRemoveSessionKey";
|
|
@@ -20,6 +20,7 @@ export { useMediaQuery } from "./useMediaQuery.js";
|
|
|
20
20
|
export { useNativeBalance, useNativeBalanceFromRPC } from "./useNativeBalance.js";
|
|
21
21
|
export { useOnchainName } from "./useOnchainName.js";
|
|
22
22
|
export { useOneBalance } from "./useOneBalance.js";
|
|
23
|
+
export { useProfile, useProfilePreference, } from "./useProfile.js";
|
|
23
24
|
export { useQueryB3 } from "./useQueryB3.js";
|
|
24
25
|
export { useQueryBSMNT } from "./useQueryBSMNT.js";
|
|
25
26
|
export { useRemoveSessionKey } from "./useRemoveSessionKey.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface Profile {
|
|
2
|
+
type: string;
|
|
3
|
+
address?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
avatar?: string | null;
|
|
6
|
+
bio?: string | null;
|
|
7
|
+
displayName?: string | null;
|
|
8
|
+
}
|
|
9
|
+
export interface CombinedProfile {
|
|
10
|
+
name: string | null;
|
|
11
|
+
address: string | null;
|
|
12
|
+
avatar: string | null;
|
|
13
|
+
bio: string | null;
|
|
14
|
+
displayName: string | null;
|
|
15
|
+
profiles: Profile[];
|
|
16
|
+
}
|
|
17
|
+
export interface PreferenceRequestBody {
|
|
18
|
+
key: string;
|
|
19
|
+
preferredType: string;
|
|
20
|
+
signature: string;
|
|
21
|
+
signer: string;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function useProfile({ address, name, fresh, }: {
|
|
25
|
+
address?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
fresh?: boolean;
|
|
28
|
+
}, options?: {
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
refetchInterval?: number;
|
|
31
|
+
staleTime?: number;
|
|
32
|
+
}): import("@tanstack/react-query").UseQueryResult<CombinedProfile, Error>;
|
|
33
|
+
export declare function useProfilePreference(): {
|
|
34
|
+
setPreference: (key: string, preferredType: string, signerAddress: string, signMessage: (message: string) => Promise<string>) => Promise<{
|
|
35
|
+
success: boolean;
|
|
36
|
+
}>;
|
|
37
|
+
};
|
|
38
|
+
export default useProfile;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
const PROFILES_API_URL = "https://profiles.b3.fun";
|
|
3
|
+
async function fetchProfile({ address, name, fresh = false, }) {
|
|
4
|
+
if (!address && !name) {
|
|
5
|
+
throw new Error("Either address or name must be provided");
|
|
6
|
+
}
|
|
7
|
+
const params = new URLSearchParams();
|
|
8
|
+
if (address)
|
|
9
|
+
params.append("address", address);
|
|
10
|
+
if (name)
|
|
11
|
+
params.append("name", name);
|
|
12
|
+
if (fresh)
|
|
13
|
+
params.append("fresh", "true");
|
|
14
|
+
const response = await fetch(`${PROFILES_API_URL}?${params.toString()}`);
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
throw new Error(`Failed to fetch profile: ${response.statusText}`);
|
|
17
|
+
}
|
|
18
|
+
return response.json();
|
|
19
|
+
}
|
|
20
|
+
async function setProfilePreference({ key, preferredType, signature, signer, timestamp, }) {
|
|
21
|
+
const response = await fetch(`${PROFILES_API_URL}/preference`, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
key,
|
|
28
|
+
preferredType,
|
|
29
|
+
signature,
|
|
30
|
+
signer,
|
|
31
|
+
timestamp,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`Failed to set preference: ${response.statusText}`);
|
|
36
|
+
}
|
|
37
|
+
return response.json();
|
|
38
|
+
}
|
|
39
|
+
export function useProfile({ address, name, fresh = false, }, options) {
|
|
40
|
+
return useQuery({
|
|
41
|
+
queryKey: ["profile", address || name, fresh],
|
|
42
|
+
queryFn: () => fetchProfile({ address, name, fresh }),
|
|
43
|
+
enabled: (options?.enabled ?? true) && (!!address || !!name),
|
|
44
|
+
refetchInterval: options?.refetchInterval,
|
|
45
|
+
staleTime: options?.staleTime ?? 5 * 60 * 1000, // 5 minutes default
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function useProfilePreference() {
|
|
49
|
+
const setPreference = async (key, preferredType, signerAddress, signMessage) => {
|
|
50
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
51
|
+
const message = `SetProfilePreference:${key}:${preferredType}:${timestamp}`;
|
|
52
|
+
try {
|
|
53
|
+
const signature = await signMessage(message);
|
|
54
|
+
return setProfilePreference({
|
|
55
|
+
key,
|
|
56
|
+
preferredType,
|
|
57
|
+
signature,
|
|
58
|
+
signer: signerAddress,
|
|
59
|
+
timestamp,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new Error(`Failed to set profile preference: ${error}`);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
return { setPreference };
|
|
67
|
+
}
|
|
68
|
+
export default useProfile;
|