@b3dotfun/sdk 0.0.65-test.1 → 0.0.65-test.3
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/AnySpend.js +4 -2
- package/dist/cjs/anyspend/react/components/AnySpendCustom.js +1 -1
- package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +1 -1
- package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +1 -1
- package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.d.ts +0 -6
- package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +4 -2
- package/dist/cjs/anyspend/react/hooks/useSigMint.d.ts +1 -1
- package/dist/cjs/global-account/react/components/AvatarEditor/AvatarEditor.js +80 -37
- package/dist/cjs/global-account/react/components/B3DynamicModal.js +1 -9
- package/dist/cjs/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.d.ts +39 -0
- package/dist/cjs/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.js +37 -0
- package/dist/cjs/global-account/react/components/ManageAccount/BalanceContent.js +4 -3
- package/dist/cjs/global-account/react/components/ManageAccount/HomeActions.js +1 -1
- package/dist/cjs/global-account/react/components/ManageAccount/ManageAccount.js +1 -1
- package/dist/cjs/global-account/react/components/ManageAccount/ProfileSection.js +6 -3
- package/dist/cjs/global-account/react/components/ManageAccount/SettingsProfileCard.js +77 -9
- package/dist/cjs/global-account/react/components/SignInWithB3/SignIn.js +3 -1
- package/dist/cjs/global-account/react/components/index.d.ts +1 -2
- package/dist/cjs/global-account/react/components/index.js +6 -8
- package/dist/cjs/global-account/react/hooks/useAccountWallet.d.ts +1 -0
- package/dist/cjs/global-account/react/hooks/useAccountWallet.js +18 -0
- package/dist/cjs/global-account/react/hooks/useAuthentication.d.ts +2 -2
- package/dist/cjs/global-account/react/hooks/useUserQuery.d.ts +2 -2
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +1 -7
- package/dist/cjs/shared/constants/chains/supported.d.ts +1 -1
- package/dist/cjs/shared/utils/ipfs.js +10 -3
- package/dist/esm/anyspend/react/components/AnySpend.js +4 -2
- package/dist/esm/anyspend/react/components/AnySpendCustom.js +1 -1
- package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +1 -1
- package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +1 -1
- package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.d.ts +0 -6
- package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +4 -2
- package/dist/esm/anyspend/react/hooks/useSigMint.d.ts +1 -1
- package/dist/esm/global-account/react/components/AvatarEditor/AvatarEditor.js +81 -38
- package/dist/esm/global-account/react/components/B3DynamicModal.js +1 -9
- package/dist/esm/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.d.ts +39 -0
- package/dist/esm/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.js +34 -0
- package/dist/esm/global-account/react/components/ManageAccount/BalanceContent.js +4 -3
- package/dist/esm/global-account/react/components/ManageAccount/HomeActions.js +1 -1
- package/dist/esm/global-account/react/components/ManageAccount/ManageAccount.js +1 -1
- package/dist/esm/global-account/react/components/ManageAccount/ProfileSection.js +6 -3
- package/dist/esm/global-account/react/components/ManageAccount/SettingsProfileCard.js +74 -9
- package/dist/esm/global-account/react/components/SignInWithB3/SignIn.js +4 -2
- package/dist/esm/global-account/react/components/index.d.ts +1 -2
- package/dist/esm/global-account/react/components/index.js +3 -4
- package/dist/esm/global-account/react/hooks/useAccountWallet.d.ts +1 -0
- package/dist/esm/global-account/react/hooks/useAccountWallet.js +17 -0
- package/dist/esm/global-account/react/hooks/useAuthentication.d.ts +2 -2
- package/dist/esm/global-account/react/hooks/useUserQuery.d.ts +2 -2
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +1 -7
- package/dist/esm/shared/constants/chains/supported.d.ts +1 -1
- package/dist/esm/shared/utils/ipfs.js +10 -3
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/react/components/common/CryptoPaymentMethod.d.ts +0 -6
- package/dist/types/anyspend/react/hooks/useSigMint.d.ts +1 -1
- package/dist/types/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.d.ts +39 -0
- package/dist/types/global-account/react/components/index.d.ts +1 -2
- package/dist/types/global-account/react/hooks/useAccountWallet.d.ts +1 -0
- package/dist/types/global-account/react/hooks/useAuthentication.d.ts +2 -2
- package/dist/types/global-account/react/hooks/useUserQuery.d.ts +2 -2
- package/dist/types/global-account/react/stores/useModalStore.d.ts +1 -7
- package/dist/types/shared/constants/chains/supported.d.ts +1 -1
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpend.tsx +4 -3
- package/src/anyspend/react/components/AnySpendCustom.tsx +0 -2
- package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +0 -2
- package/src/anyspend/react/components/AnyspendDepositHype.tsx +0 -2
- package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +6 -13
- package/src/global-account/react/components/AvatarEditor/AvatarEditor.tsx +129 -74
- package/src/global-account/react/components/B3DynamicModal.tsx +2 -10
- package/src/global-account/react/components/IPFSMediaRenderer/IPFSMediaRenderer.tsx +84 -0
- package/src/global-account/react/components/ManageAccount/BalanceContent.tsx +4 -7
- package/src/global-account/react/components/ManageAccount/HomeActions.tsx +1 -1
- package/src/global-account/react/components/ManageAccount/ManageAccount.tsx +2 -2
- package/src/global-account/react/components/ManageAccount/ProfileSection.tsx +9 -11
- package/src/global-account/react/components/ManageAccount/SettingsProfileCard.tsx +129 -23
- package/src/global-account/react/components/SignInWithB3/SignIn.tsx +11 -7
- package/src/global-account/react/components/index.ts +3 -4
- package/src/global-account/react/hooks/useAccountWallet.tsx +26 -0
- package/src/global-account/react/stores/useModalStore.ts +1 -9
- package/src/shared/utils/ipfs.ts +10 -3
- package/dist/cjs/global-account/react/components/ProfileEditor/ProfileEditor.d.ts +0 -6
- package/dist/cjs/global-account/react/components/ProfileEditor/ProfileEditor.js +0 -141
- package/dist/esm/global-account/react/components/ProfileEditor/ProfileEditor.d.ts +0 -6
- package/dist/esm/global-account/react/components/ProfileEditor/ProfileEditor.js +0 -135
- package/dist/types/global-account/react/components/ProfileEditor/ProfileEditor.d.ts +0 -6
- package/src/global-account/react/components/ProfileEditor/ProfileEditor.tsx +0 -265
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ThirdwebClient } from "thirdweb";
|
|
2
|
+
interface IPFSMediaRendererProps {
|
|
3
|
+
/** The source URL - can be IPFS URL (ipfs://...) or HTTP URL */
|
|
4
|
+
src: string | null | undefined;
|
|
5
|
+
/** Alt text for the media */
|
|
6
|
+
alt?: string;
|
|
7
|
+
/** CSS class name */
|
|
8
|
+
className?: string;
|
|
9
|
+
/** Thirdweb client instance (optional, uses default if not provided) */
|
|
10
|
+
client?: ThirdwebClient;
|
|
11
|
+
/** Width of the media */
|
|
12
|
+
width?: string | number;
|
|
13
|
+
/** Height of the media */
|
|
14
|
+
height?: string | number;
|
|
15
|
+
/** Controls property for video/audio */
|
|
16
|
+
controls?: boolean;
|
|
17
|
+
/** Style object */
|
|
18
|
+
style?: React.CSSProperties;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* IPFSMediaRenderer - A wrapper around Thirdweb's MediaRenderer that configures
|
|
22
|
+
* the IPFS gateway URL to use our validated gateway.
|
|
23
|
+
*
|
|
24
|
+
* Features:
|
|
25
|
+
* - Configures MediaRenderer to use cloudflare-ipfs.com gateway
|
|
26
|
+
* - Gateway matches our allowed list in profileDisplay.ts
|
|
27
|
+
* - Provides fallback for missing sources
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <IPFSMediaRenderer
|
|
32
|
+
* src="ipfs://QmX..."
|
|
33
|
+
* alt="Profile Avatar"
|
|
34
|
+
* className="size-14 rounded-full"
|
|
35
|
+
* />
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function IPFSMediaRenderer({ src, alt, className, client, width, height, controls, style, }: IPFSMediaRendererProps): import("react/jsx-runtime").JSX.Element;
|
|
39
|
+
export {};
|
|
@@ -15,8 +15,7 @@ export { getConnectOptionsFromStrategy, isWalletType, type AllowedStrategy } fro
|
|
|
15
15
|
export { ManageAccount } from "./ManageAccount/ManageAccount";
|
|
16
16
|
export { Deposit } from "./Deposit/Deposit";
|
|
17
17
|
export { Send } from "./Send/Send";
|
|
18
|
-
export {
|
|
19
|
-
export { ProfileEditor } from "./ProfileEditor/ProfileEditor";
|
|
18
|
+
export { IPFSMediaRenderer } from "./IPFSMediaRenderer/IPFSMediaRenderer";
|
|
20
19
|
export { RequestPermissions } from "./RequestPermissions/RequestPermissions";
|
|
21
20
|
export { RequestPermissionsButton } from "./RequestPermissions/RequestPermissionsButton";
|
|
22
21
|
export { AccountAssets } from "./AccountAssets/AccountAssets";
|
|
@@ -13,8 +13,8 @@ export declare function useAuthentication(partnerId: string): {
|
|
|
13
13
|
onConnect: (_walleAutoConnectedWith: Wallet, allConnectedWallets: Wallet[]) => Promise<void>;
|
|
14
14
|
user: {
|
|
15
15
|
email?: string | undefined;
|
|
16
|
-
username?: string | undefined;
|
|
17
16
|
telNumber?: string | undefined;
|
|
17
|
+
username?: string | undefined;
|
|
18
18
|
ens?: string | undefined;
|
|
19
19
|
avatar?: string | undefined;
|
|
20
20
|
preferences?: {} | undefined;
|
|
@@ -41,8 +41,8 @@ export declare function useAuthentication(partnerId: string): {
|
|
|
41
41
|
name?: string | undefined;
|
|
42
42
|
address?: string | undefined;
|
|
43
43
|
email?: string | undefined;
|
|
44
|
-
phone?: string | undefined;
|
|
45
44
|
username?: string | undefined;
|
|
45
|
+
phone?: string | undefined;
|
|
46
46
|
fid?: string | undefined;
|
|
47
47
|
};
|
|
48
48
|
}[] | undefined;
|
|
@@ -8,8 +8,8 @@ import { Users } from "@b3dotfun/b3-api";
|
|
|
8
8
|
export declare function useUserQuery(): {
|
|
9
9
|
user: {
|
|
10
10
|
email?: string | undefined;
|
|
11
|
-
username?: string | undefined;
|
|
12
11
|
telNumber?: string | undefined;
|
|
12
|
+
username?: string | undefined;
|
|
13
13
|
ens?: string | undefined;
|
|
14
14
|
avatar?: string | undefined;
|
|
15
15
|
preferences?: {} | undefined;
|
|
@@ -36,8 +36,8 @@ export declare function useUserQuery(): {
|
|
|
36
36
|
name?: string | undefined;
|
|
37
37
|
address?: string | undefined;
|
|
38
38
|
email?: string | undefined;
|
|
39
|
-
phone?: string | undefined;
|
|
40
39
|
username?: string | undefined;
|
|
40
|
+
phone?: string | undefined;
|
|
41
41
|
fid?: string | undefined;
|
|
42
42
|
};
|
|
43
43
|
}[] | undefined;
|
|
@@ -360,12 +360,6 @@ export interface AvatarEditorModalProps extends BaseModalProps {
|
|
|
360
360
|
/** Callback function called when avatar is successfully set */
|
|
361
361
|
onSuccess?: () => void;
|
|
362
362
|
}
|
|
363
|
-
export interface ProfileEditorModalProps extends BaseModalProps {
|
|
364
|
-
/** Modal type identifier */
|
|
365
|
-
type: "profileEditor";
|
|
366
|
-
/** Callback function called when profile is successfully updated */
|
|
367
|
-
onSuccess?: () => void;
|
|
368
|
-
}
|
|
369
363
|
/**
|
|
370
364
|
* Props for the Deposit modal
|
|
371
365
|
* Allows users to deposit tokens into their global account
|
|
@@ -391,7 +385,7 @@ export interface SendModalProps extends BaseModalProps {
|
|
|
391
385
|
/**
|
|
392
386
|
* Union type of all possible modal content types
|
|
393
387
|
*/
|
|
394
|
-
export type ModalContentType = SignInWithB3ModalProps | RequestPermissionsModalProps | ManageAccountModalProps | AnySpendModalProps | AnyspendOrderDetailsProps | AnySpendNftProps | AnySpendJoinTournamentProps | AnySpendFundTournamentProps | AnySpendOrderHistoryProps | AnySpendStakeB3Props | AnySpendStakeB3ExactInProps | AnySpendStakeUpsideProps | AnySpendStakeUpsideExactInProps | AnySpendBuySpinProps | AnySpendSignatureMintProps | AnySpendBondKitProps | LinkAccountModalProps | LinkNewAccountModalProps | AnySpendDepositHypeProps | AvatarEditorModalProps | DepositModalProps | SendModalProps
|
|
388
|
+
export type ModalContentType = SignInWithB3ModalProps | RequestPermissionsModalProps | ManageAccountModalProps | AnySpendModalProps | AnyspendOrderDetailsProps | AnySpendNftProps | AnySpendJoinTournamentProps | AnySpendFundTournamentProps | AnySpendOrderHistoryProps | AnySpendStakeB3Props | AnySpendStakeB3ExactInProps | AnySpendStakeUpsideProps | AnySpendStakeUpsideExactInProps | AnySpendBuySpinProps | AnySpendSignatureMintProps | AnySpendBondKitProps | LinkAccountModalProps | LinkNewAccountModalProps | AnySpendDepositHypeProps | AvatarEditorModalProps | DepositModalProps | SendModalProps;
|
|
395
389
|
/**
|
|
396
390
|
* State interface for the modal store
|
|
397
391
|
*/
|
|
@@ -35,13 +35,13 @@ export declare const supportedChainNetworks: {
|
|
|
35
35
|
uri: string;
|
|
36
36
|
}[];
|
|
37
37
|
};
|
|
38
|
+
_id: string | {};
|
|
38
39
|
icon: {
|
|
39
40
|
format: string;
|
|
40
41
|
height: number;
|
|
41
42
|
width: number;
|
|
42
43
|
url: string;
|
|
43
44
|
};
|
|
44
|
-
_id: string | {};
|
|
45
45
|
}[];
|
|
46
46
|
export declare const coingeckoChains: Record<number, {
|
|
47
47
|
coingecko_id: string;
|
package/package.json
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
useTokenFromUrl,
|
|
26
26
|
} from "@b3dotfun/sdk/global-account/react";
|
|
27
27
|
import BottomNavigation from "@b3dotfun/sdk/global-account/react/components/ManageAccount/BottomNavigation";
|
|
28
|
+
import { useAccountWalletImage } from "@b3dotfun/sdk/global-account/react/hooks/useAccountWallet";
|
|
28
29
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
29
30
|
import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
|
|
30
31
|
import invariant from "invariant";
|
|
@@ -474,6 +475,8 @@ function AnySpendInner({
|
|
|
474
475
|
const recipientProfile = useProfile({ address: recipientAddress, fresh: true });
|
|
475
476
|
const recipientName = recipientProfile.data?.name;
|
|
476
477
|
|
|
478
|
+
const globalWalletImage = useAccountWalletImage();
|
|
479
|
+
|
|
477
480
|
// Auto-set active wallet from wagmi
|
|
478
481
|
useAutoSetActiveWalletFromWagmi();
|
|
479
482
|
|
|
@@ -1248,7 +1251,7 @@ function AnySpendInner({
|
|
|
1248
1251
|
}}
|
|
1249
1252
|
onBack={navigateBack}
|
|
1250
1253
|
recipientEnsName={globalWallet?.ensName}
|
|
1251
|
-
recipientImageUrl={
|
|
1254
|
+
recipientImageUrl={globalWalletImage}
|
|
1252
1255
|
/>
|
|
1253
1256
|
);
|
|
1254
1257
|
|
|
@@ -1265,8 +1268,6 @@ function AnySpendInner({
|
|
|
1265
1268
|
|
|
1266
1269
|
const cryptoPaymentMethodView = (
|
|
1267
1270
|
<CryptoPaymentMethod
|
|
1268
|
-
globalAddress={globalAddress}
|
|
1269
|
-
globalWallet={globalWallet}
|
|
1270
1271
|
selectedPaymentMethod={selectedCryptoPaymentMethod}
|
|
1271
1272
|
setSelectedPaymentMethod={setSelectedCryptoPaymentMethod}
|
|
1272
1273
|
isCreatingOrder={isCreatingOrder}
|
|
@@ -1237,8 +1237,6 @@ function AnySpendCustomInner({
|
|
|
1237
1237
|
const cryptoPaymentMethodView = (
|
|
1238
1238
|
<div className={cn("bg-as-surface-primary mx-auto w-[460px] max-w-full rounded-xl p-4")}>
|
|
1239
1239
|
<CryptoPaymentMethod
|
|
1240
|
-
globalAddress={currentWallet?.wallet?.address}
|
|
1241
|
-
globalWallet={currentWallet?.wallet}
|
|
1242
1240
|
selectedPaymentMethod={selectedCryptoPaymentMethod}
|
|
1243
1241
|
setSelectedPaymentMethod={setSelectedCryptoPaymentMethod}
|
|
1244
1242
|
isCreatingOrder={isCreatingOrder}
|
|
@@ -499,8 +499,6 @@ function AnySpendCustomExactInInner({
|
|
|
499
499
|
|
|
500
500
|
const cryptoPaymentMethodView = (
|
|
501
501
|
<CryptoPaymentMethod
|
|
502
|
-
globalAddress={globalAddress}
|
|
503
|
-
globalWallet={undefined}
|
|
504
502
|
selectedPaymentMethod={selectedCryptoPaymentMethod}
|
|
505
503
|
setSelectedPaymentMethod={setSelectedCryptoPaymentMethod}
|
|
506
504
|
isCreatingOrder={isCreatingOrder}
|
|
@@ -464,8 +464,6 @@ function AnySpendDepositHypeInner({
|
|
|
464
464
|
|
|
465
465
|
const cryptoPaymentMethodView = (
|
|
466
466
|
<CryptoPaymentMethod
|
|
467
|
-
globalAddress={globalAddress}
|
|
468
|
-
globalWallet={undefined}
|
|
469
467
|
selectedPaymentMethod={selectedCryptoPaymentMethod}
|
|
470
468
|
setSelectedPaymentMethod={setSelectedCryptoPaymentMethod}
|
|
471
469
|
isCreatingOrder={isCreatingOrder}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useAccountWallet } from "@b3dotfun/sdk/global-account/react";
|
|
4
|
+
import { useAccountWalletImage } from "@b3dotfun/sdk/global-account/react/hooks/useAccountWallet";
|
|
4
5
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
5
6
|
import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
|
|
6
7
|
import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
|
|
@@ -22,12 +23,6 @@ export enum CryptoPaymentMethodType {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
interface CryptoPaymentMethodProps {
|
|
25
|
-
globalAddress?: string;
|
|
26
|
-
globalWallet?: {
|
|
27
|
-
meta?: {
|
|
28
|
-
icon?: string;
|
|
29
|
-
};
|
|
30
|
-
};
|
|
31
26
|
selectedPaymentMethod: CryptoPaymentMethodType;
|
|
32
27
|
setSelectedPaymentMethod: (method: CryptoPaymentMethodType) => void;
|
|
33
28
|
isCreatingOrder: boolean;
|
|
@@ -42,11 +37,7 @@ export function CryptoPaymentMethod({
|
|
|
42
37
|
onBack,
|
|
43
38
|
onSelectPaymentMethod,
|
|
44
39
|
}: CryptoPaymentMethodProps) {
|
|
45
|
-
const {
|
|
46
|
-
wallet: globalWallet,
|
|
47
|
-
connectedEOAWallet: connectedEOAWallet,
|
|
48
|
-
connectedSmartWallet: connectedSmartWallet,
|
|
49
|
-
} = useAccountWallet();
|
|
40
|
+
const { connectedEOAWallet: connectedEOAWallet, connectedSmartWallet: connectedSmartWallet } = useAccountWallet();
|
|
50
41
|
const { connector, address } = useAccount();
|
|
51
42
|
const { connect, connectors, isPending } = useConnect();
|
|
52
43
|
const { disconnect } = useDisconnect();
|
|
@@ -58,6 +49,8 @@ export function CryptoPaymentMethod({
|
|
|
58
49
|
const isConnected = !!connectedEOAWallet;
|
|
59
50
|
const globalAddress = connectedSmartWallet?.getAccount()?.address;
|
|
60
51
|
|
|
52
|
+
const walletImage = useAccountWalletImage();
|
|
53
|
+
|
|
61
54
|
// Use custom hook to determine wallet display logic
|
|
62
55
|
const { shouldShowConnectedEOA, shouldShowWagmiWallet } = useConnectedWalletDisplay(selectedPaymentMethod);
|
|
63
56
|
|
|
@@ -341,8 +334,8 @@ export function CryptoPaymentMethod({
|
|
|
341
334
|
>
|
|
342
335
|
<div className="flex items-center justify-between">
|
|
343
336
|
<div className="flex items-center gap-3">
|
|
344
|
-
{
|
|
345
|
-
<img src={
|
|
337
|
+
{walletImage ? (
|
|
338
|
+
<img src={walletImage} alt="Global Account" className="h-10 w-10 rounded-full" />
|
|
346
339
|
) : (
|
|
347
340
|
<div className="wallet-icon flex h-10 w-10 items-center justify-center rounded-full bg-purple-100">
|
|
348
341
|
<Wallet className="h-5 w-5 text-purple-600" />
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import app from "@b3dotfun/sdk/global-account/app";
|
|
4
|
-
import { Button, useB3, useProfile } from "@b3dotfun/sdk/global-account/react";
|
|
4
|
+
import { Button, IPFSMediaRenderer, useB3, useProfile } from "@b3dotfun/sdk/global-account/react";
|
|
5
|
+
import { validateImageUrl } from "@b3dotfun/sdk/global-account/react/utils/profileDisplay";
|
|
5
6
|
import { cn } from "@b3dotfun/sdk/shared/utils/cn";
|
|
6
7
|
import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
|
|
7
|
-
import { getIpfsUrl } from "@b3dotfun/sdk/shared/utils/ipfs";
|
|
8
8
|
import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
|
|
9
9
|
import { Loader2, Upload, X } from "lucide-react";
|
|
10
10
|
import { useRef, useState } from "react";
|
|
@@ -27,16 +27,15 @@ type ViewStep = "select" | "upload";
|
|
|
27
27
|
export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
28
28
|
const [viewStep, setViewStep] = useState<ViewStep>("select");
|
|
29
29
|
const [selectedAvatar, setSelectedAvatar] = useState<string | null>(null);
|
|
30
|
+
const [selectedProfileType, setSelectedProfileType] = useState<string | null>(null); // Track which profile was selected
|
|
30
31
|
const [hoveredProfile, setHoveredProfile] = useState<string | null>(null);
|
|
31
32
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
32
33
|
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
|
33
|
-
const [isUploading, setIsUploading] = useState(false);
|
|
34
34
|
const [isSaving, setIsSaving] = useState(false);
|
|
35
35
|
const [isDragging, setIsDragging] = useState(false);
|
|
36
36
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
37
37
|
const { setUser, user, partnerId } = useB3();
|
|
38
38
|
const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
|
|
39
|
-
const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
|
|
40
39
|
const contentType = useModalStore(state => state.contentType);
|
|
41
40
|
const { setPreference } = useProfileSettings();
|
|
42
41
|
|
|
@@ -46,11 +45,10 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
46
45
|
fresh: true,
|
|
47
46
|
});
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
: undefined;
|
|
48
|
+
// Get raw avatar URLs, convert IPFS URLs, and validate them
|
|
49
|
+
const rawCurrentAvatar = user?.avatar || profile?.avatar;
|
|
50
|
+
const currentAvatar = validateImageUrl(rawCurrentAvatar);
|
|
51
|
+
const safePreviewUrl = validateImageUrl(previewUrl);
|
|
54
52
|
|
|
55
53
|
const handleFileSelect = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
56
54
|
const file = event.target.files?.[0];
|
|
@@ -68,6 +66,8 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
setSelectedFile(file);
|
|
69
|
+
// Clear profile type selection when uploading a new file
|
|
70
|
+
setSelectedProfileType(null);
|
|
71
71
|
|
|
72
72
|
// Create preview URL
|
|
73
73
|
const url = URL.createObjectURL(file);
|
|
@@ -78,6 +78,7 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
78
78
|
|
|
79
79
|
const handleRemovePreview = () => {
|
|
80
80
|
setSelectedAvatar(currentAvatar || null);
|
|
81
|
+
setSelectedProfileType(null);
|
|
81
82
|
setSelectedFile(null);
|
|
82
83
|
if (previewUrl) {
|
|
83
84
|
URL.revokeObjectURL(previewUrl);
|
|
@@ -96,14 +97,58 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
96
97
|
|
|
97
98
|
setIsSaving(true);
|
|
98
99
|
try {
|
|
100
|
+
let fileToUpload: File | null = null;
|
|
101
|
+
|
|
99
102
|
// If user uploaded a new file
|
|
100
103
|
if (selectedFile) {
|
|
101
|
-
|
|
104
|
+
fileToUpload = selectedFile;
|
|
105
|
+
} else if (selectedProfileType && selectedAvatar) {
|
|
106
|
+
// User selected from existing profile avatars
|
|
107
|
+
// Fetch the image from the URL and convert to blob
|
|
108
|
+
debug("Fetching image from social profile:", selectedAvatar);
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const response = await fetch(selectedAvatar);
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error("Failed to fetch image");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const blob = await response.blob();
|
|
117
|
+
debug("Fetched blob with type:", blob.type);
|
|
118
|
+
|
|
119
|
+
// Determine the correct extension from the blob's MIME type
|
|
120
|
+
// This handles URLs without extensions (like Farcaster images)
|
|
121
|
+
const mimeToExtension: Record<string, string> = {
|
|
122
|
+
"image/jpeg": "jpg",
|
|
123
|
+
"image/jpg": "jpg",
|
|
124
|
+
"image/png": "png",
|
|
125
|
+
"image/gif": "gif",
|
|
126
|
+
"image/webp": "webp",
|
|
127
|
+
"image/svg+xml": "svg",
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const extension = blob.type ? mimeToExtension[blob.type.toLowerCase()] || "jpg" : "jpg";
|
|
131
|
+
const mimeType = blob.type || `image/${extension}`;
|
|
132
|
+
|
|
133
|
+
fileToUpload = new File([blob], `avatar-${selectedProfileType}.${extension}`, { type: mimeType });
|
|
134
|
+
|
|
135
|
+
debug("Successfully converted social profile image to file with extension:", extension);
|
|
136
|
+
} catch (fetchError) {
|
|
137
|
+
debug("Error fetching social profile image:", fetchError);
|
|
138
|
+
toast.error("Failed to fetch profile image. Please try uploading manually.");
|
|
139
|
+
setIsSaving(false);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Upload to IPFS if we have a file
|
|
145
|
+
if (fileToUpload) {
|
|
146
|
+
debug("Starting upload to IPFS", fileToUpload);
|
|
102
147
|
|
|
103
148
|
// Upload to IPFS using Thirdweb
|
|
104
149
|
const ipfsUrl = await upload({
|
|
105
150
|
client,
|
|
106
|
-
files: [
|
|
151
|
+
files: [fileToUpload],
|
|
107
152
|
});
|
|
108
153
|
|
|
109
154
|
debug("Upload successful", ipfsUrl);
|
|
@@ -121,23 +166,6 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
121
166
|
setUser(user);
|
|
122
167
|
|
|
123
168
|
toast.success("Looks great! Your avatar has been saved!");
|
|
124
|
-
} else if (selectedAvatar && selectedAvatar !== currentAvatar) {
|
|
125
|
-
// User selected from existing profile avatars
|
|
126
|
-
// Find the profile that matches the selected avatar
|
|
127
|
-
const selectedProfile = profile?.profiles?.find(p => p.avatar === selectedAvatar);
|
|
128
|
-
|
|
129
|
-
if (selectedProfile && selectedProfile.type) {
|
|
130
|
-
debug("Setting profile preference to:", selectedProfile.type);
|
|
131
|
-
|
|
132
|
-
// Set preference for this profile type
|
|
133
|
-
await setPreference(account.address, selectedProfile.type, account.address, async (message: string) => {
|
|
134
|
-
// Sign the message using the active account
|
|
135
|
-
const signature = await account.signMessage({ message });
|
|
136
|
-
return signature;
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
toast.success("Avatar updated successfully!");
|
|
140
|
-
}
|
|
141
169
|
}
|
|
142
170
|
|
|
143
171
|
// Refresh profile to get updated avatar
|
|
@@ -165,8 +193,15 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
165
193
|
}
|
|
166
194
|
};
|
|
167
195
|
|
|
168
|
-
const handleProfileAvatarSelect = (avatarUrl: string) => {
|
|
196
|
+
const handleProfileAvatarSelect = (avatarUrl: string, profileType: string) => {
|
|
169
197
|
setSelectedAvatar(avatarUrl);
|
|
198
|
+
setSelectedProfileType(profileType);
|
|
199
|
+
// Clear any selected file since we're selecting from profile
|
|
200
|
+
setSelectedFile(null);
|
|
201
|
+
if (previewUrl) {
|
|
202
|
+
URL.revokeObjectURL(previewUrl);
|
|
203
|
+
setPreviewUrl(null);
|
|
204
|
+
}
|
|
170
205
|
};
|
|
171
206
|
|
|
172
207
|
const handleUploadImageClick = () => {
|
|
@@ -214,6 +249,8 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
214
249
|
}
|
|
215
250
|
|
|
216
251
|
setSelectedFile(file);
|
|
252
|
+
// Clear profile type selection when uploading a new file
|
|
253
|
+
setSelectedProfileType(null);
|
|
217
254
|
|
|
218
255
|
// Create preview URL
|
|
219
256
|
const url = URL.createObjectURL(file);
|
|
@@ -230,17 +267,22 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
230
267
|
});
|
|
231
268
|
};
|
|
232
269
|
|
|
233
|
-
const isLoading =
|
|
270
|
+
const isLoading = isSaving;
|
|
234
271
|
|
|
235
|
-
// Get profile avatars
|
|
272
|
+
// Get profile avatars with validated URLs
|
|
236
273
|
const profileAvatars =
|
|
237
274
|
profile?.profiles
|
|
238
275
|
?.filter(p => p.avatar)
|
|
239
|
-
.map(p =>
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
276
|
+
.map(p => {
|
|
277
|
+
const rawAvatarUrl = p?.avatar || "";
|
|
278
|
+
const validatedUrl = validateImageUrl(rawAvatarUrl);
|
|
279
|
+
return {
|
|
280
|
+
type: p.type,
|
|
281
|
+
avatar: validatedUrl,
|
|
282
|
+
name: p.name || p.type,
|
|
283
|
+
};
|
|
284
|
+
})
|
|
285
|
+
.filter(p => p.avatar !== null) || []; // Filter out profiles with invalid avatars
|
|
244
286
|
|
|
245
287
|
return (
|
|
246
288
|
<div className={cn("flex w-full max-w-md flex-col bg-white", className)}>
|
|
@@ -254,13 +296,17 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
254
296
|
{/* Avatar Preview */}
|
|
255
297
|
<div className="relative mb-6">
|
|
256
298
|
<div className="h-32 w-32 overflow-hidden rounded-full">
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
299
|
+
{safePreviewUrl || selectedAvatar || currentAvatar ? (
|
|
300
|
+
<IPFSMediaRenderer
|
|
301
|
+
src={safePreviewUrl || selectedAvatar || currentAvatar || ""}
|
|
302
|
+
alt="Avatar preview"
|
|
303
|
+
className="h-full w-full object-cover"
|
|
304
|
+
/>
|
|
305
|
+
) : (
|
|
306
|
+
<div className="bg-b3-primary-wash h-full w-full" />
|
|
307
|
+
)}
|
|
262
308
|
</div>
|
|
263
|
-
{selectedAvatar && (
|
|
309
|
+
{(selectedAvatar !== currentAvatar || selectedFile) && (
|
|
264
310
|
<button
|
|
265
311
|
onClick={handleRemovePreview}
|
|
266
312
|
className="absolute -right-1 -top-1 flex h-8 w-8 items-center justify-center rounded-full bg-[#51525c] text-white transition-colors hover:bg-[#71717a]"
|
|
@@ -288,37 +334,42 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
288
334
|
|
|
289
335
|
{/* Profile Avatars */}
|
|
290
336
|
<div className="mb-4 flex gap-3">
|
|
291
|
-
{profileAvatars.map((profileAvatar, index) =>
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
"h-16 w-16 overflow-hidden rounded-full border-2 transition-all",
|
|
302
|
-
selectedAvatar === profileAvatar.avatar
|
|
303
|
-
? "border-[#3368ef] ring-2 ring-[#3368ef]/20"
|
|
304
|
-
: "border-transparent hover:border-[#e4e4e7]",
|
|
305
|
-
)}
|
|
337
|
+
{profileAvatars.map((profileAvatar, index) => {
|
|
338
|
+
// Skip if avatar is null (should not happen due to filter, but TypeScript doesn't know that)
|
|
339
|
+
if (!profileAvatar.avatar) return null;
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<div
|
|
343
|
+
key={index}
|
|
344
|
+
className="relative"
|
|
345
|
+
onMouseEnter={() => setHoveredProfile(profileAvatar.type)}
|
|
346
|
+
onMouseLeave={() => setHoveredProfile(null)}
|
|
306
347
|
>
|
|
307
|
-
<
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
348
|
+
<button
|
|
349
|
+
onClick={() => handleProfileAvatarSelect(profileAvatar.avatar || "", profileAvatar.type || "")}
|
|
350
|
+
className={cn(
|
|
351
|
+
"h-16 w-16 overflow-hidden rounded-full border-2 transition-all",
|
|
352
|
+
selectedProfileType === profileAvatar.type
|
|
353
|
+
? "border-[#3368ef] ring-2 ring-[#3368ef]/20"
|
|
354
|
+
: "border-transparent hover:border-[#e4e4e7]",
|
|
355
|
+
)}
|
|
356
|
+
>
|
|
357
|
+
<img
|
|
358
|
+
src={profileAvatar.avatar}
|
|
359
|
+
alt={`${profileAvatar.type} avatar`}
|
|
360
|
+
className="h-full w-full object-cover"
|
|
361
|
+
/>
|
|
362
|
+
</button>
|
|
363
|
+
|
|
364
|
+
{/* Tooltip */}
|
|
365
|
+
{hoveredProfile === profileAvatar.type && (
|
|
366
|
+
<div className="absolute -top-10 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-md bg-[#18181b] px-3 py-1.5 text-xs text-white">
|
|
367
|
+
{profileAvatar.name}
|
|
368
|
+
</div>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
);
|
|
372
|
+
})}
|
|
322
373
|
</div>
|
|
323
374
|
|
|
324
375
|
{/* Link More Account */}
|
|
@@ -369,7 +420,11 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
369
420
|
) : (
|
|
370
421
|
<div className="mb-6 w-full">
|
|
371
422
|
<div className="aspect-square w-full overflow-hidden rounded-xl bg-[#f4f4f5]">
|
|
372
|
-
|
|
423
|
+
{safePreviewUrl ? (
|
|
424
|
+
<img src={safePreviewUrl} alt="Preview" className="h-full w-full object-cover" />
|
|
425
|
+
) : (
|
|
426
|
+
<div className="bg-b3-primary-wash h-full w-full" />
|
|
427
|
+
)}
|
|
373
428
|
</div>
|
|
374
429
|
</div>
|
|
375
430
|
)}
|
|
@@ -392,7 +447,7 @@ export function AvatarEditor({ onSetAvatar, className }: AvatarEditorProps) {
|
|
|
392
447
|
</Button>
|
|
393
448
|
<Button
|
|
394
449
|
onClick={handleSaveChanges}
|
|
395
|
-
disabled={isLoading || !
|
|
450
|
+
disabled={isLoading || (!selectedFile && !selectedProfileType)}
|
|
396
451
|
className="flex-1 rounded-xl bg-[#3368ef] text-white hover:bg-[#2952cc]"
|
|
397
452
|
>
|
|
398
453
|
{isLoading ? (
|
|
@@ -23,7 +23,6 @@ import { Deposit } from "./Deposit/Deposit";
|
|
|
23
23
|
import { LinkAccount } from "./LinkAccount/LinkAccount";
|
|
24
24
|
import { LinkNewAccount } from "./LinkAccount/LinkNewAccount";
|
|
25
25
|
import { ManageAccount } from "./ManageAccount/ManageAccount";
|
|
26
|
-
import { ProfileEditor } from "./ProfileEditor/ProfileEditor";
|
|
27
26
|
import { RequestPermissions } from "./RequestPermissions/RequestPermissions";
|
|
28
27
|
import { Send } from "./Send/Send";
|
|
29
28
|
import { SignInWithB3Flow } from "./SignInWithB3/SignInWithB3Flow";
|
|
@@ -74,7 +73,6 @@ export function B3DynamicModal() {
|
|
|
74
73
|
"avatarEditor",
|
|
75
74
|
"deposit",
|
|
76
75
|
"send",
|
|
77
|
-
"profileEditor",
|
|
78
76
|
];
|
|
79
77
|
|
|
80
78
|
const freestyleTypes = [
|
|
@@ -157,8 +155,6 @@ export function B3DynamicModal() {
|
|
|
157
155
|
return <Deposit />;
|
|
158
156
|
case "send":
|
|
159
157
|
return <Send {...contentType} />;
|
|
160
|
-
case "profileEditor":
|
|
161
|
-
return <ProfileEditor onSuccess={contentType.onSuccess} />;
|
|
162
158
|
// Add other modal types here
|
|
163
159
|
default:
|
|
164
160
|
return null;
|
|
@@ -182,11 +178,7 @@ export function B3DynamicModal() {
|
|
|
182
178
|
contentType?.type === "send" ||
|
|
183
179
|
contentType?.type === "avatarEditor") &&
|
|
184
180
|
"p-0",
|
|
185
|
-
"mx-auto w-full max-w-md sm:max-w-lg",
|
|
186
|
-
// Remove default width classes for avatar editor and profile editor
|
|
187
|
-
contentType?.type === "avatarEditor" || contentType?.type === "profileEditor"
|
|
188
|
-
? "!w-[90vw] !max-w-none" // Use !important to override default styles
|
|
189
|
-
: "mx-auto w-full max-w-md sm:max-w-lg",
|
|
181
|
+
"mx-auto w-full max-w-md sm:max-w-lg",
|
|
190
182
|
)}
|
|
191
183
|
hideCloseButton={hideCloseButton}
|
|
192
184
|
>
|
|
@@ -221,7 +213,7 @@ export function B3DynamicModal() {
|
|
|
221
213
|
{renderContent()}
|
|
222
214
|
</div>
|
|
223
215
|
</ModalContent>
|
|
224
|
-
{
|
|
216
|
+
{contentType?.type === "avatarEditor" && (
|
|
225
217
|
<button
|
|
226
218
|
onClick={() => setB3ModalOpen(false)}
|
|
227
219
|
className="fixed right-5 top-5 z-[100] cursor-pointer text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white"
|