diff --git a/explorer-v2/src/app/api/urls.ts b/explorer-v2/src/app/api/urls.ts index c1cd2ede91..f6004e5604 100644 --- a/explorer-v2/src/app/api/urls.ts +++ b/explorer-v2/src/app/api/urls.ts @@ -2,8 +2,7 @@ export const CURRENT_EPOCH = "https://validator.nymtech.net/api/v1/epoch/current"; export const CURRENT_EPOCH_REWARDS = "https://validator.nymtech.net/api/v1/epoch/reward_params"; -export const NYM_NODE_BONDED = - "https://validator.nymtech.net/api/v1/nym-nodes/bonded"; + export const NYM_ACCOUNT_ADDRESS = "https://explorer.nymtech.net/api/v1/tmp/unstable/account"; export const NYM_PRICES_API = "https://api.nym.spectredao.net/api/v1/nym-price"; @@ -18,3 +17,8 @@ export const DATA_OBSERVATORY_BALANCES_URL = "https://api.nym.spectredao.net/api/v1/balances"; export const OBSERVATORY_GATEWAYS_URL = "https://mainnet-node-status-api.nymtech.cc/v2/gateways"; + +export const NS_API_MIXNODES_STATS = + process.env.NEXT_PUBLIC_NS_API_MIXNODES_STATS; + +export const NS_API_NODES = process.env.NEXT_PUBLIC_NS_API_NODES; diff --git a/explorer-v2/src/components/copyToClipboard/CopyToClipboard.tsx b/explorer-v2/src/components/copyToClipboard/CopyToClipboard.tsx index 841bf21ff3..fe86fdf98c 100644 --- a/explorer-v2/src/components/copyToClipboard/CopyToClipboard.tsx +++ b/explorer-v2/src/components/copyToClipboard/CopyToClipboard.tsx @@ -20,6 +20,7 @@ const CopyToClipboard = ({ const [copiedText, setCopiedText] = useCopyToClipboard(); const hasCopied = Boolean(copiedText); const theme = useTheme(); + const isDarkMode = theme.palette.mode === "dark"; useEffect(() => { if (hasCopied) { @@ -33,7 +34,11 @@ const CopyToClipboard = ({ if (hasCopied) { return ( - + Copied ); @@ -41,8 +46,7 @@ const CopyToClipboard = ({ return ( setCopiedText(text)}> - {Icon || - (theme.palette.mode === "dark" ? : )} + {Icon || (isDarkMode ? : )} ); }; diff --git a/explorer-v2/src/components/header/DesktopHeader.tsx b/explorer-v2/src/components/header/DesktopHeader.tsx index 548fd564f0..94a1a72227 100644 --- a/explorer-v2/src/components/header/DesktopHeader.tsx +++ b/explorer-v2/src/components/header/DesktopHeader.tsx @@ -51,7 +51,7 @@ export const DesktopHeader = () => { ))} - + diff --git a/explorer-v2/src/components/header/MobileHeader.tsx b/explorer-v2/src/components/header/MobileHeader.tsx index d0fb98cd0a..b43c7d6852 100644 --- a/explorer-v2/src/components/header/MobileHeader.tsx +++ b/explorer-v2/src/components/header/MobileHeader.tsx @@ -162,7 +162,7 @@ const MobileMenuHeader = ({ alignItems: "center", }} > - {!drawerOpen && } + {!drawerOpen && } toggleDrawer(!drawerOpen)}> {drawerOpen ? : } diff --git a/explorer-v2/src/components/header/Switch.tsx b/explorer-v2/src/components/header/Switch.tsx index ff40a40fb6..4e967d1c7e 100644 --- a/explorer-v2/src/components/header/Switch.tsx +++ b/explorer-v2/src/components/header/Switch.tsx @@ -52,27 +52,14 @@ export const DarkLightSwitch = styled(Switch)(({ theme }) => ({ }, })); -// export const DarkLightSwitchMobile: FCWithChildren = () => { -// const { toggleMode } = useMainContext(); -// return ( -// -// ); -// }; - -export const DarkLightSwitchDesktop: FCWithChildren<{ - defaultChecked: boolean; -}> = ({ defaultChecked }) => { +export const DarkLightSwitchDesktop = (): JSX.Element => { const [mode, setMode] = useLocalStorage("mode", "dark"); - console.log("mode", mode); - const toggleMode = () => setMode((m) => (m !== "light" ? "light" : "dark")); return ( ); }; diff --git a/explorer-v2/src/components/landingPageComponents/NoiseCard.tsx b/explorer-v2/src/components/landingPageComponents/NoiseCard.tsx index fdd3786404..51d218c263 100644 --- a/explorer-v2/src/components/landingPageComponents/NoiseCard.tsx +++ b/explorer-v2/src/components/landingPageComponents/NoiseCard.tsx @@ -10,6 +10,7 @@ import { useTheme, } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; +import { useState } from "react"; import type { IPacketsAndStakingData } from "../../app/api/types"; import { formatBigNum } from "../../utils/formatBigNumbers"; import ExplorerCard from "../cards/ExplorerCard"; @@ -19,6 +20,8 @@ import { UpDownPriceIndicator } from "../price/UpDownPriceIndicator"; export const NoiseCard = () => { const theme = useTheme(); const isDarkMode = theme.palette.mode === "dark"; + const [tooltipOpen, setTooltipOpen] = useState(false); + const { data, isLoading, isError } = useQuery({ queryKey: ["noise"], queryFn: fetchNoise, @@ -110,6 +113,14 @@ export const NoiseCard = () => { }) .filter((item) => item.numericData >= 2_500_000_000); + const handleTooltipOpen = () => { + setTooltipOpen(true); + }; + + const handleTooltipClose = () => { + setTooltipOpen(false); + }; + return ( @@ -124,11 +135,22 @@ export const NoiseCard = () => { {noiseLast24HFormatted} e.stopPropagation()} + enterNextDelay={300} + leaveDelay={200} > - + ({formatedNoiseVolume}) diff --git a/explorer-v2/src/components/nymNodePageComponents/BasicInfoCard.tsx b/explorer-v2/src/components/nymNodePageComponents/BasicInfoCard.tsx index 6c46d53a44..7cae2ed050 100644 --- a/explorer-v2/src/components/nymNodePageComponents/BasicInfoCard.tsx +++ b/explorer-v2/src/components/nymNodePageComponents/BasicInfoCard.tsx @@ -68,7 +68,7 @@ export const BasicInfoCard = ({ paramId }: Props) => { const selfBondFormatted = `${selfBond} NYM`; return ( - + { const router = useRouter(); const [inputValue, setInputValue] = useState(""); const [errorText, setErrorText] = useState(""); const [isLoading, setIsLoading] = useState(false); + const [searchOptions, setSearchOptions] = useState([]); + + // Use React Query to fetch nodes + const { data: nymNodes = [], isLoading: isLoadingNodes } = useQuery({ + queryKey: ["nymNodes"], + queryFn: fetchObservatoryNodes, + staleTime: 10 * 60 * 1000, // 10 minutes + refetchOnWindowFocus: false, + refetchOnReconnect: false, + refetchOnMount: false, + }); const handleSearch = async () => { setErrorText(""); // Clear any previous error messages @@ -20,9 +39,7 @@ const NodeAndAddressSearch = () => { try { if (inputValue.startsWith("n1")) { // Fetch Nym Address data - const response = await fetch( - `https://explorer.nymtech.net/api/v1/tmp/unstable/account/${inputValue}`, - ); + const response = await fetch(`${NYM_ACCOUNT_ADDRESS}/${inputValue}`); if (response.ok) { try { @@ -33,7 +50,7 @@ const NodeAndAddressSearch = () => { } } catch { setErrorText( - "It seems that this node or account does not exist. Please enter a complete Node ID or an existing Nym wallet address.", + "No node found with the provided Name, Node ID or Identity Key. Please check your input and try again." ); setIsLoading(false); // Stop loading @@ -41,56 +58,156 @@ const NodeAndAddressSearch = () => { } } else { setErrorText( - "It seems that this node or account does not exist. Please enter a complete Node ID or an existing Nym wallet address.", + "No node found with the provided Name, Node ID or Identity Key. Please check your input and try again." ); setIsLoading(false); // Stop loading return; } } else { - // Fetch Nym Nodes data - const response = await fetch(NYM_NODE_BONDED); - - if (response.ok) { - const nodes = await response.json(); - const matchingNode = nodes.data.find( - (node: NodeData) => - node.bond_information.node.identity_key === inputValue, + // Check if it's a node identity key + if (nymNodes) { + const matchingNode = nymNodes.find( + (node) => node.identity_key === inputValue ); if (matchingNode) { - router.push(`/nym-node/${matchingNode.bond_information.node_id}`); + router.push(`/nym-node/${matchingNode.identity_key}`); return; } } setErrorText( - "It seems that this node or account does not exist. Please enter a complete Node ID or an existing Nym wallet address.", + "No node found with the provided Name, Node ID or Identity Key. Please check your input and try again." ); - setIsLoading(false); // Stop loading + setIsLoading(false); } } catch (error) { setErrorText( - "It seems that this node or account does not exist. Please enter a complete Node ID or an existing Nym wallet address.", + "No node found with the provided Name, Node ID or Identity Key. Please check your input and try again." ); console.error(error); setIsLoading(false); // Stop loading } }; + // Handle search input change + const handleSearchInputChange = ( + event: React.ChangeEvent + ) => { + const value = event.target.value; + setInputValue(value); + + // Clear error message when input is empty + if (!value.trim()) { + setErrorText(""); + } + + // Filter nodes by moniker if input is not empty + if (value.trim() !== "") { + const filteredNodes = nymNodes.filter((node) => + node.self_description?.moniker + ?.toLowerCase() + .includes(value.toLowerCase()) + ); + setSearchOptions(filteredNodes); + } else { + setSearchOptions([]); + } + }; + + // Handle node selection from dropdown + const handleNodeSelect = ( + event: React.SyntheticEvent, + value: string | IObservatoryNode | null + ) => { + if (value && typeof value !== "string") { + setIsLoading(true); // Show loading spinner + router.push(`/nym-node/${value.node_id}`); + } + }; + return ( - + - setInputValue(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") { - handleSearch(); - } + { + if (typeof option === "string") return option; + return option.self_description?.moniker || ""; + }} + isOptionEqualToValue={(option, value) => { + if (typeof option === "string" || typeof value === "string") + return false; + return option.node_id === value.node_id; + }} + renderOption={(props, option) => { + if (typeof option === "string") return null; + return ( +
  • + {option.self_description?.moniker || "Unnamed Node"} +
  • + ); + }} + renderInput={(params) => ( + { + if (e.key === "Enter") { + handleSearch(); + } + }} + sx={{ + "& .MuiOutlinedInput-root": { + borderRadius: "28px", + backgroundColor: "background.paper", + "& fieldset": { + borderColor: "divider", + }, + "&:hover fieldset": { + borderColor: "primary.main", + }, + "&.Mui-focused fieldset": { + borderColor: "primary.main", + }, + }, + }} + /> + )} + onChange={handleNodeSelect} + loading={isLoadingNodes} + loadingText="Loading nodes..." + noOptionsText="No nodes found" + slotProps={{ + paper: { + sx: { + marginTop: "4px", + marginLeft: "10px", + marginRight: "10px", + width: "calc(100% - 20px)", + borderRadius: "10px", + "& .MuiAutocomplete-listbox": { + padding: "8px 0", + }, + }, + }, + }} + sx={{ + flexGrow: 1, + "& .MuiAutocomplete-popupIndicator": { + color: "text.secondary", + }, + "& .MuiAutocomplete-clearIndicator": { + color: "text.secondary", + }, }} - rounded />
    {errorText && ( - + {errorText} )}