Skip to content

Commit f6188f8

Browse files
committed
feat: update user endpoint for in app wallet
1 parent 5dc1899 commit f6188f8

File tree

6 files changed

+111
-109
lines changed

6 files changed

+111
-109
lines changed

apps/dashboard/src/@3rdweb-sdk/react/cache-keys.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,11 @@ export const embeddedWalletsKeys = {
6363
all: ["embeddedWallets"] as const,
6464
wallet: (walletAddress: string) =>
6565
[...embeddedWalletsKeys.all, walletAddress] as const,
66-
embeddedWallets: (walletAddress: string, clientId: string | undefined) =>
67-
[...embeddedWalletsKeys.wallet(walletAddress), clientId] as const,
66+
embeddedWallets: (
67+
walletAddress: string,
68+
clientId: string | undefined,
69+
page: number,
70+
) => [...embeddedWalletsKeys.wallet(walletAddress), clientId, page] as const,
6871
};
6972

7073
export const engineKeys = {

apps/dashboard/src/@3rdweb-sdk/react/hooks/useEmbeddedWallets.ts

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,43 @@
11
import { useQuery } from "@tanstack/react-query";
22
import { THIRDWEB_EWS_API_HOST } from "constants/urls";
3+
import type { WalletUser } from "thirdweb/wallets";
34
import { embeddedWalletsKeys } from "../cache-keys";
45
import { useLoggedInUser } from "./useLoggedInUser";
56

6-
// FIXME: Make API to return camelCase or transform
7-
export type EmbeddedWalletUser = {
8-
id: string;
9-
client_id: string;
10-
created_at: string;
11-
last_accessed_at: string;
12-
embedded_wallet?: {
13-
id: string;
14-
address: string;
15-
chain: string;
16-
wallet_user_id: string;
17-
}[];
18-
ews_authed_user: {
19-
id: string;
20-
authed_user_id: string;
21-
email: string;
22-
}[];
23-
};
24-
25-
export function useEmbeddedWallets(clientId: string) {
7+
export function useEmbeddedWallets(clientId: string, page: number) {
268
const { user, isLoggedIn } = useLoggedInUser();
279

2810
return useQuery({
2911
queryKey: embeddedWalletsKeys.embeddedWallets(
3012
user?.address as string,
31-
clientId as string,
13+
clientId,
14+
page,
3215
),
3316
queryFn: async () => {
34-
const res = await fetch(
35-
`${THIRDWEB_EWS_API_HOST}/api/thirdweb/embedded-wallet?clientId=${clientId}&lastAccessedAt=0`,
36-
{
37-
method: "GET",
38-
headers: {
39-
"Content-Type": "application/json",
40-
Authorization: `Bearer ${user?.jwt}`,
41-
},
42-
},
17+
const url = new URL(
18+
`${THIRDWEB_EWS_API_HOST}/api/2024-05-05/account/list`,
4319
);
20+
// TODO: replace this with the real thing
21+
url.searchParams.append("clientId", "5574e050c33e91d4526218dc7c7d2af0");
22+
url.searchParams.append("page", page.toString());
23+
24+
const res = await fetch(url.href, {
25+
method: "GET",
26+
headers: {
27+
"Content-Type": "application/json",
28+
Authorization: `Bearer ${user?.jwt}`,
29+
},
30+
});
31+
if (!res.ok) {
32+
throw new Error(`Failed to fetch wallets: ${await res.text()}`);
33+
}
4434

4535
const json = await res.json();
4636

47-
return json.walletUsers as EmbeddedWalletUser[];
37+
return json as {
38+
users: WalletUser[];
39+
totalPages: number;
40+
};
4841
},
4942
enabled: !!user?.address && isLoggedIn && !!clientId,
5043
});

apps/dashboard/src/components/embedded-wallets/Users/index.tsx

Lines changed: 76 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,54 @@
33
import { WalletAddress } from "@/components/blocks/wallet-address";
44
import { PaginationButtons } from "@/components/pagination-buttons";
55
import { Button } from "@/components/ui/button";
6-
import { Switch } from "@/components/ui/switch";
76
import {
8-
type EmbeddedWalletUser,
9-
useEmbeddedWallets,
10-
} from "@3rdweb-sdk/react/hooks/useEmbeddedWallets";
7+
Tooltip,
8+
TooltipContent,
9+
TooltipProvider,
10+
TooltipTrigger,
11+
} from "@/components/ui/tooltip";
12+
import { useEmbeddedWallets } from "@3rdweb-sdk/react/hooks/useEmbeddedWallets";
1113
import { createColumnHelper } from "@tanstack/react-table";
1214
import { TWTable } from "components/shared/TWTable";
1315
import { format } from "date-fns/format";
1416
import Papa from "papaparse";
15-
import { useCallback, useMemo, useState } from "react";
16-
import { withinDays } from "utils/date-utils";
17+
import { useCallback, useState } from "react";
18+
import type { WalletUser } from "thirdweb/wallets";
1719

1820
const ACTIVE_THRESHOLD_DAYS = 30;
21+
const getUserIdentifier = (accounts: WalletUser["linkedAccounts"]) => {
22+
const mainDetail = accounts[0]?.details;
23+
return (
24+
mainDetail?.email ??
25+
mainDetail?.phone ??
26+
mainDetail?.address ??
27+
mainDetail?.id
28+
);
29+
};
1930

20-
const columnHelper = createColumnHelper<EmbeddedWalletUser>();
21-
31+
const columnHelper = createColumnHelper<WalletUser>();
2232
const columns = [
23-
columnHelper.accessor("ews_authed_user", {
24-
header: "Email",
33+
columnHelper.accessor("linkedAccounts", {
34+
header: "User Identifier",
2535
enableColumnFilter: true,
26-
cell: (cell) => (
27-
<span className="text-sm">{cell.getValue()?.[0]?.email}</span>
28-
),
36+
cell: (cell) => {
37+
const identifier = getUserIdentifier(cell.getValue());
38+
return <span className="text-sm">{identifier}</span>;
39+
},
40+
id: "user_identifier",
2941
}),
30-
columnHelper.accessor("embedded_wallet", {
42+
columnHelper.accessor("wallets", {
3143
header: "Address",
3244
cell: (cell) => {
33-
const address = cell.getValue()?.[0]?.address;
45+
const address = cell.getValue()[0]?.address;
3446
return address ? <WalletAddress address={address} /> : null;
3547
},
48+
id: "address",
3649
}),
37-
columnHelper.accessor("created_at", {
50+
columnHelper.accessor("wallets", {
3851
header: "Created",
3952
cell: (cell) => {
40-
const value = cell.getValue();
53+
const value = cell.getValue()[0]?.createdAt;
4154

4255
if (!value) {
4356
return;
@@ -48,59 +61,64 @@ const columns = [
4861
</span>
4962
);
5063
},
64+
id: "created_at",
5165
}),
52-
columnHelper.accessor("last_accessed_at", {
53-
header: "Last login",
66+
columnHelper.accessor("linkedAccounts", {
67+
header: "Login Methods",
5468
cell: (cell) => {
5569
const value = cell.getValue();
56-
57-
if (!value) {
58-
return;
59-
}
70+
const loginMethodsDisplay = value.reduce((acc, curr) => {
71+
if (acc.length === 2) {
72+
acc.push("...");
73+
}
74+
if (acc.length < 2) {
75+
acc.push(curr.type);
76+
}
77+
return acc;
78+
}, [] as string[]);
79+
const loginMethods = value.map((v) => v.type).join(", ");
6080
return (
61-
<span className="text-sm">
62-
{format(new Date(value), "MMM dd, yyyy")}
63-
</span>
81+
<TooltipProvider>
82+
<Tooltip>
83+
<TooltipTrigger className="text-sm">
84+
{loginMethodsDisplay.join(", ")}
85+
</TooltipTrigger>
86+
<TooltipContent>
87+
<span className="text-sm">{loginMethods}</span>
88+
</TooltipContent>
89+
</Tooltip>
90+
</TooltipProvider>
6491
);
6592
},
93+
id: "login_methods",
6694
}),
6795
];
6896

6997
export const InAppWalletUsersPageContent = (props: {
7098
clientId: string;
7199
trackingCategory: string;
72100
}) => {
73-
const [onlyActive, setOnlyActive] = useState(true);
74-
const walletsQuery = useEmbeddedWallets(props.clientId);
75-
const wallets = walletsQuery?.data || [];
76-
77-
const activeWallets = useMemo(() => {
78-
if (!wallets) {
79-
return [];
80-
}
81-
82-
return wallets.filter((w) => {
83-
const lastAccessedAt = w.last_accessed_at;
84-
return (
85-
lastAccessedAt && withinDays(lastAccessedAt, ACTIVE_THRESHOLD_DAYS)
86-
);
87-
});
88-
}, [wallets]);
89-
90-
const theWalletsWeWant = useMemo(() => {
91-
return (onlyActive ? activeWallets : wallets) ?? [];
92-
}, [activeWallets, onlyActive, wallets]);
101+
const [activePage, setActivePage] = useState(1);
102+
const walletsQuery = useEmbeddedWallets(props.clientId, activePage);
103+
const { users: wallets, totalPages } = walletsQuery?.data || {
104+
users: [],
105+
totalPages: 1,
106+
};
93107

108+
// TODO: Make the download CSV grab all data instead of merely what's on the current page
94109
const downloadCSV = useCallback(() => {
95-
if (theWalletsWeWant.length === 0) {
110+
if (wallets.length === 0) {
96111
return;
97112
}
98113
const csv = Papa.unparse(
99-
theWalletsWeWant.map((row) => ({
100-
email: row.ews_authed_user[0]?.email,
101-
address: row.embedded_wallet?.[0]?.address || "",
102-
created: format(new Date(row.created_at), "MMM dd, yyyy"),
103-
last_login: format(new Date(row.last_accessed_at), "MMM dd, yyyy"),
114+
wallets.map((row) => ({
115+
user_identifier: getUserIdentifier(row.linkedAccounts),
116+
address: row.wallets[0]?.address || "",
117+
created: format(
118+
new Date(row.wallets[0]?.createdAt ?? ""),
119+
"MMM dd, yyyy",
120+
),
121+
login_methods: row.linkedAccounts.map((acc) => acc.type).join(", "),
104122
})),
105123
);
106124
const csvUrl = URL.createObjectURL(
@@ -110,50 +128,33 @@ export const InAppWalletUsersPageContent = (props: {
110128
tempLink.href = csvUrl;
111129
tempLink.setAttribute("download", "download.csv");
112130
tempLink.click();
113-
}, [theWalletsWeWant]);
114-
115-
const [activePage, setActivePage] = useState(1);
116-
const itemsPerPage = 20;
117-
const totalPages =
118-
theWalletsWeWant.length <= itemsPerPage
119-
? 1
120-
: Math.ceil(theWalletsWeWant.length / itemsPerPage);
121-
122-
const itemsToShow = useMemo(() => {
123-
const startIndex = (activePage - 1) * itemsPerPage;
124-
const endIndex = startIndex + itemsPerPage;
125-
return theWalletsWeWant.slice(startIndex, endIndex);
126-
}, [activePage, theWalletsWeWant]);
131+
}, [wallets]);
127132

128133
return (
129134
<div>
130135
<div className="flex flex-col gap-6">
131136
{/* Top section */}
132137
<div className="flex items-center justify-between">
133138
<Button
134-
disabled={theWalletsWeWant.length === 0}
139+
disabled={wallets.length === 0}
135140
variant="outline"
136141
onClick={downloadCSV}
137142
size="sm"
138143
>
139144
Download as .csv
140145
</Button>
141146

142-
<div className="flex items-center justify-end gap-2">
147+
{/* <div className="flex items-center justify-end gap-2">
143148
<p className="text-muted-foreground text-sm">
144149
Active last {ACTIVE_THRESHOLD_DAYS} days
145150
</p>
146-
<Switch
147-
checked={onlyActive}
148-
onCheckedChange={(v) => setOnlyActive(v)}
149-
disabled={wallets.length === 0}
150-
/>
151-
</div>
151+
<Switch checked={true} disabled={wallets.length === 0} />
152+
</div> */}
152153
</div>
153154

154155
<TWTable
155156
title="active in-app wallets"
156-
data={itemsToShow}
157+
data={wallets}
157158
columns={columns}
158159
isPending={walletsQuery.isPending}
159160
isFetched={walletsQuery.isFetched}

apps/dashboard/src/constants/urls.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ export const THIRDWEB_API_HOST = "/api/server-proxy/api";
66
export const THIRDWEB_ANALYTICS_API_HOST = "/api/server-proxy/analytics";
77

88
export const THIRDWEB_EWS_API_HOST =
9-
process.env.NEXT_PUBLIC_THIRDWEB_EWS_API_HOST || "https://ews.thirdweb.com";
9+
process.env.NEXT_PUBLIC_THIRDWEB_EWS_API_HOST ||
10+
"https://in-app-wallet.thirdweb.com";
1011

1112
export const THIRDWEB_PAY_DOMAIN =
1213
process.env.NEXT_PUBLIC_PAY_URL || "pay.thirdweb-dev.com";

packages/thirdweb/src/exports/wallets.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export type {
7575
} from "../wallets/smart/types.js";
7676

7777
export type {
78+
WalletUser,
7879
InAppWalletAuth,
7980
/**
8081
* @deprecated use InAppWalletAuth instead

packages/thirdweb/src/wallets/in-app/core/wallet/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
MultiStepAuthArgsType,
1313
SingleStepAuthArgsType,
1414
} from "../authentication/types.js";
15+
import type { UserStatus } from "./enclave-wallet.js";
1516

1617
export type Ecosystem = {
1718
id: EcosystemWalletId;
@@ -33,6 +34,8 @@ export type InAppWalletAutoConnectOptions = {
3334
chain?: Chain;
3435
};
3536

37+
export type WalletUser = UserStatus;
38+
3639
export type InAppWalletSocialAuth = SocialAuthOption;
3740
export type InAppWalletOAuth = OAuthOption;
3841
export type InAppWalletAuth = AuthOption;

0 commit comments

Comments
 (0)