Skip to content

Commit 6ff4ab4

Browse files
Merge pull request #148 from TTMordred/hungphanne
Update search functionality to include new search types and improve address validation
2 parents bab7430 + 463cb56 commit 6ff4ab4

3 files changed

Lines changed: 142 additions & 11 deletions

File tree

components/SearchOnTop.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const SearchOnTop = () => {
1919
searchQuery,
2020
setSearchQuery,
2121
searchType,
22-
setSearchType: setGlobalSearchType,
22+
setSearchType,
2323
isLoading,
2424
handleSearch
2525
} = useSearch();
@@ -221,15 +221,19 @@ const SearchOnTop = () => {
221221

222222
<div className="h-full px-2 flex items-center border-l border-amber-500/20">
223223
<Select
224-
value={mapSearchTypeToSelector(searchType)}
225-
onValueChange={(value) => setGlobalSearchType(mapSelectorToSearchType(value as "onchain" | "offchain"))}
224+
value={searchType}
225+
onValueChange={(value) => setSearchType(value as SearchType)}
226226
>
227227
<SelectTrigger className="w-[110px] border-0 bg-transparent focus:ring-0 text-white h-8">
228228
<SelectValue placeholder="Search Type" />
229229
</SelectTrigger>
230230
<SelectContent className="bg-gray-900/95 border border-amber-500/30 text-white">
231-
<SelectItem value="onchain" className="hover:bg-amber-500/20">On-Chain</SelectItem>
232-
<SelectItem value="offchain" className="hover:bg-amber-500/20">Off-Chain</SelectItem>
231+
<SelectItem value="All Filters" className="hover:bg-amber-500/20">All Filters</SelectItem>
232+
<SelectItem value="On-Chain" className="hover:bg-amber-500/20">On-Chain</SelectItem>
233+
<SelectItem value="Off-Chain" className="hover:bg-amber-500/20">Off-Chain</SelectItem>
234+
<SelectItem value="Tokens" className="hover:bg-amber-500/20">Tokens</SelectItem>
235+
<SelectItem value="Txn Hash" className="hover:bg-amber-500/20">Txn Hash</SelectItem>
236+
<SelectItem value="Blocks" className="hover:bg-amber-500/20">Blocks</SelectItem>
233237
</SelectContent>
234238
</Select>
235239
</div>

components/home/CryptoExplorer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const CryptoPathExplorer = ({ language = 'en' as 'en' | 'vi' }) => {
1717
const [opPrice, setOpPrice] = useState('');
1818
const [opChange, setOpChange] = useState('');
1919
const [isFilterOpen, setIsFilterOpen] = useState(false);
20-
const filters = ['All Filters', 'On-Chain', 'Off-Chain', 'Tokens', 'NFTs', 'Addresses'] as const;
20+
const filters = ['All Filters', 'On-Chain', 'Off-Chain', 'Tokens', 'Txn Hash', 'Blocks'] as const;
2121

2222
const translations = {
2323
en: {

hooks/use-search.ts

Lines changed: 132 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,158 @@
11
import { useState } from 'react';
22
import { useRouter } from "next/navigation";
3+
import { serialize } from 'v8';
4+
import { toast } from "sonner"
35

4-
export type SearchType = "All Filters" | "On-Chain" | "Off-Chain" | "Tokens" | "NFTs" | "Addresses";
5-
6+
export type SearchType = "All Filters" | "On-Chain" | "Off-Chain" | "Tokens" | "Txn Hash" | "Blocks";
7+
const ETH_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
68
export const useSearch = () => {
79
const [searchQuery, setSearchQuery] = useState('');
810
const [searchType, setSearchType] = useState<SearchType>("All Filters");
11+
const [addressError, setAddressError] = useState<string | null>(null)
912
const router = useRouter();
1013
const [isLoading, setIsLoading] = useState(false);
1114

15+
type InputType = "ADDRESS" | "TRANSACTION-HASH" | "TOKEN" | "BLOCK" | "NEO4J" | "UNKNOWN";
16+
17+
const detectInputType = (input: string): InputType => {
18+
// Clean the input
19+
const cleanInput = input.trim().toLowerCase();
20+
21+
// Check for empty input
22+
if (!cleanInput) return "UNKNOWN";
23+
24+
// Ethereum Address and Token (0x followed by 40 hex characters)
25+
if (/^0x[a-f0-9]{40}$/.test(cleanInput)) {
26+
return "ADDRESS";
27+
}
28+
29+
// Transaction Hash (0x followed by 64 hex characters)
30+
if (/^0x[a-f0-9]{64}$/.test(cleanInput)) {
31+
return "TRANSACTION-HASH";
32+
}
33+
34+
// Block Number (numeric only)
35+
if (/^\d+$/.test(cleanInput)) {
36+
return "BLOCK";
37+
}
38+
39+
// Neo4j identifier (at least 3 characters)
40+
if (/^0x[a-f0-9]{40}$/.test(cleanInput)) {
41+
return "NEO4J";
42+
}
43+
44+
return "UNKNOWN";
45+
};
46+
47+
const handleUniversalSearch = async (input: string) => {
48+
const inputType = detectInputType(input);
49+
50+
switch (inputType) {
51+
case "ADDRESS":
52+
// Check if it's a token contract
53+
const isToken = false; // You would need to implement token detection logic here
54+
if (isToken) {
55+
return `/token/?address=${encodeURIComponent(input)}`;
56+
}
57+
return `/search/?address=${encodeURIComponent(input)}&network=mainnet&provider=etherscan`;
58+
59+
case "TRANSACTION-HASH":
60+
return `/txn-hash/?hash=${encodeURIComponent(input)}`;
61+
62+
case "BLOCK":
63+
return `/block/?number=${encodeURIComponent(input)}`;
64+
65+
case "NEO4J":
66+
return `/search-offchain/?address=${encodeURIComponent(input)}`;
67+
68+
default:
69+
throw new Error("Unable to determine search type");
70+
}
71+
};
72+
73+
const validateAddress = (addr: string): boolean => {
74+
if (!addr) return false;
75+
76+
// For on-chain searches, validate Ethereum address format
77+
if (searchType === "On-Chain") {
78+
if (!ETH_ADDRESS_REGEX.test(addr)) {
79+
setAddressError("Invalid Ethereum address format. Must start with 0x followed by 40 hex characters.");
80+
return false;
81+
}
82+
} else if(searchType === "Txn Hash"){
83+
if(addr.length !== 66){
84+
setAddressError("Invalid Transaction Hash format. Must be 66 characters long.");
85+
return false;
86+
}
87+
}else if(searchType === "Tokens"){
88+
if(addr.length !== 42){
89+
setAddressError("Invalid Token address format. Must be 42 characters long.");
90+
return false;
91+
}
92+
}else if (searchType === "Blocks"){
93+
if(addr.length < 1){
94+
setAddressError("Invalid Block number format. Must be at least 1 character long.");
95+
return false;
96+
}
97+
}else if(searchType === "All Filters"){
98+
// Detect logic to search for all types
99+
const inputType = detectInputType(addr);
100+
if (inputType === "UNKNOWN") {
101+
setAddressError("Invalid search input. Please enter a valid address, transaction hash, token address, or block number.");
102+
return false;
103+
}
104+
}
105+
else {
106+
// For off-chain searches, validate Neo4j ID format
107+
if (!ETH_ADDRESS_REGEX.test(addr)) {
108+
setAddressError("Neo4j identifier must be at least 3 characters");
109+
return false;
110+
}
111+
}
112+
113+
setAddressError(null);
114+
return true;
115+
};
116+
12117
const handleSearch = async (e?: React.FormEvent) => {
13118
if (e) {
14119
e.preventDefault();
15120
}
16121

17122
if (!searchQuery.trim()) return;
123+
// Validate address before proceeding
124+
if (!validateAddress(searchQuery.trim())) {
125+
toast.error("Invalid address format", {
126+
description: addressError || "Please check the address format and try again.",
127+
action: {
128+
label: 'Learn More',
129+
onClick: () => window.open('https://ethereum.org/en/developers/docs/intro-to-ethereum/#ethereum-accounts', '_blank'),
130+
}
131+
});
132+
return;
133+
}
18134

135+
setIsLoading(true);
19136
try {
20-
setIsLoading(true);
137+
21138
await new Promise((resolve) => setTimeout(resolve, 1000));
22139

23140
// Route based on search type
24141
if (searchType === "Off-Chain") {
25142
router.push(`/search-offchain/?address=${encodeURIComponent(searchQuery)}`);
26-
} else {
143+
} else if (searchType === "Tokens") {
144+
router.push(`/token/?address=${encodeURIComponent(searchQuery)}`)
145+
}else if (searchType === "Txn Hash") {
146+
router.push(`/txn-hash/?hash=${searchQuery}`)
147+
}else if (searchType === "Blocks") {
148+
router.push(`/block/?number=${searchQuery}`)
149+
}else if (searchType === "All Filters") {
150+
const route = await handleUniversalSearch(searchQuery);
151+
router.push(route);
152+
}
153+
else {
27154
// Default to on-chain search for all other types
28-
router.push(`/search/?address=${encodeURIComponent(searchQuery)}&network=mainnet`);
155+
router.push(`/search/?address=${encodeURIComponent(searchQuery)}`);
29156
}
30157
} catch (error) {
31158
console.error("Search error:", error);

0 commit comments

Comments
 (0)