@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,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { client as defaultClient } from "@b3dotfun/sdk/shared/utils/thirdweb";
|
|
4
|
+
import type { ThirdwebClient } from "thirdweb";
|
|
5
|
+
import { MediaRenderer } from "thirdweb/react";
|
|
6
|
+
|
|
7
|
+
// Primary IPFS gateway URL - matches our allowed list in profileDisplay.ts
|
|
8
|
+
// Note: MediaRenderer expects the base gateway URL without /ipfs path
|
|
9
|
+
const IPFS_GATEWAY_URL = "https://cloudflare-ipfs.com";
|
|
10
|
+
|
|
11
|
+
interface IPFSMediaRendererProps {
|
|
12
|
+
/** The source URL - can be IPFS URL (ipfs://...) or HTTP URL */
|
|
13
|
+
src: string | null | undefined;
|
|
14
|
+
/** Alt text for the media */
|
|
15
|
+
alt?: string;
|
|
16
|
+
/** CSS class name */
|
|
17
|
+
className?: string;
|
|
18
|
+
/** Thirdweb client instance (optional, uses default if not provided) */
|
|
19
|
+
client?: ThirdwebClient;
|
|
20
|
+
/** Width of the media */
|
|
21
|
+
width?: string | number;
|
|
22
|
+
/** Height of the media */
|
|
23
|
+
height?: string | number;
|
|
24
|
+
/** Controls property for video/audio */
|
|
25
|
+
controls?: boolean;
|
|
26
|
+
/** Style object */
|
|
27
|
+
style?: React.CSSProperties;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* IPFSMediaRenderer - A wrapper around Thirdweb's MediaRenderer that configures
|
|
32
|
+
* the IPFS gateway URL to use our validated gateway.
|
|
33
|
+
*
|
|
34
|
+
* Features:
|
|
35
|
+
* - Configures MediaRenderer to use cloudflare-ipfs.com gateway
|
|
36
|
+
* - Gateway matches our allowed list in profileDisplay.ts
|
|
37
|
+
* - Provides fallback for missing sources
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* <IPFSMediaRenderer
|
|
42
|
+
* src="ipfs://QmX..."
|
|
43
|
+
* alt="Profile Avatar"
|
|
44
|
+
* className="size-14 rounded-full"
|
|
45
|
+
* />
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function IPFSMediaRenderer({
|
|
49
|
+
src,
|
|
50
|
+
alt = "Media",
|
|
51
|
+
className,
|
|
52
|
+
client = defaultClient,
|
|
53
|
+
width,
|
|
54
|
+
height,
|
|
55
|
+
controls,
|
|
56
|
+
style,
|
|
57
|
+
}: IPFSMediaRendererProps) {
|
|
58
|
+
// If no source, render fallback
|
|
59
|
+
if (!src) {
|
|
60
|
+
return (
|
|
61
|
+
<div className={className} style={style} aria-label={alt}>
|
|
62
|
+
<div className="bg-b3-primary-wash flex h-full w-full items-center justify-center">
|
|
63
|
+
<span className="text-b3-grey font-neue-montreal-semibold text-xs">{alt.charAt(0).toUpperCase()}</span>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Convert IPFS URLs to HTTP gateway URLs if needed
|
|
70
|
+
// This handles both ipfs:// URLs and existing HTTP gateway URLs
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<MediaRenderer
|
|
74
|
+
src={src}
|
|
75
|
+
client={client}
|
|
76
|
+
alt={alt}
|
|
77
|
+
className={className}
|
|
78
|
+
width={width ? width.toString() : undefined}
|
|
79
|
+
height={height ? height.toString() : undefined}
|
|
80
|
+
controls={controls}
|
|
81
|
+
style={style}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -13,11 +13,11 @@ import { BankIcon } from "@b3dotfun/sdk/global-account/react/components/icons/Ba
|
|
|
13
13
|
import { SignOutIcon } from "@b3dotfun/sdk/global-account/react/components/icons/SignOutIcon";
|
|
14
14
|
import { SwapIcon } from "@b3dotfun/sdk/global-account/react/components/icons/SwapIcon";
|
|
15
15
|
import { formatUsername } from "@b3dotfun/sdk/shared/utils";
|
|
16
|
-
import { getIpfsUrl } from "@b3dotfun/sdk/shared/utils/ipfs";
|
|
17
16
|
import { Loader2, Pencil } from "lucide-react";
|
|
18
17
|
import { useEffect, useRef, useState } from "react";
|
|
19
18
|
import { useActiveAccount } from "thirdweb/react";
|
|
20
19
|
import { useFirstEOA } from "../../hooks/useFirstEOA";
|
|
20
|
+
import { IPFSMediaRenderer } from "../IPFSMediaRenderer/IPFSMediaRenderer";
|
|
21
21
|
import { B3TokenIcon, EthereumTokenIcon } from "../TokenIcon";
|
|
22
22
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../ui/accordion";
|
|
23
23
|
import { TokenBalanceRow } from "./TokenBalanceRow";
|
|
@@ -48,7 +48,8 @@ export function BalanceContent({ onLogout, showDeposit = true, showSwap = true }
|
|
|
48
48
|
const [openAccordions, setOpenAccordions] = useState<string[]>([]);
|
|
49
49
|
const hasExpandedRef = useRef(false);
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
// IPFSMediaRenderer will handle IPFS URL conversion and validation
|
|
52
|
+
const avatarSrc = user?.avatar || profile?.avatar;
|
|
52
53
|
|
|
53
54
|
const handleEditProfile = () => {
|
|
54
55
|
setB3ModalOpen(true);
|
|
@@ -118,11 +119,7 @@ export function BalanceContent({ onLogout, showDeposit = true, showSwap = true }
|
|
|
118
119
|
<div className="flex items-center justify-between">
|
|
119
120
|
<div className="global-account-profile flex items-center gap-4">
|
|
120
121
|
<div className="global-account-profile-avatar relative">
|
|
121
|
-
{
|
|
122
|
-
<img src={avatarUrl} alt="Profile" className="size-24 rounded-full" />
|
|
123
|
-
) : (
|
|
124
|
-
<div className="bg-b3-primary-wash size-24 rounded-full" />
|
|
125
|
-
)}
|
|
122
|
+
<IPFSMediaRenderer src={avatarSrc} alt="Profile" className="size-24 rounded-full" />
|
|
126
123
|
<button
|
|
127
124
|
onClick={handleEditProfile}
|
|
128
125
|
className="bg-b3-grey border-b3-background hover:bg-b3-grey/80 absolute -bottom-1 -right-1 flex size-8 items-center justify-center rounded-full border-4 transition-colors"
|
|
@@ -41,7 +41,7 @@ const HomeActionButton = ({
|
|
|
41
41
|
return (
|
|
42
42
|
<Button
|
|
43
43
|
className={cn(
|
|
44
|
-
"border-b3-line hover:border-b3-primary-blue flex h-[84px] w-full flex-col items-center justify-center gap-2 rounded-2xl border-[1.5px] bg-[#FAFAFA] hover:bg-[#FAFAFA]",
|
|
44
|
+
"border-b3-line hover:border-b3-primary-blue shadow-xs flex h-[84px] w-full flex-col items-center justify-center gap-2 rounded-2xl border-[1.5px] bg-[#FAFAFA] hover:bg-[#FAFAFA]",
|
|
45
45
|
customClass,
|
|
46
46
|
)}
|
|
47
47
|
onClick={onClick}
|
|
@@ -93,7 +93,7 @@ export function ManageAccount({
|
|
|
93
93
|
}}
|
|
94
94
|
>
|
|
95
95
|
<div className="p-0">
|
|
96
|
-
<TabsContentPrimitive value="home" className="
|
|
96
|
+
<TabsContentPrimitive value="home" className="m-0 p-0 pb-2">
|
|
97
97
|
<HomeContent showDeposit={showDeposit} showSwap={showSwap} />
|
|
98
98
|
</TabsContentPrimitive>
|
|
99
99
|
|
|
@@ -108,7 +108,7 @@ export function ManageAccount({
|
|
|
108
108
|
{/* Swap tab content is handled by modal, so this is empty */}
|
|
109
109
|
<TabsContentPrimitive value="swap" className="hidden" />
|
|
110
110
|
|
|
111
|
-
<TabsContentPrimitive value="settings" className="
|
|
111
|
+
<TabsContentPrimitive value="settings" className="m-0 p-0 pb-2">
|
|
112
112
|
<SettingsContent partnerId={partnerId} onLogout={onLogout} chain={chain} />
|
|
113
113
|
</TabsContentPrimitive>
|
|
114
114
|
</div>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useAccountWallet, useB3, useModalStore, useProfile, useSimBalance } from "@b3dotfun/sdk/global-account/react";
|
|
2
2
|
import { formatUsername } from "@b3dotfun/sdk/shared/utils";
|
|
3
|
-
import { getIpfsUrl } from "@b3dotfun/sdk/shared/utils/ipfs";
|
|
4
3
|
import { formatDisplayNumber } from "@b3dotfun/sdk/shared/utils/number";
|
|
5
4
|
import { Pencil } from "lucide-react";
|
|
6
5
|
import { useMemo } from "react";
|
|
7
6
|
import { useActiveAccount } from "thirdweb/react";
|
|
8
7
|
import { useFirstEOA } from "../../hooks/useFirstEOA";
|
|
8
|
+
import { IPFSMediaRenderer } from "../IPFSMediaRenderer/IPFSMediaRenderer";
|
|
9
9
|
|
|
10
10
|
const ProfileSection = () => {
|
|
11
11
|
const account = useActiveAccount();
|
|
@@ -28,8 +28,6 @@ const ProfileSection = () => {
|
|
|
28
28
|
return simBalance.balances.reduce((sum, token) => sum + (token.value_usd || 0), 0);
|
|
29
29
|
}, [simBalance]);
|
|
30
30
|
|
|
31
|
-
const avatarUrl = user?.avatar ? getIpfsUrl(user?.avatar) : profile?.avatar;
|
|
32
|
-
|
|
33
31
|
const handleEditAvatar = () => {
|
|
34
32
|
setB3ModalOpen(true);
|
|
35
33
|
setB3ModalContentType({
|
|
@@ -41,15 +39,17 @@ const ProfileSection = () => {
|
|
|
41
39
|
});
|
|
42
40
|
};
|
|
43
41
|
|
|
42
|
+
// IPFSMediaRenderer will handle IPFS URL conversion and validation
|
|
43
|
+
const avatarSrc = user?.avatar || profile?.avatar;
|
|
44
|
+
|
|
45
|
+
// Get current username - prioritize user.username, fallback to profile data
|
|
46
|
+
const currentUsername = user?.username || profile?.displayName || formatUsername(profile?.name || "");
|
|
47
|
+
|
|
44
48
|
return (
|
|
45
49
|
<div className="flex items-center justify-between px-5 py-6">
|
|
46
50
|
<div className="global-account-profile flex items-center gap-4">
|
|
47
51
|
<div className="global-account-profile-avatar relative">
|
|
48
|
-
{
|
|
49
|
-
<img src={avatarUrl} alt="Profile" className="size-14 rounded-full" />
|
|
50
|
-
) : (
|
|
51
|
-
<div className="bg-b3-primary-wash size-14 rounded-full" />
|
|
52
|
-
)}
|
|
52
|
+
<IPFSMediaRenderer src={avatarSrc} alt="Profile Avatar" className="size-14 rounded-full" />
|
|
53
53
|
<button
|
|
54
54
|
onClick={handleEditAvatar}
|
|
55
55
|
className="border-b3-background hover:bg-b3-grey/80 absolute -bottom-1 -right-1 flex size-6 items-center justify-center rounded-full border-4 bg-[#a0a0ab] transition-colors"
|
|
@@ -62,9 +62,7 @@ const ProfileSection = () => {
|
|
|
62
62
|
<div className="text-b3-foreground-muted"> $</div>
|
|
63
63
|
<div className="text-[30px]">{formatDisplayNumber(totalBalanceUsd, { fractionDigits: 2 })}</div>
|
|
64
64
|
</h2>
|
|
65
|
-
<div className="font-neue-montreal-semibold text-base leading-none text-[#0B57C2]">
|
|
66
|
-
{profile?.displayName || formatUsername(profile?.name || "")}
|
|
67
|
-
</div>
|
|
65
|
+
<div className="font-neue-montreal-semibold text-base leading-none text-[#0B57C2]">{currentUsername}</div>
|
|
68
66
|
</div>
|
|
69
67
|
</div>
|
|
70
68
|
</div>
|
|
@@ -1,23 +1,44 @@
|
|
|
1
|
+
import app from "@b3dotfun/sdk/global-account/app";
|
|
1
2
|
import { useB3, useModalStore, useProfile } from "@b3dotfun/sdk/global-account/react";
|
|
2
3
|
import { formatUsername } from "@b3dotfun/sdk/shared/utils";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { Check, Loader2, Pencil, X } from "lucide-react";
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { toast } from "sonner";
|
|
5
7
|
import { useActiveAccount } from "thirdweb/react";
|
|
6
8
|
import { useFirstEOA } from "../../hooks/useFirstEOA";
|
|
9
|
+
import { IPFSMediaRenderer } from "../IPFSMediaRenderer/IPFSMediaRenderer";
|
|
7
10
|
|
|
8
11
|
const SettingsProfileCard = () => {
|
|
9
12
|
const account = useActiveAccount();
|
|
10
13
|
const { address: eoaAddress } = useFirstEOA();
|
|
11
|
-
const { data: profile } = useProfile({
|
|
14
|
+
const { data: profile, refetch: refreshProfile } = useProfile({
|
|
12
15
|
address: eoaAddress || account?.address,
|
|
13
16
|
fresh: true,
|
|
14
17
|
});
|
|
15
|
-
const { user } = useB3();
|
|
18
|
+
const { user, setUser } = useB3();
|
|
16
19
|
const setB3ModalOpen = useModalStore(state => state.setB3ModalOpen);
|
|
17
20
|
const setB3ModalContentType = useModalStore(state => state.setB3ModalContentType);
|
|
18
21
|
const navigateBack = useModalStore(state => state.navigateBack);
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
// State for inline username editing
|
|
24
|
+
const [isEditingUsername, setIsEditingUsername] = useState(false);
|
|
25
|
+
const [editedUsername, setEditedUsername] = useState("");
|
|
26
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
27
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
28
|
+
|
|
29
|
+
// IPFSMediaRenderer will handle IPFS URL conversion and validation
|
|
30
|
+
const avatarSrc = user?.avatar || profile?.avatar;
|
|
31
|
+
|
|
32
|
+
// Get current username - prioritize user.username, fallback to profile data
|
|
33
|
+
const currentUsername = user?.username || profile?.displayName || formatUsername(profile?.name || "");
|
|
34
|
+
|
|
35
|
+
// Focus input when entering edit mode
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (isEditingUsername && inputRef.current) {
|
|
38
|
+
inputRef.current.focus();
|
|
39
|
+
inputRef.current.select();
|
|
40
|
+
}
|
|
41
|
+
}, [isEditingUsername]);
|
|
21
42
|
|
|
22
43
|
const handleEditAvatar = () => {
|
|
23
44
|
setB3ModalOpen(true);
|
|
@@ -31,19 +52,69 @@ const SettingsProfileCard = () => {
|
|
|
31
52
|
};
|
|
32
53
|
|
|
33
54
|
const handleEditUsername = () => {
|
|
34
|
-
|
|
35
|
-
|
|
55
|
+
setEditedUsername(currentUsername || "");
|
|
56
|
+
setIsEditingUsername(true);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleCancelEdit = () => {
|
|
60
|
+
setIsEditingUsername(false);
|
|
61
|
+
setEditedUsername("");
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleSaveUsername = async () => {
|
|
65
|
+
if (!editedUsername.trim()) {
|
|
66
|
+
toast.error("Username cannot be empty");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (editedUsername === currentUsername) {
|
|
71
|
+
// No change, just exit edit mode
|
|
72
|
+
handleCancelEdit();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setIsSaving(true);
|
|
77
|
+
try {
|
|
78
|
+
const updatedUser = await app.service("users").registerUsername(
|
|
79
|
+
{ username: editedUsername.trim() },
|
|
80
|
+
// @ts-expect-error - our typed client is expecting context even though it's set elsewhere
|
|
81
|
+
{},
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Update user state - registerUsername returns an array with single user
|
|
85
|
+
setUser(Array.isArray(updatedUser) ? updatedUser[0] : updatedUser);
|
|
86
|
+
|
|
87
|
+
// Refresh profile to get updated data
|
|
88
|
+
await refreshProfile();
|
|
89
|
+
|
|
90
|
+
toast.success("Username updated successfully!");
|
|
91
|
+
setIsEditingUsername(false);
|
|
92
|
+
setEditedUsername("");
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error("Error updating username:", error);
|
|
95
|
+
toast.error("Failed to update username. Please try again.");
|
|
96
|
+
} finally {
|
|
97
|
+
setIsSaving(false);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
102
|
+
if (e.key === "Enter") {
|
|
103
|
+
handleSaveUsername();
|
|
104
|
+
} else if (e.key === "Escape") {
|
|
105
|
+
handleCancelEdit();
|
|
106
|
+
}
|
|
36
107
|
};
|
|
37
108
|
|
|
38
109
|
return (
|
|
39
110
|
<div className="flex w-full items-center gap-3">
|
|
40
111
|
{/* Avatar with edit badge */}
|
|
41
112
|
<div className="relative shrink-0">
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
113
|
+
<IPFSMediaRenderer
|
|
114
|
+
src={avatarSrc}
|
|
115
|
+
alt="Profile"
|
|
116
|
+
className="border-black/8 size-14 rounded-full border object-cover"
|
|
117
|
+
/>
|
|
47
118
|
<button
|
|
48
119
|
onClick={handleEditAvatar}
|
|
49
120
|
className="absolute -bottom-0.5 -right-0.5 flex size-[18px] items-center justify-center rounded-full border-[1.5px] border-white bg-[#a0a0ab] transition-colors hover:bg-[#a0a0ab]/80"
|
|
@@ -55,17 +126,52 @@ const SettingsProfileCard = () => {
|
|
|
55
126
|
|
|
56
127
|
{/* Username and edit link */}
|
|
57
128
|
<div className="flex shrink-0 flex-col gap-1">
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
129
|
+
{isEditingUsername ? (
|
|
130
|
+
/* Edit mode - inline input */
|
|
131
|
+
<div className="flex items-center gap-2">
|
|
132
|
+
<input
|
|
133
|
+
ref={inputRef}
|
|
134
|
+
type="text"
|
|
135
|
+
value={editedUsername}
|
|
136
|
+
onChange={e => setEditedUsername(e.target.value)}
|
|
137
|
+
onKeyDown={handleKeyDown}
|
|
138
|
+
disabled={isSaving}
|
|
139
|
+
className="border-b3-line bg-b3-background text-b3-grey placeholder:text-b3-foreground-muted font-neue-montreal-semibold focus:border-b3-primary-blue w-full rounded-md border px-2 py-1 text-lg leading-none transition-colors focus:outline-none disabled:opacity-50"
|
|
140
|
+
placeholder="Enter username"
|
|
141
|
+
/>
|
|
142
|
+
<div className="flex items-center gap-1">
|
|
143
|
+
<button
|
|
144
|
+
onClick={handleSaveUsername}
|
|
145
|
+
disabled={isSaving}
|
|
146
|
+
className="text-b3-primary-blue hover:text-b3-primary-blue/80 flex items-center justify-center rounded-md p-1 transition-colors disabled:opacity-50"
|
|
147
|
+
aria-label="Save username"
|
|
148
|
+
>
|
|
149
|
+
{isSaving ? <Loader2 size={18} className="animate-spin" /> : <Check size={18} strokeWidth={2.5} />}
|
|
150
|
+
</button>
|
|
151
|
+
<button
|
|
152
|
+
onClick={handleCancelEdit}
|
|
153
|
+
disabled={isSaving}
|
|
154
|
+
className="text-b3-foreground-muted hover:text-b3-grey flex items-center justify-center rounded-md p-1 transition-colors disabled:opacity-50"
|
|
155
|
+
aria-label="Cancel editing"
|
|
156
|
+
>
|
|
157
|
+
<X size={18} strokeWidth={2.5} />
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
) : (
|
|
162
|
+
/* Display mode */
|
|
163
|
+
<>
|
|
164
|
+
<div className="flex items-center gap-1">
|
|
165
|
+
<p className="font-neue-montreal-semibold text-lg leading-none text-[#0B57C2]">{currentUsername}</p>
|
|
166
|
+
</div>
|
|
167
|
+
<button
|
|
168
|
+
onClick={handleEditUsername}
|
|
169
|
+
className="flex items-center justify-center gap-1 text-left transition-opacity hover:opacity-80"
|
|
170
|
+
>
|
|
171
|
+
<p className="font-inter text-sm font-semibold leading-5 text-[#51525C]">Edit Username</p>
|
|
172
|
+
</button>
|
|
173
|
+
</>
|
|
174
|
+
)}
|
|
69
175
|
</div>
|
|
70
176
|
</div>
|
|
71
177
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
IPFSMediaRenderer,
|
|
2
3
|
SignInWithB3,
|
|
3
4
|
SignInWithB3ModalProps,
|
|
4
5
|
StyleRoot,
|
|
@@ -13,6 +14,7 @@ import { cn, truncateAddress } from "@b3dotfun/sdk/shared/utils";
|
|
|
13
14
|
import { Menu, MenuButton, MenuItems, Transition } from "@headlessui/react";
|
|
14
15
|
import { ReactNode, useEffect } from "react";
|
|
15
16
|
import { useConnectedWallets, useSetActiveWallet, useWalletInfo } from "thirdweb/react";
|
|
17
|
+
import { useAccountWalletImage } from "../../hooks/useAccountWallet";
|
|
16
18
|
import { ManageAccountButton } from "../custom/ManageAccountButton";
|
|
17
19
|
|
|
18
20
|
type SignInProps = {
|
|
@@ -44,7 +46,7 @@ export function SignIn(props: SignInWithB3Props) {
|
|
|
44
46
|
|
|
45
47
|
const isMobile = useIsMobile();
|
|
46
48
|
const { logout } = useAuthentication(partnerId);
|
|
47
|
-
const onDisconnect = async () => {
|
|
49
|
+
const onDisconnect = async (): Promise<void> => {
|
|
48
50
|
await logout();
|
|
49
51
|
};
|
|
50
52
|
|
|
@@ -73,17 +75,19 @@ export function SignIn(props: SignInWithB3Props) {
|
|
|
73
75
|
}
|
|
74
76
|
}, [connectedEOAWallet, isActiveEOAWallet, setActiveWallet, automaticallySetFirstEoa]);
|
|
75
77
|
|
|
78
|
+
const walletImage = useAccountWalletImage();
|
|
79
|
+
|
|
76
80
|
// Desktop version - original dropdown menu
|
|
77
81
|
return (
|
|
78
82
|
<StyleRoot>
|
|
79
83
|
<Menu className={`relative flex items-center ${className || ""}`} as="div">
|
|
80
84
|
{globalAddress ? (
|
|
81
85
|
<>
|
|
82
|
-
<MenuButton className="bg-b3-react-background group flex h-10 items-center gap-1 rounded-xl px-3">
|
|
83
|
-
{!!
|
|
84
|
-
<
|
|
85
|
-
src={
|
|
86
|
-
alt=
|
|
86
|
+
<MenuButton className="bg-b3-react-background group flex h-10 items-center gap-1 rounded-xl px-3 focus:outline-none">
|
|
87
|
+
{!!walletImage && (
|
|
88
|
+
<IPFSMediaRenderer
|
|
89
|
+
src={walletImage}
|
|
90
|
+
alt="Wallet Image"
|
|
87
91
|
className="bg-b3-react-primary h-6 w-6 rounded-full object-cover opacity-100"
|
|
88
92
|
/>
|
|
89
93
|
)}
|
|
@@ -98,7 +102,7 @@ export function SignIn(props: SignInWithB3Props) {
|
|
|
98
102
|
leaveTo="scale-95 opacity-0"
|
|
99
103
|
>
|
|
100
104
|
<MenuItems
|
|
101
|
-
className="b3-root absolute -right-4 top-full min-w-64 rounded-2xl border lg:right-0"
|
|
105
|
+
className="b3-root absolute -right-4 top-full min-w-64 rounded-2xl border focus:outline-none lg:right-0"
|
|
102
106
|
modal={false}
|
|
103
107
|
// TODO: Figure out why setting anchor on mobile causes z-index issues where it appears under elements
|
|
104
108
|
anchor={isMobile ? "top end" : undefined}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// TODO woj: Barrel file for all components, this might be reason of bundle size issues
|
|
1
2
|
// Core Components
|
|
2
3
|
export { B3DynamicModal } from "./B3DynamicModal";
|
|
3
4
|
export { B3Provider, InnerProvider } from "./B3Provider/B3Provider";
|
|
@@ -25,10 +26,8 @@ export { Deposit } from "./Deposit/Deposit";
|
|
|
25
26
|
// Send Components
|
|
26
27
|
export { Send } from "./Send/Send";
|
|
27
28
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
export { AvatarEditor } from "./AvatarEditor/AvatarEditor";
|
|
31
|
-
export { ProfileEditor } from "./ProfileEditor/ProfileEditor";
|
|
29
|
+
// Media Components
|
|
30
|
+
export { IPFSMediaRenderer } from "./IPFSMediaRenderer/IPFSMediaRenderer";
|
|
32
31
|
|
|
33
32
|
// RequestPermissions Components
|
|
34
33
|
export { RequestPermissions } from "./RequestPermissions/RequestPermissions";
|
|
@@ -112,3 +112,29 @@ export function useAccountWallet(): {
|
|
|
112
112
|
|
|
113
113
|
return res;
|
|
114
114
|
}
|
|
115
|
+
|
|
116
|
+
export function useAccountWalletImage(): string {
|
|
117
|
+
const { account, user } = useB3();
|
|
118
|
+
|
|
119
|
+
const activeWallet = useActiveWallet();
|
|
120
|
+
const connectedWallets = useConnectedWallets();
|
|
121
|
+
|
|
122
|
+
const connectedSmartWallet = connectedWallets.find(wallet => wallet.id === ecosystemWalletId);
|
|
123
|
+
const connectedEOAWallet = connectedWallets.find(wallet => wallet.id !== ecosystemWalletId);
|
|
124
|
+
const isActiveSmartWallet = activeWallet?.id === connectedSmartWallet?.id;
|
|
125
|
+
|
|
126
|
+
const { data: walletImage } = useWalletImage(connectedEOAWallet?.id);
|
|
127
|
+
|
|
128
|
+
// If not EOA sign in, then we need to show the smart wallet icon
|
|
129
|
+
const lastAuthProvider = useLastAuthProvider();
|
|
130
|
+
|
|
131
|
+
const smartWalletIcon =
|
|
132
|
+
lastAuthProvider && !connectedEOAWallet
|
|
133
|
+
? socialIcons[lastAuthProvider as keyof typeof socialIcons]
|
|
134
|
+
: "https://gradvatar.com/0x0000000000000000000000000000000000000000"; // show smart wallet of eoa wallet is gradvatar
|
|
135
|
+
|
|
136
|
+
const { data: profileData } = useProfile({ address: account?.address });
|
|
137
|
+
const avatarUrl = user?.avatar || profileData?.avatar;
|
|
138
|
+
|
|
139
|
+
return avatarUrl || (isActiveSmartWallet ? smartWalletIcon : walletImage) || "";
|
|
140
|
+
}
|
|
@@ -383,13 +383,6 @@ export interface AvatarEditorModalProps extends BaseModalProps {
|
|
|
383
383
|
onSuccess?: () => void;
|
|
384
384
|
}
|
|
385
385
|
|
|
386
|
-
export interface ProfileEditorModalProps extends BaseModalProps {
|
|
387
|
-
/** Modal type identifier */
|
|
388
|
-
type: "profileEditor";
|
|
389
|
-
/** Callback function called when profile is successfully updated */
|
|
390
|
-
onSuccess?: () => void;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
386
|
/**
|
|
394
387
|
* Props for the Deposit modal
|
|
395
388
|
* Allows users to deposit tokens into their global account
|
|
@@ -439,8 +432,7 @@ export type ModalContentType =
|
|
|
439
432
|
| AnySpendDepositHypeProps
|
|
440
433
|
| AvatarEditorModalProps
|
|
441
434
|
| DepositModalProps
|
|
442
|
-
| SendModalProps
|
|
443
|
-
| ProfileEditorModalProps;
|
|
435
|
+
| SendModalProps;
|
|
444
436
|
// Add other modal types here like: | OtherModalProps | AnotherModalProps
|
|
445
437
|
|
|
446
438
|
/**
|
package/src/shared/utils/ipfs.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List of IPFS gateways to use for converting ipfs:// URLs to HTTP URLs
|
|
3
|
+
* These gateways must match the allowed list in profileDisplay.ts validateImageUrl()
|
|
4
|
+
*/
|
|
1
5
|
const IPFS_GATEWAYS = [
|
|
2
|
-
"https://cloudflare-ipfs.com/ipfs",
|
|
3
|
-
"https://ipfs.io/ipfs",
|
|
4
|
-
//
|
|
6
|
+
"https://cloudflare-ipfs.com/ipfs", // Primary gateway - fast and reliable
|
|
7
|
+
"https://ipfs.io/ipfs", // Fallback gateway
|
|
8
|
+
"https://gateway.pinata.cloud/ipfs", // Additional option
|
|
9
|
+
"https://dweb.link/ipfs", // Additional option
|
|
10
|
+
"https://nftstorage.link/ipfs", // Additional option
|
|
11
|
+
"https://w3s.link/ipfs", // Additional option
|
|
5
12
|
] as const;
|
|
6
13
|
|
|
7
14
|
/**
|