Redirect from mixnode page to nodes page (#5397)

Co-authored-by: Yana <yanok87@users.noreply.github.com>
This commit is contained in:
Yana Matrosova
2025-01-28 19:30:45 +02:00
committed by GitHub
parent 6d90ffdd2c
commit 8f48ae08c4
3 changed files with 220 additions and 151 deletions
@@ -1,62 +1,62 @@
'use client'
"use client";
import * as React from 'react'
import { Alert, AlertTitle, Box, CircularProgress, Grid } from '@mui/material'
import { useParams } from 'next/navigation'
import { ColumnsType, DetailTable } from '@/app/components/DetailTable'
import { ComponentError } from '@/app/components/ComponentError'
import { ContentCard } from '@/app/components/ContentCard'
import { UptimeChart } from '@/app/components/UptimeChart'
import {
NymNodeContextProvider,
useNymNodeContext,
} from '@/app/context/node'
import { useMainContext } from '@/app/context/main'
import { Title } from '@/app/components/Title'
import * as React from "react";
import { Alert, AlertTitle, Box, CircularProgress, Grid } from "@mui/material";
import { useParams } from "next/navigation";
import { ColumnsType, DetailTable } from "@/app/components/DetailTable";
import { ComponentError } from "@/app/components/ComponentError";
import { ContentCard } from "@/app/components/ContentCard";
import { UptimeChart } from "@/app/components/UptimeChart";
import { NymNodeContextProvider, useNymNodeContext } from "@/app/context/node";
import { useMainContext } from "@/app/context/main";
import { Title } from "@/app/components/Title";
import Paper from "@mui/material/Paper";
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableRow from '@mui/material/TableRow';
import {humanReadableCurrencyToString} from "@/app/utils/currency";
import {DeclaredRole} from "@/app/network-components/nodes/DeclaredRole";
import {NodeDelegationsTable, VestingDelegationWarning} from "@/app/network-components/nodes/[id]/NodeDelegationsTable";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import { humanReadableCurrencyToString } from "@/app/utils/currency";
import { DeclaredRole } from "@/app/network-components/nodes/DeclaredRole";
import {
NodeDelegationsTable,
VestingDelegationWarning,
} from "@/app/network-components/nodes/[id]/NodeDelegationsTable";
const columns: ColumnsType[] = [
{
field: 'identity_key',
title: 'Identity Key',
headerAlign: 'left',
field: "identity_key",
title: "Identity Key",
headerAlign: "left",
width: 230,
},
{
field: 'bond',
title: 'Bond',
headerAlign: 'left',
field: "bond",
title: "Bond",
headerAlign: "left",
},
{
field: 'host',
title: 'IP',
headerAlign: 'left',
field: "host",
title: "IP",
headerAlign: "left",
width: 99,
},
{
field: 'location',
title: 'Location',
headerAlign: 'left',
field: "location",
title: "Location",
headerAlign: "left",
},
{
field: 'owner',
title: 'Owner',
headerAlign: 'left',
field: "owner",
title: "Owner",
headerAlign: "left",
},
{
field: 'version',
title: 'Version',
headerAlign: 'left',
field: "version",
title: "Version",
headerAlign: "left",
},
]
];
interface NodeEnrichedRowType {
node_id: number;
@@ -71,28 +71,33 @@ interface NodeEnrichedRowType {
function nodeEnrichedToGridRow(node: any): NodeEnrichedRowType {
return {
node_id: node.node_id,
owner: node.bond_information?.owner || '',
identity_key: node.bond_information?.node?.identity_key || '',
location: node.location?.country_name || '',
owner: node.bond_information?.owner || "",
identity_key: node.bond_information?.node?.identity_key || "",
location: node.location?.country_name || "",
bond: node.bond_information?.original_pledge.amount || 0, // TODO: format
host: node.bond_information?.node?.host || '',
version: node.description?.build_information?.build_version || '',
host: node.bond_information?.node?.host || "",
version: node.description?.build_information?.build_version || "",
};
}
/**
* Shows nym node details
*/
const PageNymNodeDetailsWithState = ({
selectedNymNode,
}: {
selectedNymNode?: any
selectedNymNode?: any;
}) => {
const { uptimeHistory } = useNymNodeContext()
const enrichedData = React.useMemo(() => selectedNymNode ? [nodeEnrichedToGridRow(selectedNymNode)] : [], []);
const { uptimeHistory } = useNymNodeContext();
const enrichedData = React.useMemo(
() => (selectedNymNode ? [nodeEnrichedToGridRow(selectedNymNode)] : []),
[]
);
const hasVestingContractDelegations = React.useMemo(() => selectedNymNode?.delegations?.filter((d: any) => d.proxy)?.length, [selectedNymNode]);
const hasVestingContractDelegations = React.useMemo(
() => selectedNymNode?.delegations?.filter((d: any) => d.proxy)?.length,
[selectedNymNode]
);
return (
<Box component="main">
@@ -109,81 +114,128 @@ const PageNymNodeDetailsWithState = ({
</Grid>
<Grid container mt={2} spacing={2}>
{selectedNymNode.rewarding_details &&
<Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>
Delegations and Rewards
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>Operator</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({ amount : selectedNymNode.rewarding_details.operator.split('.')[0], denom: "unym" })}
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>
{hasVestingContractDelegations ?
<VestingDelegationWarning plural={true}>
Delegates&nbsp;({selectedNymNode.rewarding_details.unique_delegations} delegates)
</VestingDelegationWarning> :
<>Delegates&nbsp;({selectedNymNode.rewarding_details.unique_delegations} delegates)</>
}
</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({ amount : selectedNymNode.rewarding_details.delegates.split('.')[0], denom: "unym" })}
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>Profit margin</strong>
</TableCell>
<TableCell align="right">
{selectedNymNode.rewarding_details.cost_params.profit_margin_percent * 100}%
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>Operator costs</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString(selectedNymNode.rewarding_details.cost_params.interval_operating_cost)}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>}
{selectedNymNode.rewarding_details && (
<Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>Delegations and Rewards</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>Operator</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({
amount:
selectedNymNode.rewarding_details.operator.split(
"."
)[0],
denom: "unym",
})}
</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>
{hasVestingContractDelegations ? (
<VestingDelegationWarning plural={true}>
Delegates&nbsp;(
{
selectedNymNode.rewarding_details
.unique_delegations
}{" "}
delegates)
</VestingDelegationWarning>
) : (
<>
Delegates&nbsp;(
{
selectedNymNode.rewarding_details
.unique_delegations
}{" "}
delegates)
</>
)}
</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({
amount:
selectedNymNode.rewarding_details.delegates.split(
"."
)[0],
denom: "unym",
})}
</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>Profit margin</strong>
</TableCell>
<TableCell align="right">
{selectedNymNode.rewarding_details.cost_params
.profit_margin_percent * 100}
%
</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>Operator costs</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString(
selectedNymNode.rewarding_details.cost_params
.interval_operating_cost
)}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>
)}
{selectedNymNode.description?.declared_role && <Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>
Node roles
</TableCell>
</TableRow>
<TableRow>
<TableCell>
Self declared roles
</TableCell>
<TableCell>
<DeclaredRole declared_role={selectedNymNode.description?.declared_role}/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>}
{selectedNymNode.description?.declared_role && (
<Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>Node roles</TableCell>
</TableRow>
<TableRow>
<TableCell>Self declared roles</TableCell>
<TableCell>
<DeclaredRole
declared_role={
selectedNymNode.description?.declared_role
}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>
)}
</Grid>
<Grid container spacing={2} mt={2}>
@@ -205,42 +257,41 @@ const PageNymNodeDetailsWithState = ({
</Grid>
<Box mt={2}>
<NodeDelegationsTable node={selectedNymNode}/>
<NodeDelegationsTable node={selectedNymNode} />
</Box>
</Box>
)
}
);
};
/**
* Guard component to handle loading and not found states
*/
const PageNymNodeDetailGuard = () => {
const [selectedNode, setSelectedNode] = React.useState<any>()
const [selectedNode, setSelectedNode] = React.useState<any>();
const [isLoading, setLoading] = React.useState<boolean>(true);
const [error, setError] = React.useState<string>();
const { fetchNodeById } = useMainContext()
const { id } = useParams()
const { fetchNodeById } = useMainContext();
const { id } = useParams();
React.useEffect(() => {
setSelectedNode(undefined);
setLoading(true);
(async () => {
if(typeof(id) === "string") {
if (typeof id === "string") {
try {
const res = await fetchNodeById(Number.parseInt(id));
setSelectedNode(res);
} catch(e: any) {
} catch (e: any) {
setError(e.message);
}
finally {
} finally {
setLoading(false);
}
}
})();
}, [id])
}, [id, fetchNodeById]);
if (isLoading) {
return <CircularProgress />
return <CircularProgress />;
}
// loaded, but not found
@@ -248,31 +299,38 @@ const PageNymNodeDetailGuard = () => {
return (
<Alert severity="warning">
<AlertTitle>Nym node not found</AlertTitle>
Sorry, we could not find a node with id <code>{id || ''}</code>
Sorry, we could not find a node with id <code>{id || ""}</code>
</Alert>
)
);
}
return <PageNymNodeDetailsWithState selectedNymNode={selectedNode} />
}
if (!selectedNode) {
return (
<Alert severity="warning">
<AlertTitle>Legacy Node</AlertTitle>
Unable to load details for the selected Nym node.
</Alert>
);
}
return <PageNymNodeDetailsWithState selectedNymNode={selectedNode} />;
};
/**
* Wrapper component that adds the node content based on the `id` in the address URL
*/
const PageNymNodeDetail = () => {
const { id } = useParams()
const { id } = useParams();
if (!id || typeof id !== 'string') {
return (
<Alert severity="error">Oh no! Could not find that node</Alert>
)
if (!id || typeof id !== "string") {
return <Alert severity="error">Oh no! Could not find that node</Alert>;
}
return (
<NymNodeContextProvider nymNodeId={id}>
<PageNymNodeDetailGuard />
</NymNodeContextProvider>
)
}
);
};
export default PageNymNodeDetail
export default PageNymNodeDetail;
+11 -1
View File
@@ -1,4 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
async redirects() {
return [
{
source: '/network-components/mixnode/:id', // Match the old URL
destination: '/network-components/nodes/:id', // Redirect to the new URL
permanent: true,
},
];
},
};
export default nextConfig;
+2 -1
View File
@@ -28,7 +28,8 @@
"../assets/*"
]
},
"moduleResolution": "node"
"moduleResolution": "node",
"target": "ES2017"
},
"include": [
"next-env.d.ts",