Skip to content

Commit fc36f27

Browse files
Add advanced search functionality for in-app wallet users
Co-authored-by: joaquim.verges <[email protected]>
1 parent 2111b8b commit fc36f27

File tree

5 files changed

+388
-46
lines changed

5 files changed

+388
-46
lines changed
Lines changed: 95 additions & 0 deletions
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+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
"use client";
2+
3+
import { format } from "date-fns";
4+
import type { ThirdwebClient } from "thirdweb";
5+
import { WalletAddress } from "@/components/blocks/wallet-address";
6+
import { Badge } from "@/components/ui/badge";
7+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
8+
import {
9+
Tooltip,
10+
TooltipContent,
11+
TooltipProvider,
12+
TooltipTrigger,
13+
} from "@/components/ui/tooltip";
14+
import type { UserSearchResult } from "./types";
15+
16+
const getUserIdentifier = (user: UserSearchResult) => {
17+
return user.email ?? user.phone ?? user.walletAddress ?? user.userId;
18+
};
19+
20+
export function SearchResults(props: {
21+
results: UserSearchResult[];
22+
client: ThirdwebClient;
23+
}) {
24+
if (props.results.length === 0) {
25+
return (
26+
<Card>
27+
<CardContent className="py-8">
28+
<div className="flex flex-col items-center gap-3">
29+
<p className="text-sm">No users found</p>
30+
<p className="text-muted-foreground text-sm">
31+
Try searching with different criteria
32+
</p>
33+
</div>
34+
</CardContent>
35+
</Card>
36+
);
37+
}
38+
39+
return (
40+
<div className="space-y-4">
41+
{props.results.map((user) => (
42+
<Card key={user.userId}>
43+
<CardHeader>
44+
<CardTitle className="text-lg">User Details</CardTitle>
45+
</CardHeader>
46+
<CardContent className="space-y-4">
47+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
48+
<div>
49+
<p className="text-sm font-medium text-muted-foreground">
50+
User Identifier
51+
</p>
52+
<p className="text-sm">{getUserIdentifier(user)}</p>
53+
</div>
54+
55+
<div>
56+
<p className="text-sm font-medium text-muted-foreground">
57+
Wallet Address
58+
</p>
59+
<WalletAddress
60+
address={user.walletAddress}
61+
client={props.client}
62+
/>
63+
</div>
64+
65+
{user.email && (
66+
<div>
67+
<p className="text-sm font-medium text-muted-foreground">
68+
Email
69+
</p>
70+
<p className="text-sm">{user.email}</p>
71+
</div>
72+
)}
73+
74+
{user.phone && (
75+
<div>
76+
<p className="text-sm font-medium text-muted-foreground">
77+
Phone
78+
</p>
79+
<p className="text-sm">{user.phone}</p>
80+
</div>
81+
)}
82+
83+
<div>
84+
<p className="text-sm font-medium text-muted-foreground">
85+
Created
86+
</p>
87+
<p className="text-sm">
88+
{format(new Date(user.createdAt), "MMM dd, yyyy")}
89+
</p>
90+
</div>
91+
92+
<div>
93+
<p className="text-sm font-medium text-muted-foreground">
94+
Login Methods
95+
</p>
96+
<div className="flex flex-wrap gap-1">
97+
{user.linkedAccounts.map((account, index) => (
98+
<TooltipProvider
99+
key={`${user.userId}-${account.type}-${index}`}
100+
>
101+
<Tooltip>
102+
<TooltipTrigger>
103+
<Badge variant="secondary" className="text-xs">
104+
{account.type}
105+
</Badge>
106+
</TooltipTrigger>
107+
<TooltipContent>
108+
<div className="text-sm space-y-1">
109+
{Object.entries(account.details).map(
110+
([key, value]) => (
111+
<div key={key}>
112+
<span className="font-medium">{key}:</span>{" "}
113+
{String(value)}
114+
</div>
115+
),
116+
)}
117+
</div>
118+
</TooltipContent>
119+
</Tooltip>
120+
</TooltipProvider>
121+
))}
122+
</div>
123+
</div>
124+
</div>
125+
</CardContent>
126+
</Card>
127+
))}
128+
</div>
129+
);
130+
}

0 commit comments

Comments
 (0)