Skip to content

Commit 91e3781

Browse files
[Dashboard] Improve user filtering functionality (#7572)
Co-authored-by: Cursor Agent <[email protected]>
1 parent 86e3aa8 commit 91e3781

File tree

10 files changed

+491
-110
lines changed

10 files changed

+491
-110
lines changed

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

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,34 @@ import { embeddedWalletsKeys } from "../query-keys/cache-keys";
77
const fetchAccountList = ({
88
jwt,
99
clientId,
10+
ecosystemSlug,
11+
teamId,
1012
pageNumber,
1113
}: {
1214
jwt: string;
13-
clientId: string;
15+
clientId?: string;
16+
ecosystemSlug?: string;
17+
teamId: string;
1418
pageNumber: number;
1519
}) => {
1620
return async () => {
1721
const url = new URL(`${THIRDWEB_EWS_API_HOST}/api/2024-05-05/account/list`);
18-
url.searchParams.append("clientId", clientId);
22+
23+
// Add clientId or ecosystemSlug parameter
24+
if (ecosystemSlug) {
25+
url.searchParams.append("ecosystemSlug", `ecosystem.${ecosystemSlug}`);
26+
} else if (clientId) {
27+
url.searchParams.append("clientId", clientId);
28+
}
29+
1930
url.searchParams.append("page", pageNumber.toString());
2031

2132
const res = await fetch(url.href, {
2233
headers: {
2334
Authorization: `Bearer ${jwt}`,
2435
"Content-Type": "application/json",
25-
"x-client-id": clientId,
36+
"x-thirdweb-team-id": teamId,
37+
...(clientId && { "x-client-id": clientId }),
2638
},
2739
method: "GET",
2840
});
@@ -38,23 +50,27 @@ const fetchAccountList = ({
3850
};
3951

4052
export function useEmbeddedWallets(params: {
41-
clientId: string;
53+
clientId?: string;
54+
ecosystemSlug?: string;
55+
teamId: string;
4256
page: number;
4357
authToken: string;
4458
}) {
45-
const { clientId, page, authToken } = params;
59+
const { clientId, ecosystemSlug, teamId, page, authToken } = params;
4660
const address = useActiveAccount()?.address;
4761

4862
return useQuery({
49-
enabled: !!address && !!clientId,
63+
enabled: !!address && !!(clientId || ecosystemSlug),
5064
queryFn: fetchAccountList({
5165
clientId,
66+
ecosystemSlug,
67+
teamId,
5268
jwt: authToken,
5369
pageNumber: page,
5470
}),
5571
queryKey: embeddedWalletsKeys.embeddedWallets(
5672
address || "",
57-
clientId,
73+
clientId || ecosystemSlug || "",
5874
page,
5975
),
6076
});
@@ -67,7 +83,15 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
6783
const address = useActiveAccount()?.address;
6884

6985
return useMutation({
70-
mutationFn: async ({ clientId }: { clientId: string }) => {
86+
mutationFn: async ({
87+
clientId,
88+
ecosystemSlug,
89+
teamId,
90+
}: {
91+
clientId?: string;
92+
ecosystemSlug?: string;
93+
teamId: string;
94+
}) => {
7195
const responses: WalletUser[] = [];
7296
let page = 1;
7397

@@ -77,12 +101,14 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
77101
}>({
78102
queryFn: fetchAccountList({
79103
clientId,
104+
ecosystemSlug,
105+
teamId,
80106
jwt: authToken,
81107
pageNumber: page,
82108
}),
83109
queryKey: embeddedWalletsKeys.embeddedWallets(
84110
address || "",
85-
clientId,
111+
clientId || ecosystemSlug || "",
86112
page,
87113
),
88114
});

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/components/EcosystemSlugLayout.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ export async function EcosystemLayoutSlug({
6262
name: "Analytics",
6363
path: `${ecosystemLayoutPath}/${ecosystem.slug}/analytics`,
6464
},
65+
{
66+
name: "Users",
67+
path: `${ecosystemLayoutPath}/${ecosystem.slug}/users`,
68+
},
6569
{
6670
name: "Design (coming soon)",
6771
path: `${ecosystemLayoutPath}/${ecosystem.slug}/#`,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { loginRedirect } from "@app/login/loginRedirect";
2+
import { InAppWalletUsersPageContent } from "@app/team/[team_slug]/[project_slug]/(sidebar)/wallets/users/components";
3+
import { redirect } from "next/navigation";
4+
import { getAuthToken } from "@/api/auth-token";
5+
import { fetchEcosystem } from "@/api/ecosystems";
6+
import { getTeamBySlug } from "@/api/team";
7+
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
8+
9+
export default async function EcosystemUsersPage(props: {
10+
params: Promise<{ team_slug: string; slug: string }>;
11+
}) {
12+
const params = await props.params;
13+
const [authToken, ecosystem, team] = await Promise.all([
14+
getAuthToken(),
15+
fetchEcosystem(params.slug, params.team_slug),
16+
getTeamBySlug(params.team_slug),
17+
]);
18+
19+
if (!authToken) {
20+
loginRedirect(`/team/${params.team_slug}/~/ecosystem/${params.slug}/users`);
21+
}
22+
23+
if (!ecosystem || !team) {
24+
redirect(`/team/${params.team_slug}`);
25+
}
26+
27+
const client = getClientThirdwebClient({
28+
jwt: authToken,
29+
teamId: team.id,
30+
});
31+
32+
return (
33+
<InAppWalletUsersPageContent
34+
authToken={authToken}
35+
client={client}
36+
ecosystemSlug={ecosystem.slug}
37+
teamId={team.id}
38+
/>
39+
);
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"use client";
2+
3+
import { SearchIcon } from "lucide-react";
4+
import { useState } from "react";
5+
import { Button } from "@/components/ui/button";
6+
import { Input } from "@/components/ui/input";
7+
import {
8+
Select,
9+
SelectContent,
10+
SelectItem,
11+
SelectTrigger,
12+
SelectValue,
13+
} from "@/components/ui/select";
14+
15+
import type { SearchType } from "./types";
16+
17+
export function AdvancedSearchInput(props: {
18+
onSearch: (searchType: SearchType, query: string) => void;
19+
onClear: () => void;
20+
isLoading: boolean;
21+
hasResults: boolean;
22+
}) {
23+
const [searchType, setSearchType] = useState<SearchType>("email");
24+
const [query, setQuery] = useState("");
25+
26+
const handleSearch = () => {
27+
if (query.trim()) {
28+
props.onSearch(searchType, query.trim());
29+
}
30+
};
31+
32+
const handleClear = () => {
33+
setQuery("");
34+
props.onClear();
35+
};
36+
37+
const handleKeyDown = (e: React.KeyboardEvent) => {
38+
if (e.key === "Enter") {
39+
handleSearch();
40+
}
41+
};
42+
43+
return (
44+
<div className="flex flex-col gap-2">
45+
<div className="flex gap-2">
46+
<Select
47+
value={searchType}
48+
onValueChange={(value) => setSearchType(value as SearchType)}
49+
>
50+
<SelectTrigger className="w-[140px] bg-card">
51+
<SelectValue />
52+
</SelectTrigger>
53+
<SelectContent>
54+
<SelectItem value="email">Email</SelectItem>
55+
<SelectItem value="phone">Phone</SelectItem>
56+
<SelectItem value="id">ID</SelectItem>
57+
<SelectItem value="address">Address</SelectItem>
58+
</SelectContent>
59+
</Select>
60+
61+
<div className="relative flex-1">
62+
<Input
63+
className="bg-card pl-9"
64+
placeholder={`Search by ${searchType}...`}
65+
value={query}
66+
onChange={(e) => setQuery(e.target.value)}
67+
onKeyDown={handleKeyDown}
68+
/>
69+
<SearchIcon className="-translate-y-1/2 absolute top-1/2 left-3 size-4 text-muted-foreground" />
70+
</div>
71+
72+
<Button
73+
onClick={handleSearch}
74+
disabled={!query.trim() || props.isLoading}
75+
size="sm"
76+
>
77+
{props.isLoading ? "Searching..." : "Search"}
78+
</Button>
79+
</div>
80+
81+
{props.hasResults && (
82+
<div className="flex justify-center">
83+
<Button
84+
variant="outline"
85+
size="sm"
86+
onClick={handleClear}
87+
className="gap-2"
88+
>
89+
Clear Search & Return to List
90+
</Button>
91+
</div>
92+
)}
93+
</div>
94+
);
95+
}

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/users/components/SearchInput.tsx

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)