This commit is contained in:
benedettadavico
2025-09-11 18:21:48 +02:00
parent 7863df17e4
commit 9f7d7d98b8
41 changed files with 322 additions and 237 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
},
"files": {
"ignoreUnknown": false,
"ignore": ["node_modules", ".next", "public"]
"ignore": ["node_modules", ".next", "public", "src/app/lib/strapi.d.ts"]
},
"formatter": {
"enabled": true,
+16 -16
View File
@@ -2,23 +2,23 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
reactStrictMode: true,
basePath: "/explorer",
assetPrefix: "/explorer",
trailingSlash: false,
basePath: "/explorer",
assetPrefix: "/explorer",
trailingSlash: false,
async redirects() {
return [
// Change the basePath to /explorer
{
source: "/",
destination: "/explorer",
basePath: false,
permanent: true,
},
];
},
async redirects() {
return [
// Change the basePath to /explorer
{
source: "/",
destination: "/explorer",
basePath: false,
permanent: true,
},
];
},
};
module.exports = nextConfig
module.exports = nextConfig;
+1 -2
View File
@@ -191,7 +191,6 @@ export const fetchAccountBalance = async (
return data;
};
// 🔹 Fetch NYM Price
export const fetchNymPrice = async (): Promise<NymTokenomics> => {
const res = await fetch(NYM_PRICES_API, {
@@ -226,7 +225,7 @@ export const fetchNSApiNodes = async (): Promise<NS_NODE[]> => {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
},
}
},
);
if (!response.ok) {
@@ -10,7 +10,7 @@ import { bannerApiPath } from "../../banner/config/constants";
// Fetch footer data
export const getBanner = async (
locale: Languages
locale: Languages,
): Promise<{
id?: number;
attributes?: components["schemas"]["ExplorerBanner"];
+1 -1
View File
@@ -1,5 +1,5 @@
import { Header } from "@/components/header";
import { Banner } from "@/components/banner/Banner";
import { Header } from "@/components/header";
import { Wrapper } from "@/components/wrapper";
import Providers from "@/providers";
import type { Metadata } from "next";
+14 -7
View File
@@ -27,22 +27,27 @@ type ApiNode = {
};
function toNumber(x: unknown, fallback = 0): number {
const n = typeof x === "string" || typeof x === "number" ? Number(x) : NaN;
const n =
typeof x === "string" || typeof x === "number" ? Number(x) : Number.NaN;
return Number.isFinite(n) ? n : fallback;
}
const MIN_STAKE = 50_000_000_000; // 50k NYM
const MAX_STAKE = 150_000_000_000; // 150k NYM
const MIN_STAKE = 50_000_000_000; // 50k NYM
const MAX_STAKE = 150_000_000_000; // 150k NYM
const MAX_PM = 0.2;
// require exit gw
function hasRequiredRoles(n: ApiNode): boolean {
const r = n.self_description?.declared_role ?? n.description?.declared_role ?? {};
const r =
n.self_description?.declared_role ?? n.description?.declared_role ?? {};
return r.mixnode === false && !!r.entry && !!r.exit_ipr && !!r.exit_nr;
}
function hasGoodPM(n: ApiNode): boolean {
const pm = toNumber(n.rewarding_details?.cost_params?.profit_margin_percent, NaN);
const pm = toNumber(
n.rewarding_details?.cost_params?.profit_margin_percent,
Number.NaN,
);
return !Number.isNaN(pm) && pm <= MAX_PM;
}
@@ -73,7 +78,7 @@ async function fetchAllNodes(): Promise<ApiNode[]> {
while (true) {
const res = await fetch(
`https://api.nym.spectredao.net/api/v1/nodes?page=${page}&size=${pageSize}`,
{ cache: "no-store" }
{ cache: "no-store" },
);
if (!res.ok) throw new Error(`Failed to fetch page ${page}`);
const json = await res.json();
@@ -117,7 +122,9 @@ async function fetchRecommendedNodes(): Promise<number[]> {
}
return picked
.map((n) => (typeof n.node_id === "number" ? n.node_id : toNumber(n.node_id, 0)))
.map((n) =>
typeof n.node_id === "number" ? n.node_id : toNumber(n.node_id, 0),
)
.filter((id) => Number.isFinite(id) && id > 0);
}
@@ -45,7 +45,7 @@ const getAllocation = (unyms: number, totalUnyms: number): number => {
};
const calculateStakingRewards = (
accumulatedRewards: IRewardDetails[]
accumulatedRewards: IRewardDetails[],
): number => {
if (accumulatedRewards.length > 0) {
const totalRewards = accumulatedRewards.reduce((total, rewardDetail) => {
@@ -118,7 +118,7 @@ export const AccountBalancesCard = (props: IAccountBalancesCardProps) => {
const totalBalanceUSD = getPriceInUSD(
Number(accountInfo.total_value.amount),
nymPriceData
nymPriceData,
);
const spendableNYM =
accountInfo.balances.length > 0
@@ -132,46 +132,46 @@ export const AccountBalancesCard = (props: IAccountBalancesCardProps) => {
accountInfo.balances.length > 0
? getAllocation(
Number(accountInfo.balances[0].amount),
Number(accountInfo.total_value.amount)
Number(accountInfo.total_value.amount),
)
: 0;
const delegationsNYM = getNymsFormated(
Number(accountInfo.total_delegations.amount)
Number(accountInfo.total_delegations.amount),
);
const delegationsUSD = getPriceInUSD(
Number(accountInfo.total_delegations.amount),
nymPriceData
nymPriceData,
);
const delegationsAllocation = getAllocation(
Number(accountInfo.total_delegations.amount),
Number(accountInfo.total_value.amount)
Number(accountInfo.total_value.amount),
);
const operatorRewardsAllocation = getAllocation(
Number(accountInfo.operator_rewards?.amount || 0),
Number(accountInfo.total_value.amount)
Number(accountInfo.total_value.amount),
);
const operatorRewardsNYM = getNymsFormated(
Number(accountInfo.operator_rewards?.amount || 0)
Number(accountInfo.operator_rewards?.amount || 0),
);
const operatorRewardsUSD = getPriceInUSD(
Number(accountInfo.operator_rewards?.amount || 0),
nymPriceData
nymPriceData,
);
const claimableNYM = getNymsFormated(
Number(accountInfo.claimable_rewards.amount)
Number(accountInfo.claimable_rewards.amount),
);
const claimableUSD = getPriceInUSD(
Number(accountInfo.claimable_rewards.amount),
nymPriceData
nymPriceData,
);
const claimableAllocation = getAllocation(
Number(accountInfo.claimable_rewards.amount),
Number(accountInfo.total_value.amount)
Number(accountInfo.total_value.amount),
);
const stakingRewards =
@@ -22,7 +22,7 @@ export default function AccountPageButtonGroup({ address }: Props) {
if (!nsApiNodes || isNSApiNodesError) return null;
const nymNode = nsApiNodes.find(
(node: NS_NODE) => node.bonding_address === address
(node: NS_NODE) => node.bonding_address === address,
);
if (!nymNode) return null;
+5 -5
View File
@@ -1,12 +1,12 @@
"use client";
import { useState, useEffect } from "react";
import { Box, Typography, Button, IconButton, Stack } from "@mui/material";
import { Close, Launch } from "@mui/icons-material";
import { Link } from "../muiLink";
import { Wrapper } from "../wrapper";
import { getBanner } from "@/app/features/banner/api/getBanner";
import type { components } from "@/app/lib/strapi";
import { Close, Launch } from "@mui/icons-material";
import { Box, Button, IconButton, Stack, Typography } from "@mui/material";
import { useEffect, useState } from "react";
import { Link } from "../muiLink";
import { Wrapper } from "../wrapper";
type BannerData = {
id?: number;
+1 -1
View File
@@ -24,6 +24,6 @@ type BlogArticle = {
}[];
};
export type BlogArticleWithLink = BlogArticle;
export type BlogArticleWithLink = BlogArticle;
export default BlogArticle;
@@ -1,11 +1,11 @@
"use client";
import CheckIcon from "@mui/icons-material/Check";
import { IconButton, Typography, useTheme } from "@mui/material";
import { useCopyToClipboard } from "@uidotdev/usehooks";
import { useEffect } from "react";
import CopyFile from "../icons/CopyFile";
import CopyFileDark from "../icons/CopyFileDark";
import CheckIcon from "@mui/icons-material/Check";
const CLEAR_AFTER_MS = 10_000;
@@ -1,6 +1,6 @@
"use client";
import Grid from "@mui/material/Grid2";
import { ReactNode } from "react";
import type { ReactNode } from "react";
interface ConditionalCardWrapperProps {
children: ReactNode;
@@ -40,7 +40,7 @@ export const CurrentEpochCard = () => {
const updateState = useCallback((data: NonNullable<EpochResponseData>) => {
const { startTime, endTime } = getStartEndTime(
data.current_epoch_start,
data.current_epoch_end
data.current_epoch_end,
);
const progress = calulateProgress(data.current_epoch_end);
@@ -1,7 +1,7 @@
"use client";
import { useEpochContext } from "@/providers/EpochProvider";
import { CurrentEpochCard } from "./CurrentEpochCard";
import { ConditionalCardWrapper } from "./ConditionalCardWrapper";
import { CurrentEpochCard } from "./CurrentEpochCard";
export const CurrentEpochCardWrapper = () => {
const { data, isError, isLoading, epochStatus } = useEpochContext();
@@ -1,8 +1,8 @@
"use client";
import { fetchNoise } from "@/app/api";
import { useQuery } from "@tanstack/react-query";
import { NetworkStakeCard } from "./NetworkStakeCard";
import { ConditionalCardWrapper } from "./ConditionalCardWrapper";
import { NetworkStakeCard } from "./NetworkStakeCard";
export const NetworkStakeCardWrapper = () => {
const { data, isLoading, isError } = useQuery({
@@ -1,8 +1,8 @@
"use client";
import { fetchNoise } from "@/app/api";
import { useQuery } from "@tanstack/react-query";
import { NoiseCard } from "./NoiseCard";
import { ConditionalCardWrapper } from "./ConditionalCardWrapper";
import { NoiseCard } from "./NoiseCard";
export const NoiseCardWrapper = () => {
const { data, isLoading, isError } = useQuery({
@@ -46,7 +46,7 @@ export const StakersNumberCard = () => {
const getActiveStakersNumber = (nodes: NS_NODE[]): number => {
return nodes.reduce(
(sum, node) => sum + (node.rewarding_details?.unique_delegations || 0),
0
0,
);
};
const allStakers = getActiveStakersNumber(nsApiNodes);
@@ -1,8 +1,8 @@
"use client";
import { fetchNSApiNodes } from "@/app/api";
import { useQuery } from "@tanstack/react-query";
import { StakersNumberCard } from "./StakersNumberCard";
import { ConditionalCardWrapper } from "./ConditionalCardWrapper";
import { StakersNumberCard } from "./StakersNumberCard";
export const StakersNumberCardWrapper = () => {
const { data, isLoading, isError } = useQuery({
@@ -103,7 +103,7 @@ export const TokenomicsCard = () => {
function calculateTVL(
epochRewards: ExplorerData["currentEpochRewardsData"],
nymPriceData: NymTokenomics,
packetsAndStaking: ExplorerData["packetsAndStakingData"]
packetsAndStaking: ExplorerData["packetsAndStakingData"],
): number {
const lastTotalStake =
packetsAndStaking[packetsAndStaking.length - 1]?.total_stake || 0;
@@ -114,7 +114,7 @@ export const TokenomicsCard = () => {
);
}
const TVL = formatBigNum(
calculateTVL(epochRewardsData, nymPrice, packetsAndStakingData)
calculateTVL(epochRewardsData, nymPrice, packetsAndStakingData),
);
const dataRows = [
@@ -1,8 +1,8 @@
"use client";
import { fetchEpochRewards, fetchNoise, fetchNymPrice } from "@/app/api";
import { useQuery } from "@tanstack/react-query";
import { TokenomicsCard } from "./TokenomicsCard";
import { ConditionalCardWrapper } from "./ConditionalCardWrapper";
import { TokenomicsCard } from "./TokenomicsCard";
export const TokenomicsCardWrapper = () => {
const {
@@ -1,5 +1,4 @@
"use client";
import React from "react";
import {
Box,
Button,
@@ -9,11 +8,12 @@ import {
useTheme,
} from "@mui/material";
import Grid from "@mui/material/Grid2";
import React from "react";
import FilterAltIcon from "@mui/icons-material/FilterAlt";
import AccessTimeIcon from "@mui/icons-material/AccessTime";
import PieChartIcon from "@mui/icons-material/PieChart";
import FilterAltIcon from "@mui/icons-material/FilterAlt";
import PercentIcon from "@mui/icons-material/Percent";
import PieChartIcon from "@mui/icons-material/PieChart";
import NodeFilterButtonGroup from "../toggleButton/NodeFilterButtonGroup";
type AdvancedFiltersProps = {
@@ -28,7 +28,7 @@ type AdvancedFiltersProps = {
maxSaturation?: number;
activeFilter: "all" | "mixnodes" | "gateways" | "recommended";
setActiveFilter: (
filter: "all" | "mixnodes" | "gateways" | "recommended"
filter: "all" | "mixnodes" | "gateways" | "recommended",
) => void;
nodeCounts: {
all: number;
@@ -302,7 +302,7 @@ export default function AdvancedFilters({
}}
/>
}
onClick={() => setOpen && setOpen(!open)}
onClick={() => setOpen?.(!open)}
sx={{
borderRadius: 3,
px: 4,
@@ -4,10 +4,10 @@ import {
Box,
Button,
Stack,
Tooltip,
Typography,
useMediaQuery,
useTheme,
Tooltip,
} from "@mui/material";
import { useQueryClient } from "@tanstack/react-query";
import { useLocalStorage } from "@uidotdev/usehooks";
@@ -19,8 +19,10 @@ import {
import { useRouter } from "next/navigation";
import { useCallback, useMemo, useState } from "react";
import { colours } from "@/theme/colours";
import { COSMOS_KIT_USE_CHAIN } from "../../config";
import { useNymClient } from "../../hooks/useNymClient";
import CopyToClipboard from "../copyToClipboard/CopyToClipboard";
import CountryFlag from "../countryFlag/CountryFlag";
import { Favorite } from "../favorite/Favorite";
import Loading from "../loading";
@@ -30,8 +32,6 @@ 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";
import { colours } from "@/theme/colours";
const ColumnHeading = ({
children,
@@ -103,7 +103,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
{ nodeId },
fee,
"Delegation from Nym Explorer V2",
uNymFunds
uNymFunds,
);
setSelectedNodeForStaking(undefined);
setInfoModalProps({
@@ -132,7 +132,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
}
setIsLoading(false);
},
[nymClient, handleRefetch]
[nymClient, handleRefetch],
);
const handleOnSelectStake = useCallback(
@@ -161,7 +161,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
identityKey: node.identity_key,
});
},
[isWalletConnected]
[isWalletConnected],
);
const columns: MRT_ColumnDef<MappedNymNode>[] = useMemo(
@@ -399,7 +399,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
enableSorting: false,
},
],
[isWalletConnected, handleOnSelectStake, favorites, isDarkMode]
[isWalletConnected, handleOnSelectStake, favorites, isDarkMode],
);
const table = useMaterialReactTable({
columns,
@@ -3,12 +3,12 @@
import { Card, CardContent, Skeleton, Stack, Typography } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import DOMPurify from "isomorphic-dompurify";
import { useEffect, useState } from "react";
import { fetchEpochRewards, fetchNSApiNodes } from "../../app/api";
import type { ExplorerData, NS_NODE } from "../../app/api/types";
import { countryName } from "../../utils/countryName";
import NodeTable from "./NodeTable";
import { useState, useEffect } from "react";
import AdvancedFilters from "./AdvancedFilters";
import NodeTable from "./NodeTable";
type Props = {
/** Recommended node IDs provided by the server page */
@@ -17,7 +17,7 @@ type Props = {
function getNodeSaturationPoint(
totalStake: number,
stakeSaturationPoint: string
stakeSaturationPoint: string,
): number {
const saturation = Number.parseFloat(stakeSaturationPoint);
if (Number.isNaN(saturation) || saturation <= 0) {
@@ -29,18 +29,18 @@ function getNodeSaturationPoint(
const mappedNSApiNodes = (
nodes: NS_NODE[],
epochRewardsData: ExplorerData["currentEpochRewardsData"]
epochRewardsData: ExplorerData["currentEpochRewardsData"],
) =>
nodes
.map((node) => {
const nodeSaturationPoint = getNodeSaturationPoint(
+node.total_stake,
epochRewardsData.interval.stake_saturation_point
epochRewardsData.interval.stake_saturation_point,
);
const cleanMoniker = DOMPurify.sanitize(node.description.moniker).replace(
/&amp;/g,
"&"
"&",
);
const selfBondFormatted = node.original_pledge
@@ -49,7 +49,7 @@ const mappedNSApiNodes = (
const operatingCostsFormatted = node.rewarding_details
? Number(
node.rewarding_details.cost_params.interval_operating_cost.amount
node.rewarding_details.cost_params.interval_operating_cost.amount,
) / 1_000_000
: 0;
@@ -111,7 +111,7 @@ const NodeTableWithAction = ({ recommendedIds }: Props) => {
// Wrapper functions to handle filter changes and sessionStorage
const handleActiveFilterChange = (
newFilter: "all" | "mixnodes" | "gateways" | "recommended"
newFilter: "all" | "mixnodes" | "gateways" | "recommended",
) => {
setActiveFilter(newFilter);
sessionStorage.setItem("nodeTableActiveFilter", newFilter);
@@ -126,7 +126,7 @@ const NodeTableWithAction = ({ recommendedIds }: Props) => {
setSaturation(newSaturation);
sessionStorage.setItem(
"nodeTableSaturation",
JSON.stringify(newSaturation)
JSON.stringify(newSaturation),
);
};
@@ -134,7 +134,7 @@ const NodeTableWithAction = ({ recommendedIds }: Props) => {
setProfitMargin(newProfitMargin);
sessionStorage.setItem(
"nodeTableProfitMargin",
JSON.stringify(newProfitMargin)
JSON.stringify(newProfitMargin),
);
};
@@ -142,7 +142,7 @@ const NodeTableWithAction = ({ recommendedIds }: Props) => {
setAdvancedOpen(newAdvancedOpen);
sessionStorage.setItem(
"nodeTableAdvancedOpen",
JSON.stringify(newAdvancedOpen)
JSON.stringify(newAdvancedOpen),
);
};
@@ -182,7 +182,7 @@ const NodeTableWithAction = ({ recommendedIds }: Props) => {
// Calculate max saturation from all nodes
const maxSaturation = Math.max(
100,
...nsApiNodesData.map((n) => n.stakeSaturation || 0)
...nsApiNodesData.map((n) => n.stakeSaturation || 0),
);
// Initialize saturation from sessionStorage or set to maxSaturation when data is loaded
@@ -62,17 +62,16 @@ export const BasicInfoCard = ({ paramId }: Props) => {
if (paramId.length > 10) {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.identity_key === paramId
(node: NS_NODE) => node.identity_key === paramId,
);
} else {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.node_id === Number(paramId)
(node: NS_NODE) => node.node_id === Number(paramId),
);
}
if (!nodeInfo) return null;
const selfBond = nodeInfo.original_pledge
? formatBigNum(Number(nodeInfo.original_pledge) / 1_000_000)
: 0;
@@ -1,6 +1,7 @@
"use client";
import { fetchNodeDelegations } from "@/app/api";
import { colours } from "@/theme/colours";
import { Stack, Typography, useTheme } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import {
@@ -11,7 +12,6 @@ import {
import { useRouter } from "next/navigation";
import { useMemo } from "react";
import type { NodeRewardDetails } from "../../app/api/types";
import { colours } from "@/theme/colours";
const ColumnHeading = ({
children,
@@ -88,7 +88,7 @@ const DelegationsTable = ({ id }: Props) => {
),
},
],
[]
[],
);
const table = useMaterialReactTable({
columns,
@@ -73,11 +73,11 @@ export const NodeDataCard = ({ paramId }: Props) => {
if (paramId.length > 10) {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.identity_key === paramId
(node: NS_NODE) => node.identity_key === paramId,
);
} else {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.node_id === Number(paramId)
(node: NS_NODE) => node.node_id === Number(paramId),
);
}
@@ -86,7 +86,7 @@ export const NodeDataCard = ({ paramId }: Props) => {
const softwareUpdateTime = nodeInfo.self_description
? format(
new Date(nodeInfo.self_description.build_information.build_timestamp),
"dd/MM/yyyy"
"dd/MM/yyyy",
)
: "N/A";
@@ -31,11 +31,11 @@ const NodeDelegationsCard = ({ paramId }: Props) => {
if (paramId.length > 10) {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.identity_key === paramId
(node: NS_NODE) => node.identity_key === paramId,
);
} else {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.node_id === Number(paramId)
(node: NS_NODE) => node.node_id === Number(paramId),
);
}
@@ -27,15 +27,14 @@ export default function NodePageButtonGroup({ paramId }: Props) {
if (paramId.length > 10) {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.identity_key === paramId
(node: NS_NODE) => node.identity_key === paramId,
);
} else {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.node_id === Number(paramId)
(node: NS_NODE) => node.node_id === Number(paramId),
);
}
if (!nodeInfo) return null;
if (nodeInfo.bonding_address)
@@ -72,11 +72,11 @@ export const NodeParametersCard = ({ paramId }: Props) => {
if (paramId.length > 10) {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.identity_key === paramId
(node: NS_NODE) => node.identity_key === paramId,
);
} else {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.node_id === Number(paramId)
(node: NS_NODE) => node.node_id === Number(paramId),
);
}
@@ -94,14 +94,14 @@ export const NodeParametersCard = ({ paramId }: Props) => {
const operatingCosts = nodeInfo.rewarding_details
? Number(
nodeInfo.rewarding_details.cost_params.interval_operating_cost.amount
nodeInfo.rewarding_details.cost_params.interval_operating_cost.amount,
) / 1_000_000
: 0;
const operatingCostsFormated = `${operatingCosts.toString()} NYM`;
const getNodeSaturationPoint = (
nodeTotalStake: string,
stakeSaturationPoint: string
stakeSaturationPoint: string,
): string => {
const saturation = Number.parseFloat(stakeSaturationPoint);
const totalStake = Number.parseFloat(nodeTotalStake);
@@ -117,7 +117,7 @@ export const NodeParametersCard = ({ paramId }: Props) => {
const nodeSaturationPoint = getNodeSaturationPoint(
nodeInfo.total_stake,
epochRewardsData.interval.stake_saturation_point
epochRewardsData.interval.stake_saturation_point,
);
return (
@@ -63,11 +63,11 @@ export const NodeProfileCard = ({ paramId }: Props) => {
if (paramId.length > 10) {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.identity_key === paramId
(node: NS_NODE) => node.identity_key === paramId,
);
} else {
nodeInfo = nsApiNodes.find(
(node: NS_NODE) => node.node_id === Number(paramId)
(node: NS_NODE) => node.node_id === Number(paramId),
);
}
@@ -141,7 +141,7 @@ export const NodeProfileCard = ({ paramId }: Props) => {
{ nodeId },
fee,
"Delegation from Nym Explorer V2",
uNymFunds
uNymFunds,
);
setSelectedNodeForStaking(undefined);
setInfoModalProps({
@@ -170,11 +170,11 @@ export const NodeProfileCard = ({ paramId }: Props) => {
const cleanMoniker = DOMPurify.sanitize(nodeInfo.description.moniker).replace(
/&amp;/g,
"&"
"&",
);
const cleanDescription = DOMPurify.sanitize(
nodeInfo.description.details
nodeInfo.description.details,
).replace(/&amp;/g, "&");
// get full country name
@@ -9,8 +9,8 @@ import {
} from "../../app/api";
import type {
LastProbeResult,
NodeDescription,
NS_NODE,
NodeDescription,
} from "../../app/api/types";
import ExplorerCard from "../cards/ExplorerCard";
import ExplorerListItem from "../list/ListItem";
@@ -32,7 +32,7 @@ const roleMapping: Record<DeclaredRoleKey, RoleString> = {
};
const getNodeRoles = (
declaredRoles: NodeDescriptionNotNull["declared_role"]
declaredRoles: NodeDescriptionNotNull["declared_role"],
): RoleString[] => {
return Object.entries(declaredRoles)
.filter(([, isActive]) => isActive)
@@ -81,7 +81,7 @@ function calculateConfigScoreStars(probeResult: LastProbeResult): number {
if (as_entry) {
const entryScore = [as_entry.can_connect, as_entry.can_route].filter(
Boolean
Boolean,
).length;
return entryScore === 2 ? 4 : entryScore === 1 ? 2 : 1;
@@ -190,7 +190,7 @@ export const NodeRoleCard = ({ paramId }: Props) => {
// Define whether to fetch gateway status
const shouldFetchGatewayStatus = nodeRoles.some((role) =>
["Entry Node", "Exit IPR Node", "Exit NR Node"].includes(role)
["Entry Node", "Exit IPR Node", "Exit NR Node"].includes(role),
);
// Fetch gateway status only if `shouldFetchGatewayStatus` is true
const { data: gatewayStatus } = useQuery({
@@ -226,7 +226,6 @@ export const NodeRoleCard = ({ paramId }: Props) => {
}
if (!nodeInfo) return null;
const NodeRoles = nodeRoles.map((role) => (
<Stack key={role} direction="row" gap={1}>
<Chip key={role} label={role} size="small" />
@@ -253,7 +252,7 @@ export const NodeRoleCard = ({ paramId }: Props) => {
// Function to calculate active set probability
const getActiveSetProbability = (
nodeTotalStake: string,
stakeSaturationPoint: string
stakeSaturationPoint: string,
): string => {
const totalStake = Number.parseFloat(nodeTotalStake);
const saturation = Number.parseFloat(stakeSaturationPoint);
@@ -274,7 +273,7 @@ export const NodeRoleCard = ({ paramId }: Props) => {
};
const activeSetProb = getActiveSetProbability(
nodeInfo.total_stake,
epochRewardsData.interval.stake_saturation_point
epochRewardsData.interval.stake_saturation_point,
);
return (
@@ -51,7 +51,7 @@ const NodeAndAddressSearch = () => {
}
} catch {
setErrorText(
"No node found with the provided Name, Node ID or Identity Key. Please check your input and try again."
"No node found with the provided Name, Node ID or Identity Key. Please check your input and try again.",
);
setIsLoading(false); // Stop loading
@@ -59,7 +59,7 @@ const NodeAndAddressSearch = () => {
}
} else {
setErrorText(
"No node found with the provided Name, Node ID or Identity Key. Please check your input and try again."
"No node found with the provided Name, Node ID or Identity Key. Please check your input and try again.",
);
setIsLoading(false); // Stop loading
@@ -69,7 +69,7 @@ const NodeAndAddressSearch = () => {
// Check if it's a node identity key
if (nsApiNodes) {
const matchingNode = nsApiNodes.find(
(node: NS_NODE) => node.identity_key === inputValue
(node: NS_NODE) => node.identity_key === inputValue,
);
if (matchingNode) {
@@ -78,13 +78,13 @@ const NodeAndAddressSearch = () => {
}
}
setErrorText(
"No node found with the provided Name, Node ID or Identity Key. Please check your input and try again."
"No node found with the provided Name, Node ID or Identity Key. Please check your input and try again.",
);
setIsLoading(false);
}
} catch (error) {
setErrorText(
"No node found with the provided Name, Node ID or Identity Key. Please check your input and try again."
"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
@@ -93,7 +93,7 @@ const NodeAndAddressSearch = () => {
// Handle search input change
const handleSearchInputChange = (
event: React.ChangeEvent<HTMLInputElement>
event: React.ChangeEvent<HTMLInputElement>,
) => {
const value = event.target.value;
setInputValue(value);
@@ -106,7 +106,7 @@ const NodeAndAddressSearch = () => {
// Filter nodes by moniker if input is not empty
if (value.trim() !== "") {
const filteredNodes = nsApiNodes.filter((node: NS_NODE) =>
node.description.moniker?.toLowerCase().includes(value.toLowerCase())
node.description.moniker?.toLowerCase().includes(value.toLowerCase()),
);
setSearchOptions(filteredNodes);
} else {
@@ -117,7 +117,7 @@ const NodeAndAddressSearch = () => {
// Handle node selection from dropdown
const handleNodeSelect = (
event: React.SyntheticEvent,
value: string | NS_NODE | null
value: string | NS_NODE | null,
) => {
if (value && typeof value !== "string") {
setIsLoading(true); // Show loading spinner
@@ -12,6 +12,7 @@ import {
} from "@mui/material";
import { useCallback, useEffect, useMemo, useState } from "react";
import { colours } from "@/theme/colours";
import type { Delegation } from "@nymproject/contract-clients/Mixnet.types";
import { useQueryClient } from "@tanstack/react-query";
import { useLocalStorage } from "@uidotdev/usehooks";
@@ -37,8 +38,6 @@ import StakeActions from "./StakeActions";
import StakeModal from "./StakeModal";
import type { MappedNymNode, MappedNymNodes } from "./StakeTableWithAction";
import { fee } from "./schemas";
import { colours } from "@/theme/colours";
type DelegationWithNodeDetails = {
node: MappedNymNode | undefined;
@@ -11,7 +11,7 @@ import StakeTable from "./StakeTable";
// Utility function to calculate node saturation point
function getNodeSaturationPoint(
totalStake: number,
stakeSaturationPoint: string
stakeSaturationPoint: string,
): number {
const saturation = Number.parseFloat(stakeSaturationPoint);
@@ -26,18 +26,18 @@ function getNodeSaturationPoint(
// Map nodes with rewards data
const mappedNSApiNodes = (
nodes: NS_NODE[],
epochRewardsData: ExplorerData["currentEpochRewardsData"]
epochRewardsData: ExplorerData["currentEpochRewardsData"],
) =>
nodes
.map((node) => {
const nodeSaturationPoint = getNodeSaturationPoint(
+node.total_stake,
epochRewardsData.interval.stake_saturation_point
epochRewardsData.interval.stake_saturation_point,
);
const cleanMoniker = DOMPurify.sanitize(node.description.moniker).replace(
/&amp;/g,
"&"
"&",
);
return {
@@ -18,7 +18,7 @@ import RedeemRewardsModal from "../redeemRewards/RedeemRewardsModal";
const fetchDelegations = async (
address: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
nymClient: any
nymClient: any,
): Promise<Delegation[]> => {
const data = await nymClient.getDelegatorDelegations({ delegator: address });
return data.delegations;
@@ -83,7 +83,7 @@ const SubHeaderRowActions = () => {
const client = await SigningCosmWasmClient.connectWithSigner(
"https://rpc.nymtech.net/",
signer,
{ gasPrice }
{ gasPrice },
);
const messages = delegations.map((delegation: Delegation) => ({
@@ -100,7 +100,7 @@ const SubHeaderRowActions = () => {
address,
messages,
"auto",
"Redeeming all rewards"
"Redeeming all rewards",
);
// Success state
setIsLoading(false);
@@ -19,11 +19,11 @@ const NodeFilterButtonGroup = ({
options: Options;
onPage: string;
onFilterChange: (
filter: "all" | "mixnodes" | "gateways" | "recommended"
filter: "all" | "mixnodes" | "gateways" | "recommended",
) => void;
}) => {
const handleClick = (
value: "all" | "mixnodes" | "gateways" | "recommended"
value: "all" | "mixnodes" | "gateways" | "recommended",
) => {
if (onPage === value) return;
onFilterChange(value);
@@ -1,8 +1,15 @@
"use client";
import { fetchWorldMapCountries } from "@/app/api";
import AddIcon from "@mui/icons-material/Add";
import RemoveIcon from "@mui/icons-material/Remove";
import RestartAltIcon from "@mui/icons-material/RestartAlt";
import { Box, IconButton, Skeleton, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useQuery } from "@tanstack/react-query";
import { scaleLinear } from "d3-scale";
import * as React from "react";
import Image from "next/image";
import * as React from "react";
import {
ComposableMap,
Geographies,
@@ -10,13 +17,6 @@ import {
ZoomableGroup,
} from "react-simple-maps";
import { Tooltip } from "react-tooltip";
import { fetchWorldMapCountries } from "@/app/api";
import AddIcon from "@mui/icons-material/Add";
import RemoveIcon from "@mui/icons-material/Remove";
import RestartAltIcon from "@mui/icons-material/RestartAlt";
import { IconButton, Skeleton, Typography, Box } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useQuery } from "@tanstack/react-query";
import type { CountryDataResponse } from "../../app/api/types";
import MAP_TOPOJSON from "../../assets/world-110m.json";
import ExplorerCard from "../cards/ExplorerCard";
@@ -66,7 +66,7 @@ export const WorldMap = (): JSX.Element => {
const colorScale = React.useMemo(() => {
if (countries) {
const heighestNumberOfNodes = Math.max(
...Object.values(countries).map((country) => country.nodes)
...Object.values(countries).map((country) => country.nodes),
);
return scaleLinear<string, string>()
.domain([
@@ -91,7 +91,7 @@ export const WorldMap = (): JSX.Element => {
"#147A3D", // Medium green
"#1A994C", // Light green
theme.palette.accent.main,
]
],
)
.unknown(isDarkMode ? theme.palette.pine[950] : theme.palette.pine[25]);
}
+1 -1
View File
@@ -1 +1 @@
declare module 'react-tooltip';
declare module "react-tooltip";
@@ -1,5 +1,6 @@
import * as React from 'react';
import { createNymMixnetClient, IWebWorkerEvents, MimeTypes, NymClientConfig, NymMixnetClient } from '@nymproject/sdk';
export interface BinaryMessageHeaders {
filename: string;
mimeType: string;
@@ -1,6 +1,6 @@
import * as React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";
import * as React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
const root = ReactDOM.createRoot(document.getElementById("app") as HTMLElement);
const root = ReactDOM.createRoot(document.getElementById('app') as HTMLElement);
root.render(<App />);
+173 -91
View File
@@ -1913,7 +1913,7 @@
dependencies:
"@floating-ui/utils" "^0.2.10"
"@floating-ui/dom@^1.6.10", "@floating-ui/dom@^1.7.4":
"@floating-ui/dom@^1.6.1", "@floating-ui/dom@^1.6.10", "@floating-ui/dom@^1.7.4":
version "1.7.4"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.4.tgz#ee667549998745c9c3e3e84683b909c31d6c9a77"
integrity sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==
@@ -6428,6 +6428,11 @@
resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2"
integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==
"@types/d3-color@^2":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-2.0.6.tgz#88a9a06afea2d3a400ea650a6c3e60e9bf9b0f2a"
integrity sha512-tbaFGDmJWHqnenvk3QGSvD3RVwr631BjKRD7Sc7VLRgrdX5mk5hTyoeBL6rXZaeoXzmZwIl1D2HPogEdt1rHBg==
"@types/d3-delaunay@^6.0.4":
version "6.0.4"
resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1"
@@ -6443,6 +6448,20 @@
resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-1.4.5.tgz#6392303c2ca3c287c3a1a2046455cd0a0bd50bbe"
integrity sha512-mLxrC1MSWupOSncXN/HOlWUAAIffAEBaI4+PKy2uMPsKe4FNZlk7qrbTjmzJXITQQqBHivaks4Td18azgqnotA==
"@types/d3-geo@^2":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-2.0.7.tgz#7ad48988e9af14e1e8490e25e926e5050992b397"
integrity sha512-RIXlxPdxvX+LAZFv+t78CuYpxYag4zuw9mZc+AwfB8tZpKU90rMEn2il2ADncmeZlb7nER9dDsJpRisA3lRvjA==
dependencies:
"@types/geojson" "*"
"@types/d3-interpolate@^2":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-2.0.5.tgz#063a3b81cda9f74660cc8ac4533854d5b45ff12f"
integrity sha512-UINE41RDaUMbulp+bxQMDnhOi51rh5lA2dG+dWZU0UY/IwQiG/u2x8TfnWYU9+xwGdXsJoAvrBYUEQl0r91atg==
dependencies:
"@types/d3-color" "^2"
"@types/d3-interpolate@^3.0.1":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c"
@@ -6467,6 +6486,11 @@
dependencies:
"@types/d3-time" "*"
"@types/d3-selection@^2":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-2.0.5.tgz#360d92460947b272362d3be340e494f2e55c6171"
integrity sha512-71BorcY0yXl12S7lvb01JdaN9TpeUHBDb4RRhSq8U8BEkX/nIk5p7Byho+ZRTsx5nYLMpAbY3qt5EhqFzfGJlw==
"@types/d3-shape@^3.1.0", "@types/d3-shape@^3.1.6":
version "3.1.7"
resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.7.tgz#2b7b423dc2dfe69c8c93596e673e37443348c555"
@@ -6499,6 +6523,14 @@
resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70"
integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==
"@types/d3-zoom@^2":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-2.0.7.tgz#bd0c7376c53f90be28340507f519bc91a4736120"
integrity sha512-JWke4E8ZyrKUQ68ESTWSK16fVb0OYnaiJ+WXJRYxKLn4aXU0o4CLYxMWBEiouUfO3TTCoyroOrGPcBG6u1aAxA==
dependencies:
"@types/d3-interpolate" "^2"
"@types/d3-selection" "^2"
"@types/debug@^4.0.0":
version "4.1.12"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917"
@@ -6590,6 +6622,11 @@
resolved "https://registry.yarnpkg.com/@types/flat/-/flat-5.0.5.tgz#2304df0b2b1e6dde50d81f029593e0a1bc2474d3"
integrity sha512-nPLljZQKSnac53KDUDzuzdRfGI0TDb5qPrb+SrQyN3MtdQrOnGsKniHN1iYZsJEBIVQve94Y6gNz22sgISZq+Q==
"@types/geojson@*":
version "7946.0.16"
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a"
integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==
"@types/glob@*":
version "9.0.0"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-9.0.0.tgz#7b942fafe09c55671912b34f04e8e4676faf32b1"
@@ -6852,6 +6889,16 @@
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.7.tgz#b89ddf2cd83b4feafcc4e2ea41afdfb95a0d194f"
integrity sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==
"@types/react-simple-maps@^3.0.6":
version "3.0.6"
resolved "https://registry.yarnpkg.com/@types/react-simple-maps/-/react-simple-maps-3.0.6.tgz#96728a17d3808cc17072db687b083872dc24fb26"
integrity sha512-hR01RXt6VvsE41FxDd+Bqm1PPGdKbYjCYVtCgh38YeBPt46z3SwmWPWu2L3EdCAP6bd6VYEgztucihRw1C0Klg==
dependencies:
"@types/d3-geo" "^2"
"@types/d3-zoom" "^2"
"@types/geojson" "*"
"@types/react" "*"
"@types/react-transition-group@^4.4.10", "@types/react-transition-group@^4.4.11", "@types/react-transition-group@^4.4.12":
version "4.4.12"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044"
@@ -7971,11 +8018,6 @@ ansi-html@^0.0.9:
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.9.tgz#6512d02342ae2cc68131952644a129cb734cd3f0"
integrity sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
@@ -7993,7 +8035,7 @@ ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
ansi-styles@^4.0.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
@@ -9246,38 +9288,10 @@ chain-registry@^1.69.64:
dependencies:
"@chain-registry/types" "^0.50.207"
chalk@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@4.1.0, chalk@5.3.0, chalk@^2.4.1, chalk@^2.4.2, chalk@^3.0.0, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2:
version "5.3.0"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
char-regex@^1.0.2:
version "1.0.2"
@@ -9418,6 +9432,11 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@^2.3.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
cldr-compact-number@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cldr-compact-number/-/cldr-compact-number-0.4.0.tgz#d74bb8cedab92ca63032423a1bd26aaaf76b9b2c"
@@ -9570,26 +9589,14 @@ collection-visit@^1.0.0:
map-visit "^1.0.0"
object-visit "^1.0.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-convert@^2.0.1:
color-convert@2.0.1, color-convert@^1.9.0, color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
color-name@^1.0.0, color-name@~1.1.4:
color-name@1.1.4, color-name@^1.0.0, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
@@ -9660,7 +9667,7 @@ comma-separated-tokens@^2.0.0:
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
commander@^2.19.0, commander@^2.20.0:
commander@2, commander@^2.19.0, commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -10334,7 +10341,7 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.2.tgz#673b5f233bf34d8e602b949429f8171d9121bea3"
integrity sha512-0sVXIohTfLqVIW3kb/0n6IiWF3Ifj5nm2XaSrLq2DI6fKIGa2fYAZdk917rUneaeLVpYfFcyXE2ft0fe3remsA==
d3-array@2:
d3-array@2, d3-array@^2.5.0:
version "2.12.1"
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.12.1.tgz#e20b41aafcdffdf5d50928004ececf815a465e81"
integrity sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==
@@ -10348,6 +10355,11 @@ d3-array@2:
dependencies:
internmap "1 - 2"
"d3-color@1 - 2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e"
integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==
"d3-color@1 - 3", d3-color@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
@@ -10360,6 +10372,24 @@ d3-delaunay@^6.0.4:
dependencies:
delaunator "5"
"d3-dispatch@1 - 2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-2.0.0.tgz#8a18e16f76dd3fcaef42163c97b926aa9b55e7cf"
integrity sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==
d3-drag@2:
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-2.0.0.tgz#9eaf046ce9ed1c25c88661911c1d5a4d8eb7ea6d"
integrity sha512-g9y9WbMnF5uqB9qKqwIIa/921RYWzlUDv9Jl1/yONQwxbOfszAWTCm8u7HOTgJgRDXiRZN56cHT9pd24dmXs8w==
dependencies:
d3-dispatch "1 - 2"
d3-selection "2"
"d3-ease@1 - 2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-2.0.0.tgz#fd1762bfca00dae4bacea504b1d628ff290ac563"
integrity sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==
d3-ease@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
@@ -10375,6 +10405,20 @@ d3-format@^1.4.4:
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4"
integrity sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==
d3-geo@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-2.0.2.tgz#c065c1b71fe8c5f1be657e5f43d9bdd010383c40"
integrity sha512-8pM1WGMLGFuhq9S+FpPURxic+gKzjluCD/CHTuUF3mXMeiCo0i6R0tO1s4+GArRFde96SLcW/kOFRjoAosPsFA==
dependencies:
d3-array "^2.5.0"
"d3-interpolate@1 - 2":
version "2.0.1"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163"
integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ==
dependencies:
d3-color "1 - 2"
"d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
@@ -10406,6 +10450,11 @@ d3-scale@^4.0.2:
d3-time "2.1.1 - 3"
d3-time-format "2 - 4"
d3-selection@2, d3-selection@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-2.0.0.tgz#94a11638ea2141b7565f883780dabc7ef6a61066"
integrity sha512-XoGGqhLUN/W14NmaqcO/bb1nqjDAw5WtSYb2X8wiuQWvSZUsUVYsOSkOybUrNvcBjaywBdYPy03eXHMXjk9nZA==
d3-shape@^3.1.0, d3-shape@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
@@ -10446,11 +10495,38 @@ d3-time@^1.0.11:
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1"
integrity sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==
"d3-timer@1 - 2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-2.0.0.tgz#055edb1d170cfe31ab2da8968deee940b56623e6"
integrity sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==
d3-timer@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
d3-transition@2:
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-2.0.0.tgz#366ef70c22ef88d1e34105f507516991a291c94c"
integrity sha512-42ltAGgJesfQE3u9LuuBHNbGrI/AJjNL2OAUdclE70UE6Vy239GCBEYD38uBPoLeNsOhFStGpPI0BAOV+HMxog==
dependencies:
d3-color "1 - 2"
d3-dispatch "1 - 2"
d3-ease "1 - 2"
d3-interpolate "1 - 2"
d3-timer "1 - 2"
d3-zoom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-2.0.0.tgz#f04d0afd05518becce879d04709c47ecd93fba54"
integrity sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==
dependencies:
d3-dispatch "1 - 2"
d3-drag "2"
d3-interpolate "1 - 2"
d3-selection "2"
d3-transition "2"
damerau-levenshtein@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
@@ -11245,7 +11321,7 @@ errno@^0.1.3, errno@~0.1.7:
dependencies:
prr "~1.0.1"
error-ex@^1.2.0, error-ex@^1.3.1:
error-ex@1.3.2, error-ex@^1.2.0, error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
@@ -13011,16 +13087,18 @@ hard-rejection@^2.1.0:
resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==
has-ansi@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-5.0.1.tgz#eb5ce8db3465c66e0b83c7f01fd0c1ea87687071"
integrity sha512-Fp2IsZDnnyoJkKg22ZyQFvD7QRCcMTsLAtloKXyXWJ1joGLtItRU9Bv/k1o0tELL2NF3ZZBcycSKryZUM+Yl3g==
dependencies:
ansi-regex "^6.0.1"
has-bigints@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe"
integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
@@ -13932,12 +14010,12 @@ is-ci@^2.0.0:
dependencies:
ci-info "^2.0.0"
is-core-module@^2.13.0, is-core-module@^2.16.0, is-core-module@^2.16.1, is-core-module@^2.5.0, is-core-module@^2.8.1:
version "2.16.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
is-core-module@2.13.1, is-core-module@^2.13.0, is-core-module@^2.16.0, is-core-module@^2.16.1, is-core-module@^2.5.0, is-core-module@^2.8.1:
version "2.13.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
dependencies:
hasown "^2.0.2"
hasown "^2.0.0"
is-data-descriptor@^1.0.1:
version "1.0.1"
@@ -19138,6 +19216,16 @@ react-router@6.30.1:
dependencies:
"@remix-run/router" "1.23.0"
react-simple-maps@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-simple-maps/-/react-simple-maps-3.0.0.tgz#2349d884c9ba37b68695b9f5e1e7d9c2a826c00e"
integrity sha512-vKNFrvpPG8Vyfdjnz5Ne1N56rZlDfHXv5THNXOVZMqbX1rWZA48zQuYT03mx6PAKanqarJu/PDLgshIZAfHHqw==
dependencies:
d3-geo "^2.0.2"
d3-selection "^2.0.0"
d3-zoom "^2.0.0"
topojson-client "^3.1.0"
react-smooth@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.4.tgz#a5875f8bb61963ca61b819cedc569dc2453894b4"
@@ -19179,6 +19267,14 @@ react-stately@^3.32.2:
"@react-stately/tree" "^3.9.2"
"@react-types/shared" "^3.32.0"
react-tooltip@^5.28.1:
version "5.29.1"
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.29.1.tgz#ba0b80b04d66822590976d2a32f47544fd9feee2"
integrity sha512-rmJmEb/p99xWhwmVT7F7riLG08wwKykjHiMGbDPloNJk3tdI73oHsVOwzZ4SRjqMdd5/xwb/4nmz0RcoMfY7Bw==
dependencies:
"@floating-ui/dom" "^1.6.1"
classnames "^2.3.0"
react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
@@ -20834,24 +20930,10 @@ stringify-entities@^4.0.0:
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==
dependencies:
ansi-regex "^2.0.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1:
version "7.1.2"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba"
integrity sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==
strip-ansi@7.1.0, strip-ansi@^3.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1, strip-ansi@^7.0.1:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
dependencies:
ansi-regex "^6.0.1"
@@ -20972,13 +21054,6 @@ stylis@4.2.0:
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51"
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
supports-color@^7.0.0, supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -21397,6 +21472,13 @@ toidentifier@1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
topojson-client@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/topojson-client/-/topojson-client-3.1.0.tgz#22e8b1ed08a2b922feeb4af6f53b6ef09a467b99"
integrity sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==
dependencies:
commander "2"
toposort@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330"