From 9e4e8a9b2bdc1bd0a6281db2342d9cf019b4d6c4 Mon Sep 17 00:00:00 2001 From: Yana Matrosova <42305364+yanok87@users.noreply.github.com> Date: Wed, 12 Feb 2025 15:15:45 +0200 Subject: [PATCH] Yana/pending staking events (#5400) * Add pending events to stake table * Add additional tanstack refetch on user stake/unstake/redeem rewards * refactor repeated fetch functions * clean up fetching functions * refactor & clean up * Add epoch waiting message on epoch change delay * fine tune epoch change * clean up * refactor imports * Add transaction hash to successful redeem rewards InfoModal * fix epoch time check and mobile header * Fix Loading modal width on mobile * fix epoch logic * clean up * clean up logs * add waiting for epoch to start to landing page * clean up * Add refetch of all queries on epoch change * Finalise state change on epoch change * clean up * fix build * Fix NodesTable mobile view * Fix stake table mobile view * fix typo * Fix blog articles height * Add loading skeletons to landing page cards * clean up * Add skeletons to cards * Add skeletons, and loading/error refetch on wallet balnce * clean up * Add active stakers card * clean up * change NGM to mixnet * Add TVL to Tokenomics card * Add last total stake to Stake Card * clean up * Fix stake sorting function in Stake Table * Add wrap of identity key and address to Basic Info Card * Add counter to epoch time on staking page * clean up * update epoch labels * Add circular loading on Toggle Button * Update Toggle button loading functionality * Add skeletons to account cards * Add search functionality on Enter * clean up * DOMpurify node name and description * Add column with id and identity key, wrap names to 2 lines * Set width of column headers to 110px * fix pending events for delegations * Fix Stake button proppagation * Add full country name to tooltips * Take out connect wallet from mobile menu toggle * finetune epoch change intervals * Add error text to Magic Search * fix build * Add react-markdown for Blog articles * fix graph's width and Table column headings * fix Magic Search loading * Fix grid on account page * fix account card address width * Fix permanent loading spinner on ToggleButton * clean up URL's, fix copy address on the Basic Card * replace mintscan with ping, open tx link on new page * Take out toggle button if no node bonded by address * Set fixed column width on tables * Add not-found page to account, when no node bonded * Add full country name to tables and node profile card * clean up * Table fixes * Fix sorting in Delegations table Node page * clean up * Fix line chart view * refactor epoch progress bar * remove unused imports * remove tanstack delclaration module * create epoch data provider * remove logic from togglebutton component * use epoch provider in components * invalidateQueries should be awaited * tidy up QualityIndicatorsCard component formatting * fix infinite loop in epoch provider --------- Co-authored-by: Yana Co-authored-by: fmtabbara --- explorer-nextjs/package.json | 3 + explorer-nextjs/src/actions/getNymNodes.ts | 15 - .../account/[address]/not-found/page.tsx | 57 ++ .../app/(pages)/account/[address]/page.tsx | 77 +- .../src/app/(pages)/explorer/page.tsx | 7 +- .../src/app/(pages)/nym-node/[id]/page.tsx | 30 +- .../app/(pages)/nym-node/not-found/page.tsx | 14 - .../app/(pages)/onboarding/[slug]/page.tsx | 3 +- .../src/app/(pages)/stake/page.tsx | 10 +- explorer-nextjs/src/app/api/index.tsx | 408 +++++---- explorer-nextjs/src/app/api/types.ts | 73 +- explorer-nextjs/src/app/api/urls.ts | 24 +- explorer-nextjs/src/app/page.tsx | 46 +- .../AccountBalancesCard.tsx | 61 +- .../accountPageComponents/AccountInfoCard.tsx | 51 +- .../src/components/button/index.tsx | 2 +- .../components/cards/AccountBalancesTable.tsx | 2 +- .../src/components/cards/ExplorerHeroCard.tsx | 4 +- .../src/components/cards/Skeleton.tsx | 17 - .../components/countryFlag/CountryFlag.tsx | 2 +- .../src/components/epochtime/EpochTime.tsx | 75 +- .../src/components/header/DesktopHeader.tsx | 6 +- .../src/components/header/MobileHeader.tsx | 7 +- .../src/components/input/Input.tsx | 4 + .../CurrentEpochCard.tsx | 113 ++- .../NetworkStakeCard.tsx | 69 +- .../landingPageComponents/NoiseCard.tsx | 43 +- .../landingPageComponents/RewardsCard.tsx | 16 - .../StakersNumberCard.tsx | 50 + .../landingPageComponents/TokenomicsCard.tsx | 102 ++- .../src/components/lineChart/index.tsx | 6 +- .../src/components/loading/index.tsx | 2 +- .../src/components/modal/InfoModal.tsx | 4 +- .../src/components/modal/SimpleModal.tsx | 2 +- .../src/components/modal/index.tsx | 2 +- .../src/components/muiLink/index.tsx | 2 +- .../src/components/nodeTable/NodeTable.tsx | 124 ++- .../nodeTable/NodeTableWithAction.tsx | 64 +- .../nymNodePageComponents/BasicInfoCard.tsx | 61 +- .../DelegationsTable.tsx | 7 +- .../NodeDelegationsCard.tsx | 56 +- .../nymNodePageComponents/NodeMetricsCard.tsx | 57 +- .../nymNodePageComponents/NodeProfileCard.tsx | 67 +- .../nymNodePageComponents/NodeRewardsCard.tsx | 56 +- .../QualityIndicatorsCard.tsx | 82 +- .../components/price/UpDownPriceIndicator.tsx | 2 +- .../progressBars/EpochProgressBar.tsx | 70 +- .../redeemRewards/RedeemRewardsModal.tsx | 4 +- .../search/NodeAndAddressSearch.tsx | 31 +- .../components/staking/OriginalStakeCard.tsx | 35 +- .../src/components/staking/OverviewCards.tsx | 2 +- .../src/components/staking/StakeModal.tsx | 4 +- .../src/components/staking/StakeTable.tsx | 315 +++++-- .../staking/StakeTableWithAction.tsx | 68 +- .../src/components/staking/SubHeaderRow.tsx | 2 +- .../staking/SubHeaderRowActions.tsx | 69 +- .../components/staking/TotalRewardsCard.tsx | 35 +- .../src/components/staking/TotalStakeCard.tsx | 38 +- .../src/components/staking/schemas.ts | 3 +- .../src/components/wallet/ConnectWallet.tsx | 2 +- .../src/components/wallet/WalletAddress.tsx | 2 +- .../src/components/wallet/WalletBalance.tsx | 28 +- explorer-nextjs/src/data/blog-template-1.json | 4 +- .../src/hooks/useGetPendingEvents.tsx | 53 ++ .../src/hooks/useGetWalletBalance.tsx | 70 +- explorer-nextjs/src/hooks/useNymClient.ts | 2 +- .../src/providers/EpochProvider.tsx | 109 +++ .../src/providers/ThemeProvider.tsx | 2 +- explorer-nextjs/src/providers/index.tsx | 9 +- explorer-nextjs/src/theme/typography.ts | 2 +- explorer-nextjs/src/typings/react-query.d.ts | 1 - yarn.lock | 861 +++++++++++++++++- 72 files changed, 2582 insertions(+), 1224 deletions(-) delete mode 100644 explorer-nextjs/src/actions/getNymNodes.ts create mode 100644 explorer-nextjs/src/app/(pages)/account/[address]/not-found/page.tsx delete mode 100644 explorer-nextjs/src/app/(pages)/nym-node/not-found/page.tsx delete mode 100644 explorer-nextjs/src/components/cards/Skeleton.tsx delete mode 100644 explorer-nextjs/src/components/landingPageComponents/RewardsCard.tsx create mode 100644 explorer-nextjs/src/components/landingPageComponents/StakersNumberCard.tsx create mode 100644 explorer-nextjs/src/hooks/useGetPendingEvents.tsx create mode 100644 explorer-nextjs/src/providers/EpochProvider.tsx delete mode 100644 explorer-nextjs/src/typings/react-query.d.ts diff --git a/explorer-nextjs/package.json b/explorer-nextjs/package.json index 1f7e87cb56..107c0f02a7 100644 --- a/explorer-nextjs/package.json +++ b/explorer-nextjs/package.json @@ -28,11 +28,13 @@ "@nymproject/react": "1.0.0", "@tanstack/react-query": "^5.64.2", "@tanstack/react-query-devtools": "^5.64.2", + "@tanstack/react-query-next-experimental": "^5.66.0", "@tanstack/react-table": "^8.20.6", "@uidotdev/usehooks": "^2.4.1", "chain-registry": "^1.69.64", "cldr-compact-number": "^0.4.0", "date-fns": "^4.1.0", + "isomorphic-dompurify": "^2.21.0", "material-react-table": "^3.0.3", "next": "15.0.3", "qrcode.react": "^4.1.0", @@ -45,6 +47,7 @@ "@mui/x-date-pickers": "7.1.1", "@mui/x-data-grid": "7.1.1", "@mui/x-charts": "^7.22.3", + "react-markdown": "^9.0.3", "react-random-avatars": "^1.3.1", "react-world-flags": "^1.6.0", "zod": "^3.24.1" diff --git a/explorer-nextjs/src/actions/getNymNodes.ts b/explorer-nextjs/src/actions/getNymNodes.ts deleted file mode 100644 index 70d214c683..0000000000 --- a/explorer-nextjs/src/actions/getNymNodes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { IObservatoryNode } from "@/app/api/types"; -import { DATA_OBSERVATORY_NODES_URL } from "@/app/api/urls"; - -const getNymNodes = async (): Promise => { - const response = await fetch(`${DATA_OBSERVATORY_NODES_URL}`, { - next: { - revalidate: 900, - }, - }); - const data = await response.json(); - - return data; -}; - -export default getNymNodes; diff --git a/explorer-nextjs/src/app/(pages)/account/[address]/not-found/page.tsx b/explorer-nextjs/src/app/(pages)/account/[address]/not-found/page.tsx new file mode 100644 index 0000000000..77f5e425aa --- /dev/null +++ b/explorer-nextjs/src/app/(pages)/account/[address]/not-found/page.tsx @@ -0,0 +1,57 @@ +import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; +import { ContentLayout } from "@/components/contentLayout/ContentLayout"; +import SectionHeading from "@/components/headings/SectionHeading"; +import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton"; +import { Box, Typography } from "@mui/material"; +import Grid from "@mui/material/Grid2"; + +export default async function Account({ + params, +}: { + params: Promise<{ address: string }>; +}) { + try { + const address = (await params).address; + + return ( + + + + + + + + + + + Is this your accont? Set up your node! + + + + + + + + + ); + } catch (error) { + let errorMessage = "An error occurred"; + if (error instanceof Error) { + errorMessage = error.message; + } + throw new Error(errorMessage); + } +} diff --git a/explorer-nextjs/src/app/(pages)/account/[address]/page.tsx b/explorer-nextjs/src/app/(pages)/account/[address]/page.tsx index 3f9b94d023..e3a1525c21 100644 --- a/explorer-nextjs/src/app/(pages)/account/[address]/page.tsx +++ b/explorer-nextjs/src/app/(pages)/account/[address]/page.tsx @@ -1,14 +1,12 @@ -import type { IAccountBalancesInfo, NymTokenomics } from "@/app/api/types"; -import type NodeData from "@/app/api/types"; -import { NYM_ACCOUNT_ADDRESS, NYM_NODES, NYM_PRICES_API } from "@/app/api/urls"; -import { AccountBalancesCard } from "@/components/accountPageComponents/AccountBalancesCard"; -import { AccountInfoCard } from "@/components/accountPageComponents/AccountInfoCard"; -import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; -import { ContentLayout } from "@/components/contentLayout/ContentLayout"; -import SectionHeading from "@/components/headings/SectionHeading"; -import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton"; +import { fetchNodes } from "@/app/api"; +import type { NodeData } from "@/app/api/types"; import { Box, Typography } from "@mui/material"; import Grid from "@mui/material/Grid2"; +import { AccountBalancesCard } from "../../../../components/accountPageComponents/AccountBalancesCard"; +import { AccountInfoCard } from "../../../../components/accountPageComponents/AccountInfoCard"; +import { ContentLayout } from "../../../../components/contentLayout/ContentLayout"; +import SectionHeading from "../../../../components/headings/SectionHeading"; +import ExplorerButtonGroup from "../../../../components/toggleButton/ToggleButton"; export default async function Account({ params, @@ -16,56 +14,21 @@ export default async function Account({ params: Promise<{ address: string }>; }) { try { - const { address } = await params; + const address = (await params).address; - const accountData = await fetch(`${NYM_ACCOUNT_ADDRESS}${address}`, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - next: { revalidate: 60 }, - // refresh event list cache at given interval - }); - - const response = await fetch(NYM_NODES, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - next: { revalidate: 60 }, - // refresh event list cache at given interval - }); - - const nymNodes: NodeData[] = await response.json(); + const nymNodes: NodeData[] = await fetchNodes(); const nymNode = nymNodes.find( (node) => node.bond_information.owner === address, ); - const nymAccountBalancesData: IAccountBalancesInfo = - await accountData.json(); - - if (!nymAccountBalancesData) { - return Account not found; - } - - const nymPrice = await fetch(NYM_PRICES_API, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - next: { revalidate: 60 }, - // refresh event list cache at given interval - }); - - const nymPriceData: NymTokenomics = await nymPrice.json(); - return ( + - - + + + - - + + - - - - - - ); } catch (error) { diff --git a/explorer-nextjs/src/app/(pages)/explorer/page.tsx b/explorer-nextjs/src/app/(pages)/explorer/page.tsx index 439dcc2e0d..e9ff897a24 100644 --- a/explorer-nextjs/src/app/(pages)/explorer/page.tsx +++ b/explorer-nextjs/src/app/(pages)/explorer/page.tsx @@ -1,20 +1,19 @@ import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; -import CardSkeleton from "@/components/cards/Skeleton"; import { ContentLayout } from "@/components/contentLayout/ContentLayout"; import SectionHeading from "@/components/headings/SectionHeading"; import NodeTableWithAction from "@/components/nodeTable/NodeTableWithAction"; import { Wrapper } from "@/components/wrapper"; +import { Box } from "@mui/material"; import Grid from "@mui/material/Grid2"; -import { Suspense } from "react"; export default function ExplorerPage() { return ( - }> + - + diff --git a/explorer-nextjs/src/app/(pages)/nym-node/[id]/page.tsx b/explorer-nextjs/src/app/(pages)/nym-node/[id]/page.tsx index 1c7893e371..1c0e6ac06a 100644 --- a/explorer-nextjs/src/app/(pages)/nym-node/[id]/page.tsx +++ b/explorer-nextjs/src/app/(pages)/nym-node/[id]/page.tsx @@ -1,6 +1,4 @@ -import type { IObservatoryNode } from "@/app/api/types"; -import { DATA_OBSERVATORY_NODES_URL } from "@/app/api/urls"; -import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; +import { fetchNodeInfo } from "@/app/api"; import { ContentLayout } from "@/components/contentLayout/ContentLayout"; import SectionHeading from "@/components/headings/SectionHeading"; import { BasicInfoCard } from "@/components/nymNodePageComponents/BasicInfoCard"; @@ -22,25 +20,7 @@ export default async function NymNode({ try { const id = Number((await params).id); - const observatoryResponse = await fetch(DATA_OBSERVATORY_NODES_URL, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - next: { revalidate: 60 }, - // refresh event list cache at given interval - }); - - const observatoryNymNodes: IObservatoryNode[] = - await observatoryResponse.json(); - - if (!observatoryNymNodes) { - return null; - } - - const observatoryNymNode = observatoryNymNodes.find( - (node) => node.node_id === id, - ); + const observatoryNymNode = await fetchNodeInfo(id); if (!observatoryNymNode) { return null; @@ -126,12 +106,6 @@ export default async function NymNode({ - - - - - - ); } catch (error) { diff --git a/explorer-nextjs/src/app/(pages)/nym-node/not-found/page.tsx b/explorer-nextjs/src/app/(pages)/nym-node/not-found/page.tsx deleted file mode 100644 index 819ef4143d..0000000000 --- a/explorer-nextjs/src/app/(pages)/nym-node/not-found/page.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { ContentLayout } from "@/components/contentLayout/ContentLayout"; -import SectionHeading from "@/components/headings/SectionHeading"; -import { Typography } from "@mui/material"; - -const NotFound = () => { - return ( - - - This Nym Node could not be found - - ); -}; - -export default NotFound; diff --git a/explorer-nextjs/src/app/(pages)/onboarding/[slug]/page.tsx b/explorer-nextjs/src/app/(pages)/onboarding/[slug]/page.tsx index c5d19791c4..43ccbf5048 100644 --- a/explorer-nextjs/src/app/(pages)/onboarding/[slug]/page.tsx +++ b/explorer-nextjs/src/app/(pages)/onboarding/[slug]/page.tsx @@ -9,6 +9,7 @@ import { Box, Stack, Typography } from "@mui/material"; import Grid from "@mui/material/Grid2"; import { format } from "date-fns"; import Image from "next/image"; +import Markdown from "react-markdown"; export default async function BlogPage({ params, @@ -104,7 +105,7 @@ export default async function BlogPage({ {section.text.map(({ text }) => ( - {text} + {text} ))} diff --git a/explorer-nextjs/src/app/(pages)/stake/page.tsx b/explorer-nextjs/src/app/(pages)/stake/page.tsx index be60428e4d..2d840e15dd 100644 --- a/explorer-nextjs/src/app/(pages)/stake/page.tsx +++ b/explorer-nextjs/src/app/(pages)/stake/page.tsx @@ -1,8 +1,8 @@ -import { ContentLayout } from "@/components/contentLayout/ContentLayout"; -import SectionHeading from "@/components/headings/SectionHeading"; -import OverviewCards from "@/components/staking/OverviewCards"; -import StakeTableWithAction from "@/components/staking/StakeTableWithAction"; -import SubHeaderRow from "@/components/staking/SubHeaderRow"; +import { ContentLayout } from "../../../components/contentLayout/ContentLayout"; +import SectionHeading from "../../../components/headings/SectionHeading"; +import OverviewCards from "../../../components/staking/OverviewCards"; +import StakeTableWithAction from "../../../components/staking/StakeTableWithAction"; +import SubHeaderRow from "../../../components/staking/SubHeaderRow"; export default async function StakingPage() { return ( diff --git a/explorer-nextjs/src/app/api/index.tsx b/explorer-nextjs/src/app/api/index.tsx index c33d8acdc3..72da24e86b 100644 --- a/explorer-nextjs/src/app/api/index.tsx +++ b/explorer-nextjs/src/app/api/index.tsx @@ -1,218 +1,38 @@ +import { addSeconds } from "date-fns"; /* eslint-disable @typescript-eslint/no-explicit-any */ +import type { + CurrentEpochData, + ExplorerData, + GatewayStatus, + IAccountBalancesInfo, + IObservatoryNode, + IPacketsAndStakingData, + NodeData, + NymTokenomics, + ObservatoryBalance, +} from "./types"; import { - CIRCULATING_NYM_SUPPLY, + CURRENT_EPOCH, CURRENT_EPOCH_REWARDS, + DATA_OBSERVATORY_BALANCES_URL, DATA_OBSERVATORY_NODES_URL, HARBOURMASTER_API_MIXNODES_STATS, - NYM_NODES_DESCRIBED, - NYM_NODE_DESCRIPTION, + NYM_ACCOUNT_ADDRESS, + NYM_NODES, + NYM_PRICES_API, + OBSERVATORY_GATEWAYS_URL, } from "./urls"; -type Denom = "unym" | "nym"; - -export interface IPacketsAndStakingData { - date_utc: string; - total_packets_received: number; - total_packets_sent: number; - total_packets_dropped: number; - total_stake: number; -} - -export interface CurrentEpochData { - id: number; - current_epoch_id: number; - current_epoch_start: string; - epoch_length: { secs: number; nanos: number }; - epochs_in_interval: number; - total_elapsed_epochs: number; -} -export interface ExplorerData { - circulatingNymSupplyData: { - circulating_supply: { denom: Denom; amount: string }; - mixmining_reserve: { denom: Denom; amount: string }; - total_supply: { denom: Denom; amount: string }; - vesting_tokens: { denom: Denom; amount: string }; - }; - nymNodesData: { - gateways: { - bonded: { count: number; last_updated_utc: string }; - blacklisted: { count: number; last_updated_utc: string }; - historical: { count: number; last_updated_utc: string }; - explorer: { count: number; last_updated_utc: string }; - }; - mixnodes: { - bonded: { - count: number; - active: number; - inactive: number; - reserve: number; - last_updated_utc: string; - }; - blacklisted: { - count: number; - last_updated_utc: string; - }; - historical: { count: number; last_updated_utc: string }; - }; - }; - packetsAndStakingData: IPacketsAndStakingData[]; - - currentEpochRewardsData: { - interval: { - reward_pool: string; - staking_supply: string; - staking_supply_scale_factor: string; - epoch_reward_budget: string; - stake_saturation_point: string; - active_set_work_factor: string; - interval_pool_emission: string; - sybil_resistance: string; - }; - rewarded_set: { - entry_gateways: number; - exit_gateways: number; - mixnodes: number; - standby: number; - }; - }; -} - -const CACHE_TIME_SECONDS = 60 * 5; // 5 minutes - -export interface ExplorerCache { - explorerCache?: { - data?: ExplorerData; - lastUpdated?: Date; - }; -} - -const getExplorerData = async () => { - // FETCH NYMNODES - const fetchNymNodes = await fetch(NYM_NODES_DESCRIBED, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - // refresh event list cache at given interval - next: { revalidate: Number(process.env.NEXT_PUBLIC_REVALIDATE_CACHE) }, - }); - - // FETCH CIRCULATING NYM SUPPLY - const fetchCirculatingNymSupply = await fetch(CIRCULATING_NYM_SUPPLY, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - // refresh event list cache at given interval - next: { revalidate: Number(process.env.NEXT_PUBLIC_REVALIDATE_CACHE) }, - }); - - // FETCH PACKETS AND STAKING - const fetchPacketsAndStaking = await fetch(HARBOURMASTER_API_MIXNODES_STATS, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - // refresh event list cache at given interval - next: { revalidate: Number(process.env.NEXT_PUBLIC_REVALIDATE_CACHE) }, - }); - - const fetchNymNodeDescription = await fetch(NYM_NODE_DESCRIPTION, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - // refresh event list cache at given interval - next: { revalidate: Number(process.env.NEXT_PUBLIC_REVALIDATE_CACHE) }, - }); - - const [ - circulatingNymSupplyRes, - nymNodesRes, - packetsAndStakingRes, - nymNodeDescriptionRes, - ] = await Promise.all([ - fetchCirculatingNymSupply, - fetchNymNodes, - fetchPacketsAndStaking, - fetchNymNodeDescription, - ]); - - const [ - circulatingNymSupplyData, - nymNodesData, - packetsAndStakingData, - nymNodeDescriptionData, - ] = await Promise.all([ - circulatingNymSupplyRes.json(), - nymNodesRes.json(), - packetsAndStakingRes.json(), - nymNodeDescriptionRes.json(), - ]); - - return [ - circulatingNymSupplyData, - nymNodesData, - packetsAndStakingData, - nymNodeDescriptionData, - ]; -}; - -export async function ensureCacheExists() { - // makes sure the cache exists in global memory - let doUpdate = false; - const now = new Date(); - if (!(global as ExplorerCache).explorerCache) { - (global as any).explorerCache = {}; - doUpdate = true; - } - if ( - (global as ExplorerCache)?.explorerCache?.lastUpdated && - now.getDate() - (global as any).explorerCache.lastUpdated.getDate() > - CACHE_TIME_SECONDS - ) { - doUpdate = true; - } - - // if the cache has expired or never existed, get it from API's - if (doUpdate) { - const [ - circulatingNymSupplyData, - nymNodesData, - packetsAndStakingData, - currentEpochData, - currentEpochRewardsData, - ] = await getExplorerData(); - - packetsAndStakingData.pop(); - - (global as any).explorerCache.data = { - circulatingNymSupplyData, - nymNodesData, - packetsAndStakingData, - currentEpochData, - currentEpochRewardsData, - }; - (global as any).explorerCache.lastUpdated = now; - } -} - -export async function getCacheExplorerData() { - await ensureCacheExists(); - - if (!(global as ExplorerCache).explorerCache?.data) { - return null; - } - - return (global as ExplorerCache)?.explorerCache?.data || null; -} - -export const fetchEpochRewards = async () => { +// Fetch function for epoch rewards +export const fetchEpochRewards = async (): Promise< + ExplorerData["currentEpochRewardsData"] +> => { const response = await fetch(CURRENT_EPOCH_REWARDS, { headers: { Accept: "application/json", "Content-Type": "application/json; charset=utf-8", }, + cache: "no-store", // Ensures fresh data on every request }); if (!response.ok) { @@ -222,7 +42,22 @@ export const fetchEpochRewards = async () => { return response.json(); }; -export const fetchObservatoryNodes = async () => { +// Fetch gateway status based on identity key +export const fetchGatewayStatus = async ( + identityKey: string, +): Promise => { + const response = await fetch(`${OBSERVATORY_GATEWAYS_URL}/${identityKey}`); + + if (!response.ok) { + throw new Error("Failed to fetch gateway status"); + } + + return response.json(); +}; + +export const fetchNodeInfo = async ( + id: number, +): Promise => { const response = await fetch(DATA_OBSERVATORY_NODES_URL, { headers: { Accept: "application/json", @@ -234,7 +69,8 @@ export const fetchObservatoryNodes = async () => { throw new Error("Failed to fetch observatory nodes"); } - return response.json(); + const nodes: IObservatoryNode[] = await response.json(); + return nodes.find((node) => node.node_id === id); }; export const fetchNodeDelegations = async (id: number) => { @@ -254,3 +90,165 @@ export const fetchNodeDelegations = async (id: number) => { return response.json(); }; + +export const fetchCurrentEpoch = async () => { + const response = await fetch(CURRENT_EPOCH, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + cache: "no-store", // Ensures fresh data on every request + }); + + if (!response.ok) { + throw new Error("Failed to fetch current epoch data"); + } + + const data: CurrentEpochData = await response.json(); + const epochEndTime = addSeconds( + new Date(data.current_epoch_start), + data.epoch_length.secs, + ).toISOString(); + + return { ...data, current_epoch_end: epochEndTime }; +}; + +// Fetch balances based on the address +export const fetchBalances = async (address: string): Promise => { + const response = await fetch(`${DATA_OBSERVATORY_BALANCES_URL}/${address}`, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch balances"); + } + + const balances: ObservatoryBalance = await response.json(); + + // Calculate total stake + return ( + Number(balances.rewards.staking_rewards.amount) + + Number(balances.delegated.amount) + ); +}; + +// Fetch function to get total staker rewards +export const fetchTotalStakerRewards = async ( + address: string, +): Promise => { + const response = await fetch(`${DATA_OBSERVATORY_BALANCES_URL}/${address}`, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch balances"); + } + + const balances: ObservatoryBalance = await response.json(); + + // Return the staking rewards amount + return Number(balances.rewards.staking_rewards.amount); +}; + +// Fetch function to get the original stake +export const fetchOriginalStake = async (address: string): Promise => { + const response = await fetch(`${DATA_OBSERVATORY_BALANCES_URL}/${address}`, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch balances"); + } + + const balances: ObservatoryBalance = await response.json(); + + // Return the delegated amount + return Number(balances.delegated.amount); +}; + +export const fetchNoise = async (): Promise => { + const response = await fetch(HARBOURMASTER_API_MIXNODES_STATS, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + + const data: IPacketsAndStakingData[] = await response.json(); + return data; +}; + +// Fetch Account Balance +export const fetchAccountBalance = async ( + address: string, +): Promise => { + const res = await fetch(`${NYM_ACCOUNT_ADDRESS}/${address}`, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + if (!res.ok) { + throw new Error("Failed to fetch account balance error from api"); + } + + const data: IAccountBalancesInfo = await res.json(); + return data; +}; + +// 🔹 Fetch Nodes +export const fetchNodes = async (): Promise => { + const res = await fetch(NYM_NODES, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + if (!res.ok) { + throw new Error("Failed to fetch nodes"); + } + const data: NodeData[] = await res.json(); + return data; +}; + +export const fetchObservatoryNodes = async (): Promise< + IObservatoryNode[] | null +> => { + const response = await fetch(DATA_OBSERVATORY_NODES_URL, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + + if (!response.ok) { + throw new Error("Failed to fetch observatory nodes"); + } + + const nodes: IObservatoryNode[] = await response.json(); + return nodes; +}; + +// 🔹 Fetch NYM Price +export const fetchNymPrice = async (): Promise => { + const res = await fetch(NYM_PRICES_API, { + headers: { + Accept: "application/json", + "Content-Type": "application/json; charset=utf-8", + }, + }); + if (!res.ok) { + throw new Error("Failed to fetch NYM price"); + } + const data: NymTokenomics = await res.json(); + return data; +}; diff --git a/explorer-nextjs/src/app/api/types.ts b/explorer-nextjs/src/app/api/types.ts index 2abd88a41b..bc306f1a6e 100644 --- a/explorer-nextjs/src/app/api/types.ts +++ b/explorer-nextjs/src/app/api/types.ts @@ -2,6 +2,75 @@ export type API_RESPONSE = { data: T[]; }; +export type Denom = "unym" | "nym"; + +export interface IPacketsAndStakingData { + date_utc: string; + total_packets_received: number; + total_packets_sent: number; + total_packets_dropped: number; + total_stake: number; +} + +export interface CurrentEpochData { + id: number; + current_epoch_id: number; + current_epoch_start: string; + epoch_length: { secs: number; nanos: number }; + epochs_in_interval: number; + total_elapsed_epochs: number; +} +export interface ExplorerData { + circulatingNymSupplyData: { + circulating_supply: { denom: Denom; amount: string }; + mixmining_reserve: { denom: Denom; amount: string }; + total_supply: { denom: Denom; amount: string }; + vesting_tokens: { denom: Denom; amount: string }; + }; + nymNodesData: { + gateways: { + bonded: { count: number; last_updated_utc: string }; + blacklisted: { count: number; last_updated_utc: string }; + historical: { count: number; last_updated_utc: string }; + explorer: { count: number; last_updated_utc: string }; + }; + mixnodes: { + bonded: { + count: number; + active: number; + inactive: number; + reserve: number; + last_updated_utc: string; + }; + blacklisted: { + count: number; + last_updated_utc: string; + }; + historical: { count: number; last_updated_utc: string }; + }; + }; + packetsAndStakingData: IPacketsAndStakingData[]; + + currentEpochRewardsData: { + interval: { + reward_pool: string; + staking_supply: string; + staking_supply_scale_factor: string; + epoch_reward_budget: string; + stake_saturation_point: string; + active_set_work_factor: string; + interval_pool_emission: string; + sybil_resistance: string; + }; + rewarded_set: { + entry_gateways: number; + exit_gateways: number; + mixnodes: number; + standby: number; + }; + }; +} + export type NodeDescription = { last_polled: string; host_information: { @@ -96,7 +165,7 @@ export type Location = { longitude?: number; }; -type NodeData = { +export type NodeData = { node_id: number; contract_node_type: string; description: NodeDescription; @@ -105,8 +174,6 @@ type NodeData = { location: Location; }; -export default NodeData; - // ACCOUNT BALANCES export interface IRewardDetails { diff --git a/explorer-nextjs/src/app/api/urls.ts b/explorer-nextjs/src/app/api/urls.ts index a4f4676772..aa0862c9d5 100644 --- a/explorer-nextjs/src/app/api/urls.ts +++ b/explorer-nextjs/src/app/api/urls.ts @@ -1,33 +1,15 @@ -export const HARBOURMASTER_API_SUMMARY = - "https://harbourmaster.nymtech.net/v2/summary"; -export const NYM_NODES_DESCRIBED = - "https://validator.nymtech.net/api/v1/nym-nodes/described"; -export const BONDED_NODES = - "https://validator.nymtech.net/api/v1/nym-nodes/bonded"; export const NYM_NODES = "https://explorer.nymtech.net/api/v1/tmp/unstable/nym-nodes"; -export const EXPLORER_API = "https://explorer.nymtech.net/api/v1/countries"; -export const VALIDATOR_API_SUPPLY = - "https://validator.nymtech.net/api/v1/circulating-supply"; -export const COSMOS_API = - "https://api.nymtech.net/cosmos/bank/v1beta1/balances/n1a53udazy8ayufvy0s434pfwjcedzqv34yg485t"; -export const VALIDATOR_API_EPOCH = - "https://validator.nymtech.net/api/v1/epoch/reward_params"; export const HARBOURMASTER_API_MIXNODES_STATS = "https://harbourmaster.nymtech.net/v2/mixnodes/stats"; -export const HARBOURMASTER_API_BASE = "https://harbourmaster.nymtech.net"; export const CURRENT_EPOCH = - " https://validator.nymtech.net/api/v1/epoch/current"; + "https://validator.nymtech.net/api/v1/epoch/current"; export const CURRENT_EPOCH_REWARDS = "https://validator.nymtech.net/api/v1/epoch/reward_params"; -export const CIRCULATING_NYM_SUPPLY = - "https://validator.nymtech.net/api/v1/circulating-supply"; -export const NYM_NODE_DESCRIPTION = - "https://validator.nymtech.net/api/v1/nym-nodes/described"; 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/"; + "https://explorer.nymtech.net/api/v1/tmp/unstable/account"; export const NYM_PRICES_API = "https://api.nym.spectredao.net/api/v1/nym-price"; export const VALIDATOR_BASE_URL = process.env.NEXT_PUBLIC_VALIDATOR_URL || "https://rpc.nymtech.net"; @@ -37,3 +19,5 @@ export const DATA_OBSERVATORY_DELEGATIONS_URL = "https://api.nym.spectredao.net/api/v1/delegations"; 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"; diff --git a/explorer-nextjs/src/app/page.tsx b/explorer-nextjs/src/app/page.tsx index e621804faa..db2467012e 100644 --- a/explorer-nextjs/src/app/page.tsx +++ b/explorer-nextjs/src/app/page.tsx @@ -1,17 +1,15 @@ -import BlogArticlesCards from "@/components/blogs/BlogArticleCards"; -import CardSkeleton from "@/components/cards/Skeleton"; -import { ContentLayout } from "@/components/contentLayout/ContentLayout"; -import SectionHeading from "@/components/headings/SectionHeading"; -import { CurrentEpochCard } from "@/components/landingPageComponents/CurrentEpochCard"; -import { NetworkStakeCard } from "@/components/landingPageComponents/NetworkStakeCard"; -import { NoiseCard } from "@/components/landingPageComponents/NoiseCard"; -import { RewardsCard } from "@/components/landingPageComponents/RewardsCard"; -import { TokenomicsCard } from "@/components/landingPageComponents/TokenomicsCard"; -import NodeTable from "@/components/nodeTable/NodeTableWithAction"; -import NodeAndAddressSearch from "@/components/search/NodeAndAddressSearch"; import { Stack, Typography } from "@mui/material"; import Grid from "@mui/material/Grid2"; -import { Suspense } from "react"; +import BlogArticlesCards from "../components/blogs/BlogArticleCards"; +import { ContentLayout } from "../components/contentLayout/ContentLayout"; +import SectionHeading from "../components/headings/SectionHeading"; +import { CurrentEpochCard } from "../components/landingPageComponents/CurrentEpochCard"; +import { NetworkStakeCard } from "../components/landingPageComponents/NetworkStakeCard"; +import { NoiseCard } from "../components/landingPageComponents/NoiseCard"; +import { RewardsCard } from "../components/landingPageComponents/StakersNumberCard"; +import { TokenomicsCard } from "../components/landingPageComponents/TokenomicsCard"; +import NodeTable from "../components/nodeTable/NodeTableWithAction"; +import NodeAndAddressSearch from "../components/search/NodeAndAddressSearch"; export default async function Home() { return ( @@ -27,9 +25,7 @@ export default async function Home() { - }> - - + - }> - - + - }> - - + - }> - - + - }> - - + @@ -65,9 +53,7 @@ export default async function Home() { - }> - - + diff --git a/explorer-nextjs/src/components/accountPageComponents/AccountBalancesCard.tsx b/explorer-nextjs/src/components/accountPageComponents/AccountBalancesCard.tsx index 00665bce86..2ef7914241 100644 --- a/explorer-nextjs/src/components/accountPageComponents/AccountBalancesCard.tsx +++ b/explorer-nextjs/src/components/accountPageComponents/AccountBalancesCard.tsx @@ -1,5 +1,8 @@ "use client"; -import type { IAccountBalancesInfo, IRewardDetails } from "@/app/api/types"; +import { fetchAccountBalance, fetchNymPrice } from "@/app/api"; +import { Skeleton, Stack, Typography } from "@mui/material"; +import { useQuery } from "@tanstack/react-query"; +import type { IRewardDetails } from "../../app/api/types"; import { AccountBalancesTable } from "../cards/AccountBalancesTable"; import ExplorerCard from "../cards/ExplorerCard"; @@ -14,8 +17,7 @@ export interface IAccontStatsRowProps { } interface IAccountBalancesCardProps { - accountInfo: IAccountBalancesInfo; - nymPrice: number; + address: string; } const getNymsFormated = (unyms: number) => { @@ -46,16 +48,59 @@ const calculateStakingRewards = ( }; export const AccountBalancesCard = (props: IAccountBalancesCardProps) => { - const { accountInfo, nymPrice } = props; + const { address } = props; + + const { + data: accountInfo, + isLoading, + isError, + } = useQuery({ + queryKey: ["accountBalance", address], + queryFn: () => fetchAccountBalance(address), + enabled: !!address, + }); + + const { + data: nymPrice, + isLoading: isLoadingPrice, + error: priceError, + } = useQuery({ + queryKey: ["nymPrice"], + queryFn: fetchNymPrice, + }); + + if (isLoading || isLoadingPrice) { + return ( + + + + + + + ); + } + + if (isError || priceError || !accountInfo || !nymPrice) { + return ( + + + Failed to account data. + + + + ); + } + + const nymPriceData = nymPrice.quotes.USD.price; const totalBalanceUSD = getPriceInUSD( Number(accountInfo.total_value.amount), - nymPrice, + nymPriceData, ); const spendableNYM = getNymsFormated(Number(accountInfo.balances[0].amount)); const spendableUSD = getPriceInUSD( Number(accountInfo.balances[0].amount), - nymPrice, + nymPriceData, ); const spendableAllocation = getAllocation( Number(accountInfo.balances[0].amount), @@ -67,7 +112,7 @@ export const AccountBalancesCard = (props: IAccountBalancesCardProps) => { ); const delegationsUSD = getPriceInUSD( Number(accountInfo.total_delegations.amount), - nymPrice, + nymPriceData, ); const delegationsAllocation = getAllocation( Number(accountInfo.total_delegations.amount), @@ -79,7 +124,7 @@ export const AccountBalancesCard = (props: IAccountBalancesCardProps) => { ); const claimableUSD = getPriceInUSD( Number(accountInfo.claimable_rewards.amount), - nymPrice, + nymPriceData, ); const claimableAllocation = getAllocation( Number(accountInfo.claimable_rewards.amount), diff --git a/explorer-nextjs/src/components/accountPageComponents/AccountInfoCard.tsx b/explorer-nextjs/src/components/accountPageComponents/AccountInfoCard.tsx index 9b1fca9bb5..f2c3583848 100644 --- a/explorer-nextjs/src/components/accountPageComponents/AccountInfoCard.tsx +++ b/explorer-nextjs/src/components/accountPageComponents/AccountInfoCard.tsx @@ -1,19 +1,49 @@ "use client"; -import type { IAccountBalancesInfo } from "@/app/api/types"; -import { Box, Stack, Typography } from "@mui/material"; +import { fetchAccountBalance } from "@/app/api"; +import { Box, Skeleton, Stack, Typography } from "@mui/material"; +import { useQuery } from "@tanstack/react-query"; import ExplorerCard from "../cards/ExplorerCard"; import CopyToClipboard from "../copyToClipboard/CopyToClipboard"; import ExplorerListItem from "../list/ListItem"; import { CardQRCode } from "../qrCode/QrCode"; interface IAccountInfoCardProps { - accountInfo: IAccountBalancesInfo; + address: string; } export const AccountInfoCard = (props: IAccountInfoCardProps) => { - const { accountInfo } = props; + const { address } = props; - const balance = Number(accountInfo.balances[0].amount) / 1000000; + const { data, isLoading, isError } = useQuery({ + queryKey: ["accountBalance", address], + queryFn: () => fetchAccountBalance(address), + enabled: !!address, + }); + + if (isLoading) { + return ( + + + + + + + + ); + } + + if (isError || !data) { + return ( + + + Failed to account data. + + + + ); + } + + const balance = Number(data.balances[0].amount) / 1000000; const balanceFormated = `${balance} NYM`; return ( @@ -24,7 +54,7 @@ export const AccountInfoCard = (props: IAccountInfoCardProps) => { > - + { justifyContent="space-between" width="100%" > - {accountInfo.address} - + + {data.address} + + } /> diff --git a/explorer-nextjs/src/components/button/index.tsx b/explorer-nextjs/src/components/button/index.tsx index 018913a09f..5deee5e75e 100644 --- a/explorer-nextjs/src/components/button/index.tsx +++ b/explorer-nextjs/src/components/button/index.tsx @@ -1,10 +1,10 @@ "use client"; -import { colours } from "@/theme/colours"; import { Button as MUIButton } from "@mui/material"; import CircularProgress from "@mui/material/CircularProgress"; import Typography from "@mui/material/Typography"; import type { Theme } from "@mui/material/styles"; import Link from "next/link"; +import { colours } from "../../theme/colours"; import type { ButtonProps } from "./types"; export const Button = ({ diff --git a/explorer-nextjs/src/components/cards/AccountBalancesTable.tsx b/explorer-nextjs/src/components/cards/AccountBalancesTable.tsx index a3e14dc693..33f140293f 100644 --- a/explorer-nextjs/src/components/cards/AccountBalancesTable.tsx +++ b/explorer-nextjs/src/components/cards/AccountBalancesTable.tsx @@ -1,6 +1,5 @@ "use client"; -import { TABLET_WIDTH } from "@/app/constants"; import CircleIcon from "@mui/icons-material/Circle"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; @@ -15,6 +14,7 @@ import TableRow from "@mui/material/TableRow"; import Typography from "@mui/material/Typography"; import useMediaQuery from "@mui/material/useMediaQuery"; import * as React from "react"; +import { TABLET_WIDTH } from "../../app/constants"; import { MultiSegmentProgressBar } from "../progressBars/MultiSegmentProgressBar"; import { StaticProgressBar } from "../progressBars/StaticProgressBar"; diff --git a/explorer-nextjs/src/components/cards/ExplorerHeroCard.tsx b/explorer-nextjs/src/components/cards/ExplorerHeroCard.tsx index 16d6f47214..218b01d89b 100644 --- a/explorer-nextjs/src/components/cards/ExplorerHeroCard.tsx +++ b/explorer-nextjs/src/components/cards/ExplorerHeroCard.tsx @@ -1,4 +1,3 @@ -import ArrowUpRight from "@/components/icons/ArrowUpRight"; import { Card, CardContent, @@ -8,6 +7,7 @@ import { Typography, } from "@mui/material"; import Image from "next/image"; +import ArrowUpRight from "../../components/icons/ArrowUpRight"; import { Link } from "../muiLink"; const cardStyles = { @@ -40,7 +40,7 @@ const ExplorerHeroCard = ({ sx?: SxProps; }) => { return ( - + { - return ( - } sx={sx}> - - - - - - - - ); -}; - -export default CardSkeleton; diff --git a/explorer-nextjs/src/components/countryFlag/CountryFlag.tsx b/explorer-nextjs/src/components/countryFlag/CountryFlag.tsx index 834a3cac75..093e942e3f 100644 --- a/explorer-nextjs/src/components/countryFlag/CountryFlag.tsx +++ b/explorer-nextjs/src/components/countryFlag/CountryFlag.tsx @@ -3,7 +3,7 @@ import Flag from "react-world-flags"; interface ICountryFlag { countryCode: string; - countryName?: string; + countryName?: string | JSX.Element; } const CountryFlag = ({ countryCode, countryName }: ICountryFlag) => { diff --git a/explorer-nextjs/src/components/epochtime/EpochTime.tsx b/explorer-nextjs/src/components/epochtime/EpochTime.tsx index 33b647f896..694e2f009a 100644 --- a/explorer-nextjs/src/components/epochtime/EpochTime.tsx +++ b/explorer-nextjs/src/components/epochtime/EpochTime.tsx @@ -1,49 +1,46 @@ "use client"; -import { CURRENT_EPOCH } from "@/app/api/urls"; +import { + type EpochResponseData, + useEpochContext, +} from "@/providers/EpochProvider"; import { AccessTime } from "@mui/icons-material"; import { Stack, Typography } from "@mui/material"; -import { useQuery } from "@tanstack/react-query"; -import { addSeconds } from "date-fns"; -import { format } from "date-fns"; +import { differenceInMinutes } from "date-fns"; +import { useCallback, useEffect, useState } from "react"; -// Fetch function for the next epoch -const fetchNextEpoch = async () => { - const res = await fetch(CURRENT_EPOCH, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - }); - - if (!res.ok) { - throw new Error("Failed to fetch current epoch"); - } - - const data = await res.json(); - const dateTime = addSeconds( - new Date(data.current_epoch_start), - data.epoch_length.secs, - ); - - return { data, dateTime }; +const calculateMinutesRemaining = (epochEndTime: string) => { + const endDate = new Date(epochEndTime); + const difference = differenceInMinutes(endDate, new Date()); + return difference; }; const NextEpochTime = () => { - // Use React Query to fetch next epoch data - const { data, isLoading, isError } = useQuery({ - queryKey: ["nextEpoch"], // Unique key for this query - queryFn: fetchNextEpoch, // Fetch function - refetchInterval: 60000, // Refetch every 60 seconds - staleTime: 60000, // Data is considered fresh for 60 seconds - }); + const { data, isLoading, isError, epochStatus } = useEpochContext(); + const [minutesRemaining, setMinutesRemaining] = useState(0); + + const updateState = useCallback((data: EpochResponseData) => { + if (!data) return; + const minutesRemaining = calculateMinutesRemaining(data.current_epoch_end); + setMinutesRemaining(minutesRemaining); + }, []); + + useEffect(() => { + updateState(data); + + const interval = setInterval(() => { + updateState(data); + }, 30_000); + + return () => clearInterval(interval); + }, [data, updateState]); if (isLoading) { return ( - Loading next epoch... + Loading next mixnet epoch... ); @@ -60,14 +57,18 @@ const NextEpochTime = () => { ); } - const formattedDate = format(data.dateTime, "HH:mm:ss"); - return ( - - Next epoch: {formattedDate} - + {epochStatus === "pending" ? ( + + Waiting for next mixnet epoch to start... + + ) : ( + + Next mixnet epoch starts in: {minutesRemaining} min + + )} ); }; diff --git a/explorer-nextjs/src/components/header/DesktopHeader.tsx b/explorer-nextjs/src/components/header/DesktopHeader.tsx index fe974b567f..f9b0411ae4 100644 --- a/explorer-nextjs/src/components/header/DesktopHeader.tsx +++ b/explorer-nextjs/src/components/header/DesktopHeader.tsx @@ -1,7 +1,7 @@ -import NymLogo from "@/components/icons/NymLogo"; -import { Link } from "@/components/muiLink"; -import { Wrapper } from "@/components/wrapper"; import { Box, Divider } from "@mui/material"; +import NymLogo from "../../components/icons/NymLogo"; +import { Link } from "../../components/muiLink"; +import { Wrapper } from "../../components/wrapper"; import ConnectWallet from "../wallet/ConnectWallet"; import HeaderItem from "./HeaderItem"; import MENU_DATA from "./menuItems"; diff --git a/explorer-nextjs/src/components/header/MobileHeader.tsx b/explorer-nextjs/src/components/header/MobileHeader.tsx index 593a10dc0e..2f8f9ba4a0 100644 --- a/explorer-nextjs/src/components/header/MobileHeader.tsx +++ b/explorer-nextjs/src/components/header/MobileHeader.tsx @@ -1,9 +1,9 @@ "use client"; -import { Link } from "@/components/muiLink"; -import { Wrapper } from "@/components/wrapper"; import { Close as CloseIcon, Menu as MenuIcon } from "@mui/icons-material"; import { Box, Drawer, IconButton, Typography } from "@mui/material"; import { useState } from "react"; +import { Link } from "../../components/muiLink"; +import { Wrapper } from "../../components/wrapper"; import NymLogo from "../icons/NymLogo"; import ConnectWallet from "../wallet/ConnectWallet"; import MENU_DATA from "./menuItems"; @@ -148,7 +148,8 @@ const MobileMenuHeader = ({ - + {!drawerOpen && } + ); }; diff --git a/explorer-nextjs/src/components/input/Input.tsx b/explorer-nextjs/src/components/input/Input.tsx index 5924c66cfe..4bc147243a 100644 --- a/explorer-nextjs/src/components/input/Input.tsx +++ b/explorer-nextjs/src/components/input/Input.tsx @@ -1,4 +1,5 @@ import { type SxProps, TextField } from "@mui/material"; +import type { KeyboardEventHandler } from "react"; const Input = ({ placeholder, @@ -6,6 +7,7 @@ const Input = ({ value, rounded = false, onChange, + onKeyDown, }: { placeholder?: string; fullWidth?: boolean; @@ -13,6 +15,7 @@ const Input = ({ sx?: SxProps; value: string; onChange: (event: React.ChangeEvent) => void; + onKeyDown?: KeyboardEventHandler | undefined; }) => { return ( => { - const response = await fetch(CURRENT_EPOCH, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - }); +const calulateProgress = (end: string) => { + const endDate = new Date(end); + const difference = differenceInMinutes(endDate, new Date()); + const progress = Math.max(0, 100 - (difference / 60) * 100); - if (!response.ok) { - throw new Error("Failed to fetch current epoch data"); - } + return progress; +}; - return response.json(); +const getStartEndTime = (start: string, end: string) => { + const startDate = new Date(start); + const endDate = new Date(end); + + const startTime = format(startDate, "HH:mm:ss"); + const endTime = format(endDate, "HH:mm:ss"); + + return { startTime, endTime }; }; export const CurrentEpochCard = () => { - // Use React Query to fetch data - const { data, isError, isLoading } = useQuery({ - queryKey: ["currentEpoch"], // Unique query key - queryFn: fetchCurrentEpoch, // Fetch function - refetchInterval: 60000, // Refetch every 60 seconds - staleTime: 60000, // Data is considered fresh for 60 seconds - }); + const { data, isError, isLoading, epochStatus } = useEpochContext(); + + const [startTime, setStartTime] = useState(""); + const [endTime, setEndTime] = useState(""); + const [progress, setProgress] = useState(0); + + const updateState = useCallback((data: NonNullable) => { + const { startTime, endTime } = getStartEndTime( + data.current_epoch_start, + data.current_epoch_end, + ); + const progress = calulateProgress(data.current_epoch_end); + + setStartTime(startTime); + setEndTime(endTime); + setProgress(progress); + }, []); + + useEffect(() => { + if (!data) return; + updateState(data); + + const intervalId = setInterval(() => { + updateState(data); + }, 30_000); + + return () => clearInterval(intervalId); + }, [data, updateState]); if (isLoading) { - return Loading...; - } - - if (isError || !data) { return ( - Failed to load data + + + ); } - const currentEpochStart = data.current_epoch_start || ""; + if (isError) { + return ( + + Failed to load data + + ); + } + + if (!data) { + return ( + + No data available + + ); + } + + if (epochStatus === "pending") { + return ( + + + Waiting for next epoch to start... + + + ); + } return ( - - + + ); }; diff --git a/explorer-nextjs/src/components/landingPageComponents/NetworkStakeCard.tsx b/explorer-nextjs/src/components/landingPageComponents/NetworkStakeCard.tsx index aac8258e70..3add50d0f1 100644 --- a/explorer-nextjs/src/components/landingPageComponents/NetworkStakeCard.tsx +++ b/explorer-nextjs/src/components/landingPageComponents/NetworkStakeCard.tsx @@ -1,40 +1,49 @@ -import type { ExplorerData, IPacketsAndStakingData } from "@/app/api"; -import { - CURRENT_EPOCH_REWARDS, - HARBOURMASTER_API_MIXNODES_STATS, -} from "@/app/api/urls"; -import { formatBigNum } from "@/utils/formatBigNumbers"; -import { Box, Stack, Typography } from "@mui/material"; +"use client"; +import { fetchNoise } from "@/app/api"; +import { Box, Skeleton, Stack, Typography } from "@mui/material"; +import { useQuery } from "@tanstack/react-query"; +import type { ExplorerData, IPacketsAndStakingData } from "../../app/api/types"; +import { formatBigNum } from "../../utils/formatBigNumbers"; import ExplorerCard from "../cards/ExplorerCard"; import { LineChart } from "../lineChart"; -export const NetworkStakeCard = async () => { - const epochRewards = await fetch(CURRENT_EPOCH_REWARDS, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, +export const NetworkStakeCard = () => { + const { + data: packetsAndStaking, + isLoading: isStakingLoading, + isError: isStakingError, + } = useQuery({ + queryKey: ["noise"], + queryFn: fetchNoise, }); - const packetsAndStaking = await fetch(HARBOURMASTER_API_MIXNODES_STATS, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - }); - - const epochRewardsData: ExplorerData["currentEpochRewardsData"] = - await epochRewards.json(); - const packetsAndStakingData: ExplorerData["packetsAndStakingData"] = - await packetsAndStaking.json(); - - if (!epochRewardsData || !packetsAndStakingData) { - return null; + if (isStakingLoading) { + return ( + + + + + + + ); } - const currentStake = - Number(epochRewardsData.interval.staking_supply) / 1000000 || 0; + if (isStakingError || !packetsAndStaking) { + return ( + + + Failed to load data + + + + ); + } + const packetsAndStakingData: ExplorerData["packetsAndStakingData"] = + packetsAndStaking; + + const lastTotalStake = + packetsAndStaking[packetsAndStaking.length - 1]?.total_stake / 1000000; const data = packetsAndStakingData.map((item: IPacketsAndStakingData) => { return { date_utc: item.date_utc, @@ -48,7 +57,7 @@ export const NetworkStakeCard = async () => { data, }; - const title = `${formatBigNum(currentStake)} NYM`; + const title = `${formatBigNum(lastTotalStake)} NYM`; return ( diff --git a/explorer-nextjs/src/components/landingPageComponents/NoiseCard.tsx b/explorer-nextjs/src/components/landingPageComponents/NoiseCard.tsx index e221ab439b..b10b0511b0 100644 --- a/explorer-nextjs/src/components/landingPageComponents/NoiseCard.tsx +++ b/explorer-nextjs/src/components/landingPageComponents/NoiseCard.tsx @@ -1,23 +1,40 @@ -import type { IPacketsAndStakingData } from "@/app/api"; -import { HARBOURMASTER_API_MIXNODES_STATS } from "@/app/api/urls"; -import { formatBigNum } from "@/utils/formatBigNumbers"; -import { Box, Stack, Typography } from "@mui/material"; +"use client"; +import { Box, Skeleton, Stack, Typography } from "@mui/material"; +import { useQuery } from "@tanstack/react-query"; +import type { IPacketsAndStakingData } from "../../app/api/types"; +import { formatBigNum } from "../../utils/formatBigNumbers"; import ExplorerCard from "../cards/ExplorerCard"; import { LineChart } from "../lineChart"; import { UpDownPriceIndicator } from "../price/UpDownPriceIndicator"; -export const NoiseCard = async () => { - const response = await fetch(HARBOURMASTER_API_MIXNODES_STATS, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, +import { fetchNoise } from "@/app/api"; + +export const NoiseCard = () => { + const { data, isLoading, isError } = useQuery({ + queryKey: ["noise"], + queryFn: fetchNoise, }); - const data: IPacketsAndStakingData[] = await response.json(); + if (isLoading) { + return ( + + + + + + + ); + } - if (!data) { - return null; + if (isError || !data) { + return ( + + + Failed to load data + + + + ); } const todaysData = data[data.length - 1]; diff --git a/explorer-nextjs/src/components/landingPageComponents/RewardsCard.tsx b/explorer-nextjs/src/components/landingPageComponents/RewardsCard.tsx deleted file mode 100644 index a886458617..0000000000 --- a/explorer-nextjs/src/components/landingPageComponents/RewardsCard.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { formatBigNum } from "@/utils/formatBigNumbers"; -import { Typography } from "@mui/material"; -import ExplorerCard from "../cards/ExplorerCard"; - -export const RewardsCard = async () => { - return ( - - - {`${formatBigNum(10_000_111)} NYM`} - - - ); -}; diff --git a/explorer-nextjs/src/components/landingPageComponents/StakersNumberCard.tsx b/explorer-nextjs/src/components/landingPageComponents/StakersNumberCard.tsx new file mode 100644 index 0000000000..38687b334b --- /dev/null +++ b/explorer-nextjs/src/components/landingPageComponents/StakersNumberCard.tsx @@ -0,0 +1,50 @@ +"use client"; +import { fetchObservatoryNodes } from "@/app/api"; +import type { IObservatoryNode } from "@/app/api/types"; +import { Skeleton, Typography } from "@mui/material"; +import { useQuery } from "@tanstack/react-query"; +import ExplorerCard from "../cards/ExplorerCard"; + +export const RewardsCard = () => { + const { + data: observatoryNodes, + isLoading, + isError, + } = useQuery({ + queryKey: ["observatoryNodes"], + queryFn: () => fetchObservatoryNodes(), + }); + + if (isLoading) { + return ( + + + + ); + } + + if (isError || !observatoryNodes) { + return ( + + + Failed to load node data. + + + ); + } + const getActiveStakersNumber = (nodes: IObservatoryNode[]): number => { + return nodes.reduce( + (sum, node) => sum + node.rewarding_details.unique_delegations, + 0, + ); + }; + const allStakers = getActiveStakersNumber(observatoryNodes); + + return ( + + + {allStakers} + + + ); +}; diff --git a/explorer-nextjs/src/components/landingPageComponents/TokenomicsCard.tsx b/explorer-nextjs/src/components/landingPageComponents/TokenomicsCard.tsx index 3d64930f75..62174e8183 100644 --- a/explorer-nextjs/src/components/landingPageComponents/TokenomicsCard.tsx +++ b/explorer-nextjs/src/components/landingPageComponents/TokenomicsCard.tsx @@ -1,21 +1,71 @@ -import type { NymTokenomics } from "@/app/api/types"; -import { NYM_PRICES_API } from "@/app/api/urls"; -import { Box, Stack } from "@mui/material"; +"use client"; +import { fetchEpochRewards, fetchNoise, fetchNymPrice } from "@/app/api"; +import { formatBigNum } from "@/utils/formatBigNumbers"; +import { Box, Skeleton, Stack, Typography } from "@mui/material"; +import { useQuery } from "@tanstack/react-query"; +import type { ExplorerData, NymTokenomics } from "../../app/api/types"; import ExplorerCard from "../cards/ExplorerCard"; import ExplorerListItem from "../list/ListItem"; import { TitlePrice } from "../price/TitlePrice"; -export const TokenomicsCard = async () => { - const nymPrice = await fetch(NYM_PRICES_API, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - next: { revalidate: 60 }, - // refresh event list cache at given interval +export const TokenomicsCard = () => { + const { + data: nymPrice, + isLoading, + isError, + } = useQuery({ + queryKey: ["nymPrice"], + queryFn: fetchNymPrice, }); - const nymPriceData: NymTokenomics = await nymPrice.json(); + const { + data: epochRewards, + isLoading: isEpochLoading, + isError: isEpochError, + } = useQuery({ + queryKey: ["epochRewards"], + queryFn: fetchEpochRewards, + }); + + const { + data: packetsAndStaking, + isLoading: isStakingLoading, + isError: isStakingError, + } = useQuery({ + queryKey: ["noise"], + queryFn: fetchNoise, + }); + + if (isLoading || isEpochLoading || isStakingLoading) { + return ( + + + + + + + ); + } + + if ( + isStakingError || + isEpochError || + isError || + !nymPrice || + !epochRewards || + !packetsAndStaking + ) { + return ( + + + Failed to load tokenomics overview. + + + + ); + } + + const nymPriceData: NymTokenomics = nymPrice; const nymPriceDataFormated = Number(nymPriceData.quotes.USD.price.toFixed(2)); const titlePrice = { @@ -25,11 +75,35 @@ export const TokenomicsCard = async () => { // numberWentUp: true, // }, }; - const marketCap = nymPriceData.quotes.USD.market_cap; - const volume24H = nymPriceData.quotes.USD.volume_24h.toFixed(2); + const marketCap = formatBigNum(nymPriceData.quotes.USD.market_cap); + const volume24H = formatBigNum(nymPriceData.quotes.USD.volume_24h); + + const epochRewardsData: ExplorerData["currentEpochRewardsData"] = + epochRewards; + const packetsAndStakingData: ExplorerData["packetsAndStakingData"] = + packetsAndStaking; + + function calculateTVL( + epochRewards: ExplorerData["currentEpochRewardsData"], + nymPriceData: NymTokenomics, + packetsAndStaking: ExplorerData["packetsAndStakingData"], + ): number { + const lastTotalStake = + packetsAndStaking[packetsAndStaking.length - 1]?.total_stake || 0; + return ( + (Number.parseFloat(epochRewards.interval.reward_pool) / 1000000 + + lastTotalStake / 1000000) * + nymPriceData.quotes.USD.price + ); + } + const TVL = formatBigNum( + calculateTVL(epochRewardsData, nymPrice, packetsAndStakingData), + ); + const dataRows = [ { key: "Market cap", value: `$ ${marketCap}` }, { key: "24H VOL", value: `$ ${volume24H}` }, + { key: "TVL", value: `$ ${TVL}` }, ]; return ( diff --git a/explorer-nextjs/src/components/lineChart/index.tsx b/explorer-nextjs/src/components/lineChart/index.tsx index 48a8cc3071..d5c1b140f5 100644 --- a/explorer-nextjs/src/components/lineChart/index.tsx +++ b/explorer-nextjs/src/components/lineChart/index.tsx @@ -66,8 +66,8 @@ export const LineChart = ({ enableSlices="x" margin={{ bottom: 24, - left: 30, - right: 12, + left: 36, + right: 18, top: 20, }} theme={{ @@ -106,7 +106,7 @@ export const LineChart = ({ legendOffset: 12, tickSize: 3, format: yformat, - tickValues: 5, + tickValues: 8, }} axisBottom={{ format: "%b %d", diff --git a/explorer-nextjs/src/components/loading/index.tsx b/explorer-nextjs/src/components/loading/index.tsx index bd665517e1..ff8c43f47b 100644 --- a/explorer-nextjs/src/components/loading/index.tsx +++ b/explorer-nextjs/src/components/loading/index.tsx @@ -12,7 +12,7 @@ const modalStyle: SxProps = { top: "50%", left: "50%", transform: "translate(-50%, -50%)", - width: 500, + width: 300, bgcolor: "background.paper", boxShadow: 24, borderRadius: "16px", diff --git a/explorer-nextjs/src/components/modal/InfoModal.tsx b/explorer-nextjs/src/components/modal/InfoModal.tsx index c578e76bd6..05b995fc38 100644 --- a/explorer-nextjs/src/components/modal/InfoModal.tsx +++ b/explorer-nextjs/src/components/modal/InfoModal.tsx @@ -22,7 +22,7 @@ const InfoModal = (props: InfoModalProps) => { } const { open, onClose, title, message, tx, Action } = props; - const mintscanURL = tx ? `https://www.mintscan.io/nyx/tx/${tx}` : "/"; + const pingURL = tx ? `https://www.ping.pub/nyx/tx/${tx}` : "/"; return ( { {title} {message} {tx && ( - + Block explorer link )} diff --git a/explorer-nextjs/src/components/modal/SimpleModal.tsx b/explorer-nextjs/src/components/modal/SimpleModal.tsx index c3579f40a4..13ade7da95 100644 --- a/explorer-nextjs/src/components/modal/SimpleModal.tsx +++ b/explorer-nextjs/src/components/modal/SimpleModal.tsx @@ -1,4 +1,3 @@ -import Cross from "@/components/icons/Cross"; import { Dialog, DialogActions, @@ -7,6 +6,7 @@ import { Stack, Typography, } from "@mui/material"; +import Cross from "../../components/icons/Cross"; type SimpleModalPropsClosed = { open: false; diff --git a/explorer-nextjs/src/components/modal/index.tsx b/explorer-nextjs/src/components/modal/index.tsx index 6eb868dc70..a2200bece2 100644 --- a/explorer-nextjs/src/components/modal/index.tsx +++ b/explorer-nextjs/src/components/modal/index.tsx @@ -1,4 +1,3 @@ -import { TABLET_WIDTH } from "@/app/constants"; import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; import CloseIcon from "@mui/icons-material/Close"; import ErrorOutline from "@mui/icons-material/ErrorOutline"; @@ -13,6 +12,7 @@ import { useMediaQuery, } from "@mui/material"; import type React from "react"; +import { TABLET_WIDTH } from "../../app/constants"; export const modalStyle = (width: number | string = 600) => ({ position: "absolute", diff --git a/explorer-nextjs/src/components/muiLink/index.tsx b/explorer-nextjs/src/components/muiLink/index.tsx index 16b6f89ead..43230fe1da 100644 --- a/explorer-nextjs/src/components/muiLink/index.tsx +++ b/explorer-nextjs/src/components/muiLink/index.tsx @@ -1,6 +1,6 @@ -// Imports import { Link as MuiLink, type LinkProps as MuiLinkProps } from "@mui/material"; import NextLink, { type LinkProps as NextLinkProps } from "next/link"; +// Imports import type { ReactNode } from "react"; interface CustomLinkProps extends NextLinkProps, Omit { diff --git a/explorer-nextjs/src/components/nodeTable/NodeTable.tsx b/explorer-nextjs/src/components/nodeTable/NodeTable.tsx index 0661295b3f..2007c3761e 100644 --- a/explorer-nextjs/src/components/nodeTable/NodeTable.tsx +++ b/explorer-nextjs/src/components/nodeTable/NodeTable.tsx @@ -1,9 +1,14 @@ "use client"; - -import { COSMOS_KIT_USE_CHAIN } from "@/config"; -import { useNymClient } from "@/hooks/useNymClient"; import { useChain } from "@cosmos-kit/react"; -import { Box, Button, Stack, Tooltip, Typography } from "@mui/material"; +import { + Box, + Button, + Stack, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { useQueryClient } from "@tanstack/react-query"; import { useLocalStorage } from "@uidotdev/usehooks"; import { type MRT_ColumnDef, @@ -12,9 +17,13 @@ import { } 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"; @@ -26,16 +35,42 @@ const ColumnHeading = ({ }: { children: string | React.ReactNode; }) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down("sm")); return ( - - {children} - + + + {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 [infoModalProps, setInfoModalProps] = useState({ open: false, @@ -48,6 +83,10 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { 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(); @@ -69,8 +108,12 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { message: "This operation can take up to one hour to process", tx: tx?.transactionHash, - onClose: () => setInfoModalProps({ open: false }), + onClose: async () => { + await handleRefetch(); + setInfoModalProps({ open: false }); + }, }); + await handleRefetch(); } catch (e) { const errorMessage = e instanceof Error ? e.message : "An error occurred while staking"; @@ -85,7 +128,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { } setIsLoading(false); }, - [nymClient], + [nymClient, handleRefetch], ); const handleOnSelectStake = useCallback( @@ -116,6 +159,12 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { }, [isWalletConnected], ); + // get full country name + const countryName = useCallback((countryCode: string) => { + const regionNames = new Intl.DisplayNames(["en"], { type: "region" }); + + return {regionNames.of(countryCode)}; + }, []); const columns: MRT_ColumnDef[] = useMemo( () => [ @@ -131,23 +180,36 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { ), }, { - id: "node", + id: "id", header: "", - Header: Node, - accessorKey: "identity_key", + Header: Node ID, + accessorKey: "nodeId", + size: 90, Cell: ({ row }) => ( {row.original.nodeId} + + ), + }, + { + id: "identity_key", + header: "", + Header: Identity Key, + accessorKey: "identity_key", + Cell: ({ row }) => ( + {row.original.identity_key} ), }, { id: "qos", - header: "Quality of Service", + header: "Qlt of Service", align: "center", accessorKey: "qualityOfService", - Header: Quality of Service, + size: 100, + + Header: Qlt of Service, Cell: ({ row }) => ( {row.original.qualityOfService}% @@ -158,17 +220,16 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { id: "location", header: "Location", accessorKey: "countryName", + size: 160, Header: Location, Cell: ({ row }) => row.original.countryCode && row.original.countryName ? ( - - - - - + + + ) : ( "-" ), @@ -199,6 +260,8 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { id: "Action", header: "Action", accessorKey: "Action", + size: 120, + Header: Action, hidden: !isWalletConnected, enableColumnFilter: false, @@ -221,6 +284,8 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { enableColumnFilter: false, header: "Favorite", accessorKey: "Favorite", + size: 110, + Header: ( Favorite @@ -241,7 +306,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { Cell: ({ row }) => , }, ], - [isWalletConnected, handleOnSelectStake, favorites], + [isWalletConnected, handleOnSelectStake, favorites, countryName], ); const table = useMaterialReactTable({ columns, @@ -264,7 +329,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { shape: "circular", }, initialState: { - columnPinning: { right: ["Action", "Favorite"] }, + columnPinning: isMobile ? {} : { right: ["Action", "Favorite"] }, // No pinning on mobile }, muiColumnActionsButtonProps: { sx: { @@ -280,9 +345,17 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { bgcolor: "background.paper", }, }, + muiTableHeadCellProps: { + sx: { + alignItems: "center", + }, + }, + muiTableBodyCellProps: { sx: { border: "none", + whiteSpace: "unset", // Allow text wrapping in body cells + wordBreak: "break-word", // Ensure long text breaks correctly }, }, muiTableBodyRowProps: ({ row }) => ({ @@ -301,9 +374,11 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => { }, }), }); + return ( <> {isLoading && } + { /> + ); diff --git a/explorer-nextjs/src/components/nodeTable/NodeTableWithAction.tsx b/explorer-nextjs/src/components/nodeTable/NodeTableWithAction.tsx index 348904c481..903fd03f42 100644 --- a/explorer-nextjs/src/components/nodeTable/NodeTableWithAction.tsx +++ b/explorer-nextjs/src/components/nodeTable/NodeTableWithAction.tsx @@ -1,30 +1,12 @@ "use client"; -import getNymNodes from "@/actions/getNymNodes"; -import type { ExplorerData } from "@/app/api"; -import type { IObservatoryNode } from "@/app/api/types"; -import { CURRENT_EPOCH_REWARDS } from "@/app/api/urls"; +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 NodeTable from "./NodeTable"; -// Fetch function for epoch rewards -const fetchEpochRewards = async (): Promise< - ExplorerData["currentEpochRewardsData"] -> => { - const response = await fetch(CURRENT_EPOCH_REWARDS, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch epoch rewards"); - } - - return response.json(); -}; - // Utility function to calculate node saturation point function getNodeSaturationPoint( totalStake: number, @@ -52,8 +34,12 @@ const mappedNymNodes = ( epochRewardsData.interval.stake_saturation_point, ); + const cleanMoniker = DOMPurify.sanitize( + node.self_description.moniker, + ).replace(/&/g, "&"); + return { - name: node.self_description.moniker, + name: cleanMoniker, nodeId: node.node_id, identity_key: node.identity_key, countryCode: node.description.auxiliary_details.location || null, @@ -78,8 +64,6 @@ const NodeTableWithAction = () => { } = useQuery({ queryKey: ["epochRewards"], queryFn: fetchEpochRewards, - staleTime: 60000, // Data is fresh for 60 seconds - refetchInterval: 60000, // Refetch every 60 seconds }); // Use React Query to fetch Nym nodes @@ -89,23 +73,41 @@ const NodeTableWithAction = () => { isError: isNodesError, } = useQuery({ queryKey: ["nymNodes"], - queryFn: getNymNodes, - staleTime: 60000, - refetchInterval: 60000, + queryFn: fetchObservatoryNodes, }); // Handle loading state if (isEpochLoading || isNodesLoading) { - return
Loading...
; + return ( + + + + + + + + + ); } // Handle error state if (isEpochError || isNodesError) { - return
Error loading data. Please try again later.
; + return ( + + + Error loading data. Please try again later. + + + ); } // Map nodes with rewards data - const data = mappedNymNodes(nymNodes, epochRewardsData); + + if (!epochRewardsData) { + return null; + } + + const data = mappedNymNodes(nymNodes || [], epochRewardsData); return ; }; diff --git a/explorer-nextjs/src/components/nymNodePageComponents/BasicInfoCard.tsx b/explorer-nextjs/src/components/nymNodePageComponents/BasicInfoCard.tsx index 4fe07b3723..265146c543 100644 --- a/explorer-nextjs/src/components/nymNodePageComponents/BasicInfoCard.tsx +++ b/explorer-nextjs/src/components/nymNodePageComponents/BasicInfoCard.tsx @@ -1,70 +1,51 @@ "use client"; -import type { IObservatoryNode } from "@/app/api/types"; -import { DATA_OBSERVATORY_NODES_URL } from "@/app/api/urls"; -import { formatBigNum } from "@/utils/formatBigNumbers"; -import { Stack, Typography } from "@mui/material"; +import { Skeleton, Stack, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; import { format } from "date-fns"; +import { fetchNodeInfo } from "../../app/api"; +import { formatBigNum } from "../../utils/formatBigNumbers"; import ExplorerCard from "../cards/ExplorerCard"; import CopyToClipboard from "../copyToClipboard/CopyToClipboard"; import ExplorerListItem from "../list/ListItem"; interface IBasicInfoCardProps { - id: number; // Node ID + id: number; } -// Fetch function to get the node data -const fetchNodeInfo = async (id: number): Promise => { - const response = await fetch(DATA_OBSERVATORY_NODES_URL, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - next: { revalidate: 60 }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch observatory nodes"); - } - - const observatoryNymNodes: IObservatoryNode[] = await response.json(); - - return observatoryNymNodes.find((node) => node.node_id === id) || null; -}; - export const BasicInfoCard = ({ id }: IBasicInfoCardProps) => { - // Use React Query to fetch the node info const { data: nodeInfo, isLoading, isError, } = useQuery({ - queryKey: ["nodeInfo", id], // Unique query key based on the node ID - queryFn: () => fetchNodeInfo(id), // Fetch function - refetchInterval: 60000, // Refetch every 60 seconds - staleTime: 60000, // Data is considered fresh for 60 seconds + queryKey: ["nodeInfo", id], + queryFn: () => fetchNodeInfo(id), }); - // Loading state if (isLoading) { return ( - Loading... + + + + + + ); } - // Error state if (isError || !nodeInfo) { return ( - Failed to load node information. + + Failed to load node data. + ); } - // Derived data from nodeInfo const timeBonded = format( new Date(nodeInfo.description.build_information.build_timestamp), "dd/MM/yyyy", @@ -92,7 +73,10 @@ export const BasicInfoCard = ({ id }: IBasicInfoCardProps) => { justifyContent="space-between" width="100%" > - + {nodeInfo.bonding_address} @@ -110,7 +94,12 @@ export const BasicInfoCard = ({ id }: IBasicInfoCardProps) => { justifyContent="space-between" width="100%" > - {nodeInfo.identity_key} + + {nodeInfo.identity_key} +
} diff --git a/explorer-nextjs/src/components/nymNodePageComponents/DelegationsTable.tsx b/explorer-nextjs/src/components/nymNodePageComponents/DelegationsTable.tsx index 7aa7a69ab7..03be5367d5 100644 --- a/explorer-nextjs/src/components/nymNodePageComponents/DelegationsTable.tsx +++ b/explorer-nextjs/src/components/nymNodePageComponents/DelegationsTable.tsx @@ -1,6 +1,5 @@ "use client"; -import type { NodeRewardDetails } from "@/app/api/types"; import { Stack, Typography } from "@mui/material"; import { type MRT_ColumnDef, @@ -9,6 +8,7 @@ import { } from "material-react-table"; import { useRouter } from "next/navigation"; import { useMemo } from "react"; +import type { NodeRewardDetails } from "../../app/api/types"; const ColumnHeading = ({ children, @@ -62,6 +62,11 @@ const DelegationsTable = ({ header: "Amount", accessorKey: "amount", Header: Amount, + sortingFn: (rowA, rowB) => { + const stakeA = Number.parseFloat(rowA.original.amount.amount); + const stakeB = Number.parseFloat(rowB.original.amount.amount); + return stakeA - stakeB; + }, Cell: ({ row }) => ( {getNymsFormated(row.original.amount.amount)} NYM diff --git a/explorer-nextjs/src/components/nymNodePageComponents/NodeDelegationsCard.tsx b/explorer-nextjs/src/components/nymNodePageComponents/NodeDelegationsCard.tsx index 1d97cf81fd..3db9996a40 100644 --- a/explorer-nextjs/src/components/nymNodePageComponents/NodeDelegationsCard.tsx +++ b/explorer-nextjs/src/components/nymNodePageComponents/NodeDelegationsCard.tsx @@ -1,7 +1,7 @@ "use client"; -import type { NodeRewardDetails } from "@/app/api/types"; -import { DATA_OBSERVATORY_NODES_URL } from "@/app/api/urls"; +import { fetchNodeDelegations } from "@/app/api"; +import { Skeleton, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; import ExplorerCard from "../cards/ExplorerCard"; import DelegationsTable from "./DelegationsTable"; @@ -10,30 +10,7 @@ interface NodeDelegationsCardProps { id: number; // Node ID } -// Fetch delegations dynamically based on ID -const fetchNodeDelegations = async ( - id: number, -): Promise => { - const response = await fetch( - `${DATA_OBSERVATORY_NODES_URL}/${id}/delegations`, - { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - next: { revalidate: 60 }, - }, - ); - - if (!response.ok) { - throw new Error("Failed to fetch delegations"); - } - - return response.json(); -}; - const NodeDelegationsCard = ({ id }: NodeDelegationsCardProps) => { - // Use React Query to fetch delegations const { data: delegations = [], isLoading, @@ -41,17 +18,32 @@ const NodeDelegationsCard = ({ id }: NodeDelegationsCardProps) => { } = useQuery({ queryKey: ["nodeDelegations", id], queryFn: () => fetchNodeDelegations(id), - refetchInterval: 60000, // Refetch every 60 seconds - staleTime: 60000, // Data is fresh for 60 seconds }); + if (isLoading) { + return ( + + + + + + + ); + } + + if (isError) { + return ( + + + Failed to load delegations. Please try again later. + + + ); + } + return ( - {isLoading &&
Loading delegations...
} - {isError && ( -
Failed to load delegations. Please try again later.
- )} - {!isLoading && !isError && } +
); }; diff --git a/explorer-nextjs/src/components/nymNodePageComponents/NodeMetricsCard.tsx b/explorer-nextjs/src/components/nymNodePageComponents/NodeMetricsCard.tsx index 491ad63e43..da34536556 100644 --- a/explorer-nextjs/src/components/nymNodePageComponents/NodeMetricsCard.tsx +++ b/explorer-nextjs/src/components/nymNodePageComponents/NodeMetricsCard.tsx @@ -1,12 +1,9 @@ "use client"; -import type { ExplorerData } from "@/app/api"; -import type { IObservatoryNode } from "@/app/api/types"; -import { - CURRENT_EPOCH_REWARDS, - DATA_OBSERVATORY_NODES_URL, -} from "@/app/api/urls"; import { useQuery } from "@tanstack/react-query"; +import { fetchEpochRewards, fetchNodeInfo } from "../../app/api"; + +import { Skeleton, Typography } from "@mui/material"; import ExplorerCard from "../cards/ExplorerCard"; import ExplorerListItem from "../list/ListItem"; @@ -14,42 +11,7 @@ interface INodeMetricsCardProps { id: number; // Node ID } -// Fetch functions -const fetchEpochRewards = async (): Promise< - ExplorerData["currentEpochRewardsData"] -> => { - const response = await fetch(CURRENT_EPOCH_REWARDS, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch epoch rewards"); - } - - return response.json(); -}; - -const fetchNodeInfo = async (id: number): Promise => { - const response = await fetch(DATA_OBSERVATORY_NODES_URL, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch observatory nodes"); - } - - const nodes: IObservatoryNode[] = await response.json(); - return nodes.find((node) => node.node_id === id) || null; -}; - export const NodeMetricsCard = ({ id }: INodeMetricsCardProps) => { - // Fetch epoch rewards const { data: epochRewardsData, isLoading: isEpochLoading, @@ -57,8 +19,6 @@ export const NodeMetricsCard = ({ id }: INodeMetricsCardProps) => { } = useQuery({ queryKey: ["epochRewards"], queryFn: fetchEpochRewards, - refetchInterval: 60000, // Refetch every 60 seconds - staleTime: 60000, // Data is fresh for 60 seconds }); // Fetch node information @@ -69,14 +29,15 @@ export const NodeMetricsCard = ({ id }: INodeMetricsCardProps) => { } = useQuery({ queryKey: ["nodeInfo", id], queryFn: () => fetchNodeInfo(id), - refetchInterval: 60000, // Refetch every 60 seconds - staleTime: 60000, // Data is fresh for 60 seconds }); if (isEpochLoading || isNodeLoading) { return ( -
Loading...
+ + + +
); } @@ -84,7 +45,9 @@ export const NodeMetricsCard = ({ id }: INodeMetricsCardProps) => { if (isEpochError || isNodeError || !nodeInfo || !epochRewardsData) { return ( -
Failed to load data
+ + Failed to load node data. +
); } diff --git a/explorer-nextjs/src/components/nymNodePageComponents/NodeProfileCard.tsx b/explorer-nextjs/src/components/nymNodePageComponents/NodeProfileCard.tsx index 82486e3511..c55ddc4a7a 100644 --- a/explorer-nextjs/src/components/nymNodePageComponents/NodeProfileCard.tsx +++ b/explorer-nextjs/src/components/nymNodePageComponents/NodeProfileCard.tsx @@ -1,13 +1,14 @@ "use client"; -import { DATA_OBSERVATORY_NODES_URL } from "@/app/api/urls"; -import { COSMOS_KIT_USE_CHAIN } from "@/config"; -import { useNymClient } from "@/hooks/useNymClient"; import { useChain } from "@cosmos-kit/react"; -import { Box, Button, Stack, Typography } from "@mui/material"; +import { Box, Button, Skeleton, Stack, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; +import DOMPurify from "isomorphic-dompurify"; import { useCallback, useState } from "react"; import { RandomAvatar } from "react-random-avatars"; +import { fetchNodeInfo } from "../../app/api"; +import { COSMOS_KIT_USE_CHAIN } from "../../config"; +import { useNymClient } from "../../hooks/useNymClient"; import ExplorerCard from "../cards/ExplorerCard"; import CountryFlag from "../countryFlag/CountryFlag"; import { Favorite } from "../favorite/Favorite"; @@ -21,23 +22,6 @@ interface INodeProfileCardProps { id: number; // Node ID } -// Fetch node info -const fetchNodeInfo = async (id: number) => { - const response = await fetch(DATA_OBSERVATORY_NODES_URL, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch observatory nodes"); - } - - const nodes = await response.json(); - return nodes.find((node: { node_id: number }) => node.node_id === id) || null; -}; - export const NodeProfileCard = ({ id }: INodeProfileCardProps) => { const { isWalletConnected } = useChain(COSMOS_KIT_USE_CHAIN); const { nymClient } = useNymClient(); @@ -58,8 +42,6 @@ export const NodeProfileCard = ({ id }: INodeProfileCardProps) => { } = useQuery({ queryKey: ["nodeInfo", id], queryFn: () => fetchNodeInfo(id), - refetchInterval: 60000, - staleTime: 60000, }); const handleStakeOnNode = async ({ @@ -135,7 +117,9 @@ export const NodeProfileCard = ({ id }: INodeProfileCardProps) => { if (isNodeLoading) { return ( -
Loading...
+ + +
); } @@ -143,10 +127,26 @@ export const NodeProfileCard = ({ id }: INodeProfileCardProps) => { if (isNodeError || !nodeInfo) { return ( -
Failed to load node information.
+ + Failed to load node data. +
); } + const cleanMoniker = DOMPurify.sanitize( + nodeInfo?.self_description.moniker, + ).replace(/&/g, "&"); + + const cleanDescription = DOMPurify.sanitize( + nodeInfo?.self_description.details, + ).replace(/&/g, "&"); + + // get full country name + const countryName = (countryCode: string) => { + const regionNames = new Intl.DisplayNames(["en"], { type: "region" }); + + return {regionNames.of(countryCode)}; + }; return ( @@ -160,20 +160,25 @@ export const NodeProfileCard = ({ id }: INodeProfileCardProps) => { mb={1} sx={{ color: "pine.950", wordWrap: "break-word", maxWidth: "95%" }} > - {nodeInfo?.self_description.moniker || "Moniker"} + {cleanMoniker || "Moniker"}
{nodeInfo.description.auxiliary_details.location && ( Location: - + + + + )} {nodeInfo && ( - {nodeInfo.self_description.details} + {cleanDescription} )} diff --git a/explorer-nextjs/src/components/nymNodePageComponents/NodeRewardsCard.tsx b/explorer-nextjs/src/components/nymNodePageComponents/NodeRewardsCard.tsx index 4c4c29ef74..097e31e6f8 100644 --- a/explorer-nextjs/src/components/nymNodePageComponents/NodeRewardsCard.tsx +++ b/explorer-nextjs/src/components/nymNodePageComponents/NodeRewardsCard.tsx @@ -1,12 +1,9 @@ "use client"; -import type { ExplorerData } from "@/app/api"; -import type { IObservatoryNode, RewardingDetails } from "@/app/api/types"; -import { - CURRENT_EPOCH_REWARDS, - DATA_OBSERVATORY_NODES_URL, -} from "@/app/api/urls"; +import { Skeleton, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; +import { fetchEpochRewards, fetchNodeInfo } from "../../app/api"; +import type { RewardingDetails } from "../../app/api/types"; import ExplorerCard from "../cards/ExplorerCard"; import ExplorerListItem from "../list/ListItem"; @@ -14,40 +11,6 @@ interface INodeRewardsCardProps { id: number; // Node ID } -// Fetch functions -const fetchEpochRewards = async (): Promise< - ExplorerData["currentEpochRewardsData"] -> => { - const response = await fetch(CURRENT_EPOCH_REWARDS, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch epoch rewards"); - } - - return response.json(); -}; - -const fetchNodeInfo = async (id: number): Promise => { - const response = await fetch(DATA_OBSERVATORY_NODES_URL, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch observatory nodes"); - } - - const nodes: IObservatoryNode[] = await response.json(); - return nodes.find((node) => node.node_id === id) || null; -}; - export const NodeRewardsCard = ({ id }: INodeRewardsCardProps) => { // Fetch epoch rewards const { @@ -57,8 +20,6 @@ export const NodeRewardsCard = ({ id }: INodeRewardsCardProps) => { } = useQuery({ queryKey: ["epochRewards"], queryFn: fetchEpochRewards, - refetchInterval: 60000, // Refetch every 60 seconds - staleTime: 60000, // Data is fresh for 60 seconds }); // Fetch node information @@ -69,8 +30,6 @@ export const NodeRewardsCard = ({ id }: INodeRewardsCardProps) => { } = useQuery({ queryKey: ["nodeInfo", id], queryFn: () => fetchNodeInfo(id), - refetchInterval: 60000, // Refetch every 60 seconds - staleTime: 60000, // Data is fresh for 60 seconds }); if (isEpochLoading || isNodeLoading) { @@ -79,7 +38,10 @@ export const NodeRewardsCard = ({ id }: INodeRewardsCardProps) => { label="Node rewards (last epoch/hour)" sx={{ height: "100%" }} > -
Loading...
+ + + +
); } @@ -90,7 +52,9 @@ export const NodeRewardsCard = ({ id }: INodeRewardsCardProps) => { label="Node rewards (last epoch/hour)" sx={{ height: "100%" }} > -
Failed to load data
+ + Failed to load node data. +
); } diff --git a/explorer-nextjs/src/components/nymNodePageComponents/QualityIndicatorsCard.tsx b/explorer-nextjs/src/components/nymNodePageComponents/QualityIndicatorsCard.tsx index 4ba57b75fb..19f65b8bac 100644 --- a/explorer-nextjs/src/components/nymNodePageComponents/QualityIndicatorsCard.tsx +++ b/explorer-nextjs/src/components/nymNodePageComponents/QualityIndicatorsCard.tsx @@ -1,14 +1,9 @@ "use client"; -import type { - GatewayStatus, - IObservatoryNode, - LastProbeResult, - NodeDescription, -} from "@/app/api/types"; -import { DATA_OBSERVATORY_NODES_URL } from "@/app/api/urls"; -import { Chip, Stack } from "@mui/material"; +import { Chip, Skeleton, Stack, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; +import { fetchGatewayStatus, fetchNodeInfo } from "../../app/api"; +import type { LastProbeResult, NodeDescription } from "../../app/api/types"; import ExplorerCard from "../cards/ExplorerCard"; import ExplorerListItem from "../list/ListItem"; import StarRating from "../starRating/StarRating"; @@ -28,38 +23,6 @@ const roleMapping: Record = { mixnode: "Mix Node", }; -// Fetch node data based on ID -const fetchNodeInfo = async (id: number): Promise => { - const response = await fetch(DATA_OBSERVATORY_NODES_URL, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch observatory nodes"); - } - - const nodes: IObservatoryNode[] = await response.json(); - return nodes.find((node) => node.node_id === id) || null; -}; - -// Fetch gateway status based on identity key -const fetchGatewayStatus = async ( - identityKey: string, -): Promise => { - const response = await fetch( - `https://mainnet-node-status-api.nymtech.cc/v2/gateways/${identityKey}`, - ); - - if (!response.ok) { - throw new Error("Failed to fetch gateway status"); - } - - return response.json(); -}; - const getNodeRoles = ( declaredRoles: NodeDescriptionNotNull["declared_role"], ): RoleString[] => { @@ -180,39 +143,50 @@ export const QualityIndicatorsCard = ({ id }: IQualityIndicatorsCardProps) => { // Fetch node info const { data: nodeInfo, - isLoading: isNodeLoading, - isError: isNodeError, + isLoading, + isError, } = useQuery({ queryKey: ["nodeInfo", id], queryFn: () => fetchNodeInfo(id), - refetchInterval: 60000, - staleTime: 60000, }); - // Fetch gateway status if nodeInfo is available + // Extract node roles once `nodeInfo` is available + const nodeRoles = nodeInfo + ? getNodeRoles(nodeInfo.description.declared_role) + : []; + + // Define whether to fetch gateway status + const shouldFetchGatewayStatus = nodeRoles.some((role) => + ["Entry Node", "Exit IPR Node", "Exit NR Node"].includes(role), + ); + + // Fetch gateway status only if `shouldFetchGatewayStatus` is true const { data: gatewayStatus } = useQuery({ queryKey: ["gatewayStatus", nodeInfo?.identity_key], - queryFn: () => fetchGatewayStatus(nodeInfo?.identity_key), - enabled: !!nodeInfo?.identity_key, // Only fetch if identity key is available + queryFn: () => fetchGatewayStatus(nodeInfo?.identity_key || ""), + enabled: !!nodeInfo?.identity_key && shouldFetchGatewayStatus, // ✅ Only fetch if needed }); - if (isNodeLoading) { + if (isLoading) { return ( -
Loading...
+ + +
); } - if (isNodeError || !nodeInfo) { + if (isError || !nodeInfo) { return ( -
Failed to load node data.
+ + Failed to load node data. +
); } - const nodeRoles = getNodeRoles(nodeInfo.description.declared_role); const NodeRoles = nodeRoles.map((role) => ( @@ -254,7 +228,7 @@ export const QualityIndicatorsCard = ({ id }: IQualityIndicatorsCardProps) => { label="Quality of service" value={} /> - {!nodeIsMixNodeOnly && ( + {!nodeIsMixNodeOnly && gatewayStatus && ( { value={} /> )} - {!nodeIsMixNodeOnly && ( + {!nodeIsMixNodeOnly && gatewayStatus && ( { - const [progress, setProgress] = React.useState(0); - - const startDate = new Date(start); - const endDate = addHours(new Date(start), 1); - const startTime = format(startDate, "HH:mm dd-MM-yyyy"); - const endTime = format(endDate, "HH:mm dd-MM-yyyy"); - - React.useEffect(() => { - // Parse the start timestamp - const startTime = new Date(start).getTime(); - const endTime = startTime + 60 * 60 * 1000; // Add 1 hour to the start time - - // Validate start timestamp - if (Number.isNaN(startTime)) { - console.error("Invalid start timestamp:", { start }); - return; - } - - // Function to calculate progress - const calculateProgress = () => { - const currentTime = Date.now(); - if (currentTime < startTime) { - return 0; - } - if (currentTime >= endTime) { - return 100; - } - const elapsed = currentTime - startTime; - const total = endTime - startTime; - return (elapsed / total) * 100; - }; - - // Set initial progress and start timer - setProgress(calculateProgress()); - const timer = setInterval(() => { - setProgress(calculateProgress()); - }, 60000); // Update every minute (60000 milliseconds) - - // Cleanup on unmount - return () => { - clearInterval(timer); - }; - }, [start]); - +const EpochProgressBar = ({ + startTime, + endTime, + progress, +}: IDynamicProgressBarProps) => { return ( - {showEpoch && ( - - - - - - - )} + + + + + + ); }; diff --git a/explorer-nextjs/src/components/redeemRewards/RedeemRewardsModal.tsx b/explorer-nextjs/src/components/redeemRewards/RedeemRewardsModal.tsx index 3c28cfc41a..98c1b09035 100644 --- a/explorer-nextjs/src/components/redeemRewards/RedeemRewardsModal.tsx +++ b/explorer-nextjs/src/components/redeemRewards/RedeemRewardsModal.tsx @@ -1,6 +1,6 @@ -import SimpleModal from "@/components/modal/SimpleModal"; -import { formatBigNum } from "@/utils/formatBigNumbers"; import { Button, Stack, Typography } from "@mui/material"; +import SimpleModal from "../../components/modal/SimpleModal"; +import { formatBigNum } from "../../utils/formatBigNumbers"; const RedeemRewardsModal = ({ totalRewardsAmount, diff --git a/explorer-nextjs/src/components/search/NodeAndAddressSearch.tsx b/explorer-nextjs/src/components/search/NodeAndAddressSearch.tsx index 9da03a1a7f..8fcc72aba5 100644 --- a/explorer-nextjs/src/components/search/NodeAndAddressSearch.tsx +++ b/explorer-nextjs/src/components/search/NodeAndAddressSearch.tsx @@ -1,10 +1,10 @@ "use client"; -import type NodeData from "@/app/api/types"; -import { NYM_NODE_BONDED } from "@/app/api/urls"; +import type { NodeData } from "@/app/api/types"; import { Search } from "@mui/icons-material"; import { Button, CircularProgress, Stack, Typography } from "@mui/material"; import { useRouter } from "next/navigation"; import { useState } from "react"; +import { NYM_NODE_BONDED } from "../../app/api/urls"; import Input from "../input/Input"; const NodeAndAddressSearch = () => { @@ -32,11 +32,19 @@ const NodeAndAddressSearch = () => { return; } } catch { - setErrorText("Such Nym address doesn't exist"); + setErrorText( + "It seems that this node or account does not exist. Please enter a complete Node ID or an existing Nym wallet address.", + ); + setIsLoading(false); // Stop loading + return; } } else { - setErrorText("Such Nym address doesn't exist"); + setErrorText( + "It seems that this node or account does not exist. Please enter a complete Node ID or an existing Nym wallet address.", + ); + setIsLoading(false); // Stop loading + return; } } else { @@ -55,12 +63,16 @@ const NodeAndAddressSearch = () => { return; } } - setErrorText("Such Nym Node identity key doesn't exist"); + setErrorText( + "It seems that this node or account does not exist. Please enter a complete Node ID or an existing Nym wallet address.", + ); + setIsLoading(false); // Stop loading } } catch (error) { - setErrorText("An unexpected error occurred. Please try again."); + setErrorText( + "It seems that this node or account does not exist. Please enter a complete Node ID or an existing Nym wallet address.", + ); console.error(error); - } finally { setIsLoading(false); // Stop loading } }; @@ -73,6 +85,11 @@ const NodeAndAddressSearch = () => { fullWidth value={inputValue} onChange={(e) => setInputValue(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + handleSearch(); + } + }} rounded /> {isLoading && } diff --git a/explorer-nextjs/src/components/staking/TotalRewardsCard.tsx b/explorer-nextjs/src/components/staking/TotalRewardsCard.tsx index c6502770e7..3017a7ca37 100644 --- a/explorer-nextjs/src/components/staking/TotalRewardsCard.tsx +++ b/explorer-nextjs/src/components/staking/TotalRewardsCard.tsx @@ -1,33 +1,12 @@ "use client"; -import type { ObservatoryBalance } from "@/app/api/types"; -import { DATA_OBSERVATORY_BALANCES_URL } from "@/app/api/urls"; -import { useNymClient } from "@/hooks/useNymClient"; -import { formatBigNum } from "@/utils/formatBigNumbers"; -import { Typography } from "@mui/material"; +import { Skeleton, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; +import { fetchTotalStakerRewards } from "../../app/api"; +import { useNymClient } from "../../hooks/useNymClient"; +import { formatBigNum } from "../../utils/formatBigNumbers"; import ExplorerCard from "../cards/ExplorerCard"; -// Fetch function to get total staker rewards -const fetchTotalStakerRewards = async (address: string): Promise => { - const response = await fetch(`${DATA_OBSERVATORY_BALANCES_URL}/${address}`, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - next: { revalidate: 60 }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch balances"); - } - - const balances: ObservatoryBalance = await response.json(); - - // Return the staking rewards amount - return Number(balances.rewards.staking_rewards.amount); -}; - const TotalRewardsCard = () => { const { address } = useNymClient(); @@ -40,8 +19,6 @@ const TotalRewardsCard = () => { queryKey: ["totalStakerRewards", address], queryFn: () => fetchTotalStakerRewards(address || ""), enabled: !!address, // Only fetch if address exists - refetchInterval: 60000, // Refetch every 60 seconds - staleTime: 60000, // Data is fresh for 60 seconds }); if (!address) { @@ -51,7 +28,7 @@ const TotalRewardsCard = () => { if (isLoading) { return ( - Loading... + ); } @@ -59,7 +36,7 @@ const TotalRewardsCard = () => { if (isError) { return ( - + Failed to load total rewards. diff --git a/explorer-nextjs/src/components/staking/TotalStakeCard.tsx b/explorer-nextjs/src/components/staking/TotalStakeCard.tsx index 218caa6d5b..020d0c73a6 100644 --- a/explorer-nextjs/src/components/staking/TotalStakeCard.tsx +++ b/explorer-nextjs/src/components/staking/TotalStakeCard.tsx @@ -1,36 +1,12 @@ "use client"; -import type { ObservatoryBalance } from "@/app/api/types"; -import { DATA_OBSERVATORY_BALANCES_URL } from "@/app/api/urls"; -import { useNymClient } from "@/hooks/useNymClient"; -import { formatBigNum } from "@/utils/formatBigNumbers"; -import { Typography } from "@mui/material"; +import { Skeleton, Typography } from "@mui/material"; import { useQuery } from "@tanstack/react-query"; +import { fetchBalances } from "../../app/api"; +import { useNymClient } from "../../hooks/useNymClient"; +import { formatBigNum } from "../../utils/formatBigNumbers"; import ExplorerCard from "../cards/ExplorerCard"; -// Fetch balances based on the address -const fetchBalances = async (address: string): Promise => { - const response = await fetch(`${DATA_OBSERVATORY_BALANCES_URL}/${address}`, { - headers: { - Accept: "application/json", - "Content-Type": "application/json; charset=utf-8", - }, - next: { revalidate: 60 }, - }); - - if (!response.ok) { - throw new Error("Failed to fetch balances"); - } - - const balances: ObservatoryBalance = await response.json(); - - // Calculate total stake - return ( - Number(balances.rewards.staking_rewards.amount) + - Number(balances.delegated.amount) - ); -}; - const TotalStakeCard = () => { const { address } = useNymClient(); @@ -43,8 +19,6 @@ const TotalStakeCard = () => { queryKey: ["totalStake", address], queryFn: () => fetchBalances(address || ""), enabled: !!address, // Only fetch if address exists - refetchInterval: 60000, // Refetch every 60 seconds - staleTime: 60000, // Data is fresh for 60 seconds }); if (!address) { @@ -54,7 +28,7 @@ const TotalStakeCard = () => { if (isLoading) { return ( - Loading... + ); } @@ -62,7 +36,7 @@ const TotalStakeCard = () => { if (isError) { return ( - + Failed to load total stake. diff --git a/explorer-nextjs/src/components/staking/schemas.ts b/explorer-nextjs/src/components/staking/schemas.ts index b97e3b5cd0..179167a120 100644 --- a/explorer-nextjs/src/components/staking/schemas.ts +++ b/explorer-nextjs/src/components/staking/schemas.ts @@ -1,5 +1,5 @@ -import { validateAmount } from "@/utils/currency"; import { z } from "zod"; +import { validateAmount } from "../../utils/currency"; const MIN_AMOUNT_TO_DELEGATE = "10"; const fee = { gas: "1000000", amount: [{ amount: "1000000", denom: "unym" }] }; @@ -36,7 +36,6 @@ const stakingSchema = z (data) => { const balance = Number.parseFloat(data.balance); const amount = Number.parseFloat(data.amount); - console.log(balance); return balance - amount >= 0; }, { diff --git a/explorer-nextjs/src/components/wallet/ConnectWallet.tsx b/explorer-nextjs/src/components/wallet/ConnectWallet.tsx index a7c18ee472..33308e77e7 100644 --- a/explorer-nextjs/src/components/wallet/ConnectWallet.tsx +++ b/explorer-nextjs/src/components/wallet/ConnectWallet.tsx @@ -1,6 +1,5 @@ "use client"; -import { COSMOS_KIT_USE_CHAIN } from "@/config"; import { useChain } from "@cosmos-kit/react"; import { Box, @@ -9,6 +8,7 @@ import { IconButton, Typography, } from "@mui/material"; +import { COSMOS_KIT_USE_CHAIN } from "../../config"; import Cross from "../icons/Cross"; import { WalletAddress } from "./WalletAddress"; import { WalletBalance } from "./WalletBalance"; diff --git a/explorer-nextjs/src/components/wallet/WalletAddress.tsx b/explorer-nextjs/src/components/wallet/WalletAddress.tsx index 9fd1020c99..8e0a2c5444 100644 --- a/explorer-nextjs/src/components/wallet/WalletAddress.tsx +++ b/explorer-nextjs/src/components/wallet/WalletAddress.tsx @@ -1,6 +1,6 @@ -import { Elips } from "@/components/icons/Elips"; import { Stack, Typography } from "@mui/material"; import React from "react"; +import { Elips } from "../../components/icons/Elips"; export const trimAddress = (address = "", trimBy = 6) => `${address.slice(0, trimBy)}...${address.slice(-trimBy)}`; diff --git a/explorer-nextjs/src/components/wallet/WalletBalance.tsx b/explorer-nextjs/src/components/wallet/WalletBalance.tsx index 0ece0250c5..e233f2b090 100644 --- a/explorer-nextjs/src/components/wallet/WalletBalance.tsx +++ b/explorer-nextjs/src/components/wallet/WalletBalance.tsx @@ -1,10 +1,32 @@ -import { Token } from "@/components/icons/Token"; -import useGetWalletBalance from "@/hooks/useGetWalletBalance"; +"use client"; +import { COSMOS_KIT_USE_CHAIN } from "@/config"; +import { useChain } from "@cosmos-kit/react"; import { Stack, Typography } from "@mui/material"; import React from "react"; +import { Token } from "../../components/icons/Token"; +import useGetWalletBalance from "../../hooks/useGetWalletBalance"; export const WalletBalance = () => { - const { formattedBalance } = useGetWalletBalance(); + const { isWalletConnected } = useChain(COSMOS_KIT_USE_CHAIN); + const { formattedBalance, isLoading, isError, refetch } = + useGetWalletBalance(); + + if (isLoading) { + return ( + + + + Loading... + + + ); + } + if (isError) { + if (isWalletConnected) { + refetch(); + } + return; + } return ( diff --git a/explorer-nextjs/src/data/blog-template-1.json b/explorer-nextjs/src/data/blog-template-1.json index df75ba738a..42e544594b 100644 --- a/explorer-nextjs/src/data/blog-template-1.json +++ b/explorer-nextjs/src/data/blog-template-1.json @@ -1,5 +1,5 @@ { - "title": "Welcome to the allNym Network Explorer", + "title": "Welcome to the Nym Network Explorer", "label": "Onboarding", "description": "Let's unpack the key features of the long awaited features together!", "attributes": { @@ -36,7 +36,7 @@ }, { "type": "paragraph", - "text": "**Designed in a mobile first way, with integrated onboarding articles and live network data. The application is intentionally kept lightweight and barebones, while opening up a new social interaction layer of the mixnet. The NGM Explorer is currently in open beta release, which means that each released version of the app is live-tested by the core community of Nym node operators, where future features and updates are informed by feedback on the Node Operators forum." + "text": "Designed in a mobile first way, with integrated onboarding articles and live network data. The application is intentionally kept lightweight and barebones, while opening up a new social interaction layer of the mixnet. The NGM Explorer is currently in open beta release, which means that each released version of the app is live-tested by the core community of Nym node operators, where future features and updates are informed by feedback on the Node Operators forum." } ] }, diff --git a/explorer-nextjs/src/hooks/useGetPendingEvents.tsx b/explorer-nextjs/src/hooks/useGetPendingEvents.tsx new file mode 100644 index 0000000000..72902b7106 --- /dev/null +++ b/explorer-nextjs/src/hooks/useGetPendingEvents.tsx @@ -0,0 +1,53 @@ +import type { PendingEpochEventKind } from "@nymproject/contract-clients/Mixnet.types"; +import { useQuery } from "@tanstack/react-query"; + +export const getEventsByAddress = ( + kind: PendingEpochEventKind, + address: string, +) => { + if ("delegate" in kind && kind.delegate.owner === address) { + return { + kind: "delegate" as const, + mixId: kind.delegate.node_id, + amount: kind.delegate.amount, + }; + } + + if ("undelegate" in kind && kind.undelegate.owner === address) { + return { + kind: "undelegate" as const, + mixId: kind.undelegate.node_id, + }; + } + + return undefined; +}; +export type PendingEvent = ReturnType; + +// Custom Hook for fetching pending events +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const usePendingEvents = (nymQueryClient: any, address: string | undefined) => { + return useQuery({ + queryKey: ["pendingEvents", address], // Query key to uniquely identify this query + queryFn: async () => { + if (!nymQueryClient || !address) { + throw new Error("Missing required dependencies"); + } + + const response = await nymQueryClient.getPendingEpochEvents({}); + const pendingEvents: PendingEvent[] = []; + + for (const e of response.events) { + const event = getEventsByAddress(e.event.kind, address); + if (event) { + pendingEvents.push(event); + } + } + + return pendingEvents; + }, + enabled: !!nymQueryClient && !!address, // Prevents execution if dependencies are missing + }); +}; + +export default usePendingEvents; diff --git a/explorer-nextjs/src/hooks/useGetWalletBalance.tsx b/explorer-nextjs/src/hooks/useGetWalletBalance.tsx index 149fd7be0e..195937be0f 100644 --- a/explorer-nextjs/src/hooks/useGetWalletBalance.tsx +++ b/explorer-nextjs/src/hooks/useGetWalletBalance.tsx @@ -1,46 +1,42 @@ -import { COSMOS_KIT_USE_CHAIN } from "@/config"; -import { unymToNym } from "@/utils/currency"; import { useChain } from "@cosmos-kit/react"; -import { useCallback, useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { COSMOS_KIT_USE_CHAIN } from "../config"; +import { unymToNym } from "../utils/currency"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const fetchNYMBalance = async (address: string, getCosmWasmClient: any) => { + if (!address) return { NYMBalance: "0", formattedBalance: "-" }; + + const account = await getCosmWasmClient(); + const uNYMBalance = await account.getBalance(address, "unym"); + const NYMBalance = unymToNym(uNYMBalance.amount); + if (!NYMBalance) return; + const formattedBalance = Intl.NumberFormat().format(+NYMBalance); + + return { NYMBalance, formattedBalance }; +}; const useGetWalletBalance = () => { - const [balance, setBalance] = useState("0"); - const [formattedBalance, setFormattedBalance] = useState("-"); const { getCosmWasmClient, address } = useChain(COSMOS_KIT_USE_CHAIN); - const getNYMBalance = useCallback( - async (address: string) => { - const account = await getCosmWasmClient(); - const uNYMBalance = await account.getBalance(address, "unym"); - const NYMBalance = unymToNym(uNYMBalance.amount); - if (!NYMBalance) { - return undefined; - } - const formattedBalance = Intl.NumberFormat().format(+NYMBalance); - return { - NYMBalance, - formattedBalance, - }; - }, - [getCosmWasmClient], - ); + const { + data = { NYMBalance: "0", formattedBalance: "-" }, + isLoading, + isError, + refetch, + } = useQuery({ + queryKey: ["nymBalance", address], + queryFn: () => fetchNYMBalance(address || "", getCosmWasmClient), + enabled: !!address, // Only fetch if address exists + }); - useEffect(() => { - if (!address) { - return; - } - - getNYMBalance(address) - .then((balance) => { - setFormattedBalance(balance?.formattedBalance || "-"); - setBalance(balance?.NYMBalance || "0"); - }) - .catch((e) => { - console.error("Failed to get balance", e); - }); - }, [address, getNYMBalance]); - - return { balance, formattedBalance }; + return { + balance: data.NYMBalance, + formattedBalance: data.formattedBalance, + isLoading, + isError, + refetch, // Expose refetch function if needed + }; }; export default useGetWalletBalance; diff --git a/explorer-nextjs/src/hooks/useNymClient.ts b/explorer-nextjs/src/hooks/useNymClient.ts index 59ae877159..5224190e3f 100644 --- a/explorer-nextjs/src/hooks/useNymClient.ts +++ b/explorer-nextjs/src/hooks/useNymClient.ts @@ -1,6 +1,5 @@ "use client"; -import { COSMOS_KIT_USE_CHAIN, NYM_MIXNET_CONTRACT } from "@/config"; import { useChain } from "@cosmos-kit/react"; import { contracts } from "@nymproject/contract-clients"; import type { @@ -8,6 +7,7 @@ import type { MixnetQueryClient, } from "@nymproject/contract-clients/Mixnet.client"; import { useEffect, useState } from "react"; +import { COSMOS_KIT_USE_CHAIN, NYM_MIXNET_CONTRACT } from "../config"; export const useNymClient = () => { const [nymClient, setNymClient] = useState(); diff --git a/explorer-nextjs/src/providers/EpochProvider.tsx b/explorer-nextjs/src/providers/EpochProvider.tsx new file mode 100644 index 0000000000..b69cce8457 --- /dev/null +++ b/explorer-nextjs/src/providers/EpochProvider.tsx @@ -0,0 +1,109 @@ +"use client"; + +import { fetchCurrentEpoch } from "@/app/api"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { differenceInMilliseconds } from "date-fns"; +import { createContext, useContext, useEffect, useState } from "react"; + +type EpochStatus = "active" | "pending"; + +export type EpochResponseData = + | Awaited> + | undefined; + +type EpochContext = { + epochStatus: EpochStatus; + data: EpochResponseData; + isError: boolean; + isLoading: boolean; +}; + +const initialState = { + epochStatus: "pending" as const, + data: undefined, + isError: false, + isLoading: false, +}; + +const EpochContext = createContext(initialState); + +const checkIsEpochTimeValid = (epochEndTime: string) => + new Date(epochEndTime) >= new Date(); + +const calculateRefetchInterval = (epochEndTime: string) => { + return differenceInMilliseconds(new Date(epochEndTime), new Date()); +}; + +const useEpochContext = () => { + const context = useContext(EpochContext); + + if (context === undefined) { + throw new Error("useEpochContext must be used within a EpochProvider"); + } + + return context; +}; + +const EpochProvider = ({ children }: { children: React.ReactNode }) => { + const [epochStatus, setEpochStatus] = useState("pending"); + + const QueryClient = useQueryClient(); + + const { data, isError, isLoading } = useQuery({ + refetchOnWindowFocus: false, + queryKey: ["currentEpoch"], + queryFn: fetchCurrentEpoch, + refetchInterval: ({ state }) => { + // refetchInterval can be set dynamically based on the current state + + // if there is no data, refetch in 30 secs + if (!state.data) { + return 30_000; + } + + const isEpochTimeValid = checkIsEpochTimeValid( + state.data.current_epoch_end.toString(), + ); + + // if epoch time is not valid (i.e current_time > epoch_start_time) refetch in 30 secs + if (!isEpochTimeValid) { + setEpochStatus("pending"); + return 30_000; + } + + // if epoch time is valid, refetch based on the epoch end time + const newRefetchInterval = calculateRefetchInterval( + state.data.current_epoch_end.toString(), + ); + + setEpochStatus("active"); + return newRefetchInterval; + }, + }); + + useEffect(() => { + const refreshQueries = async () => { + await QueryClient.invalidateQueries({ + predicate: (query) => query.queryKey[0] !== "currentEpoch", + }); + }; + + // when new epoch starts, refresh all data + if (epochStatus === "active") { + refreshQueries(); + } + }, [epochStatus, QueryClient]); + + const value = { + epochStatus, + data, + isError, + isLoading, + }; + + return ( + {children} + ); +}; + +export { EpochProvider, useEpochContext }; diff --git a/explorer-nextjs/src/providers/ThemeProvider.tsx b/explorer-nextjs/src/providers/ThemeProvider.tsx index 71bda454ae..1cc4fa2b9a 100644 --- a/explorer-nextjs/src/providers/ThemeProvider.tsx +++ b/explorer-nextjs/src/providers/ThemeProvider.tsx @@ -1,8 +1,8 @@ "use client"; -import { lightTheme } from "@/theme/theme"; import { CssBaseline } from "@mui/material"; import { ThemeProvider as MUIThemeProvider } from "@mui/material"; import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter"; +import { lightTheme } from "../theme/theme"; const ThemeProvider = ({ children }: { children: React.ReactNode }) => { return ( diff --git a/explorer-nextjs/src/providers/index.tsx b/explorer-nextjs/src/providers/index.tsx index 1eee2c958b..1708bdf010 100644 --- a/explorer-nextjs/src/providers/index.tsx +++ b/explorer-nextjs/src/providers/index.tsx @@ -1,13 +1,16 @@ import CosmosKitProvider from "./CosmosKitProvider"; +import { EpochProvider } from "./EpochProvider"; import { QueryProvider } from "./QueryProvider"; import ThemeProvider from "./ThemeProvider"; const Providers = ({ children }: { children: React.ReactNode }) => { return ( - - {children} - + + + {children} + + ); }; diff --git a/explorer-nextjs/src/theme/typography.ts b/explorer-nextjs/src/theme/typography.ts index 9fa3320a39..372fe8a5b7 100644 --- a/explorer-nextjs/src/theme/typography.ts +++ b/explorer-nextjs/src/theme/typography.ts @@ -1,4 +1,4 @@ -import { labGrotesque, labGrotesqueMono } from "@/fonts"; +import { labGrotesque, labGrotesqueMono } from "../fonts"; export const headings = { display: { diff --git a/explorer-nextjs/src/typings/react-query.d.ts b/explorer-nextjs/src/typings/react-query.d.ts deleted file mode 100644 index 33d788a7a2..0000000000 --- a/explorer-nextjs/src/typings/react-query.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "@tanstack/react-query"; diff --git a/yarn.lock b/yarn.lock index 1ba94b4b12..72496a5171 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15,6 +15,17 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@asamuzakjp/css-color@^2.8.2": + version "2.8.3" + resolved "https://registry.yarnpkg.com/@asamuzakjp/css-color/-/css-color-2.8.3.tgz#665f0f5e8edb95d8f543847529e30fe5cc437ef7" + integrity sha512-GIc76d9UI1hCvOATjZPyHFmE5qhRccp3/zGfMPapK3jBi+yocEzp6BBB0UnfRYP9NP4FANqUZYb0hnfs3TM3hw== + dependencies: + "@csstools/css-calc" "^2.1.1" + "@csstools/css-color-parser" "^3.0.7" + "@csstools/css-parser-algorithms" "^3.0.4" + "@csstools/css-tokenizer" "^3.0.3" + lru-cache "^10.4.3" + "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" @@ -1976,6 +1987,34 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@csstools/color-helpers@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.0.1.tgz#829f1c76f5800b79c51c709e2f36821b728e0e10" + integrity sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA== + +"@csstools/css-calc@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-2.1.1.tgz#a7dbc66627f5cf458d42aed14bda0d3860562383" + integrity sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag== + +"@csstools/css-color-parser@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.0.7.tgz#442d61d58e54ad258d52c309a787fceb33906484" + integrity sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA== + dependencies: + "@csstools/color-helpers" "^5.0.1" + "@csstools/css-calc" "^2.1.1" + +"@csstools/css-parser-algorithms@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz#74426e93bd1c4dcab3e441f5cc7ba4fb35d94356" + integrity sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A== + +"@csstools/css-tokenizer@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz#a5502c8539265fecbd873c1e395a890339f119c2" + integrity sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw== + "@dao-dao/cosmiframe@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@dao-dao/cosmiframe/-/cosmiframe-0.1.0.tgz#5ba241d0c14a45d62df60cff7d48ba26844ebeac" @@ -7652,6 +7691,11 @@ dependencies: "@tanstack/query-devtools" "5.64.2" +"@tanstack/react-query-next-experimental@^5.66.0": + version "5.66.0" + resolved "https://registry.yarnpkg.com/@tanstack/react-query-next-experimental/-/react-query-next-experimental-5.66.0.tgz#ef7e0fabdf40edbf43f264dcf323ece7c0c8d288" + integrity sha512-BCBlqWVtCSev1Bmk5QJs99gN7TsiLGgRsg4efaI/P9zwPD9EmpvltMNixyMb/mwojDX+n6xMKZ8EKkXSgFp32w== + "@tanstack/react-query@^5.64.2": version "5.64.2" resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.64.2.tgz#199c8a5a8ff92a8565f8cdd378747398347512a2" @@ -8064,6 +8108,13 @@ resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== +"@types/debug@^4.0.0": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + "@types/eslint-scope@^3.7.7": version "3.7.7" resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" @@ -8080,6 +8131,13 @@ "@types/estree" "*" "@types/json-schema" "*" +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== + dependencies: + "@types/estree" "*" + "@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" @@ -8177,6 +8235,13 @@ dependencies: "@types/unist" "^2" +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + "@types/html-minifier-terser@^5.0.0": version "5.1.2" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz#693b316ad323ea97eed6b38ed1a3cc02b1672b57" @@ -8266,6 +8331,13 @@ dependencies: "@types/unist" "^2" +"@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + "@types/mime@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" @@ -8286,6 +8358,11 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + "@types/node-fetch@^2.5.7": version "2.6.12" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" @@ -8493,6 +8570,11 @@ dependencies: "@types/jest" "*" +"@types/trusted-types@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" + integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== + "@types/uglify-js@*": version "3.17.5" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.17.5.tgz#905ce03a3cbbf2e31cbefcbc68d15497ee2e17df" @@ -8500,6 +8582,11 @@ dependencies: source-map "^0.6.1" +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + "@types/unist@^2", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": version "2.0.11" resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" @@ -8751,6 +8838,11 @@ resolved "https://registry.yarnpkg.com/@uidotdev/usehooks/-/usehooks-2.4.1.tgz#4b733eaeae09a7be143c6c9ca158b56cc1ea75bf" integrity sha512-1I+RwWyS+kdv3Mv0Vmc+p0dPYH0DTRAo04HLyXReYBL9AeseDWUJyi4THuksBJcu9F0Pih69Ak150VDnqbVnXg== +"@ungap/structured-clone@^1.0.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== + "@ungap/structured-clone@^1.2.0": version "1.2.1" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.1.tgz#28fa185f67daaf7b7a1a8c1d445132c5d979f8bd" @@ -9291,6 +9383,11 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" +agent-base@^7.1.0, agent-base@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" + integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== + agentkeepalive@^4.2.1: version "4.6.0" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" @@ -10113,6 +10210,11 @@ bail@^1.0.0: resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -10781,6 +10883,11 @@ ccount@^1.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + chain-registry@^1.69.64: version "1.69.78" resolved "https://registry.yarnpkg.com/chain-registry/-/chain-registry-1.69.78.tgz#1f599e6deea62a5a61a29291e958523e70437d03" @@ -10837,21 +10944,41 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + character-entities-legacy@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + character-entities@^1.0.0: version "1.2.4" resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + character-reference-invalid@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + chardet@^0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" @@ -11219,6 +11346,11 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + commander@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" @@ -12000,6 +12132,14 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" +cssstyle@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.2.1.tgz#5142782410fea95db66fb68147714a652a7c2381" + integrity sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw== + dependencies: + "@asamuzakjp/css-color" "^2.8.2" + rrweb-cssom "^0.8.0" + csstype@^3.0.2, csstype@^3.0.7, csstype@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" @@ -12170,6 +12310,14 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +data-urls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" + integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== + dependencies: + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + data-view-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" @@ -12221,7 +12369,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@4, debug@^4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7: +debug@4, debug@^4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.3.7: version "4.4.0" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== @@ -12263,6 +12411,18 @@ decimal.js@10, decimal.js@^10.2.1: resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== +decimal.js@^10.4.3: + version "10.5.0" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.5.0.tgz#0f371c7cf6c4898ce0afb09836db73cd82010f22" + integrity sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw== + +decode-named-character-reference@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e" + integrity sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg== + dependencies: + character-entities "^2.0.0" + decode-uri-component@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" @@ -12449,6 +12609,11 @@ deprecation@^2.0.0: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== +dequal@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + des.js@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.1.0.tgz#1d37f5766f3bbff4ee9638e871a8768c173b81da" @@ -12514,6 +12679,13 @@ detect-port@^1.3.0: address "^1.0.1" debug "4" +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + diff-sequences@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" @@ -12669,6 +12841,13 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" +dompurify@^3.2.3: + version "3.2.4" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.4.tgz#af5a5a11407524431456cf18836c55d13441cd8e" + integrity sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg== + optionalDependencies: + "@types/trusted-types" "^2.0.7" + domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" @@ -13613,6 +13792,11 @@ estree-to-babel@^3.1.0: "@babel/types" "^7.2.0" c8 "^7.6.0" +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + estree-walker@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362" @@ -14341,7 +14525,7 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@^4.0.0: +form-data@^4.0.0, form-data@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== @@ -15086,6 +15270,27 @@ hast-util-raw@6.0.1: xtend "^4.0.0" zwitch "^1.0.0" +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz#6d11b027473e69adeaa00ca4cfb5bb68e3d282fa" + integrity sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + hast-util-to-parse5@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" @@ -15097,6 +15302,13 @@ hast-util-to-parse5@^6.0.0: xtend "^4.0.0" zwitch "^1.0.0" +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + hastscript@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" @@ -15182,6 +15394,13 @@ html-encoding-sniffer@^2.0.1: dependencies: whatwg-encoding "^1.0.5" +html-encoding-sniffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" + integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== + dependencies: + whatwg-encoding "^3.1.1" + html-entities@^2.1.0, html-entities@^2.3.2: version "2.5.2" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" @@ -15223,6 +15442,11 @@ html-tags@^3.1.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== +html-url-attributes@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" + integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== + html-void-elements@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" @@ -15347,6 +15571,14 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + http-proxy-middleware@^2.0.3, http-proxy-middleware@^2.0.6: version "2.0.7" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" @@ -15380,6 +15612,14 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +https-proxy-agent@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -15404,7 +15644,7 @@ iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: +iconv-lite@0.6.3, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -15564,6 +15804,11 @@ inline-style-parser@0.1.1: resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== +inline-style-parser@0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.4.tgz#f4af5fe72e612839fcd453d989a586566d695f22" + integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q== + inquirer-autocomplete-prompt@^0.11.1: version "0.11.1" resolved "https://registry.yarnpkg.com/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-0.11.1.tgz#f90ca9510a4c489882e9be294934bd8c2e575e09" @@ -15735,6 +15980,11 @@ is-alphabetical@1.0.4, is-alphabetical@^1.0.0: resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + is-alphanumerical@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" @@ -15743,6 +15993,14 @@ is-alphanumerical@^1.0.0: is-alphabetical "^1.0.0" is-decimal "^1.0.0" +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + is-arguments@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.2.0.tgz#ad58c6aecf563b78ef2bf04df540da8f5d7d8e1b" @@ -15878,6 +16136,11 @@ is-decimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + is-descriptor@^0.1.0: version "0.1.7" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.7.tgz#2727eb61fd789dcd5bdf0ed4569f551d2fe3be33" @@ -15982,6 +16245,11 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + is-in-browser@^1.0.2, is-in-browser@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835" @@ -16081,6 +16349,11 @@ is-plain-obj@^3.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + is-plain-object@5.0.0, is-plain-object@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" @@ -16284,6 +16557,14 @@ isobject@^4.0.0: resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== +isomorphic-dompurify@^2.21.0: + version "2.21.0" + resolved "https://registry.yarnpkg.com/isomorphic-dompurify/-/isomorphic-dompurify-2.21.0.tgz#57369b409bd72a67c111d065eae412610725588b" + integrity sha512-IjBG/D6ApEiO6ciXvrzPqbE4xOLbjRTshSWRSeeycQVhvsxf1O9PhA7LoqwMYS0/2PKLNFJRugwsNV5UihtLxQ== + dependencies: + dompurify "^3.2.3" + jsdom "^26.0.0" + isomorphic-unfetch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" @@ -17371,6 +17652,33 @@ jsdom@^16.6.0: ws "^7.4.6" xml-name-validator "^3.0.0" +jsdom@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-26.0.0.tgz#446dd1ad8cfc50df7e714e58f1f972c1763b354c" + integrity sha512-BZYDGVAIriBWTpIxYzrXjv3E/4u8+/pSG5bQdIYCbNCGOvsPkDQfTVLAIXAf9ETdCpduCVTkDe2NNZ8NIwUVzw== + dependencies: + cssstyle "^4.2.1" + data-urls "^5.0.0" + decimal.js "^10.4.3" + form-data "^4.0.1" + html-encoding-sniffer "^4.0.0" + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.6" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.16" + parse5 "^7.2.1" + rrweb-cssom "^0.8.0" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^5.0.0" + w3c-xmlserializer "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^3.1.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.1.0" + ws "^8.18.0" + xml-name-validator "^5.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -18114,6 +18422,11 @@ long@^5.2.0: resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== + loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -18377,6 +18690,74 @@ mdast-util-definitions@^4.0.0: dependencies: unist-util-visit "^2.0.0" +mdast-util-from-markdown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a" + integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-jsx@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + mdast-util-to-hast@10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb" @@ -18391,11 +18772,48 @@ mdast-util-to-hast@10.0.1: unist-util-position "^3.0.0" unist-util-visit "^2.0.0" +mdast-util-to-hast@^13.0.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4" + integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +mdast-util-to-markdown@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" + integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + mdast-util-to-string@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -18543,6 +18961,200 @@ microevent.ts@~0.1.1: resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== +micromark-core-commonmark@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz#6a45bbb139e126b3f8b361a10711ccc7c6e15e93" + integrity sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-space@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz#50d8ca981373c717f497dc64a0dbfccce6c03ed2" + integrity sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.1.tgz#a3edfda3022c6c6b55bfb049ef5b75d70af50709" + integrity sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ== + +micromark@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.1.tgz#294c2f12364759e5f9e925a767ae3dfde72223ff" + integrity sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -19500,7 +20112,7 @@ num2fraction@^1.2.2: resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg== -nwsapi@^2.2.0: +nwsapi@^2.2.0, nwsapi@^2.2.16: version "2.2.16" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.16.tgz#177760bba02c351df1d2644e220c31dfec8cdb43" integrity sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ== @@ -20092,6 +20704,19 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== + dependencies: + "@types/unist" "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + parse-json@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" @@ -20141,6 +20766,13 @@ parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== + dependencies: + entities "^4.5.0" + parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -21136,6 +21768,11 @@ property-information@^5.0.0, property-information@^5.3.0: dependencies: xtend "^4.0.0" +property-information@^6.0.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec" + integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== + protobufjs@^6.11.2, protobufjs@^6.8.8, protobufjs@~6.11.2, protobufjs@~6.11.3: version "6.11.4" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" @@ -21503,6 +22140,22 @@ react-is@^19.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.0.0.tgz#d6669fd389ff022a9684f708cf6fa4962d1fea7a" integrity sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g== +react-markdown@^9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-9.0.3.tgz#c12bf60dad05e9bf650b86bcc612d80636e8456e" + integrity sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw== + dependencies: + "@types/hast" "^3.0.0" + devlop "^1.0.0" + hast-util-to-jsx-runtime "^2.0.0" + html-url-attributes "^3.0.0" + mdast-util-to-hast "^13.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + unified "^11.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + react-merge-refs@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" @@ -21994,6 +22647,27 @@ remark-parse@8.0.3: vfile-location "^3.0.0" xtend "^4.0.1" +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-rehype@^11.0.0: + version "11.1.1" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.1.tgz#f864dd2947889a11997c0a2667cd6b38f685bca7" + integrity sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + remark-slug@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/remark-slug/-/remark-slug-6.1.0.tgz#0503268d5f0c4ecb1f33315c00465ccdd97923ce" @@ -22282,6 +22956,11 @@ rollup@^3.17.2, rollup@^3.2.1, rollup@^3.9.1: optionalDependencies: fsevents "~2.3.2" +rrweb-cssom@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz#3021d1b4352fbf3b614aaeed0bc0d5739abe0bc2" + integrity sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw== + rsvp@^4.8.4: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -22419,6 +23098,13 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + scheduler@0.25.0-rc-66855b96-20241106: version "0.25.0-rc-66855b96-20241106" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.25.0-rc-66855b96-20241106.tgz#8bbb728eca4de5a5deca1f18370fbce41aee91d1" @@ -23036,6 +23722,11 @@ space-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== +space-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + spdx-correct@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" @@ -23397,6 +24088,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1", 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" @@ -23515,6 +24214,13 @@ style-to-object@0.3.0, style-to-object@^0.3.0: dependencies: inline-style-parser "0.1.1" +style-to-object@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.8.tgz#67a29bca47eaa587db18118d68f9d95955e81292" + integrity sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g== + dependencies: + inline-style-parser "0.2.4" + styled-jsx@5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.6.tgz#83b90c077e6c6a80f7f5e8781d0f311b2fe41499" @@ -23929,6 +24635,18 @@ tinyrainbow@^1.2.0: resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== +tldts-core@^6.1.76: + version "6.1.76" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.76.tgz#1ea632bbf11b288645dd7e2f3fb9cb6fa41e6bd7" + integrity sha512-uzhJ02RaMzgQR3yPoeE65DrcHI6LoM4saUqXOt/b5hmb3+mc4YWpdSeAQqVqRUlQ14q8ZuLRWyBR1ictK1dzzg== + +tldts@^6.1.32: + version "6.1.76" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.76.tgz#c8b60fba55ca78e1c228bead4f69d18731526079" + integrity sha512-6U2ti64/nppsDxQs9hw8ephA3nO6nSQvVVfxwRw8wLQPFtLI1cFI1a1eP22g+LUP+1TA2pKKjUTwWB+K2coqmQ== + dependencies: + tldts-core "^6.1.76" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -24018,6 +24736,13 @@ tough-cookie@^4.0.0: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.0.tgz#0667b0f2fbb5901fe6f226c3e0b710a9a4292f87" + integrity sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg== + dependencies: + tldts "^6.1.32" + tr46@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" @@ -24025,11 +24750,23 @@ tr46@^2.1.0: dependencies: punycode "^2.1.1" +tr46@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" + integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== + dependencies: + punycode "^2.3.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" @@ -24055,6 +24792,11 @@ trough@^1.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== +trough@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" + integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== + ts-api-utils@^1.3.0: version "1.4.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" @@ -24504,6 +25246,19 @@ unified@9.2.0: trough "^1.0.0" vfile "^4.0.0" +unified@^11.0.0: + version "11.0.5" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -24571,11 +25326,25 @@ unist-util-is@^4.0.0: resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-position@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + unist-util-remove-position@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" @@ -24597,6 +25366,13 @@ unist-util-stringify-position@^2.0.0: dependencies: "@types/unist" "^2.0.2" +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + unist-util-visit-parents@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" @@ -24605,6 +25381,14 @@ unist-util-visit-parents@^3.0.0: "@types/unist" "^2.0.0" unist-util-is "^4.0.0" +unist-util-visit-parents@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit@2.0.3, unist-util-visit@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" @@ -24614,6 +25398,15 @@ unist-util-visit@2.0.3, unist-util-visit@^2.0.0: unist-util-is "^4.0.0" unist-util-visit-parents "^3.0.0" +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + universal-user-agent@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.1.tgz#15f20f55da3c930c57bddbf1734c6654d5fd35aa" @@ -24875,6 +25668,14 @@ vfile-message@^2.0.0: "@types/unist" "^2.0.0" unist-util-stringify-position "^2.0.0" +vfile-message@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181" + integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + vfile@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" @@ -24885,6 +25686,14 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" +vfile@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + victory-vendor@^36.6.8: version "36.9.2" resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.2.tgz#668b02a448fa4ea0f788dbf4228b7e64669ff801" @@ -24939,6 +25748,13 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" +w3c-xmlserializer@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" + integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== + dependencies: + xml-name-validator "^5.0.0" + walker@^1.0.7, walker@^1.0.8, walker@~1.0.5: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -25033,6 +25849,11 @@ webidl-conversions@^6.1.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + webpack-cli@^4.8.0: version "4.10.0" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" @@ -25282,11 +26103,31 @@ whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +whatwg-url@^14.0.0, whatwg-url@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.1.0.tgz#fffebec86cc8e6c2a657e50dc606207b870f0ab3" + integrity sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w== + dependencies: + tr46 "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -25538,7 +26379,7 @@ ws@^7, ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.13.0, ws@^8.14.2, ws@^8.17.1, ws@^8.2.3, ws@~8.18.0: +ws@^8.13.0, ws@^8.14.2, ws@^8.17.1, ws@^8.18.0, ws@^8.2.3, ws@~8.18.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== @@ -25555,6 +26396,11 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml-name-validator@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" + integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== + xml2js@^0.6.1: version "0.6.2" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" @@ -25712,6 +26558,11 @@ zwitch@^1.0.0: resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== +zwitch@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== + zxcvbn@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/zxcvbn/-/zxcvbn-4.4.2.tgz#28ec17cf09743edcab056ddd8b1b06262cc73c30"