"use client"; import { useChain } from "@cosmos-kit/react"; import { Box, Button, Stack, Typography, useMediaQuery, useTheme, Tooltip, } from "@mui/material"; import { useQueryClient } from "@tanstack/react-query"; import { useLocalStorage } from "@uidotdev/usehooks"; import { type MRT_ColumnDef, MaterialReactTable, useMaterialReactTable, } from "material-react-table"; import { useRouter } from "next/navigation"; import { useCallback, useMemo, useState } from "react"; import { COSMOS_KIT_USE_CHAIN } from "../../config"; import { useNymClient } from "../../hooks/useNymClient"; import CountryFlag from "../countryFlag/CountryFlag"; import { Favorite } from "../favorite/Favorite"; import Loading from "../loading"; // import Loading from "../loading"; import InfoModal, { type InfoModalProps } from "../modal/InfoModal"; import StakeModal from "../staking/StakeModal"; import { fee } from "../staking/schemas"; import ConnectWallet from "../wallet/ConnectWallet"; import type { MappedNymNode, MappedNymNodes } from "./NodeTableWithAction"; import CopyToClipboard from "../copyToClipboard/CopyToClipboard"; const ColumnHeading = ({ children, }: { children: string | React.ReactNode; }) => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down("sm")); return ( {children} ); }; const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { const router = useRouter(); const { nymClient } = useNymClient(); const queryClient = useQueryClient(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down("sm")); const isDarkMode = theme.palette.mode === "dark"; const [infoModalProps, setInfoModalProps] = useState({ open: false, }); const [isLoading, setIsLoading] = useState(false); const [selectedNodeForStaking, setSelectedNodeForStaking] = useState<{ nodeId: number; identityKey: string; }>(); const [favorites] = useLocalStorage("nym-node-favorites", []); const { isWalletConnected } = useChain(COSMOS_KIT_USE_CHAIN); const handleRefetch = useCallback(async () => { await queryClient.invalidateQueries(); }, [queryClient]); const handleStakeOnNode = useCallback( async ({ nodeId, amount }: { nodeId: number; amount: string }) => { const amountToDelegate = (Number(amount) * 1_000_000).toString(); const uNymFunds = [{ amount: amountToDelegate, denom: "unym" }]; setIsLoading(true); setSelectedNodeForStaking(undefined); try { const tx = await nymClient?.delegate( { nodeId }, fee, "Delegation from Nym Explorer V2", uNymFunds ); setSelectedNodeForStaking(undefined); setInfoModalProps({ open: true, title: "Success", message: "This operation can take up to one hour to process", tx: tx?.transactionHash, onClose: async () => { await handleRefetch(); setInfoModalProps({ open: false }); }, }); await handleRefetch(); } catch (e) { const errorMessage = e instanceof Error ? e.message : "An error occurred while staking"; setInfoModalProps({ open: true, title: "Error", message: errorMessage, onClose: () => { setInfoModalProps({ open: false }); }, }); } setIsLoading(false); }, [nymClient, handleRefetch] ); const handleOnSelectStake = useCallback( (node: MappedNymNode) => { if (!isWalletConnected) { setInfoModalProps({ open: true, title: "Connect Wallet", message: "Connect your wallet to stake", Action: ( setInfoModalProps({ open: false, }) } /> ), onClose: () => setInfoModalProps({ open: false }), }); return; } setSelectedNodeForStaking({ nodeId: node.nodeId, identityKey: node.identity_key, }); }, [isWalletConnected] ); const columns: MRT_ColumnDef[] = useMemo( () => [ { id: "Favorite", enableColumnFilter: false, header: "Favorite", accessorKey: "Favorite", size: 50, Header: ( Fav ), sortingFn: (a, b) => { const aIsFavorite = favorites.includes(a.original.owner); const bIsFavorite = favorites.includes(b.original.owner); if (aIsFavorite && !bIsFavorite) { return -1; } if (!aIsFavorite && bIsFavorite) { return 1; } return 0; }, Cell: ({ row }) => , }, { id: "location", header: "Location", accessorKey: "countryName", size: 140, Header: Location, Cell: ({ row }) => row.original.countryCode && row.original.countryName ? ( ) : ( "-" ), }, { id: "name", header: "", size: 190, Header: Node, accessorKey: "name", Cell: ({ row }) => ( {row.original.name || "-"} e.stopPropagation()} sx={{ height: "24px" }} > {row.original.identity_key.length > 17 ? `${row.original.identity_key.slice(0, 10)}...${row.original.identity_key.slice(-8)}` : row.original.identity_key} ), }, { id: "id", header: "", Header: Node ID, accessorKey: "nodeId", size: 90, Cell: ({ row }) => ( {row.original.nodeId} ), }, { id: "qos", header: "Qlt of Service", align: "center", accessorKey: "qualityOfService", size: 100, Header: Uptime, Cell: ({ row }) => { const value = row.original.qualityOfService; let color = "#000000"; if (value >= 80) { color = "#22C55E"; // green } else if (value >= 50) { color = "#F59E0B"; // amber/orange-yellow } else { color = "#EF4444"; // red } return ( {value.toFixed()}% ); }, }, { id: "stakeSaturation", header: "Stake saturation", accessorKey: "stakeSaturation", size: 120, Header: Saturation, Cell: ({ row }) => { const value = row.original.stakeSaturation; let color = "#000000"; if (value > 100) { color = "#EF4444"; } else if (value >= 75) { color = "#22C55E"; } else if (value >= 25) { color = "#F59E0B"; } else { color = "#EF4444"; } return ( {value}% ); }, }, { id: "profitMarginPercentage", header: "Profit margin", accessorKey: "profitMarginPercentage", Header: Profit margin, Cell: ({ row }) => ( {row.original.profitMarginPercentage}% ), }, { id: "Action", header: "Action", accessorKey: "Action", size: 120, Header: Action, hidden: !isWalletConnected, enableColumnFilter: false, Cell: ({ row }) => ( ), enableSorting: false, }, ], [isWalletConnected, handleOnSelectStake, favorites] ); const table = useMaterialReactTable({ columns, data: nodes, enableRowSelection: false, enableColumnOrdering: false, enableColumnActions: false, enableFullScreenToggle: false, enableHiding: false, enableDensityToggle: false, enableFilterMatchHighlighting: true, paginationDisplayMode: "pages", muiPaginationProps: { showRowsPerPage: false, SelectProps: { sx: { fontFamily: "labGrotesqueMono", fontSize: "14px", }, }, color: "primary", shape: "circular", }, initialState: { columnPinning: isMobile ? {} : { right: ["Action"] }, // No pinning on mobile }, muiColumnActionsButtonProps: { sx: { color: "red", }, size: "small", }, muiTablePaperProps: { elevation: 0, sx: { bgcolor: isDarkMode ? "#0F1720" : "background.paper", }, }, muiTableHeadRowProps: { sx: { bgcolor: isDarkMode ? "#374042" : "background.paper", }, }, muiTableHeadCellProps: { sx: { alignItems: "center", paddingRight: 0, color: isDarkMode ? "#FFFFFF" : "inherit", }, }, muiSearchTextFieldProps: { InputProps: { style: { color: isDarkMode ? "#475569" : "inherit", }, }, sx: { backgroundColor: isDarkMode ? "#374042" : "white", "& .MuiOutlinedInput-root": { color: isDarkMode ? "#475569" : "inherit", backgroundColor: isDarkMode ? "#374042" : "white", }, "& .MuiOutlinedInput-notchedOutline": { borderColor: isDarkMode ? "#334155" : "inherit", }, "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: isDarkMode ? "#475569" : "inherit", }, }, variant: "outlined", size: "small", }, muiFilterTextFieldProps: { InputProps: { sx: { color: isDarkMode ? "#FFFFFF" : "inherit", }, }, sx: { "& .MuiInputBase-root": { backgroundColor: isDarkMode ? "#1C2A2E" : "white", }, "& .MuiInputBase-input::placeholder": { color: isDarkMode ? "#94A3B8" : "inherit", opacity: 1, }, "& .MuiOutlinedInput-notchedOutline": { borderColor: isDarkMode ? "#334155" : "inherit", }, "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: isDarkMode ? "#475569" : "inherit", }, }, variant: "outlined", size: "small", }, muiTableBodyCellProps: { sx: { border: "none", whiteSpace: "unset", wordBreak: "break-word", paddingRight: 0, color: isDarkMode ? "#FFFFFF" : "inherit", }, }, muiTableBodyRowProps: ({ row }) => ({ onClick: () => { router.push(`/nym-node/${row.original.nodeId}`); }, hover: true, sx: { backgroundColor: isDarkMode ? row.index % 2 === 0 ? "#3E4A4C !important" : "#374042 !important" : row.index % 2 === 0 ? "#F3F7FB" : "white", "&:hover": { backgroundColor: `${isDarkMode ? "#2A3436" : "#E5E7EB"} !important`, transition: "background-color 0.2s ease", }, cursor: "pointer", }, }), }); return ( <> {isLoading && } setSelectedNodeForStaking(undefined)} /> ); }; export default NodeTable;