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}
)}