@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,15 +1,15 @@
|
|
|
1
1
|
import { USDC_BASE } from "@b3dotfun/sdk/anyspend";
|
|
2
|
-
import { useStripeClientSecret } from "@b3dotfun/sdk/anyspend/react";
|
|
3
2
|
import { STRIPE_CONFIG } from "@b3dotfun/sdk/anyspend/constants";
|
|
4
|
-
import {
|
|
3
|
+
import { useStripeClientSecret } from "@b3dotfun/sdk/anyspend/react";
|
|
4
|
+
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
5
|
+
import { ShinyButton, useB3, useModalStore } from "@b3dotfun/sdk/global-account/react";
|
|
5
6
|
import { formatStripeAmount } from "@b3dotfun/sdk/shared/utils/payment.utils";
|
|
6
|
-
import { Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
7
|
-
import { loadStripe } from "@stripe/stripe-js";
|
|
7
|
+
import { AddressElement, Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
8
|
+
import { loadStripe, PaymentIntentResult, StripePaymentElementOptions } from "@stripe/stripe-js";
|
|
8
9
|
import { HelpCircle, Info, X } from "lucide-react";
|
|
9
10
|
import { useEffect, useState } from "react";
|
|
10
11
|
import HowItWorks from "./HowItWorks";
|
|
11
12
|
import PaymentMethodIcons from "./PaymentMethodIcons";
|
|
12
|
-
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
13
13
|
|
|
14
14
|
const stripePromise = loadStripe(STRIPE_CONFIG.publishableKey);
|
|
15
15
|
|
|
@@ -90,12 +90,14 @@ function StripePaymentForm({
|
|
|
90
90
|
}) {
|
|
91
91
|
const stripe = useStripe();
|
|
92
92
|
const elements = useElements();
|
|
93
|
+
const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
|
|
93
94
|
|
|
94
95
|
const [loading, setLoading] = useState<boolean>(false);
|
|
95
96
|
const [message, setMessage] = useState<string | null>(null);
|
|
96
97
|
const [amount, setAmount] = useState<string | null>(null);
|
|
97
98
|
const [stripeReady, setStripeReady] = useState<boolean>(false);
|
|
98
99
|
const [showHowItWorks, setShowHowItWorks] = useState<boolean>(false);
|
|
100
|
+
const [showAddressElement, setShowAddressElement] = useState<boolean>(false);
|
|
99
101
|
|
|
100
102
|
useEffect(() => {
|
|
101
103
|
if (stripe && elements) {
|
|
@@ -122,6 +124,13 @@ function StripePaymentForm({
|
|
|
122
124
|
fetchPaymentIntent();
|
|
123
125
|
}, [clientSecret, stripe]);
|
|
124
126
|
|
|
127
|
+
// Handle payment element changes
|
|
128
|
+
const handlePaymentElementChange = (event: any) => {
|
|
129
|
+
// Show address element only for card payments
|
|
130
|
+
console.log("@@stripe-web2-payment:payment-element-change:", JSON.stringify(event, null, 2));
|
|
131
|
+
setShowAddressElement(event.value.type === "card");
|
|
132
|
+
};
|
|
133
|
+
|
|
125
134
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
126
135
|
e.preventDefault();
|
|
127
136
|
|
|
@@ -135,30 +144,38 @@ function StripePaymentForm({
|
|
|
135
144
|
|
|
136
145
|
try {
|
|
137
146
|
console.log("@@stripe-web2-payment:confirming-payment:", JSON.stringify({ orderId: order.id }, null, 2));
|
|
138
|
-
|
|
147
|
+
|
|
148
|
+
const result = (await stripe.confirmPayment({
|
|
139
149
|
elements,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
// Payment succeeded without redirect - handle success in the modal
|
|
152
|
-
setMessage(null);
|
|
153
|
-
|
|
154
|
-
// Add waitingForDeposit=true to query params
|
|
155
|
-
const currentUrl = new URL(window.location.href);
|
|
156
|
-
currentUrl.searchParams.set("waitingForDeposit", "true");
|
|
157
|
-
window.history.replaceState(null, "", currentUrl.toString());
|
|
158
|
-
|
|
159
|
-
// Call the success callback if provided
|
|
160
|
-
onPaymentSuccess?.(paymentIntent);
|
|
150
|
+
confirmParams: {
|
|
151
|
+
return_url: `${window.location.origin}/?orderId=${order.id}&waitingForDeposit=true&fromStripe=true`,
|
|
152
|
+
},
|
|
153
|
+
})) as PaymentIntentResult;
|
|
154
|
+
|
|
155
|
+
if (result.error) {
|
|
156
|
+
// This point will only be reached if there is an immediate error.
|
|
157
|
+
// Otherwise, the customer will be redirected to the `return_url`.
|
|
158
|
+
console.error("@@stripe-web2-payment:error:", JSON.stringify(result.error, null, 2));
|
|
159
|
+
setMessage(result.error.message || "An unexpected error occurred.");
|
|
160
|
+
return;
|
|
161
161
|
}
|
|
162
|
+
|
|
163
|
+
// At this point TypeScript knows result.paymentIntent exists and error is undefined
|
|
164
|
+
console.log(
|
|
165
|
+
"@@stripe-web2-payment:success:",
|
|
166
|
+
JSON.stringify({ orderId: order.id, paymentIntentId: result.paymentIntent.id }, null, 2),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Payment succeeded without redirect - handle success in the modal
|
|
170
|
+
setMessage(null);
|
|
171
|
+
|
|
172
|
+
// Add waitingForDeposit=true to query params
|
|
173
|
+
const currentUrl = new URL(window.location.href);
|
|
174
|
+
currentUrl.searchParams.set("waitingForDeposit", "true");
|
|
175
|
+
window.history.replaceState(null, "", currentUrl.toString());
|
|
176
|
+
|
|
177
|
+
// Call the success callback if provided
|
|
178
|
+
onPaymentSuccess?.(result.paymentIntent);
|
|
162
179
|
} catch (error) {
|
|
163
180
|
console.error("@@stripe-web2-payment:confirmation-error:", JSON.stringify(error, null, 2));
|
|
164
181
|
setMessage("Payment failed. Please try again.");
|
|
@@ -167,18 +184,32 @@ function StripePaymentForm({
|
|
|
167
184
|
}
|
|
168
185
|
};
|
|
169
186
|
|
|
187
|
+
// Handle 3DS redirect
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
// Check if we're returning from Stripe
|
|
190
|
+
const url = new URL(window.location.href);
|
|
191
|
+
const fromStripe = url.searchParams.get("fromStripe");
|
|
192
|
+
const paymentIntent = url.searchParams.get("payment_intent");
|
|
193
|
+
|
|
194
|
+
console.log("@@stripe-web2-payment:fromStripe:", fromStripe);
|
|
195
|
+
console.log("@@stripe-web2-payment:paymentIntent:", paymentIntent);
|
|
196
|
+
|
|
197
|
+
if (fromStripe && paymentIntent) {
|
|
198
|
+
// Close the modal as we're returning from 3DS
|
|
199
|
+
setB3ModalOpen(true);
|
|
200
|
+
|
|
201
|
+
// Clean up URL params
|
|
202
|
+
url.searchParams.delete("fromStripe");
|
|
203
|
+
window.history.replaceState({}, "", url.toString());
|
|
204
|
+
}
|
|
205
|
+
}, [setB3ModalOpen]);
|
|
206
|
+
|
|
170
207
|
if (!stripeReady) {
|
|
171
208
|
return <StripeLoadingState />;
|
|
172
209
|
}
|
|
173
210
|
|
|
174
|
-
const stripeElementOptions = {
|
|
211
|
+
const stripeElementOptions: StripePaymentElementOptions = {
|
|
175
212
|
layout: "tabs" as const,
|
|
176
|
-
defaultValues: {
|
|
177
|
-
billingDetails: {
|
|
178
|
-
name: "",
|
|
179
|
-
email: "",
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
213
|
fields: {
|
|
183
214
|
billingDetails: "auto" as const,
|
|
184
215
|
},
|
|
@@ -282,7 +313,27 @@ function StripePaymentForm({
|
|
|
282
313
|
{/* Simplified Payment Form */}
|
|
283
314
|
<div className="bg-as-on-surface-1 w-full rounded-2xl p-6">
|
|
284
315
|
<div className="text-as-primary mb-4 text-lg font-semibold">Payment Details</div>
|
|
285
|
-
<PaymentElement options={stripeElementOptions} />
|
|
316
|
+
<PaymentElement onChange={handlePaymentElementChange} options={stripeElementOptions} />
|
|
317
|
+
{showAddressElement && (
|
|
318
|
+
<AddressElement
|
|
319
|
+
options={{
|
|
320
|
+
mode: "billing",
|
|
321
|
+
fields: {
|
|
322
|
+
phone: "always",
|
|
323
|
+
},
|
|
324
|
+
// More granular control
|
|
325
|
+
display: {
|
|
326
|
+
name: "split", // or 'split' for first/last name separately
|
|
327
|
+
},
|
|
328
|
+
// Validation
|
|
329
|
+
validation: {
|
|
330
|
+
phone: {
|
|
331
|
+
required: "auto", // or 'always', 'never'
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
}}
|
|
335
|
+
/>
|
|
336
|
+
)}
|
|
286
337
|
</div>
|
|
287
338
|
|
|
288
339
|
{/* Error Message */}
|
|
@@ -3,7 +3,7 @@ import { useAnyspendCreateOnrampOrder, useGeoOnrampOptions, useStripeClientSecre
|
|
|
3
3
|
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
4
4
|
import { GetQuoteResponse } from "@b3dotfun/sdk/anyspend/types/api_req_res";
|
|
5
5
|
import centerTruncate from "@b3dotfun/sdk/shared/utils/centerTruncate";
|
|
6
|
-
import { Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
6
|
+
import { AddressElement, Elements, PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
7
7
|
import { loadStripe } from "@stripe/stripe-js";
|
|
8
8
|
import { motion } from "framer-motion";
|
|
9
9
|
import { Loader2 } from "lucide-react";
|
|
@@ -35,6 +35,7 @@ function StripePaymentForm({
|
|
|
35
35
|
const elements = useElements();
|
|
36
36
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
37
37
|
const [error, setError] = useState<string | null>(null);
|
|
38
|
+
const [showAddressElement, setShowAddressElement] = useState<boolean>(false);
|
|
38
39
|
|
|
39
40
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
40
41
|
e.preventDefault();
|
|
@@ -67,13 +68,21 @@ function StripePaymentForm({
|
|
|
67
68
|
}
|
|
68
69
|
};
|
|
69
70
|
|
|
71
|
+
// Handle payment element changes
|
|
72
|
+
const handlePaymentElementChange = (event: any) => {
|
|
73
|
+
// Show address element only for card payments
|
|
74
|
+
console.log("@@stripe-web2-payment:payment-element-change:", JSON.stringify(event, null, 2));
|
|
75
|
+
setShowAddressElement(event.value.type === "card");
|
|
76
|
+
};
|
|
77
|
+
|
|
70
78
|
const stripeElementOptions = {
|
|
71
79
|
layout: "tabs" as const,
|
|
72
|
-
|
|
73
|
-
billingDetails:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
fields: {
|
|
81
|
+
billingDetails: "auto" as const,
|
|
82
|
+
},
|
|
83
|
+
wallets: {
|
|
84
|
+
applePay: "auto" as const,
|
|
85
|
+
googlePay: "auto" as const,
|
|
77
86
|
},
|
|
78
87
|
};
|
|
79
88
|
|
|
@@ -82,7 +91,30 @@ function StripePaymentForm({
|
|
|
82
91
|
<div className="overflow-hidden rounded-xl bg-white">
|
|
83
92
|
<div className="px-6 py-4">
|
|
84
93
|
<h2 className="mb-4 text-lg font-semibold">Payment Details</h2>
|
|
85
|
-
<PaymentElement options={stripeElementOptions} />
|
|
94
|
+
<PaymentElement options={stripeElementOptions} onChange={handlePaymentElementChange} />
|
|
95
|
+
|
|
96
|
+
{showAddressElement && (
|
|
97
|
+
<div className="mt-4">
|
|
98
|
+
<AddressElement
|
|
99
|
+
options={{
|
|
100
|
+
mode: "billing",
|
|
101
|
+
fields: {
|
|
102
|
+
phone: "always",
|
|
103
|
+
},
|
|
104
|
+
// More granular control
|
|
105
|
+
display: {
|
|
106
|
+
name: "split", // or 'split' for first/last name separately
|
|
107
|
+
},
|
|
108
|
+
// Validation
|
|
109
|
+
validation: {
|
|
110
|
+
phone: {
|
|
111
|
+
required: "auto", // or 'always', 'never'
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
}}
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
86
118
|
|
|
87
119
|
{error && (
|
|
88
120
|
<div className="mt-4 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-600">{error}</div>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { TooltipProvider } from "@b3dotfun/sdk/global-account/react";
|
|
4
4
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
5
|
import { ReactNode, useState } from "react";
|
|
6
|
+
import { StripeRedirectHandler } from "./StripeRedirectHandler";
|
|
6
7
|
|
|
7
8
|
interface AnyspendProviderProps {
|
|
8
9
|
children: ReactNode;
|
|
@@ -27,6 +28,7 @@ const defaultQueryClientConfig = {
|
|
|
27
28
|
* - Optimized for performance with React.memo
|
|
28
29
|
* - Safe to use at the application root
|
|
29
30
|
* - Configures sensible defaults for query caching
|
|
31
|
+
* - Handles Stripe payment redirects and modal state
|
|
30
32
|
*
|
|
31
33
|
* @example
|
|
32
34
|
* ```tsx
|
|
@@ -44,7 +46,10 @@ export const AnyspendProvider = function AnyspendProvider({ children }: Anyspend
|
|
|
44
46
|
|
|
45
47
|
return (
|
|
46
48
|
<QueryClientProvider client={queryClient}>
|
|
47
|
-
<TooltipProvider>
|
|
49
|
+
<TooltipProvider>
|
|
50
|
+
<StripeRedirectHandler />
|
|
51
|
+
{children}
|
|
52
|
+
</TooltipProvider>
|
|
48
53
|
</QueryClientProvider>
|
|
49
54
|
);
|
|
50
55
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useModalStore } from "@b3dotfun/sdk/global-account/react";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Handles Stripe payment redirects by showing the order details modal
|
|
8
|
+
* when returning from Stripe's payment flow.
|
|
9
|
+
*
|
|
10
|
+
* This component should be mounted inside AnyspendProvider to ensure
|
|
11
|
+
* proper modal state management.
|
|
12
|
+
*/
|
|
13
|
+
export function StripeRedirectHandler() {
|
|
14
|
+
const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
|
|
15
|
+
const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const url = new URL(window.location.href);
|
|
19
|
+
const fromStripe = url.searchParams.get("fromStripe");
|
|
20
|
+
const paymentIntent = url.searchParams.get("payment_intent");
|
|
21
|
+
const orderId = url.searchParams.get("orderId");
|
|
22
|
+
|
|
23
|
+
console.log("@@stripe-web2-payment:fromStripe:", fromStripe);
|
|
24
|
+
console.log("@@stripe-web2-payment:paymentIntent:", paymentIntent);
|
|
25
|
+
console.log("@@stripe-web2-payment:orderId:", orderId);
|
|
26
|
+
|
|
27
|
+
if (fromStripe && paymentIntent && orderId) {
|
|
28
|
+
// Re-open the modal with the order details
|
|
29
|
+
setB3ModalOpen(true);
|
|
30
|
+
setB3ModalContentType({
|
|
31
|
+
type: "anyspendOrderDetails",
|
|
32
|
+
orderId,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Clean up URL params
|
|
36
|
+
url.searchParams.delete("fromStripe");
|
|
37
|
+
url.searchParams.delete("payment_intent");
|
|
38
|
+
url.searchParams.delete("payment_intent_client_secret");
|
|
39
|
+
url.searchParams.delete("redirect_status");
|
|
40
|
+
window.history.replaceState({}, "", url.toString());
|
|
41
|
+
}
|
|
42
|
+
}, [setB3ModalOpen, setB3ModalContentType]);
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
@@ -41,7 +41,7 @@ export function B3DynamicModal() {
|
|
|
41
41
|
"anySpendBondKit",
|
|
42
42
|
].find(type => contentType?.type === type)
|
|
43
43
|
) {
|
|
44
|
-
contentClass += "
|
|
44
|
+
contentClass += " w-full";
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
if (
|
|
@@ -115,7 +115,7 @@ export function B3DynamicModal() {
|
|
|
115
115
|
<ModalContent className={contentClass} hideCloseButton={hideCloseButton}>
|
|
116
116
|
<ModalTitle className="sr-only hidden">{contentType?.type || "Modal"}</ModalTitle>
|
|
117
117
|
<ModalDescription className="sr-only hidden">{contentType?.type || "Modal Body"}</ModalDescription>
|
|
118
|
-
<div className="overflow-auto">
|
|
118
|
+
<div className="no-scrollbar max-h-[90dvh] overflow-auto sm:max-h-[80dvh]">
|
|
119
119
|
{history.length > 0 && contentType?.showBackButton && (
|
|
120
120
|
<button
|
|
121
121
|
onClick={navigateBack}
|
|
@@ -50,13 +50,12 @@ const DialogContent: React.ForwardRefExoticComponent<DialogContentProps & React.
|
|
|
50
50
|
<DialogPrimitive.Content
|
|
51
51
|
ref={ref}
|
|
52
52
|
className={cn(
|
|
53
|
-
"bg-b3-react-background fixed left-
|
|
53
|
+
"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",
|
|
54
54
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 duration-500",
|
|
55
55
|
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
56
56
|
"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%]",
|
|
57
|
-
"data-[state=closed]:rotate-x-10 data-[state=open]:rotate-x-0",
|
|
58
57
|
"[perspective:1200px] [transform-style:preserve-3d] sm:rounded-xl",
|
|
59
|
-
"
|
|
58
|
+
"transition-all ease-out",
|
|
60
59
|
className,
|
|
61
60
|
)}
|
|
62
61
|
{...props}
|
|
@@ -65,7 +64,7 @@ const DialogContent: React.ForwardRefExoticComponent<DialogContentProps & React.
|
|
|
65
64
|
{!hideCloseButton && (
|
|
66
65
|
<DialogPrimitive.Close
|
|
67
66
|
className={cn(
|
|
68
|
-
"data-[state=open]:bg-
|
|
67
|
+
"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",
|
|
69
68
|
closeBtnClassName,
|
|
70
69
|
)}
|
|
71
70
|
>
|
|
@@ -40,9 +40,7 @@ const DrawerContent = React.forwardRef<
|
|
|
40
40
|
)}
|
|
41
41
|
{...props}
|
|
42
42
|
>
|
|
43
|
-
{/* <div className="bg-b3-react-muted mx-auto mt-4 h-2 w-[100px] rounded-full" /> */}
|
|
44
43
|
{children}
|
|
45
|
-
{/* <div className="bg-b3-react-muted mx-auto mt-4 h-2 w-[100px] rounded-full" /> */}
|
|
46
44
|
</DrawerPrimitive.Content>
|
|
47
45
|
</DrawerPortal>
|
|
48
46
|
);
|
|
@@ -20,6 +20,13 @@ 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 {
|
|
24
|
+
useProfile,
|
|
25
|
+
useProfilePreference,
|
|
26
|
+
type Profile,
|
|
27
|
+
type CombinedProfile,
|
|
28
|
+
type PreferenceRequestBody,
|
|
29
|
+
} from "./useProfile";
|
|
23
30
|
export { useQueryB3 } from "./useQueryB3";
|
|
24
31
|
export { useQueryBSMNT } from "./useQueryBSMNT";
|
|
25
32
|
export { useRemoveSessionKey } from "./useRemoveSessionKey";
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
// TypeScript interface for profile data
|
|
4
|
+
export interface Profile {
|
|
5
|
+
type: string;
|
|
6
|
+
address?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
avatar?: string | null;
|
|
9
|
+
bio?: string | null;
|
|
10
|
+
displayName?: string | null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CombinedProfile {
|
|
14
|
+
name: string | null;
|
|
15
|
+
address: string | null;
|
|
16
|
+
avatar: string | null;
|
|
17
|
+
bio: string | null;
|
|
18
|
+
displayName: string | null;
|
|
19
|
+
profiles: Profile[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// TypeScript interface for preference request body
|
|
23
|
+
export interface PreferenceRequestBody {
|
|
24
|
+
key: string;
|
|
25
|
+
preferredType: string;
|
|
26
|
+
signature: string;
|
|
27
|
+
signer: string;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const PROFILES_API_URL = "https://profiles.b3.fun";
|
|
32
|
+
|
|
33
|
+
async function fetchProfile({
|
|
34
|
+
address,
|
|
35
|
+
name,
|
|
36
|
+
fresh = false,
|
|
37
|
+
}: {
|
|
38
|
+
address?: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
fresh?: boolean;
|
|
41
|
+
}): Promise<CombinedProfile> {
|
|
42
|
+
if (!address && !name) {
|
|
43
|
+
throw new Error("Either address or name must be provided");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const params = new URLSearchParams();
|
|
47
|
+
if (address) params.append("address", address);
|
|
48
|
+
if (name) params.append("name", name);
|
|
49
|
+
if (fresh) params.append("fresh", "true");
|
|
50
|
+
|
|
51
|
+
const response = await fetch(`${PROFILES_API_URL}?${params.toString()}`);
|
|
52
|
+
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new Error(`Failed to fetch profile: ${response.statusText}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return response.json();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function setProfilePreference({
|
|
61
|
+
key,
|
|
62
|
+
preferredType,
|
|
63
|
+
signature,
|
|
64
|
+
signer,
|
|
65
|
+
timestamp,
|
|
66
|
+
}: PreferenceRequestBody): Promise<{ success: boolean }> {
|
|
67
|
+
const response = await fetch(`${PROFILES_API_URL}/preference`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
key,
|
|
74
|
+
preferredType,
|
|
75
|
+
signature,
|
|
76
|
+
signer,
|
|
77
|
+
timestamp,
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
throw new Error(`Failed to set preference: ${response.statusText}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return response.json();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function useProfile(
|
|
89
|
+
{
|
|
90
|
+
address,
|
|
91
|
+
name,
|
|
92
|
+
fresh = false,
|
|
93
|
+
}: {
|
|
94
|
+
address?: string;
|
|
95
|
+
name?: string;
|
|
96
|
+
fresh?: boolean;
|
|
97
|
+
},
|
|
98
|
+
options?: {
|
|
99
|
+
enabled?: boolean;
|
|
100
|
+
refetchInterval?: number;
|
|
101
|
+
staleTime?: number;
|
|
102
|
+
},
|
|
103
|
+
) {
|
|
104
|
+
return useQuery({
|
|
105
|
+
queryKey: ["profile", address || name, fresh],
|
|
106
|
+
queryFn: () => fetchProfile({ address, name, fresh }),
|
|
107
|
+
enabled: (options?.enabled ?? true) && (!!address || !!name),
|
|
108
|
+
refetchInterval: options?.refetchInterval,
|
|
109
|
+
staleTime: options?.staleTime ?? 5 * 60 * 1000, // 5 minutes default
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function useProfilePreference() {
|
|
114
|
+
const setPreference = async (
|
|
115
|
+
key: string,
|
|
116
|
+
preferredType: string,
|
|
117
|
+
signerAddress: string,
|
|
118
|
+
signMessage: (message: string) => Promise<string>,
|
|
119
|
+
) => {
|
|
120
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
121
|
+
const message = `SetProfilePreference:${key}:${preferredType}:${timestamp}`;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const signature = await signMessage(message);
|
|
125
|
+
|
|
126
|
+
return setProfilePreference({
|
|
127
|
+
key,
|
|
128
|
+
preferredType,
|
|
129
|
+
signature,
|
|
130
|
+
signer: signerAddress,
|
|
131
|
+
timestamp,
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
throw new Error(`Failed to set profile preference: ${error}`);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return { setPreference };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export default useProfile;
|
package/src/styles/index.css
CHANGED