Explorer V2 (#5548)

* remove pnpm lock file (should only be using yarn)

* Add lefthook configuration for pre-commit checks

* Add explorer-v2 to package.json dependencies

* add explorer v2

* update explorer v2 package name

* + basepath
+ redirect to basepath
+ blog icons refactor
+ icons refactor

* Add Getting Started instructions to README

* fix noise graph bug and line graph UI

* Delete unused translations, clean up console logs

* / test image url

* update yarn.lock

---------

Co-authored-by: RadekSabacky <radek@nymtech.net>
Co-authored-by: windy-ux <75579979+windy-ux@users.noreply.github.com>
Co-authored-by: Yana <iana.matrosova@gmail.com>
Co-authored-by: Mark Sinclair <mmsinclair@users.noreply.github.com>
This commit is contained in:
Fouad
2025-03-13 11:31:59 +00:00
committed by GitHub
parent 79ce611d21
commit dc88650d6d
219 changed files with 62763 additions and 5362 deletions
@@ -0,0 +1,117 @@
"use client";
import { Card, CardContent, Skeleton, Stack, Typography } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import DOMPurify from "isomorphic-dompurify";
import { fetchEpochRewards, fetchObservatoryNodes } from "../../app/api";
import type { ExplorerData, IObservatoryNode } from "../../app/api/types";
import { countryName } from "../../utils/countryName";
import NodeTable from "./NodeTable";
// Utility function to calculate node saturation point
function getNodeSaturationPoint(
totalStake: number,
stakeSaturationPoint: string,
): number {
const saturation = Number.parseFloat(stakeSaturationPoint);
if (Number.isNaN(saturation) || saturation <= 0) {
throw new Error("Invalid stake saturation point provided");
}
const ratio = (totalStake / saturation) * 100;
return Number(ratio.toFixed());
}
// Map nodes with rewards data
const mappedNymNodes = (
nodes: IObservatoryNode[],
epochRewardsData: ExplorerData["currentEpochRewardsData"],
) =>
nodes.map((node) => {
const nodeSaturationPoint = getNodeSaturationPoint(
node.total_stake,
epochRewardsData.interval.stake_saturation_point,
);
const cleanMoniker = DOMPurify.sanitize(
node.self_description.moniker,
).replace(/&amp;/g, "&");
return {
name: cleanMoniker,
nodeId: node.node_id,
identity_key: node.identity_key,
countryCode: node.description.auxiliary_details.location || null,
countryName:
countryName(node.description.auxiliary_details.location) || null,
profitMarginPercentage:
+node.rewarding_details.cost_params.profit_margin_percent * 100,
owner: node.bonding_address,
stakeSaturation: nodeSaturationPoint,
qualityOfService: +node.uptime * 100,
};
});
export type MappedNymNodes = ReturnType<typeof mappedNymNodes>;
export type MappedNymNode = MappedNymNodes[0];
const NodeTableWithAction = () => {
// Use React Query to fetch epoch rewards
const {
data: epochRewardsData,
isLoading: isEpochLoading,
isError: isEpochError,
} = useQuery({
queryKey: ["epochRewards"],
queryFn: fetchEpochRewards,
});
// Use React Query to fetch Nym nodes
const {
data: nymNodes = [],
isLoading: isNodesLoading,
isError: isNodesError,
} = useQuery({
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
});
// Handle loading state
if (isEpochLoading || isNodesLoading) {
return (
<Card sx={{ height: "100%", mt: 5 }}>
<CardContent>
<Skeleton variant="text" height={100} />
<Skeleton variant="text" height={100} />
<Skeleton variant="text" height={100} />
<Skeleton variant="text" height={100} />
</CardContent>
</Card>
);
}
// Handle error state
if (isEpochError || isNodesError) {
return (
<Stack direction="row" spacing={1}>
<Typography variant="h5" sx={{ color: "pine.600", letterSpacing: 0.7 }}>
Error loading data. Please try again later.
</Typography>
</Stack>
);
}
// Map nodes with rewards data
if (!epochRewardsData) {
return null;
}
const data = mappedNymNodes(nymNodes || [], epochRewardsData);
return <NodeTable nodes={data} />;
};
export default NodeTableWithAction;