Skip to content

Commit f2121e9

Browse files
authored
[Dashboard] Add userId support across wallet APIs and UI (#8290)
1 parent 29d05a5 commit f2121e9

File tree

9 files changed

+166
-56
lines changed

9 files changed

+166
-56
lines changed

.changeset/breezy-heads-crash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@thirdweb-dev/api": patch
3+
---
4+
5+
added support for userId across wallet apis

apps/dashboard/src/@/components/in-app-wallet-users-content/AdvancedSearchInput.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ import {
1414
} from "@/components/ui/select";
1515
import type { SearchType } from "./types";
1616

17+
const searchTypeLabels: Record<SearchType, string> = {
18+
email: "Email",
19+
phone: "Phone",
20+
id: "Auth Identifier",
21+
address: "Address",
22+
externalWallet: "External Wallet",
23+
userId: "User Identifier",
24+
};
25+
1726
export function AdvancedSearchInput(props: {
1827
onSearch: (searchType: SearchType, query: string) => void;
1928
onClear: () => void;
@@ -52,7 +61,8 @@ export function AdvancedSearchInput(props: {
5261
<SelectContent>
5362
<SelectItem value="email">Email</SelectItem>
5463
<SelectItem value="phone">Phone</SelectItem>
55-
<SelectItem value="id">ID</SelectItem>
64+
<SelectItem value="id">Auth Identifier</SelectItem>
65+
<SelectItem value="userId">User Identifier</SelectItem>
5666
<SelectItem value="address">Address</SelectItem>
5767
<SelectItem value="externalWallet">External Wallet</SelectItem>
5868
</SelectContent>
@@ -62,7 +72,7 @@ export function AdvancedSearchInput(props: {
6272
<div className="relative flex-1">
6373
<Input
6474
className="bg-background pl-9 border-r-0 rounded-r-none rounded-l-full"
65-
placeholder={`Search by ${searchType}...`}
75+
placeholder={`Search by ${searchTypeLabels[searchType]}...`}
6676
value={query}
6777
onChange={(e) => setQuery(e.target.value)}
6878
onKeyDown={handleKeyDown}

apps/dashboard/src/@/components/in-app-wallet-users-content/SearchResults.tsx

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,25 @@ import {
1212
TooltipProvider,
1313
TooltipTrigger,
1414
} from "@/components/ui/tooltip";
15+
import { CopyTextButton } from "../ui/CopyTextButton";
1516

16-
const getUserIdentifier = (user: WalletUser) => {
17+
const getAuthIdentifier = (user: WalletUser) => {
1718
const mainDetail = user.linkedAccounts[0]?.details;
1819
return (
20+
mainDetail?.id ??
1921
mainDetail?.email ??
2022
mainDetail?.phone ??
21-
mainDetail?.address ??
22-
mainDetail?.id ??
23-
user.id
23+
mainDetail?.address
2424
);
2525
};
2626

27+
const truncateIdentifier = (value: string) => {
28+
if (value.length <= 18) {
29+
return value;
30+
}
31+
return `${value.slice(0, 8)}...${value.slice(-4)}`;
32+
};
33+
2734
export function SearchResults(props: {
2835
results: WalletUser[];
2936
client: ThirdwebClient;
@@ -51,6 +58,8 @@ export function SearchResults(props: {
5158
const mainDetail = user.linkedAccounts?.[0]?.details;
5259
const email = mainDetail?.email as string | undefined;
5360
const phone = mainDetail?.phone as string | undefined;
61+
const authIdentifier = getAuthIdentifier(user);
62+
const userIdentifier = user.id;
5463

5564
// Get external wallet addresses from linkedAccounts where type is 'siwe'
5665
const externalWalletAccounts =
@@ -68,7 +77,34 @@ export function SearchResults(props: {
6877
<p className="text-sm font-medium text-muted-foreground">
6978
User Identifier
7079
</p>
71-
<p className="text-sm">{getUserIdentifier(user)}</p>
80+
{userIdentifier ? (
81+
<CopyTextButton
82+
textToShow={truncateIdentifier(userIdentifier)}
83+
textToCopy={userIdentifier}
84+
tooltip="Copy User Identifier"
85+
copyIconPosition="left"
86+
variant="ghost"
87+
/>
88+
) : (
89+
<p className="text-sm">N/A</p>
90+
)}
91+
</div>
92+
93+
<div>
94+
<p className="text-sm font-medium text-muted-foreground">
95+
Auth Identifier
96+
</p>
97+
{authIdentifier ? (
98+
<CopyTextButton
99+
textToShow={truncateIdentifier(authIdentifier)}
100+
textToCopy={authIdentifier}
101+
tooltip="Copy Auth Identifier"
102+
copyIconPosition="left"
103+
variant="ghost"
104+
/>
105+
) : (
106+
<p className="text-sm">N/A</p>
107+
)}
72108
</div>
73109

74110
{walletAddress && (

apps/dashboard/src/@/components/in-app-wallet-users-content/in-app-wallet-users-content.tsx

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,42 @@ import { SearchResults } from "./SearchResults";
2828
import { searchUsers } from "./searchUsers";
2929
import type { SearchType } from "./types";
3030

31-
const getUserIdentifier = (accounts: WalletUser["linkedAccounts"]) => {
31+
const getAuthIdentifier = (accounts: WalletUser["linkedAccounts"]) => {
3232
const mainDetail = accounts[0]?.details;
3333
return (
34+
mainDetail?.id ??
3435
mainDetail?.email ??
3536
mainDetail?.phone ??
36-
mainDetail?.address ??
37-
mainDetail?.id
37+
mainDetail?.address
3838
);
3939
};
4040

41-
const getExternalWallets = (accounts: WalletUser["linkedAccounts"]) => {
42-
return accounts?.filter((account) => account.type === "siwe") || [];
41+
const getPrimaryEmail = (accounts: WalletUser["linkedAccounts"]) => {
42+
const emailFromPrimary = accounts[0]?.details?.email;
43+
if (emailFromPrimary) {
44+
return emailFromPrimary;
45+
}
46+
47+
const emailAccount = accounts.find((account) => {
48+
return typeof account.details?.email === "string" && account.details.email;
49+
});
50+
51+
if (emailAccount && typeof emailAccount.details.email === "string") {
52+
return emailAccount.details.email;
53+
}
54+
55+
return undefined;
4356
};
4457

4558
const columnHelper = createColumnHelper<WalletUser>();
4659

60+
const truncateIdentifier = (value: string) => {
61+
if (value.length <= 18) {
62+
return value;
63+
}
64+
return `${value.slice(0, 8)}...${value.slice(-4)}`;
65+
};
66+
4767
export function InAppWalletUsersPageContent(
4868
props: {
4969
authToken: string;
@@ -56,31 +76,48 @@ export function InAppWalletUsersPageContent(
5676
) {
5777
const columns = useMemo(() => {
5878
return [
79+
columnHelper.accessor("id", {
80+
cell: (cell) => {
81+
const userId = cell.getValue();
82+
83+
if (!userId) {
84+
return "N/A";
85+
}
86+
87+
return (
88+
<CopyTextButton
89+
textToShow={truncateIdentifier(userId)}
90+
textToCopy={userId}
91+
tooltip="Copy User Identifier"
92+
copyIconPosition="left"
93+
variant="ghost"
94+
/>
95+
);
96+
},
97+
header: "User Identifier",
98+
id: "user_identifier",
99+
}),
59100
columnHelper.accessor("linkedAccounts", {
60101
cell: (cell) => {
61-
const identifier = getUserIdentifier(cell.getValue());
102+
const identifier = getAuthIdentifier(cell.getValue());
62103

63104
if (!identifier) {
64105
return "N/A";
65106
}
66107

67108
return (
68109
<CopyTextButton
69-
textToShow={
70-
identifier.length > 30
71-
? `${identifier.slice(0, 30)}...`
72-
: identifier
73-
}
110+
textToShow={truncateIdentifier(identifier)}
74111
textToCopy={identifier}
75-
tooltip="Copy User Identifier"
112+
tooltip="Copy Auth Identifier"
76113
copyIconPosition="left"
77114
variant="ghost"
78115
/>
79116
);
80117
},
81118
enableColumnFilter: true,
82-
header: "User Identifier",
83-
id: "user_identifier",
119+
header: "Auth Identifier",
120+
id: "auth_identifier",
84121
}),
85122
columnHelper.accessor("wallets", {
86123
cell: (cell) => {
@@ -94,33 +131,24 @@ export function InAppWalletUsersPageContent(
94131
}),
95132
columnHelper.accessor("linkedAccounts", {
96133
cell: (cell) => {
97-
const externalWallets = getExternalWallets(cell.getValue());
98-
if (externalWallets.length === 0) {
99-
return <span className="text-muted-foreground text-sm">None</span>;
134+
const email = getPrimaryEmail(cell.getValue());
135+
136+
if (!email) {
137+
return <span className="text-muted-foreground text-sm">N/A</span>;
100138
}
139+
101140
return (
102-
<div className="space-y-1">
103-
{externalWallets.slice(0, 2).map((account) => {
104-
const address = account.details?.address as string | undefined;
105-
return address ? (
106-
<div
107-
key={`external-${address}-${account.details?.id}`}
108-
className="text-xs"
109-
>
110-
<WalletAddress address={address} client={props.client} />
111-
</div>
112-
) : null;
113-
})}
114-
{externalWallets.length > 2 && (
115-
<span className="text-muted-foreground text-xs">
116-
+{externalWallets.length - 2} more
117-
</span>
118-
)}
119-
</div>
141+
<CopyTextButton
142+
textToShow={email}
143+
textToCopy={email}
144+
tooltip="Copy Email"
145+
copyIconPosition="left"
146+
variant="ghost"
147+
/>
120148
);
121149
},
122-
header: "External Wallets",
123-
id: "external_wallets",
150+
header: "Email",
151+
id: "email",
124152
}),
125153
columnHelper.accessor("wallets", {
126154
cell: (cell) => {
@@ -230,20 +258,17 @@ export function InAppWalletUsersPageContent(
230258
});
231259
const csv = Papa.unparse(
232260
usersWallets.map((row) => {
233-
const externalWallets = getExternalWallets(row.linkedAccounts);
234-
const externalWalletAddresses = externalWallets
235-
.map((account) => account.details?.address)
236-
.filter(Boolean)
237-
.join(", ");
261+
const email = getPrimaryEmail(row.linkedAccounts);
238262

239263
return {
240264
address: row.wallets[0]?.address || "Uninitialized",
241265
created: row.wallets[0]?.createdAt
242266
? new Date(row.wallets[0].createdAt).toISOString()
243267
: "Wallet not created yet",
244-
external_wallets: externalWalletAddresses || "None",
268+
email: email || "N/A",
245269
login_methods: row.linkedAccounts.map((acc) => acc.type).join(", "),
246-
user_identifier: getUserIdentifier(row.linkedAccounts),
270+
auth_identifier: getAuthIdentifier(row.linkedAccounts) || "N/A",
271+
user_identifier: row.id || "N/A",
247272
};
248273
}),
249274
);

apps/dashboard/src/@/components/in-app-wallet-users-content/searchUsers.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ configure({
1818
type APIWallet = ListUserWalletsResponses[200]["result"]["wallets"][0];
1919
type APIProfile = APIWallet["profiles"][0];
2020

21-
// Transform API response to match existing WalletUser format
21+
// Transform API response to match wallet user format
2222
function transformToWalletUser(apiWallet: APIWallet): WalletUser {
2323
return {
24-
id: getProfileId(apiWallet.profiles[0]) || "",
24+
id: apiWallet.userId || getProfileId(apiWallet.profiles[0]) || "",
2525
linkedAccounts: apiWallet.profiles.map((profile) => {
2626
// Create details object based on the profile data
2727
let details:
@@ -105,6 +105,9 @@ export async function searchUsers(
105105
case "externalWallet":
106106
queryParams.externalWalletAddress = query;
107107
break;
108+
case "userId":
109+
queryParams.userId = query;
110+
break;
108111
}
109112

110113
// Use the generated API function with Bearer authentication

apps/dashboard/src/@/components/in-app-wallet-users-content/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export type SearchType =
33
| "phone"
44
| "id"
55
| "address"
6-
| "externalWallet";
6+
| "externalWallet"
7+
| "userId";

apps/dashboard/src/@/hooks/useEmbeddedWallets.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ const fetchAccountList = ({
7676
};
7777
};
7878

79-
// Transform API response to match existing WalletUser format
79+
// Transform API response to match the wallet user format
8080
function transformToWalletUser(apiWallet: APIWallet): WalletUser {
8181
return {
82-
id: getProfileId(apiWallet.profiles[0]) || "",
82+
id: apiWallet.userId || getProfileId(apiWallet.profiles[0]) || "",
8383
linkedAccounts: apiWallet.profiles.map((profile) => {
8484
// Create details object based on the profile data
8585
let details:

packages/api/src/client/sdk.gen.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export const initiateAuthentication = <ThrowOnError extends boolean = false>(
244244
* - `isNewUser` - Whether this is a new wallet creation
245245
* - `token` - JWT token for authenticated API requests
246246
* - `type` - The authentication method used
247+
* - `userId` - Unique identifier for the authenticated user
247248
* - `walletAddress` - Your new or existing wallet address
248249
*
249250
* **Next step - Verify your token:**
@@ -442,6 +443,7 @@ export const socialAuthentication = <ThrowOnError extends boolean = false>(
442443
* Retrieve the authenticated user's wallet information including wallet addresses and linked authentication wallets. This endpoint provides comprehensive user data for the currently authenticated session.
443444
*
444445
* **Returns:**
446+
* - userId - Unique identifier for this wallet in thirdweb auth
445447
* - Primary wallet address
446448
* - Smart wallet address (if available)
447449
* - Wallet creation timestamp

0 commit comments

Comments
 (0)