@b3dotfun/sdk 0.0.63 → 0.0.64-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/cjs/global-account/react/components/B3DynamicModal.js +7 -3
  2. package/dist/cjs/global-account/react/components/ManageAccount/BalanceContent.js +3 -3
  3. package/dist/cjs/global-account/react/components/ProfileEditor/ProfileEditor.d.ts +6 -0
  4. package/dist/cjs/global-account/react/components/ProfileEditor/ProfileEditor.js +141 -0
  5. package/dist/cjs/global-account/react/components/index.d.ts +2 -0
  6. package/dist/cjs/global-account/react/components/index.js +7 -2
  7. package/dist/cjs/global-account/react/hooks/useAuthentication.js +0 -11
  8. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +7 -1
  9. package/dist/cjs/global-account/react/utils/profileDisplay.d.ts +6 -0
  10. package/dist/cjs/global-account/react/utils/profileDisplay.js +60 -4
  11. package/dist/esm/global-account/react/components/B3DynamicModal.js +7 -3
  12. package/dist/esm/global-account/react/components/ManageAccount/BalanceContent.js +3 -3
  13. package/dist/esm/global-account/react/components/ProfileEditor/ProfileEditor.d.ts +6 -0
  14. package/dist/esm/global-account/react/components/ProfileEditor/ProfileEditor.js +135 -0
  15. package/dist/esm/global-account/react/components/index.d.ts +2 -0
  16. package/dist/esm/global-account/react/components/index.js +3 -0
  17. package/dist/esm/global-account/react/hooks/useAuthentication.js +0 -11
  18. package/dist/esm/global-account/react/stores/useModalStore.d.ts +7 -1
  19. package/dist/esm/global-account/react/utils/profileDisplay.d.ts +6 -0
  20. package/dist/esm/global-account/react/utils/profileDisplay.js +59 -4
  21. package/dist/types/global-account/react/components/ProfileEditor/ProfileEditor.d.ts +6 -0
  22. package/dist/types/global-account/react/components/index.d.ts +2 -0
  23. package/dist/types/global-account/react/stores/useModalStore.d.ts +7 -1
  24. package/dist/types/global-account/react/utils/profileDisplay.d.ts +6 -0
  25. package/package.json +2 -2
  26. package/src/global-account/react/components/B3DynamicModal.tsx +7 -3
  27. package/src/global-account/react/components/ManageAccount/BalanceContent.tsx +4 -4
  28. package/src/global-account/react/components/ProfileEditor/ProfileEditor.tsx +265 -0
  29. package/src/global-account/react/components/index.ts +4 -0
  30. package/src/global-account/react/hooks/useAuthentication.ts +0 -12
  31. package/src/global-account/react/stores/useModalStore.ts +9 -1
  32. package/src/global-account/react/utils/profileDisplay.ts +67 -4
@@ -0,0 +1,135 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import app from "../../../../global-account/app.js";
4
+ import { Button, useB3, useProfile } from "../../../../global-account/react/index.js";
5
+ import { validateImageUrl } from "../../../../global-account/react/utils/profileDisplay.js";
6
+ import { cn } from "../../../../shared/utils/cn.js";
7
+ import { debugB3React } from "../../../../shared/utils/debug.js";
8
+ import { getIpfsUrl } from "../../../../shared/utils/ipfs.js";
9
+ import { client } from "../../../../shared/utils/thirdweb.js";
10
+ import { Check, Loader2, Upload, X } from "lucide-react";
11
+ import { useRef, useState } from "react";
12
+ import { toast } from "sonner";
13
+ import { useActiveAccount } from "thirdweb/react";
14
+ import { upload } from "thirdweb/storage";
15
+ const debug = debugB3React("ProfileEditor");
16
+ export function ProfileEditor({ onSuccess, className }) {
17
+ const [selectedFile, setSelectedFile] = useState(null);
18
+ const [previewUrl, setPreviewUrl] = useState(null);
19
+ const [username, setUsername] = useState("");
20
+ const [isUploading, setIsUploading] = useState(false);
21
+ const [isSaving, setIsSaving] = useState(false);
22
+ const fileInputRef = useRef(null);
23
+ const { user, setUser } = useB3();
24
+ const account = useActiveAccount();
25
+ const { data: profile, refetch: refreshProfile } = useProfile({
26
+ address: account?.address,
27
+ fresh: true,
28
+ });
29
+ const rawAvatarUrl = user?.avatar ? getIpfsUrl(user?.avatar) : profile?.avatar;
30
+ const avatarUrl = validateImageUrl(rawAvatarUrl);
31
+ const safePreviewUrl = validateImageUrl(previewUrl);
32
+ const hasAvatar = !!avatarUrl;
33
+ const currentUsername = user?.username || "";
34
+ const handleFileSelect = (event) => {
35
+ const file = event.target.files?.[0];
36
+ if (file) {
37
+ // Validate file type
38
+ if (!file.type.startsWith("image/")) {
39
+ toast.error("Please select an image file");
40
+ return;
41
+ }
42
+ // Validate file size (max 5MB)
43
+ if (file.size > 5 * 1024 * 1024) {
44
+ toast.error("File size must be less than 5MB");
45
+ return;
46
+ }
47
+ setSelectedFile(file);
48
+ // Create preview URL
49
+ const url = URL.createObjectURL(file);
50
+ setPreviewUrl(url);
51
+ }
52
+ };
53
+ const handleRemoveFile = () => {
54
+ setSelectedFile(null);
55
+ if (previewUrl) {
56
+ URL.revokeObjectURL(previewUrl);
57
+ setPreviewUrl(null);
58
+ }
59
+ if (fileInputRef.current) {
60
+ fileInputRef.current.value = "";
61
+ }
62
+ };
63
+ const handleSave = async () => {
64
+ // Check if there are any changes
65
+ const hasAvatarChange = selectedFile !== null;
66
+ const hasUsernameChange = username.trim() !== "" && username !== currentUsername;
67
+ if (!hasAvatarChange && !hasUsernameChange) {
68
+ toast.error("Please make at least one change");
69
+ return;
70
+ }
71
+ setIsSaving(true);
72
+ try {
73
+ let ipfsUrl;
74
+ // Upload avatar if selected
75
+ if (hasAvatarChange && selectedFile) {
76
+ debug("Starting upload to IPFS", selectedFile);
77
+ setIsUploading(true);
78
+ ipfsUrl = await upload({
79
+ client,
80
+ files: [selectedFile],
81
+ });
82
+ debug("Upload successful", ipfsUrl);
83
+ setIsUploading(false);
84
+ }
85
+ // Update user profile
86
+ let updatedUser = user;
87
+ // If both avatar and username need updating, do them sequentially
88
+ // Update avatar first if uploaded
89
+ if (ipfsUrl) {
90
+ // @ts-expect-error this resolved fine, look into why expect-error needed
91
+ updatedUser = await app.service("users").setAvatar({
92
+ avatar: ipfsUrl,
93
+ },
94
+ // @ts-expect-error - our typed client is expecting context even though it's set elsewhere
95
+ {});
96
+ }
97
+ // Update username if changed (this will use the updated user from avatar change if both were updated)
98
+ if (hasUsernameChange && user?._id) {
99
+ // @ts-expect-error this resolved fine, look into why expect-error needed
100
+ updatedUser = await app.service("users").registerUsername({ username: username },
101
+ // @ts-expect-error - our typed client is expecting context even though it's set elsewhere
102
+ {});
103
+ }
104
+ // Update user state
105
+ setUser(updatedUser);
106
+ // Refresh profile to get updated data
107
+ await refreshProfile();
108
+ // Show success message
109
+ const changes = [];
110
+ if (hasAvatarChange)
111
+ changes.push("avatar");
112
+ if (hasUsernameChange)
113
+ changes.push("username");
114
+ toast.success(`Successfully updated ${changes.join(" and ")}!`);
115
+ onSuccess?.();
116
+ // Clean up
117
+ handleRemoveFile();
118
+ setUsername("");
119
+ }
120
+ catch (error) {
121
+ debug("Error updating profile:", error);
122
+ toast.error("Failed to update profile. Please try again.");
123
+ }
124
+ finally {
125
+ setIsUploading(false);
126
+ setIsSaving(false);
127
+ }
128
+ };
129
+ const handleFileInputClick = () => {
130
+ fileInputRef.current?.click();
131
+ };
132
+ const isLoading = isUploading || isSaving;
133
+ const hasChanges = selectedFile !== null || (username.trim() !== "" && username !== currentUsername);
134
+ return (_jsxs("div", { className: cn("flex flex-col items-center justify-center space-y-6 p-8", className), children: [_jsxs("div", { className: "space-y-2 text-center", children: [_jsx("h2", { className: "font-neue-montreal-semibold text-b3-grey text-2xl", children: "Edit Your Profile" }), _jsx("p", { className: "text-b3-foreground-muted font-neue-montreal-medium", children: "Update your avatar and username" })] }), _jsxs("div", { className: "w-full max-w-md space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-b3-grey font-neue-montreal-semibold text-sm", children: "Avatar" }), _jsx("div", { className: "flex justify-center", children: safePreviewUrl || avatarUrl ? (_jsxs("div", { className: "relative", children: [_jsx("div", { className: "border-b3-primary-blue h-32 w-32 overflow-hidden rounded-full border-4", children: _jsx("img", { src: safePreviewUrl || avatarUrl || "", alt: safePreviewUrl ? "Preview" : "Current avatar", className: "h-full w-full object-cover" }) }), safePreviewUrl && (_jsx("button", { onClick: handleRemoveFile, className: "bg-b3-negative absolute -right-2 -top-2 flex h-8 w-8 items-center justify-center rounded-full text-white transition-colors hover:bg-red-600", disabled: isLoading, children: _jsx(X, { size: 16 }) }))] })) : (_jsx("div", { className: "bg-b3-primary-wash h-32 w-32 rounded-full" })) }), !selectedFile && (_jsxs(Button, { variant: "outline", onClick: handleFileInputClick, disabled: isLoading, className: "w-full", children: [_jsx(Upload, { className: "mr-2 h-4 w-4" }), hasAvatar ? "Change Avatar" : "Upload Avatar"] })), _jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", onChange: handleFileSelect, className: "hidden" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { htmlFor: "username", className: "text-b3-grey font-neue-montreal-semibold text-sm", children: "Username" }), _jsx("input", { id: "username", type: "text", value: username, onChange: e => setUsername(e.target.value), placeholder: currentUsername || "Enter username", className: "border-b3-line bg-b3-background text-b3-grey placeholder:text-b3-foreground-muted font-neue-montreal-medium focus:border-b3-primary-blue w-full rounded-lg border px-4 py-3 transition-colors focus:outline-none", disabled: isLoading }), currentUsername && (_jsxs("p", { className: "text-b3-foreground-muted font-neue-montreal-medium text-xs", children: ["Current: ", currentUsername] }))] })] }), _jsx("div", { className: "flex w-full max-w-md gap-3", children: _jsx(Button, { onClick: handleSave, disabled: isLoading || !hasChanges, className: "bg-b3-primary-blue hover:bg-b3-primary-blue/90 flex-1 text-white disabled:opacity-50", children: isLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isUploading ? "Uploading..." : "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(Check, { className: "mr-2 h-4 w-4" }), "Save Changes"] })) }) }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium max-w-md text-center text-xs", children: _jsx("p", { children: "Your avatar will be uploaded to IPFS and stored securely. Make sure you have the rights to use this image." }) })] }));
135
+ }
@@ -13,6 +13,8 @@ export { SignInWithB3Privy } from "./SignInWithB3/SignInWithB3Privy";
13
13
  export { LoginStepContainer } from "./SignInWithB3/steps/LoginStep";
14
14
  export { getConnectOptionsFromStrategy, isWalletType, type AllowedStrategy } from "./SignInWithB3/utils/signInUtils";
15
15
  export { ManageAccount } from "./ManageAccount/ManageAccount";
16
+ export { AvatarEditor } from "./AvatarEditor/AvatarEditor";
17
+ export { ProfileEditor } from "./ProfileEditor/ProfileEditor";
16
18
  export { RequestPermissions } from "./RequestPermissions/RequestPermissions";
17
19
  export { RequestPermissionsButton } from "./RequestPermissions/RequestPermissionsButton";
18
20
  export { AccountAssets } from "./AccountAssets/AccountAssets";
@@ -16,6 +16,9 @@ export { LoginStepContainer } from "./SignInWithB3/steps/LoginStep.js";
16
16
  export { getConnectOptionsFromStrategy, isWalletType } from "./SignInWithB3/utils/signInUtils.js";
17
17
  // ManageAccount Components
18
18
  export { ManageAccount } from "./ManageAccount/ManageAccount.js";
19
+ // Profile Components
20
+ export { AvatarEditor } from "./AvatarEditor/AvatarEditor.js";
21
+ export { ProfileEditor } from "./ProfileEditor/ProfileEditor.js";
19
22
  // RequestPermissions Components
20
23
  export { RequestPermissions } from "./RequestPermissions/RequestPermissions.js";
21
24
  export { RequestPermissionsButton } from "./RequestPermissions/RequestPermissionsButton.js";
@@ -38,17 +38,6 @@ export function useAuthentication(partnerId) {
38
38
  const activeWagmiAccount = useAccount();
39
39
  const { switchAccount } = useSwitchAccount();
40
40
  debug("@@activeWagmiAccount", activeWagmiAccount);
41
- // Check localStorage version and clear if not found or mismatched
42
- useEffect(() => {
43
- if (typeof localStorage !== "undefined") {
44
- const version = localStorage.getItem("version");
45
- if (version !== "1") {
46
- debug("@@localStorage:clearing due to version mismatch", { version });
47
- localStorage.clear();
48
- localStorage.setItem("version", "1");
49
- }
50
- }
51
- }, []);
52
41
  const wallet = ecosystemWallet(ecosystemWalletId, {
53
42
  partnerId: partnerId,
54
43
  });
@@ -351,10 +351,16 @@ export interface AvatarEditorModalProps extends BaseModalProps {
351
351
  /** Callback function called when avatar is successfully set */
352
352
  onSuccess?: () => void;
353
353
  }
354
+ export interface ProfileEditorModalProps extends BaseModalProps {
355
+ /** Modal type identifier */
356
+ type: "profileEditor";
357
+ /** Callback function called when profile is successfully updated */
358
+ onSuccess?: () => void;
359
+ }
354
360
  /**
355
361
  * Union type of all possible modal content types
356
362
  */
357
- export type ModalContentType = SignInWithB3ModalProps | RequestPermissionsModalProps | ManageAccountModalProps | AnySpendModalProps | AnyspendOrderDetailsProps | AnySpendNftProps | AnySpendJoinTournamentProps | AnySpendFundTournamentProps | AnySpendOrderHistoryProps | AnySpendStakeB3Props | AnySpendStakeB3ExactInProps | AnySpendStakeUpsideProps | AnySpendStakeUpsideExactInProps | AnySpendBuySpinProps | AnySpendSignatureMintProps | AnySpendBondKitProps | LinkAccountModalProps | AnySpendDepositHypeProps | AvatarEditorModalProps;
363
+ export type ModalContentType = SignInWithB3ModalProps | RequestPermissionsModalProps | ManageAccountModalProps | AnySpendModalProps | AnyspendOrderDetailsProps | AnySpendNftProps | AnySpendJoinTournamentProps | AnySpendFundTournamentProps | AnySpendOrderHistoryProps | AnySpendStakeB3Props | AnySpendStakeB3ExactInProps | AnySpendStakeUpsideProps | AnySpendStakeUpsideExactInProps | AnySpendBuySpinProps | AnySpendSignatureMintProps | AnySpendBondKitProps | LinkAccountModalProps | AnySpendDepositHypeProps | AvatarEditorModalProps | ProfileEditorModalProps;
358
364
  /**
359
365
  * State interface for the modal store
360
366
  */
@@ -1,4 +1,10 @@
1
1
  import { type Profile } from "thirdweb/wallets";
2
+ /**
3
+ * Validates that an image URL uses an allowed schema
4
+ * @param url - The URL to validate
5
+ * @returns The URL if valid, null otherwise
6
+ */
7
+ export declare function validateImageUrl(url: string | null | undefined): string | null;
2
8
  export interface ExtendedProfileDetails {
3
9
  id?: string;
4
10
  email?: string;
@@ -1,3 +1,58 @@
1
+ import { debugB3React } from "../../../shared/utils/debug.js";
2
+ const debug = debugB3React("profileDisplay");
3
+ /**
4
+ * Validates that an image URL uses an allowed schema
5
+ * @param url - The URL to validate
6
+ * @returns The URL if valid, null otherwise
7
+ */
8
+ export function validateImageUrl(url) {
9
+ if (!url)
10
+ return null;
11
+ try {
12
+ // For blob URLs (from createObjectURL)
13
+ if (url.startsWith("blob:")) {
14
+ return url;
15
+ }
16
+ // For IPFS protocol URLs
17
+ if (url.startsWith("ipfs://")) {
18
+ return url;
19
+ }
20
+ // Parse URL to validate protocol and hostname
21
+ const parsedUrl = new URL(url);
22
+ // Only allow http and https protocols
23
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
24
+ debug("Rejected unsafe protocol:", parsedUrl.protocol, url);
25
+ return null;
26
+ }
27
+ // Whitelist of allowed IPFS gateway hostnames
28
+ const allowedIpfsGateways = [
29
+ "ipfs.io",
30
+ "gateway.pinata.cloud",
31
+ "cloudflare-ipfs.com",
32
+ "dweb.link",
33
+ "nftstorage.link",
34
+ "w3s.link",
35
+ ];
36
+ // Check if hostname matches allowed IPFS gateways
37
+ const hostname = parsedUrl.hostname.toLowerCase();
38
+ const isAllowedIpfsGateway = allowedIpfsGateways.some(gateway => {
39
+ // Exact match or subdomain of the gateway
40
+ return hostname === gateway || hostname.endsWith(`.${gateway}`);
41
+ });
42
+ if (isAllowedIpfsGateway) {
43
+ return url;
44
+ }
45
+ // For standard HTTP(S) URLs from trusted sources
46
+ // Add additional hostname validation here if needed
47
+ // For now, allow all HTTP(S) URLs (can be restricted further if needed)
48
+ return url;
49
+ }
50
+ catch (error) {
51
+ // Invalid URL format
52
+ debug("Invalid image URL format:", url, error);
53
+ return null;
54
+ }
55
+ }
1
56
  export function getProfileDisplayInfo(profile) {
2
57
  const { type, details } = profile;
3
58
  // Default display info
@@ -14,7 +69,7 @@ export function getProfileDisplayInfo(profile) {
14
69
  displayInfo = {
15
70
  title: details.name || details.username || "Unknown",
16
71
  subtitle: details.username ? `@${details.username}` : "X Account",
17
- imageUrl: details.profileImageUrl || null,
72
+ imageUrl: validateImageUrl(details.profileImageUrl),
18
73
  initial: "X",
19
74
  type,
20
75
  };
@@ -23,7 +78,7 @@ export function getProfileDisplayInfo(profile) {
23
78
  displayInfo = {
24
79
  title: details.name || details.username || "Unknown",
25
80
  subtitle: details.username ? `@${details.username}` : "Farcaster Account",
26
- imageUrl: details.profileImageUrl || null,
81
+ imageUrl: validateImageUrl(details.profileImageUrl),
27
82
  initial: "F",
28
83
  type,
29
84
  };
@@ -32,7 +87,7 @@ export function getProfileDisplayInfo(profile) {
32
87
  displayInfo = {
33
88
  title: details.name || details.email || "Unknown",
34
89
  subtitle: details.email || "Google Account",
35
- imageUrl: details.profileImageUrl || null,
90
+ imageUrl: validateImageUrl(details.profileImageUrl),
36
91
  initial: "G",
37
92
  type,
38
93
  };
@@ -41,7 +96,7 @@ export function getProfileDisplayInfo(profile) {
41
96
  displayInfo = {
42
97
  title: details.username || details.name || "Unknown",
43
98
  subtitle: "Discord Account",
44
- imageUrl: details.profileImageUrl || null,
99
+ imageUrl: validateImageUrl(details.profileImageUrl),
45
100
  initial: "D",
46
101
  type,
47
102
  };
@@ -0,0 +1,6 @@
1
+ interface ProfileEditorProps {
2
+ onSuccess?: () => void;
3
+ className?: string;
4
+ }
5
+ export declare function ProfileEditor({ onSuccess, className }: ProfileEditorProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -13,6 +13,8 @@ export { SignInWithB3Privy } from "./SignInWithB3/SignInWithB3Privy";
13
13
  export { LoginStepContainer } from "./SignInWithB3/steps/LoginStep";
14
14
  export { getConnectOptionsFromStrategy, isWalletType, type AllowedStrategy } from "./SignInWithB3/utils/signInUtils";
15
15
  export { ManageAccount } from "./ManageAccount/ManageAccount";
16
+ export { AvatarEditor } from "./AvatarEditor/AvatarEditor";
17
+ export { ProfileEditor } from "./ProfileEditor/ProfileEditor";
16
18
  export { RequestPermissions } from "./RequestPermissions/RequestPermissions";
17
19
  export { RequestPermissionsButton } from "./RequestPermissions/RequestPermissionsButton";
18
20
  export { AccountAssets } from "./AccountAssets/AccountAssets";
@@ -351,10 +351,16 @@ export interface AvatarEditorModalProps extends BaseModalProps {
351
351
  /** Callback function called when avatar is successfully set */
352
352
  onSuccess?: () => void;
353
353
  }
354
+ export interface ProfileEditorModalProps extends BaseModalProps {
355
+ /** Modal type identifier */
356
+ type: "profileEditor";
357
+ /** Callback function called when profile is successfully updated */
358
+ onSuccess?: () => void;
359
+ }
354
360
  /**
355
361
  * Union type of all possible modal content types
356
362
  */
357
- export type ModalContentType = SignInWithB3ModalProps | RequestPermissionsModalProps | ManageAccountModalProps | AnySpendModalProps | AnyspendOrderDetailsProps | AnySpendNftProps | AnySpendJoinTournamentProps | AnySpendFundTournamentProps | AnySpendOrderHistoryProps | AnySpendStakeB3Props | AnySpendStakeB3ExactInProps | AnySpendStakeUpsideProps | AnySpendStakeUpsideExactInProps | AnySpendBuySpinProps | AnySpendSignatureMintProps | AnySpendBondKitProps | LinkAccountModalProps | AnySpendDepositHypeProps | AvatarEditorModalProps;
363
+ export type ModalContentType = SignInWithB3ModalProps | RequestPermissionsModalProps | ManageAccountModalProps | AnySpendModalProps | AnyspendOrderDetailsProps | AnySpendNftProps | AnySpendJoinTournamentProps | AnySpendFundTournamentProps | AnySpendOrderHistoryProps | AnySpendStakeB3Props | AnySpendStakeB3ExactInProps | AnySpendStakeUpsideProps | AnySpendStakeUpsideExactInProps | AnySpendBuySpinProps | AnySpendSignatureMintProps | AnySpendBondKitProps | LinkAccountModalProps | AnySpendDepositHypeProps | AvatarEditorModalProps | ProfileEditorModalProps;
358
364
  /**
359
365
  * State interface for the modal store
360
366
  */
@@ -1,4 +1,10 @@
1
1
  import { type Profile } from "thirdweb/wallets";
2
+ /**
3
+ * Validates that an image URL uses an allowed schema
4
+ * @param url - The URL to validate
5
+ * @returns The URL if valid, null otherwise
6
+ */
7
+ export declare function validateImageUrl(url: string | null | undefined): string | null;
2
8
  export interface ExtendedProfileDetails {
3
9
  id?: string;
4
10
  email?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.0.63",
3
+ "version": "0.0.64-alpha.1",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -282,7 +282,7 @@
282
282
  "constants"
283
283
  ],
284
284
  "dependencies": {
285
- "@b3dotfun/b3-api": "0.0.77",
285
+ "@b3dotfun/b3-api": "0.0.84",
286
286
  "@b3dotfun/basement-api": "0.0.11",
287
287
  "@feathersjs/authentication-client": "5.0.33",
288
288
  "@feathersjs/feathers": "5.0.33",
@@ -21,6 +21,7 @@ import { useSetActiveWallet } from "thirdweb/react";
21
21
  import { AvatarEditor } from "./AvatarEditor/AvatarEditor";
22
22
  import { useB3 } from "./B3Provider/useB3";
23
23
  import { LinkAccount } from "./LinkAccount/LinkAccount";
24
+ import { ProfileEditor } from "./ProfileEditor/ProfileEditor";
24
25
  import { ManageAccount } from "./ManageAccount/ManageAccount";
25
26
  import { RequestPermissions } from "./RequestPermissions/RequestPermissions";
26
27
  import { SignInWithB3Flow } from "./SignInWithB3/SignInWithB3Flow";
@@ -68,6 +69,7 @@ export function B3DynamicModal() {
68
69
  "anySpendBondKit",
69
70
  "linkAccount",
70
71
  "avatarEditor",
72
+ "profileEditor",
71
73
  ];
72
74
 
73
75
  const freestyleTypes = [
@@ -144,6 +146,8 @@ export function B3DynamicModal() {
144
146
  return <AnySpendDepositHype {...contentType} mode="modal" />;
145
147
  case "avatarEditor":
146
148
  return <AvatarEditor onSetAvatar={contentType.onSuccess} />;
149
+ case "profileEditor":
150
+ return <ProfileEditor onSuccess={contentType.onSuccess} />;
147
151
  // Add other modal types here
148
152
  default:
149
153
  return null;
@@ -162,8 +166,8 @@ export function B3DynamicModal() {
162
166
  contentClass,
163
167
  "rounded-2xl bg-white shadow-xl dark:bg-gray-900",
164
168
  "border border-gray-200 dark:border-gray-800",
165
- // Remove default width classes for avatar editor
166
- contentType?.type === "avatarEditor"
169
+ // Remove default width classes for avatar editor and profile editor
170
+ contentType?.type === "avatarEditor" || contentType?.type === "profileEditor"
167
171
  ? "!w-[90vw] !max-w-none" // Use !important to override default styles
168
172
  : "mx-auto w-full max-w-md sm:max-w-lg",
169
173
  )}
@@ -199,7 +203,7 @@ export function B3DynamicModal() {
199
203
  {renderContent()}
200
204
  </div>
201
205
  </ModalContent>
202
- {contentType?.type === "avatarEditor" && (
206
+ {(contentType?.type === "avatarEditor" || contentType?.type === "profileEditor") && (
203
207
  <button
204
208
  onClick={() => setB3ModalOpen(false)}
205
209
  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"
@@ -50,10 +50,10 @@ export function BalanceContent({ onLogout, showDeposit = true, showSwap = true }
50
50
 
51
51
  const avatarUrl = user?.avatar ? getIpfsUrl(user?.avatar) : profile?.avatar;
52
52
 
53
- const handleEditAvatar = () => {
53
+ const handleEditProfile = () => {
54
54
  setB3ModalOpen(true);
55
55
  setB3ModalContentType({
56
- type: "avatarEditor",
56
+ type: "profileEditor",
57
57
  showBackButton: true,
58
58
  onSuccess: () => {
59
59
  // navigate back on success
@@ -125,7 +125,7 @@ export function BalanceContent({ onLogout, showDeposit = true, showSwap = true }
125
125
  <div className="bg-b3-primary-wash size-24 rounded-full" />
126
126
  )}
127
127
  <button
128
- onClick={handleEditAvatar}
128
+ onClick={handleEditProfile}
129
129
  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"
130
130
  >
131
131
  <Pencil size={16} className="text-b3-background" />
@@ -133,7 +133,7 @@ export function BalanceContent({ onLogout, showDeposit = true, showSwap = true }
133
133
  </div>
134
134
  <div className="global-account-profile-info">
135
135
  <h2 className="text-b3-grey text-xl font-semibold">
136
- {profile?.displayName || formatUsername(profile?.name || "")}
136
+ {user?.username || profile?.displayName || formatUsername(profile?.name || "")}
137
137
  </h2>
138
138
  <div className="address-button border-b3-line bg-b3-line/20 hover:bg-b3-line/40 flex w-fit items-center gap-2 rounded-full border px-3 py-1 transition-colors">
139
139
  <span className="text-b3-foreground-muted font-mono text-xs">