|
| 1 | +import React, { useState, useEffect, useRef } from 'react'; |
| 2 | +import { NETWORK_LIST } from '@/components/common/constants'; |
| 3 | +import { useRouter } from 'next/router'; |
| 4 | +import { checkIfValidTerm, constructRedirectUrl } from '@/components/common/utils'; |
| 5 | +import { ToastContainer, toast } from 'react-toastify'; |
| 6 | +import 'react-toastify/dist/ReactToastify.css'; |
| 7 | +import { LinearProgress } from '@mui/material'; |
| 8 | + |
| 9 | +const showToast = (toast: any, message: string) => { |
| 10 | + toast.error(message, { |
| 11 | + position: 'bottom-left', |
| 12 | + autoClose: 5000, |
| 13 | + hideProgressBar: false, |
| 14 | + closeOnClick: true, |
| 15 | + pauseOnHover: true, |
| 16 | + theme: 'colored', |
| 17 | + }); |
| 18 | +}; |
| 19 | + |
| 20 | +const SearchBox = ({networkValue, setNetworkValue}: any) => { |
| 21 | + const [isDropdownOpen, setIsDropdownOpen] = useState(false); |
| 22 | + const [open, setOpen] = useState<boolean>(false); |
| 23 | + const [searchTerm, setSearchTerm] = useState(''); |
| 24 | + const [filteredNetworks, setFilteredNetworks] = useState(NETWORK_LIST); |
| 25 | + |
| 26 | + const [searching, setSearching] = useState(false); |
| 27 | + const [animateState, setAnimateState] = useState(false); |
| 28 | + const searchRef = useRef(null); |
| 29 | + const { push } = useRouter(); |
| 30 | + // const [networkDropdown, setNetworkDropdown] = useState<string | null>("mainnet"); |
| 31 | + |
| 32 | + const handleDropdownToggle = () => { |
| 33 | + setIsDropdownOpen(!isDropdownOpen); |
| 34 | + }; |
| 35 | + |
| 36 | + const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => { |
| 37 | + setSearchTerm(e.target.value); |
| 38 | + }; |
| 39 | + |
| 40 | + |
| 41 | + const handleValue = (index: number) => { |
| 42 | + setNetworkValue(index); |
| 43 | + |
| 44 | + setIsDropdownOpen(false); // Close dropdown after selection |
| 45 | + setOpen(false); |
| 46 | + |
| 47 | + }; |
| 48 | + |
| 49 | + const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { |
| 50 | + if (e.key === 'Enter') { |
| 51 | + handleSubmit(e); |
| 52 | + } |
| 53 | + }; |
| 54 | + |
| 55 | + const handleSubmit = async (e?: React.FormEvent) => { |
| 56 | + if (e) e.preventDefault(); // Prevent form submission |
| 57 | + if (checkIfValidTerm(searchTerm)) { |
| 58 | + setSearching(true); |
| 59 | + try { |
| 60 | + const res = await fetch(`https://api.jiffyscan.xyz/v0/searchEntry?entry=${searchTerm.toLowerCase()}${networkValue !== -1 ? `&network=${NETWORK_LIST[networkValue].key}` : ''}`); |
| 61 | + if (res.status === 200) { |
| 62 | + const data = await res.json(); |
| 63 | + console.log('API Response:', data); |
| 64 | + let redirectUrl; |
| 65 | + if (data.foundInNetwork && data.type && data.term) |
| 66 | + redirectUrl = constructRedirectUrl(data.type, data.foundInNetwork, data.term); |
| 67 | + console.log('Constructed URL:', redirectUrl); |
| 68 | + if (redirectUrl) { |
| 69 | + push(redirectUrl); |
| 70 | + } else { |
| 71 | + showToast(toast, 'No results found'); |
| 72 | + } |
| 73 | + } else { |
| 74 | + showToast(toast, 'Invalid search term ?'); |
| 75 | + } |
| 76 | + } catch (error) { |
| 77 | + console.error('Error:', error); |
| 78 | + showToast(toast, 'An error occurred'); |
| 79 | + } finally { |
| 80 | + setSearching(false); |
| 81 | + } |
| 82 | + } else { |
| 83 | + showToast(toast, 'Invalid search term ?'); |
| 84 | + } |
| 85 | + }; |
| 86 | + |
| 87 | + // Filter networks based on search term |
| 88 | + useEffect(() => { |
| 89 | + if (searchTerm.trim() === '') { |
| 90 | + setFilteredNetworks(NETWORK_LIST); // Reset to full list when search term is empty |
| 91 | + } else { |
| 92 | + const filtered = NETWORK_LIST.filter((network) => |
| 93 | + network.name.toLowerCase().includes(searchTerm.toLowerCase()) |
| 94 | + ); |
| 95 | + setFilteredNetworks(filtered); |
| 96 | + } |
| 97 | + }, [searchTerm]); |
| 98 | + |
| 99 | + return ( |
| 100 | + <form className="w-full h-16"> |
| 101 | + <label htmlFor="default-search" className="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white"> |
| 102 | + Search |
| 103 | + </label> |
| 104 | + <div className="relative"> |
| 105 | + <div className="absolute inset-y-0 left-2 flex items-center ps-3 cursor-pointer" onClick={handleDropdownToggle}> |
| 106 | + <div className='flex' onClick={() => setOpen((v) => !v)}> |
| 107 | + <img src={networkValue !== -1 ? NETWORK_LIST[networkValue].iconPathInverted : "/zap2.svg"} alt="" style={{ width: '20px', height: 'auto' }} /> |
| 108 | + <img className={`duration-100 ${open ? 'rotate-180' : ''}`} src="/images/chevron-down.svg" alt="" /> |
| 109 | + </div> |
| 110 | + <div className="border-r-2 h-6 mx-2"></div> |
| 111 | + </div> |
| 112 | + |
| 113 | + {isDropdownOpen && ( |
| 114 | + <div className="absolute top-full left-0 mt-2 bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700 max-h-60 overflow-y-auto" > |
| 115 | + <div className="p-2"> |
| 116 | + <input |
| 117 | + type="text" |
| 118 | + className="block w-full px-3 py-2 text-sm border border-gray-300 rounded-md dark:bg-gray-600 dark:border-gray-500" |
| 119 | + placeholder="Search Chains..." |
| 120 | + value={searchTerm} |
| 121 | + |
| 122 | + /> |
| 123 | + </div> |
| 124 | + <ul className="py-2 text-sm text-gray-700 dark:text-gray-200"> |
| 125 | + {filteredNetworks.map(({ name, iconPath }, index) => ( |
| 126 | + <li key={index}> |
| 127 | + <a |
| 128 | + href="#" |
| 129 | + className="flex items-center gap-2 px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white" |
| 130 | + onClick={() => handleValue(index)} |
| 131 | + > |
| 132 | + <img src={iconPath} alt={name} className="w-5 h-5" /> |
| 133 | + <span>{name}</span> |
| 134 | + </a> |
| 135 | + </li> |
| 136 | + ))} |
| 137 | + </ul> |
| 138 | + </div> |
| 139 | + )} |
| 140 | + |
| 141 | + <input |
| 142 | + type="search" |
| 143 | + id="default-search" |
| 144 | + className="block w-full p-4 pl-24 text-sm md:text-base font-inter text-gray-900 border border-gray-300 rounded-t-2xl bg-[#FFFFFF] focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" |
| 145 | + placeholder="Search for a transaction using Transaction Id" |
| 146 | + value={searchTerm} |
| 147 | + onChange={handleSearchChange} |
| 148 | + onKeyDown={handleKeyPress} |
| 149 | + required |
| 150 | + /> |
| 151 | + |
| 152 | + <p className="text-[#9E9E9E] hidden md:block border absolute right-11 bottom-3.5 bg-white focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg shadow-sm text-sm px-2.5 py-1 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"> |
| 153 | + Ctrl |
| 154 | + </p> |
| 155 | + <p className="text-[#9E9E9E] hidden md:block absolute right-4 bottom-3.5 border bg-white focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg shadow-sm text-sm px-2 py-1 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"> |
| 156 | + K |
| 157 | + </p> |
| 158 | + </div> |
| 159 | + {searching && <LinearProgress />} |
| 160 | + <ToastContainer /> |
| 161 | + </form> |
| 162 | + ); |
| 163 | +}; |
| 164 | + |
| 165 | +export default SearchBox; |
0 commit comments