@b3dotfun/sdk 0.0.31-alpha.1 → 0.0.31-alpha.2

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.
@@ -72,7 +72,7 @@ export declare const useSigMintCollection: ({ address, chainId }: FindByAddressP
72
72
  description?: string | undefined;
73
73
  createdAt?: string | undefined;
74
74
  updatedAt?: string | undefined;
75
- status?: "DRAFT" | "ACTIVE" | "INACTIVE" | undefined;
75
+ status?: "ACTIVE" | "DRAFT" | "INACTIVE" | undefined;
76
76
  metadata?: {} | undefined;
77
77
  logoURI?: string | undefined;
78
78
  creator?: string | undefined;
@@ -1,17 +1,16 @@
1
- import { Account } from "thirdweb/wallets";
2
- import { User } from "../../../../global-account/types/b3-api.types";
3
- import { Wallet } from "thirdweb/wallets";
1
+ import { Users } from "@b3dotfun/b3-api";
4
2
  import { PermissionsConfig } from "../../../../global-account/types/permissions";
3
+ import { Account, Wallet } from "thirdweb/wallets";
5
4
  /**
6
5
  * Context type for B3Provider
7
6
  */
8
7
  export interface B3ContextType {
9
8
  account?: Account;
10
9
  automaticallySetFirstEoa: boolean;
11
- user?: User;
10
+ user?: Users;
12
11
  setWallet: (wallet: Wallet) => void;
13
12
  wallet?: Wallet;
14
- setUser: (user?: User) => void;
13
+ setUser: (user?: Users) => void;
15
14
  initialized: boolean;
16
15
  ready: boolean;
17
16
  environment?: "development" | "production";
@@ -1,13 +1,18 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.ManageAccount = ManageAccount;
4
7
  const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const app_1 = __importDefault(require("../../../../global-account/app"));
5
9
  const react_1 = require("../../../../global-account/react");
6
10
  const SignOutIcon_1 = require("../../../../global-account/react/components/icons/SignOutIcon");
7
11
  const formatNumber_1 = require("../../../../shared/utils/formatNumber");
8
12
  const thirdweb_1 = require("../../../../shared/utils/thirdweb");
9
13
  const lucide_react_1 = require("lucide-react");
10
14
  const react_2 = require("react");
15
+ const sonner_1 = require("sonner");
11
16
  const react_3 = require("thirdweb/react");
12
17
  const viem_1 = require("viem");
13
18
  const profileDisplay_1 = require("../../utils/profileDisplay");
@@ -55,6 +60,45 @@ function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit, chain
55
60
  const { data: profilesRaw = [], isLoading: isLoadingProfiles } = (0, react_3.useProfiles)({ client: thirdweb_1.client });
56
61
  const { mutate: unlinkProfile, isPending: isUnlinking } = (0, react_3.useUnlinkProfile)();
57
62
  const { setB3ModalOpen, setB3ModalContentType, isLinking } = (0, react_1.useModalStore)();
63
+ const { user, setUser } = (0, react_1.useB3)();
64
+ const [isUpdatingCode, setIsUpdatingCode] = (0, react_2.useState)(false);
65
+ const [newReferralCode, setNewReferralCode] = (0, react_2.useState)("");
66
+ const [isEditingCode, setIsEditingCode] = (0, react_2.useState)(false);
67
+ const referallCodeRef = (0, react_2.useRef)(null);
68
+ const { data: referrals, isLoading: isLoadingReferrals } = (0, react_1.useQueryB3)("referrals", "find", { query: { referrerId: user?.userId } }, !!user?.userId);
69
+ // Fetch referred users
70
+ const currentReferralCode = user?.referralCode || user?.userId || "";
71
+ const handleCopyCode = async () => {
72
+ try {
73
+ await navigator.clipboard.writeText(currentReferralCode);
74
+ sonner_1.toast.success("Referral code copied to clipboard!");
75
+ }
76
+ catch (error) {
77
+ sonner_1.toast.error("Failed to copy referral code");
78
+ }
79
+ };
80
+ const handleUpdateReferralCode = async () => {
81
+ if (!newReferralCode)
82
+ return;
83
+ setIsUpdatingCode(true);
84
+ try {
85
+ // @ts-expect-error - setReferralCode is not typed for some reason
86
+ const newUser = await app_1.default.service("users").setReferralCode({
87
+ userId: user?.userId,
88
+ referralCode: newReferralCode,
89
+ });
90
+ setUser(newUser);
91
+ sonner_1.toast.success("Referral code updated successfully!");
92
+ setIsEditingCode(false);
93
+ setNewReferralCode("");
94
+ }
95
+ catch (error) {
96
+ sonner_1.toast.error("Failed to update referral code");
97
+ }
98
+ finally {
99
+ setIsUpdatingCode(false);
100
+ }
101
+ };
58
102
  const profiles = profilesRaw
59
103
  .filter((profile) => !["custom_auth_endpoint", "siwe"].includes(profile.type))
60
104
  .map((profile) => ({
@@ -91,7 +135,15 @@ function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit, chain
91
135
  },
92
136
  });
93
137
  };
94
- return ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-8", children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Linked Accounts" }), (0, jsx_runtime_1.jsxs)(react_1.Button, { className: "bg-b3-primary-wash hover:bg-b3-primary-wash/70 flex items-center gap-2 rounded-full px-4 py-2", onClick: handleOpenLinkModal, disabled: isLinking, children: [isLinking ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "text-b3-primary-blue animate-spin", size: 16 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.LinkIcon, { size: 16, className: "text-b3-primary-blue" })), (0, jsx_runtime_1.jsx)("span", { className: "text-b3-grey font-neue-montreal-semibold", children: isLinking ? "Linking..." : "Link New Account" })] })] }), isLoadingProfiles ? ((0, jsx_runtime_1.jsx)("div", { className: "flex justify-center py-8", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "text-b3-grey animate-spin" }) })) : profiles.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "space-y-4", children: profiles.map(profile => ((0, jsx_runtime_1.jsxs)("div", { className: "bg-b3-line flex items-center justify-between rounded-xl p-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [profile.imageUrl ? ((0, jsx_runtime_1.jsx)("img", { src: profile.imageUrl, alt: profile.title, className: "size-10 rounded-full" })) : ((0, jsx_runtime_1.jsx)("div", { className: "bg-b3-primary-wash flex h-10 w-10 items-center justify-center rounded-full", children: (0, jsx_runtime_1.jsx)("span", { className: "text-b3-grey font-neue-montreal-semibold text-sm uppercase", children: profile.initial }) })), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-b3-grey font-neue-montreal-semibold", children: profile.title }), (0, jsx_runtime_1.jsx)("span", { className: "text-b3-foreground-muted font-neue-montreal-medium bg-b3-primary-wash rounded px-2 py-0.5 text-xs", children: profile.type.toUpperCase() })] }), (0, jsx_runtime_1.jsx)("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: profile.subtitle })] })] }), (0, jsx_runtime_1.jsx)(react_1.Button, { variant: "ghost", size: "icon", className: "text-b3-grey hover:text-b3-negative", onClick: () => handleUnlink(profile), disabled: unlinkingAccountId === profile.title || isUnlinking, children: unlinkingAccountId === profile.title || isUnlinking ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "animate-spin" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.UnlinkIcon, { size: 16 })) })] }, profile.title))) })) : ((0, jsx_runtime_1.jsx)("div", { className: "text-b3-foreground-muted py-8 text-center", children: "No linked accounts found" }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Account Preferences" }), (0, jsx_runtime_1.jsx)("div", { className: "bg-b3-line rounded-xl p-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Dark Mode" }), (0, jsx_runtime_1.jsx)("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Switch between light and dark theme" })] }), (0, jsx_runtime_1.jsx)("div", { className: "bg-b3-primary-wash h-6 w-12 rounded-full" })] }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "border-b3-line flex items-center justify-between rounded-2xl border p-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("img", { src: "https://cdn.b3.fun/b3_logo.svg", alt: "B3", className: "h-4" }), (0, jsx_runtime_1.jsx)("h3", { className: "font-neue-montreal-semibold text-b3-grey", children: "Global Account" })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-b3-foreground-muted font-neue-montreal-medium mt-2 text-sm", children: "Your universal account for all B3 apps" })] }), (0, jsx_runtime_1.jsx)("button", { className: "text-b3-grey hover:text-b3-grey/80 hover:bg-b3-line border-b3-line flex size-12 items-center justify-center rounded-full border", onClick: onLogoutEnhanced, children: logoutLoading ? (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "animate-spin" }) : (0, jsx_runtime_1.jsx)(SignOutIcon_1.SignOutIcon, { size: 16, className: "text-b3-grey" }) })] })] }));
138
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "space-y-8", children: [(0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Linked Accounts" }), (0, jsx_runtime_1.jsxs)(react_1.Button, { className: "bg-b3-primary-wash hover:bg-b3-primary-wash/70 flex items-center gap-2 rounded-full px-4 py-2", onClick: handleOpenLinkModal, disabled: isLinking, children: [isLinking ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "text-b3-primary-blue animate-spin", size: 16 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.LinkIcon, { size: 16, className: "text-b3-primary-blue" })), (0, jsx_runtime_1.jsx)("span", { className: "text-b3-grey font-neue-montreal-semibold", children: isLinking ? "Linking..." : "Link New Account" })] })] }), isLoadingProfiles ? ((0, jsx_runtime_1.jsx)("div", { className: "flex justify-center py-8", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "text-b3-grey animate-spin" }) })) : profiles.length > 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "space-y-4", children: profiles.map(profile => ((0, jsx_runtime_1.jsxs)("div", { className: "bg-b3-line flex items-center justify-between rounded-xl p-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [profile.imageUrl ? ((0, jsx_runtime_1.jsx)("img", { src: profile.imageUrl, alt: profile.title, className: "size-10 rounded-full" })) : ((0, jsx_runtime_1.jsx)("div", { className: "bg-b3-primary-wash flex h-10 w-10 items-center justify-center rounded-full", children: (0, jsx_runtime_1.jsx)("span", { className: "text-b3-grey font-neue-montreal-semibold text-sm uppercase", children: profile.initial }) })), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("span", { className: "text-b3-grey font-neue-montreal-semibold", children: profile.title }), (0, jsx_runtime_1.jsx)("span", { className: "text-b3-foreground-muted font-neue-montreal-medium bg-b3-primary-wash rounded px-2 py-0.5 text-xs", children: profile.type.toUpperCase() })] }), (0, jsx_runtime_1.jsx)("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: profile.subtitle })] })] }), (0, jsx_runtime_1.jsx)(react_1.Button, { variant: "ghost", size: "icon", className: "text-b3-grey hover:text-b3-negative", onClick: () => handleUnlink(profile), disabled: unlinkingAccountId === profile.title || isUnlinking, children: unlinkingAccountId === profile.title || isUnlinking ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "animate-spin" })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.UnlinkIcon, { size: 16 })) })] }, profile.title))) })) : ((0, jsx_runtime_1.jsx)("div", { className: "text-b3-foreground-muted py-8 text-center", children: "No linked accounts found" }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Referrals" }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-b3-line rounded-xl p-4", children: [isEditingCode && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Your Referral Code" }), (0, jsx_runtime_1.jsx)("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Share this code with friends to earn rewards" })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [!isEditingCode && ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Your Referral Code" }), (0, jsx_runtime_1.jsx)("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Share this code with friends to earn rewards" })] })), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-2", children: isEditingCode ? ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", value: newReferralCode, onChange: e => setNewReferralCode(e.target.value), className: "rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm", placeholder: "Enter new code", ref: referallCodeRef }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", onClick: handleUpdateReferralCode, disabled: isUpdatingCode || !newReferralCode, children: isUpdatingCode ? (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "h-4 w-4 animate-spin" }) : "Save" }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "sm", variant: "ghost", onClick: () => {
139
+ setIsEditingCode(false);
140
+ setNewReferralCode("");
141
+ }, children: "Cancel" })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm", children: currentReferralCode }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "icon", variant: "ghost", onClick: handleCopyCode, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { className: "h-4 w-4" }) }), (0, jsx_runtime_1.jsx)(react_1.Button, { size: "icon", variant: "ghost", onClick: () => {
142
+ setIsEditingCode(true);
143
+ setTimeout(() => {
144
+ referallCodeRef.current?.focus();
145
+ }, 100);
146
+ }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Pencil, { className: "h-4 w-4" }) })] })) })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "bg-b3-line rounded-xl p-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-b3-grey font-neue-montreal-semibold mb-4", children: "Referred Users" }), isLoadingReferrals ? ((0, jsx_runtime_1.jsx)("div", { className: "flex justify-center py-4", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "h-6 w-6 animate-spin text-gray-400" }) })) : referrals?.data?.length ? ((0, jsx_runtime_1.jsx)("div", { className: "space-y-3", children: referrals.data.map((referral) => ((0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between rounded-lg bg-white p-3", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-sm font-medium", children: referral.referreeId }), (0, jsx_runtime_1.jsx)("div", { className: "text-sm text-gray-500", children: new Date(referral.createdAt).toLocaleDateString() })] }, String(referral._id)))) })) : ((0, jsx_runtime_1.jsx)("div", { className: "py-4 text-center text-gray-500", children: "No referred users yet" }))] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "space-y-4", children: [(0, jsx_runtime_1.jsx)("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Account Preferences" }), (0, jsx_runtime_1.jsx)("div", { className: "bg-b3-line rounded-xl p-4", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Dark Mode" }), (0, jsx_runtime_1.jsx)("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Switch between light and dark theme" })] }), (0, jsx_runtime_1.jsx)("div", { className: "bg-b3-primary-wash h-6 w-12 rounded-full" })] }) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "border-b3-line flex items-center justify-between rounded-2xl border p-4", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("img", { src: "https://cdn.b3.fun/b3_logo.svg", alt: "B3", className: "h-4" }), (0, jsx_runtime_1.jsx)("h3", { className: "font-neue-montreal-semibold text-b3-grey", children: "Global Account" })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-b3-foreground-muted font-neue-montreal-medium mt-2 text-sm", children: "Your universal account for all B3 apps" })] }), (0, jsx_runtime_1.jsx)("button", { className: "text-b3-grey hover:text-b3-grey/80 hover:bg-b3-line border-b3-line flex size-12 items-center justify-center rounded-full border", onClick: onLogoutEnhanced, children: logoutLoading ? (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "animate-spin" }) : (0, jsx_runtime_1.jsx)(SignOutIcon_1.SignOutIcon, { size: 16, className: "text-b3-grey" }) })] })] }));
95
147
  };
96
148
  return ((0, jsx_runtime_1.jsx)("div", { className: "b3-manage-account bg-b3-background flex flex-col rounded-xl", children: (0, jsx_runtime_1.jsx)("div", { className: "flex-1", children: (0, jsx_runtime_1.jsxs)(react_1.TabsPrimitive, { defaultValue: activeTab, onValueChange: value => {
97
149
  const tab = value;
@@ -9,11 +9,11 @@ const debug_1 = __importDefault(require("../../../shared/utils/debug"));
9
9
  const react_1 = require("react");
10
10
  const useSearchParamsSSR_1 = require("./useSearchParamsSSR");
11
11
  function useSiwe() {
12
- const referrerId = (0, useSearchParamsSSR_1.useSearchParam)("referrerId");
12
+ const referralCode = (0, useSearchParamsSSR_1.useSearchParam)("referralCode");
13
13
  const authenticate = (0, react_1.useCallback)(async (account, partnerId) => {
14
14
  if (!account || !account.signMessage)
15
15
  throw new Error("Account not found");
16
- console.log("@@useAuthenticate:referrerId", referrerId);
16
+ console.log("@@useAuthenticate:referralCode", referralCode);
17
17
  // generate challenge
18
18
  const challenge = await app_1.default.service("global-accounts-challenge").create({
19
19
  address: account.address,
@@ -31,13 +31,13 @@ function useSiwe() {
31
31
  signature,
32
32
  serverSignature: challenge.serverSignature,
33
33
  nonce: challenge.nonce,
34
- // http://localhost:5173/?referrerId=cd8fda06-3840-43d3-8f35-ae9472a13759
35
- referrerId: referrerId,
34
+ // http://localhost:5173/?referralCode=GIO2
35
+ referralCode,
36
36
  partnerId: partnerId,
37
37
  });
38
38
  (0, debug_1.default)("@@useAuthenticate:response", response);
39
39
  return response;
40
- }, [referrerId]);
40
+ }, [referralCode]);
41
41
  return {
42
42
  authenticate,
43
43
  };
@@ -72,7 +72,7 @@ export declare const useSigMintCollection: ({ address, chainId }: FindByAddressP
72
72
  description?: string | undefined;
73
73
  createdAt?: string | undefined;
74
74
  updatedAt?: string | undefined;
75
- status?: "DRAFT" | "ACTIVE" | "INACTIVE" | undefined;
75
+ status?: "ACTIVE" | "DRAFT" | "INACTIVE" | undefined;
76
76
  metadata?: {} | undefined;
77
77
  logoURI?: string | undefined;
78
78
  creator?: string | undefined;
@@ -1,17 +1,16 @@
1
- import { Account } from "thirdweb/wallets";
2
- import { User } from "../../../../global-account/types/b3-api.types";
3
- import { Wallet } from "thirdweb/wallets";
1
+ import { Users } from "@b3dotfun/b3-api";
4
2
  import { PermissionsConfig } from "../../../../global-account/types/permissions";
3
+ import { Account, Wallet } from "thirdweb/wallets";
5
4
  /**
6
5
  * Context type for B3Provider
7
6
  */
8
7
  export interface B3ContextType {
9
8
  account?: Account;
10
9
  automaticallySetFirstEoa: boolean;
11
- user?: User;
10
+ user?: Users;
12
11
  setWallet: (wallet: Wallet) => void;
13
12
  wallet?: Wallet;
14
- setUser: (user?: User) => void;
13
+ setUser: (user?: Users) => void;
15
14
  initialized: boolean;
16
15
  ready: boolean;
17
16
  environment?: "development" | "production";
@@ -1,10 +1,12 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button, TabsContentPrimitive, TabsListPrimitive, TabsPrimitive, TabTriggerPrimitive, useAccountAssets, useAuthentication, useGetAllTWSigners, useModalStore, useRemoveSessionKey, } from "../../../../global-account/react/index.js";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import app from "../../../../global-account/app.js";
3
+ import { Button, TabsContentPrimitive, TabsListPrimitive, TabsPrimitive, TabTriggerPrimitive, useAccountAssets, useAuthentication, useB3, useGetAllTWSigners, useModalStore, useQueryB3, useRemoveSessionKey, } from "../../../../global-account/react/index.js";
3
4
  import { SignOutIcon } from "../../../../global-account/react/components/icons/SignOutIcon.js";
4
5
  import { formatNumber } from "../../../../shared/utils/formatNumber.js";
5
6
  import { client } from "../../../../shared/utils/thirdweb.js";
6
- import { BarChart3, Coins, Image, LinkIcon, Loader2, Settings, UnlinkIcon } from "lucide-react";
7
- import { useState } from "react";
7
+ import { BarChart3, Coins, Copy, Image, LinkIcon, Loader2, Pencil, Settings, UnlinkIcon } from "lucide-react";
8
+ import { useRef, useState } from "react";
9
+ import { toast } from "sonner";
8
10
  import { useActiveAccount, useProfiles, useUnlinkProfile } from "thirdweb/react";
9
11
  import { formatUnits } from "viem";
10
12
  import { getProfileDisplayInfo } from "../../utils/profileDisplay.js";
@@ -52,6 +54,45 @@ export function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit
52
54
  const { data: profilesRaw = [], isLoading: isLoadingProfiles } = useProfiles({ client });
53
55
  const { mutate: unlinkProfile, isPending: isUnlinking } = useUnlinkProfile();
54
56
  const { setB3ModalOpen, setB3ModalContentType, isLinking } = useModalStore();
57
+ const { user, setUser } = useB3();
58
+ const [isUpdatingCode, setIsUpdatingCode] = useState(false);
59
+ const [newReferralCode, setNewReferralCode] = useState("");
60
+ const [isEditingCode, setIsEditingCode] = useState(false);
61
+ const referallCodeRef = useRef(null);
62
+ const { data: referrals, isLoading: isLoadingReferrals } = useQueryB3("referrals", "find", { query: { referrerId: user?.userId } }, !!user?.userId);
63
+ // Fetch referred users
64
+ const currentReferralCode = user?.referralCode || user?.userId || "";
65
+ const handleCopyCode = async () => {
66
+ try {
67
+ await navigator.clipboard.writeText(currentReferralCode);
68
+ toast.success("Referral code copied to clipboard!");
69
+ }
70
+ catch (error) {
71
+ toast.error("Failed to copy referral code");
72
+ }
73
+ };
74
+ const handleUpdateReferralCode = async () => {
75
+ if (!newReferralCode)
76
+ return;
77
+ setIsUpdatingCode(true);
78
+ try {
79
+ // @ts-expect-error - setReferralCode is not typed for some reason
80
+ const newUser = await app.service("users").setReferralCode({
81
+ userId: user?.userId,
82
+ referralCode: newReferralCode,
83
+ });
84
+ setUser(newUser);
85
+ toast.success("Referral code updated successfully!");
86
+ setIsEditingCode(false);
87
+ setNewReferralCode("");
88
+ }
89
+ catch (error) {
90
+ toast.error("Failed to update referral code");
91
+ }
92
+ finally {
93
+ setIsUpdatingCode(false);
94
+ }
95
+ };
55
96
  const profiles = profilesRaw
56
97
  .filter((profile) => !["custom_auth_endpoint", "siwe"].includes(profile.type))
57
98
  .map((profile) => ({
@@ -88,7 +129,15 @@ export function ManageAccount({ onLogout, onSwap: _onSwap, onDeposit: _onDeposit
88
129
  },
89
130
  });
90
131
  };
91
- return (_jsxs("div", { className: "space-y-8", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Linked Accounts" }), _jsxs(Button, { className: "bg-b3-primary-wash hover:bg-b3-primary-wash/70 flex items-center gap-2 rounded-full px-4 py-2", onClick: handleOpenLinkModal, disabled: isLinking, children: [isLinking ? (_jsx(Loader2, { className: "text-b3-primary-blue animate-spin", size: 16 })) : (_jsx(LinkIcon, { size: 16, className: "text-b3-primary-blue" })), _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: isLinking ? "Linking..." : "Link New Account" })] })] }), isLoadingProfiles ? (_jsx("div", { className: "flex justify-center py-8", children: _jsx(Loader2, { className: "text-b3-grey animate-spin" }) })) : profiles.length > 0 ? (_jsx("div", { className: "space-y-4", children: profiles.map(profile => (_jsxs("div", { className: "bg-b3-line flex items-center justify-between rounded-xl p-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [profile.imageUrl ? (_jsx("img", { src: profile.imageUrl, alt: profile.title, className: "size-10 rounded-full" })) : (_jsx("div", { className: "bg-b3-primary-wash flex h-10 w-10 items-center justify-center rounded-full", children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold text-sm uppercase", children: profile.initial }) })), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: profile.title }), _jsx("span", { className: "text-b3-foreground-muted font-neue-montreal-medium bg-b3-primary-wash rounded px-2 py-0.5 text-xs", children: profile.type.toUpperCase() })] }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: profile.subtitle })] })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "text-b3-grey hover:text-b3-negative", onClick: () => handleUnlink(profile), disabled: unlinkingAccountId === profile.title || isUnlinking, children: unlinkingAccountId === profile.title || isUnlinking ? (_jsx(Loader2, { className: "animate-spin" })) : (_jsx(UnlinkIcon, { size: 16 })) })] }, profile.title))) })) : (_jsx("div", { className: "text-b3-foreground-muted py-8 text-center", children: "No linked accounts found" }))] }), _jsxs("div", { className: "space-y-4", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Account Preferences" }), _jsx("div", { className: "bg-b3-line rounded-xl p-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Dark Mode" }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Switch between light and dark theme" })] }), _jsx("div", { className: "bg-b3-primary-wash h-6 w-12 rounded-full" })] }) })] }), _jsxs("div", { className: "border-b3-line flex items-center justify-between rounded-2xl border p-4", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("img", { src: "https://cdn.b3.fun/b3_logo.svg", alt: "B3", className: "h-4" }), _jsx("h3", { className: "font-neue-montreal-semibold text-b3-grey", children: "Global Account" })] }), _jsx("p", { className: "text-b3-foreground-muted font-neue-montreal-medium mt-2 text-sm", children: "Your universal account for all B3 apps" })] }), _jsx("button", { className: "text-b3-grey hover:text-b3-grey/80 hover:bg-b3-line border-b3-line flex size-12 items-center justify-center rounded-full border", onClick: onLogoutEnhanced, children: logoutLoading ? _jsx(Loader2, { className: "animate-spin" }) : _jsx(SignOutIcon, { size: 16, className: "text-b3-grey" }) })] })] }));
132
+ return (_jsxs("div", { className: "space-y-8", children: [_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Linked Accounts" }), _jsxs(Button, { className: "bg-b3-primary-wash hover:bg-b3-primary-wash/70 flex items-center gap-2 rounded-full px-4 py-2", onClick: handleOpenLinkModal, disabled: isLinking, children: [isLinking ? (_jsx(Loader2, { className: "text-b3-primary-blue animate-spin", size: 16 })) : (_jsx(LinkIcon, { size: 16, className: "text-b3-primary-blue" })), _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: isLinking ? "Linking..." : "Link New Account" })] })] }), isLoadingProfiles ? (_jsx("div", { className: "flex justify-center py-8", children: _jsx(Loader2, { className: "text-b3-grey animate-spin" }) })) : profiles.length > 0 ? (_jsx("div", { className: "space-y-4", children: profiles.map(profile => (_jsxs("div", { className: "bg-b3-line flex items-center justify-between rounded-xl p-4", children: [_jsxs("div", { className: "flex items-center gap-3", children: [profile.imageUrl ? (_jsx("img", { src: profile.imageUrl, alt: profile.title, className: "size-10 rounded-full" })) : (_jsx("div", { className: "bg-b3-primary-wash flex h-10 w-10 items-center justify-center rounded-full", children: _jsx("span", { className: "text-b3-grey font-neue-montreal-semibold text-sm uppercase", children: profile.initial }) })), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-b3-grey font-neue-montreal-semibold", children: profile.title }), _jsx("span", { className: "text-b3-foreground-muted font-neue-montreal-medium bg-b3-primary-wash rounded px-2 py-0.5 text-xs", children: profile.type.toUpperCase() })] }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: profile.subtitle })] })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "text-b3-grey hover:text-b3-negative", onClick: () => handleUnlink(profile), disabled: unlinkingAccountId === profile.title || isUnlinking, children: unlinkingAccountId === profile.title || isUnlinking ? (_jsx(Loader2, { className: "animate-spin" })) : (_jsx(UnlinkIcon, { size: 16 })) })] }, profile.title))) })) : (_jsx("div", { className: "text-b3-foreground-muted py-8 text-center", children: "No linked accounts found" }))] }), _jsxs("div", { className: "space-y-4", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Referrals" }), _jsxs("div", { className: "bg-b3-line rounded-xl p-4", children: [isEditingCode && (_jsxs("div", { children: [_jsx("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Your Referral Code" }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Share this code with friends to earn rewards" })] })), _jsxs("div", { className: "flex items-center justify-between", children: [!isEditingCode && (_jsxs("div", { children: [_jsx("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Your Referral Code" }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Share this code with friends to earn rewards" })] })), _jsx("div", { className: "flex items-center gap-2", children: isEditingCode ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { type: "text", value: newReferralCode, onChange: e => setNewReferralCode(e.target.value), className: "rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm", placeholder: "Enter new code", ref: referallCodeRef }), _jsx(Button, { size: "sm", onClick: handleUpdateReferralCode, disabled: isUpdatingCode || !newReferralCode, children: isUpdatingCode ? _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : "Save" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => {
133
+ setIsEditingCode(false);
134
+ setNewReferralCode("");
135
+ }, children: "Cancel" })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm", children: currentReferralCode }), _jsx(Button, { size: "icon", variant: "ghost", onClick: handleCopyCode, children: _jsx(Copy, { className: "h-4 w-4" }) }), _jsx(Button, { size: "icon", variant: "ghost", onClick: () => {
136
+ setIsEditingCode(true);
137
+ setTimeout(() => {
138
+ referallCodeRef.current?.focus();
139
+ }, 100);
140
+ }, children: _jsx(Pencil, { className: "h-4 w-4" }) })] })) })] })] }), _jsxs("div", { className: "bg-b3-line rounded-xl p-4", children: [_jsx("div", { className: "text-b3-grey font-neue-montreal-semibold mb-4", children: "Referred Users" }), isLoadingReferrals ? (_jsx("div", { className: "flex justify-center py-4", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-gray-400" }) })) : referrals?.data?.length ? (_jsx("div", { className: "space-y-3", children: referrals.data.map((referral) => (_jsxs("div", { className: "flex items-center justify-between rounded-lg bg-white p-3", children: [_jsx("div", { className: "text-sm font-medium", children: referral.referreeId }), _jsx("div", { className: "text-sm text-gray-500", children: new Date(referral.createdAt).toLocaleDateString() })] }, String(referral._id)))) })) : (_jsx("div", { className: "py-4 text-center text-gray-500", children: "No referred users yet" }))] })] }), _jsxs("div", { className: "space-y-4", children: [_jsx("h3", { className: "text-b3-grey font-neue-montreal-semibold text-xl", children: "Account Preferences" }), _jsx("div", { className: "bg-b3-line rounded-xl p-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-b3-grey font-neue-montreal-semibold", children: "Dark Mode" }), _jsx("div", { className: "text-b3-foreground-muted font-neue-montreal-medium text-sm", children: "Switch between light and dark theme" })] }), _jsx("div", { className: "bg-b3-primary-wash h-6 w-12 rounded-full" })] }) })] }), _jsxs("div", { className: "border-b3-line flex items-center justify-between rounded-2xl border p-4", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("img", { src: "https://cdn.b3.fun/b3_logo.svg", alt: "B3", className: "h-4" }), _jsx("h3", { className: "font-neue-montreal-semibold text-b3-grey", children: "Global Account" })] }), _jsx("p", { className: "text-b3-foreground-muted font-neue-montreal-medium mt-2 text-sm", children: "Your universal account for all B3 apps" })] }), _jsx("button", { className: "text-b3-grey hover:text-b3-grey/80 hover:bg-b3-line border-b3-line flex size-12 items-center justify-center rounded-full border", onClick: onLogoutEnhanced, children: logoutLoading ? _jsx(Loader2, { className: "animate-spin" }) : _jsx(SignOutIcon, { size: 16, className: "text-b3-grey" }) })] })] }));
92
141
  };
93
142
  return (_jsx("div", { className: "b3-manage-account bg-b3-background flex flex-col rounded-xl", children: _jsx("div", { className: "flex-1", children: _jsxs(TabsPrimitive, { defaultValue: activeTab, onValueChange: value => {
94
143
  const tab = value;
@@ -3,11 +3,11 @@ import debug from "../../../shared/utils/debug.js";
3
3
  import { useCallback } from "react";
4
4
  import { useSearchParam } from "./useSearchParamsSSR.js";
5
5
  export function useSiwe() {
6
- const referrerId = useSearchParam("referrerId");
6
+ const referralCode = useSearchParam("referralCode");
7
7
  const authenticate = useCallback(async (account, partnerId) => {
8
8
  if (!account || !account.signMessage)
9
9
  throw new Error("Account not found");
10
- console.log("@@useAuthenticate:referrerId", referrerId);
10
+ console.log("@@useAuthenticate:referralCode", referralCode);
11
11
  // generate challenge
12
12
  const challenge = await app.service("global-accounts-challenge").create({
13
13
  address: account.address,
@@ -25,13 +25,13 @@ export function useSiwe() {
25
25
  signature,
26
26
  serverSignature: challenge.serverSignature,
27
27
  nonce: challenge.nonce,
28
- // http://localhost:5173/?referrerId=cd8fda06-3840-43d3-8f35-ae9472a13759
29
- referrerId: referrerId,
28
+ // http://localhost:5173/?referralCode=GIO2
29
+ referralCode,
30
30
  partnerId: partnerId,
31
31
  });
32
32
  debug("@@useAuthenticate:response", response);
33
33
  return response;
34
- }, [referrerId]);
34
+ }, [referralCode]);
35
35
  return {
36
36
  authenticate,
37
37
  };
@@ -72,7 +72,7 @@ export declare const useSigMintCollection: ({ address, chainId }: FindByAddressP
72
72
  description?: string | undefined;
73
73
  createdAt?: string | undefined;
74
74
  updatedAt?: string | undefined;
75
- status?: "DRAFT" | "ACTIVE" | "INACTIVE" | undefined;
75
+ status?: "ACTIVE" | "DRAFT" | "INACTIVE" | undefined;
76
76
  metadata?: {} | undefined;
77
77
  logoURI?: string | undefined;
78
78
  creator?: string | undefined;
@@ -1,17 +1,16 @@
1
- import { Account } from "thirdweb/wallets";
2
- import { User } from "@b3dotfun/sdk/global-account/types/b3-api.types";
3
- import { Wallet } from "thirdweb/wallets";
1
+ import { Users } from "@b3dotfun/b3-api";
4
2
  import { PermissionsConfig } from "@b3dotfun/sdk/global-account/types/permissions";
3
+ import { Account, Wallet } from "thirdweb/wallets";
5
4
  /**
6
5
  * Context type for B3Provider
7
6
  */
8
7
  export interface B3ContextType {
9
8
  account?: Account;
10
9
  automaticallySetFirstEoa: boolean;
11
- user?: User;
10
+ user?: Users;
12
11
  setWallet: (wallet: Wallet) => void;
13
12
  wallet?: Wallet;
14
- setUser: (user?: User) => void;
13
+ setUser: (user?: Users) => void;
15
14
  initialized: boolean;
16
15
  ready: boolean;
17
16
  environment?: "development" | "production";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.0.31-alpha.1",
3
+ "version": "0.0.31-alpha.2",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -237,7 +237,7 @@
237
237
  "constants"
238
238
  ],
239
239
  "dependencies": {
240
- "@b3dotfun/b3-api": "0.0.43",
240
+ "@b3dotfun/b3-api": "0.0.45",
241
241
  "@b3dotfun/basement-api": "0.0.11",
242
242
  "@chakra-ui/react": "2.10.7",
243
243
  "@feathersjs/authentication-client": "5.0.33",
@@ -4,7 +4,7 @@ import { useState } from "react";
4
4
  import { ThirdwebProvider, useActiveAccount } from "thirdweb/react";
5
5
  import { Account } from "thirdweb/wallets";
6
6
 
7
- import { User } from "@b3dotfun/sdk/global-account/types/b3-api.types";
7
+ import { Users } from "@b3dotfun/b3-api";
8
8
  import { B3Context, B3ContextType } from "./types";
9
9
 
10
10
  /**
@@ -64,7 +64,7 @@ export function InnerProvider({
64
64
  theme: "light" | "dark";
65
65
  }) {
66
66
  const activeAccount = useActiveAccount();
67
- const [user, setUser] = useState<User | undefined>(undefined);
67
+ const [user, setUser] = useState<Users | undefined>(undefined);
68
68
 
69
69
  // Use given accountOverride or activeAccount from thirdweb
70
70
  const effectiveAccount = accountOverride || activeAccount;
@@ -1,5 +1,4 @@
1
1
  import { RelayKitProviderWrapper, TooltipProvider, useAuthStore } from "@b3dotfun/sdk/global-account/react";
2
- import { User } from "@b3dotfun/sdk/global-account/types/b3-api.types";
3
2
  import { PermissionsConfig } from "@b3dotfun/sdk/global-account/types/permissions";
4
3
  import { loadGA4Script } from "@b3dotfun/sdk/global-account/utils/analytics";
5
4
  import { supportedChains } from "@b3dotfun/sdk/shared/constants/chains/supported";
@@ -18,6 +17,7 @@ import { createConfig, http, WagmiProvider } from "wagmi";
18
17
  import { StyleRoot } from "../StyleRoot";
19
18
  import { B3Context, B3ContextType } from "./types";
20
19
 
20
+ import { Users } from "@b3dotfun/b3-api";
21
21
  import "@reservoir0x/relay-kit-ui/styles.css";
22
22
 
23
23
  /**
@@ -115,7 +115,7 @@ export function InnerProvider({
115
115
  const setActiveWallet = useSetActiveWallet();
116
116
  const isAuthenticated = useAuthStore(state => state.isAuthenticated);
117
117
 
118
- const [user, setUser] = useState<User | undefined>(undefined);
118
+ const [user, setUser] = useState<Users | undefined>(undefined);
119
119
 
120
120
  // Use given accountOverride or activeAccount from thirdweb
121
121
  const effectiveAccount = isAuthenticated ? accountOverride || activeAccount : undefined;
@@ -1,8 +1,7 @@
1
- import { Account } from "thirdweb/wallets";
2
- import { User } from "@b3dotfun/sdk/global-account/types/b3-api.types";
3
- import { Wallet } from "thirdweb/wallets";
1
+ import { Users } from "@b3dotfun/b3-api";
4
2
  import { PermissionsConfig } from "@b3dotfun/sdk/global-account/types/permissions";
5
3
  import { createContext } from "react";
4
+ import { Account, Wallet } from "thirdweb/wallets";
6
5
 
7
6
  /**
8
7
  * Context type for B3Provider
@@ -10,10 +9,10 @@ import { createContext } from "react";
10
9
  export interface B3ContextType {
11
10
  account?: Account;
12
11
  automaticallySetFirstEoa: boolean;
13
- user?: User;
12
+ user?: Users;
14
13
  setWallet: (wallet: Wallet) => void;
15
14
  wallet?: Wallet;
16
- setUser: (user?: User) => void;
15
+ setUser: (user?: Users) => void;
17
16
  initialized: boolean;
18
17
  ready: boolean;
19
18
  environment?: "development" | "production";
@@ -1,3 +1,4 @@
1
+ import app from "@b3dotfun/sdk/global-account/app";
1
2
  import {
2
3
  Button,
3
4
  ManageAccountModalProps,
@@ -8,16 +9,18 @@ import {
8
9
  TWSignerWithMetadata,
9
10
  useAccountAssets,
10
11
  useAuthentication,
12
+ useB3,
11
13
  useGetAllTWSigners,
12
14
  useModalStore,
15
+ useQueryB3,
13
16
  useRemoveSessionKey,
14
17
  } from "@b3dotfun/sdk/global-account/react";
15
18
  import { SignOutIcon } from "@b3dotfun/sdk/global-account/react/components/icons/SignOutIcon";
16
19
  import { formatNumber } from "@b3dotfun/sdk/shared/utils/formatNumber";
17
-
18
20
  import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
19
- import { BarChart3, Coins, Image, LinkIcon, Loader2, Settings, UnlinkIcon } from "lucide-react";
20
- import { useState } from "react";
21
+ import { BarChart3, Coins, Copy, Image, LinkIcon, Loader2, Pencil, Settings, UnlinkIcon } from "lucide-react";
22
+ import { useRef, useState } from "react";
23
+ import { toast } from "sonner";
21
24
  import { Chain } from "thirdweb";
22
25
  import { useActiveAccount, useProfiles, useUnlinkProfile } from "thirdweb/react";
23
26
  import { formatUnits } from "viem";
@@ -26,6 +29,7 @@ import { getProfileDisplayInfo } from "../../utils/profileDisplay";
26
29
  import { AccountAssets } from "../AccountAssets/AccountAssets";
27
30
  import { ContentTokens } from "./ContentTokens";
28
31
 
32
+ import { Referrals, Users } from "@b3dotfun/b3-api";
29
33
  import { BalanceContent } from "./BalanceContent";
30
34
 
31
35
  type TabValue = "overview" | "tokens" | "nfts" | "apps" | "settings";
@@ -130,6 +134,50 @@ export function ManageAccount({
130
134
  const { data: profilesRaw = [], isLoading: isLoadingProfiles } = useProfiles({ client });
131
135
  const { mutate: unlinkProfile, isPending: isUnlinking } = useUnlinkProfile();
132
136
  const { setB3ModalOpen, setB3ModalContentType, isLinking } = useModalStore();
137
+ const { user, setUser } = useB3();
138
+ const [isUpdatingCode, setIsUpdatingCode] = useState(false);
139
+ const [newReferralCode, setNewReferralCode] = useState("");
140
+ const [isEditingCode, setIsEditingCode] = useState(false);
141
+ const referallCodeRef = useRef<HTMLInputElement>(null);
142
+ const { data: referrals, isLoading: isLoadingReferrals } = useQueryB3(
143
+ "referrals",
144
+ "find",
145
+ { query: { referrerId: user?.userId } },
146
+ !!user?.userId,
147
+ );
148
+
149
+ // Fetch referred users
150
+ const currentReferralCode = user?.referralCode || user?.userId || "";
151
+
152
+ const handleCopyCode = async () => {
153
+ try {
154
+ await navigator.clipboard.writeText(currentReferralCode);
155
+ toast.success("Referral code copied to clipboard!");
156
+ } catch (error) {
157
+ toast.error("Failed to copy referral code");
158
+ }
159
+ };
160
+
161
+ const handleUpdateReferralCode = async () => {
162
+ if (!newReferralCode) return;
163
+
164
+ setIsUpdatingCode(true);
165
+ try {
166
+ // @ts-expect-error - setReferralCode is not typed for some reason
167
+ const newUser = await app.service("users").setReferralCode({
168
+ userId: user?.userId,
169
+ referralCode: newReferralCode,
170
+ });
171
+ setUser(newUser as unknown as Users);
172
+ toast.success("Referral code updated successfully!");
173
+ setIsEditingCode(false);
174
+ setNewReferralCode("");
175
+ } catch (error) {
176
+ toast.error("Failed to update referral code");
177
+ } finally {
178
+ setIsUpdatingCode(false);
179
+ }
180
+ };
133
181
 
134
182
  const profiles = profilesRaw
135
183
  .filter((profile: any) => !["custom_auth_endpoint", "siwe"].includes(profile.type))
@@ -241,6 +289,102 @@ export function ManageAccount({
241
289
  )}
242
290
  </div>
243
291
 
292
+ {/* Referral Section */}
293
+ <div className="space-y-4">
294
+ <h3 className="text-b3-grey font-neue-montreal-semibold text-xl">Referrals</h3>
295
+
296
+ {/* Referral Code */}
297
+ <div className="bg-b3-line rounded-xl p-4">
298
+ {isEditingCode && (
299
+ <div>
300
+ <div className="text-b3-grey font-neue-montreal-semibold">Your Referral Code</div>
301
+ <div className="text-b3-foreground-muted font-neue-montreal-medium text-sm">
302
+ Share this code with friends to earn rewards
303
+ </div>
304
+ </div>
305
+ )}
306
+ <div className="flex items-center justify-between">
307
+ {!isEditingCode && (
308
+ <div>
309
+ <div className="text-b3-grey font-neue-montreal-semibold">Your Referral Code</div>
310
+ <div className="text-b3-foreground-muted font-neue-montreal-medium text-sm">
311
+ Share this code with friends to earn rewards
312
+ </div>
313
+ </div>
314
+ )}
315
+ <div className="flex items-center gap-2">
316
+ {isEditingCode ? (
317
+ <div className="flex items-center gap-2">
318
+ <input
319
+ type="text"
320
+ value={newReferralCode}
321
+ onChange={e => setNewReferralCode(e.target.value)}
322
+ className="rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm"
323
+ placeholder="Enter new code"
324
+ ref={referallCodeRef}
325
+ />
326
+ <Button size="sm" onClick={handleUpdateReferralCode} disabled={isUpdatingCode || !newReferralCode}>
327
+ {isUpdatingCode ? <Loader2 className="h-4 w-4 animate-spin" /> : "Save"}
328
+ </Button>
329
+ <Button
330
+ size="sm"
331
+ variant="ghost"
332
+ onClick={() => {
333
+ setIsEditingCode(false);
334
+ setNewReferralCode("");
335
+ }}
336
+ >
337
+ Cancel
338
+ </Button>
339
+ </div>
340
+ ) : (
341
+ <>
342
+ <div className="rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm">
343
+ {currentReferralCode}
344
+ </div>
345
+ <Button size="icon" variant="ghost" onClick={handleCopyCode}>
346
+ <Copy className="h-4 w-4" />
347
+ </Button>
348
+ <Button
349
+ size="icon"
350
+ variant="ghost"
351
+ onClick={() => {
352
+ setIsEditingCode(true);
353
+ setTimeout(() => {
354
+ referallCodeRef.current?.focus();
355
+ }, 100);
356
+ }}
357
+ >
358
+ <Pencil className="h-4 w-4" />
359
+ </Button>
360
+ </>
361
+ )}
362
+ </div>
363
+ </div>
364
+ </div>
365
+
366
+ {/* Referred Users */}
367
+ <div className="bg-b3-line rounded-xl p-4">
368
+ <div className="text-b3-grey font-neue-montreal-semibold mb-4">Referred Users</div>
369
+ {isLoadingReferrals ? (
370
+ <div className="flex justify-center py-4">
371
+ <Loader2 className="h-6 w-6 animate-spin text-gray-400" />
372
+ </div>
373
+ ) : referrals?.data?.length ? (
374
+ <div className="space-y-3">
375
+ {referrals.data.map((referral: Referrals) => (
376
+ <div key={String(referral._id)} className="flex items-center justify-between rounded-lg bg-white p-3">
377
+ <div className="text-sm font-medium">{referral.referreeId}</div>
378
+ <div className="text-sm text-gray-500">{new Date(referral.createdAt).toLocaleDateString()}</div>
379
+ </div>
380
+ ))}
381
+ </div>
382
+ ) : (
383
+ <div className="py-4 text-center text-gray-500">No referred users yet</div>
384
+ )}
385
+ </div>
386
+ </div>
387
+
244
388
  {/* Additional Settings Sections */}
245
389
  <div className="space-y-4">
246
390
  <h3 className="text-b3-grey font-neue-montreal-semibold text-xl">Account Preferences</h3>
@@ -5,13 +5,13 @@ import { Account } from "thirdweb/wallets";
5
5
  import { useSearchParam } from "./useSearchParamsSSR";
6
6
 
7
7
  export function useSiwe() {
8
- const referrerId = useSearchParam("referrerId");
8
+ const referralCode = useSearchParam("referralCode");
9
9
 
10
10
  const authenticate = useCallback(
11
11
  async (account: Account, partnerId: string) => {
12
12
  if (!account || !account.signMessage) throw new Error("Account not found");
13
13
 
14
- console.log("@@useAuthenticate:referrerId", referrerId);
14
+ console.log("@@useAuthenticate:referralCode", referralCode);
15
15
  // generate challenge
16
16
  const challenge = await app.service("global-accounts-challenge").create({
17
17
  address: account.address,
@@ -32,15 +32,15 @@ export function useSiwe() {
32
32
  signature,
33
33
  serverSignature: challenge.serverSignature,
34
34
  nonce: challenge.nonce,
35
- // http://localhost:5173/?referrerId=cd8fda06-3840-43d3-8f35-ae9472a13759
36
- referrerId: referrerId,
35
+ // http://localhost:5173/?referralCode=GIO2
36
+ referralCode,
37
37
  partnerId: partnerId,
38
38
  });
39
39
  debug("@@useAuthenticate:response", response);
40
40
 
41
41
  return response;
42
42
  },
43
- [referrerId],
43
+ [referralCode],
44
44
  );
45
45
 
46
46
  return {