refactor EnvironmentProvider, add environment to NodesTable

This commit is contained in:
Yana
2025-06-16 20:57:20 +03:00
parent 6268544c5b
commit 202ca37745
6 changed files with 113 additions and 63 deletions
+17 -12
View File
@@ -22,6 +22,7 @@ import {
NYM_ACCOUNT_ADDRESS,
NYM_PRICES_API,
OBSERVATORY_GATEWAYS_URL,
SANDBOX_NS_API_NODES,
} from "./urls";
// Fetch function for epoch rewards
@@ -118,7 +119,9 @@ export const fetchBalances = async (address: string): Promise<number> => {
};
// Fetch function to get total staker rewards
export const fetchTotalStakerRewards = async (address: string): Promise<number> => {
export const fetchTotalStakerRewards = async (
address: string
): Promise<number> => {
const response = await fetch(`${DATA_OBSERVATORY_BALANCES_URL}/${address}`, {
headers: {
Accept: "application/json",
@@ -205,8 +208,13 @@ export const fetchNymPrice = async (): Promise<NymTokenomics> => {
return data;
};
export const fetchNSApiNodes = async (): Promise<NS_NODE[]> => {
if (!NS_API_NODES) {
export const fetchNSApiNodes = async (
environment: Environment
): Promise<NS_NODE[]> => {
const baseUrl =
environment === "sandbox" ? SANDBOX_NS_API_NODES : NS_API_NODES;
if (!baseUrl) {
throw new Error("NS_API_NODES URL is not defined");
}
@@ -217,15 +225,12 @@ export const fetchNSApiNodes = async (): Promise<NS_NODE[]> => {
let hasMoreData = true;
while (hasMoreData) {
const response = await fetch(
`${NS_API_NODES}?page=${page}&size=${PAGE_SIZE}`,
{
headers: {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
},
}
);
const response = await fetch(`${baseUrl}?page=${page}&size=${PAGE_SIZE}`, {
headers: {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
},
});
if (!response.ok) {
throw new Error(
@@ -8,7 +8,6 @@ export const Environment: React.FC = () => {
const theme = useTheme();
const { environment, setEnvironment } = useEnvironment();
console.log("environment", environment);
const explorerName = environment
? `${environment} Explorer`
@@ -1,5 +1,5 @@
"use client";
import React from "react";
import React, { useEffect } from "react";
import {
Box,
Button,
@@ -14,7 +14,9 @@ import FilterAltIcon from "@mui/icons-material/FilterAlt";
import AccessTimeIcon from "@mui/icons-material/AccessTime";
import PieChartIcon from "@mui/icons-material/PieChart";
import PercentIcon from "@mui/icons-material/Percent";
import NodeFilterButtonGroup from "../toggleButton/NodeFilterButtonGroup";
import NodeFilterButtonGroup, {
type Option,
} from "../toggleButton/NodeFilterButtonGroup";
import { RECOMMENDED_NODES } from "@/app/constants";
type AdvancedFiltersProps = {
@@ -36,6 +38,7 @@ type AdvancedFiltersProps = {
mixnodes: number;
gateways: number;
};
environment: "mainnet" | "sandbox";
};
export default function AdvancedFilters({
@@ -51,6 +54,7 @@ export default function AdvancedFilters({
activeFilter,
setActiveFilter,
nodeCounts,
environment,
}: AdvancedFiltersProps) {
const theme = useTheme();
const green = "#14e76f"; // from theme colours
@@ -248,6 +252,54 @@ export default function AdvancedFilters({
</Box>
);
const filterOptions =
environment === "mainnet"
? [
{
label: `Recommended servers (${RECOMMENDED_NODES.length})`,
isSelected: activeFilter === "recommended",
value: "recommended" as const,
},
{
label: `All servers (${nodeCounts.all})`,
isSelected: activeFilter === "all",
value: "all" as const,
},
{
label: `Mixnodes (${nodeCounts.mixnodes})`,
isSelected: activeFilter === "mixnodes",
value: "mixnodes" as const,
},
{
label: `Gateways (${nodeCounts.gateways})`,
isSelected: activeFilter === "gateways",
value: "gateways" as const,
},
]
: [
{
label: `All servers (${nodeCounts.all})`,
isSelected: activeFilter === "all",
value: "all" as const,
},
{
label: `Mixnodes (${nodeCounts.mixnodes})`,
isSelected: activeFilter === "mixnodes",
value: "mixnodes" as const,
},
{
label: `Gateways (${nodeCounts.gateways})`,
isSelected: activeFilter === "gateways",
value: "gateways" as const,
},
];
useEffect(() => {
if (environment === "sandbox" && activeFilter === "recommended") {
setActiveFilter("all");
}
}, [environment, activeFilter, setActiveFilter]);
return (
<Box sx={{ width: "100%" }}>
<Box
@@ -261,28 +313,7 @@ export default function AdvancedFilters({
<Box sx={{ width: { xs: "100%", sm: "auto" } }}>
<NodeFilterButtonGroup
size="medium"
options={[
{
label: `Recommended servers (${RECOMMENDED_NODES.length})`,
isSelected: activeFilter === "recommended",
value: "recommended",
},
{
label: `All servers (${nodeCounts.all})`,
isSelected: activeFilter === "all",
value: "all",
},
{
label: `Mixnodes (${nodeCounts.mixnodes})`,
isSelected: activeFilter === "mixnodes",
value: "mixnodes",
},
{
label: `Gateways (${nodeCounts.gateways})`,
isSelected: activeFilter === "gateways",
value: "gateways",
},
]}
options={filterOptions}
onPage={activeFilter}
onFilterChange={setActiveFilter}
/>
@@ -10,6 +10,7 @@ import NodeTable from "./NodeTable";
import { useState, useEffect } from "react";
import AdvancedFilters from "./AdvancedFilters";
import { RECOMMENDED_NODES } from "@/app/constants";
import { useEnvironment } from "@/providers/EnvironmentProvider";
// Utility function to calculate node saturation point
function getNodeSaturationPoint(
@@ -114,6 +115,8 @@ const NodeTableWithAction = () => {
return stored ? JSON.parse(stored) : false;
});
const { environment } = useEnvironment();
// Wrapper functions to handle filter changes and sessionStorage
const handleActiveFilterChange = (
newFilter: "all" | "mixnodes" | "gateways" | "recommended"
@@ -171,8 +174,8 @@ const NodeTableWithAction = () => {
isLoading: isNSApiNodesLoading,
isError: isNSApiNodesError,
} = useQuery({
queryKey: ["nsApiNodes"],
queryFn: fetchNSApiNodes,
queryKey: ["nsApiNodes", environment],
queryFn: () => fetchNSApiNodes(environment),
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
@@ -290,6 +293,7 @@ const NodeTableWithAction = () => {
activeFilter={activeFilter}
setActiveFilter={handleActiveFilterChange}
nodeCounts={nodeCounts}
environment={environment}
/>
<NodeTable nodes={filteredNodes} />
</Stack>
@@ -1,13 +1,13 @@
"use client";
import { Button, ButtonGroup, Stack } from "@mui/material";
type Option = {
export type Option = {
label: string;
isSelected: boolean;
value: "all" | "mixnodes" | "gateways" | "recommended";
};
type Options = [Option, Option, Option, Option];
type Options = Option[];
const NodeFilterButtonGroup = ({
size = "small",
@@ -1,5 +1,6 @@
"use client";
import React, { createContext, useContext, useEffect, useState } from "react";
import React, { createContext, useContext } from "react";
import dynamic from "next/dynamic";
type Environment = "mainnet" | "sandbox";
@@ -14,30 +15,40 @@ const EnvironmentContext = createContext<EnvironmentContextType | undefined>(
const ENVIRONMENT_STORAGE_KEY = "environment";
const ClientStorage = dynamic(
() =>
import("@uidotdev/usehooks").then((mod) => {
const { useLocalStorage } = mod;
return function ClientStorageComponent({
children,
}: {
children: React.ReactNode;
}) {
const [stored, setStored] = useLocalStorage<{ env: Environment }>(
ENVIRONMENT_STORAGE_KEY,
{ env: "mainnet" }
);
const setEnvironment = (env: Environment) => {
setStored({ env });
};
return (
<EnvironmentContext.Provider
value={{ environment: stored.env, setEnvironment }}
>
{children}
</EnvironmentContext.Provider>
);
};
}),
{ ssr: false }
);
export const EnvironmentProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [environment, setEnvironmentState] = useState<Environment>(() => {
// Try to get the environment from localStorage on initial load
const storedEnv = localStorage.getItem(ENVIRONMENT_STORAGE_KEY);
return (storedEnv as Environment) || "mainnet";
});
const setEnvironment = (env: Environment) => {
setEnvironmentState(env);
localStorage.setItem(ENVIRONMENT_STORAGE_KEY, env);
};
// Update localStorage when environment changes
useEffect(() => {
localStorage.setItem(ENVIRONMENT_STORAGE_KEY, environment);
}, [environment]);
return (
<EnvironmentContext.Provider value={{ environment, setEnvironment }}>
{children}
</EnvironmentContext.Provider>
);
return <ClientStorage>{children}</ClientStorage>;
};
export const useEnvironment = () => {