Files
nym/explorer/src/context/main.tsx
T
2023-06-07 12:05:32 +01:00

242 lines
7.4 KiB
TypeScript

import * as React from 'react';
import { PaletteMode } from '@mui/material';
import {
ApiState,
BlockResponse,
CountryDataResponse,
GatewayResponse,
MixNodeResponse,
MixnodeStatus,
SummaryOverviewResponse,
ValidatorsResponse,
Environment,
DirectoryServiceProvider,
} from '../typeDefs/explorer-api';
import { EnumFilterKey } from '../typeDefs/filters';
import { Api, getEnvironment } from '../api';
import { NavOptionType, originalNavOptions } from './nav';
import { toPercentIntegerString } from '../utils';
interface StateData {
summaryOverview?: ApiState<SummaryOverviewResponse>;
block?: ApiState<BlockResponse>;
countryData?: ApiState<CountryDataResponse>;
gateways?: ApiState<GatewayResponse>;
globalError?: string | undefined;
mixnodes?: ApiState<MixNodeResponse>;
mode: PaletteMode;
navState: NavOptionType[];
validators?: ApiState<ValidatorsResponse>;
environment?: Environment;
serviceProviders?: ApiState<DirectoryServiceProvider[]>;
}
interface StateApi {
fetchMixnodes: (status?: MixnodeStatus) => Promise<MixNodeResponse | undefined>;
filterMixnodes: (filters: any, status: any) => void;
toggleMode: () => void;
updateNavState: (id: number) => void;
}
type State = StateData & StateApi;
export const MainContext = React.createContext<State>({
mode: 'dark',
updateNavState: () => null,
navState: originalNavOptions,
toggleMode: () => undefined,
filterMixnodes: () => null,
fetchMixnodes: () => Promise.resolve(undefined),
});
export const useMainContext = (): React.ContextType<typeof MainContext> => React.useContext<State>(MainContext);
export const MainContextProvider: FCWithChildren = ({ children }) => {
// network explorer environment
const [environment, setEnvironment] = React.useState<Environment>();
// light/dark mode
const [mode, setMode] = React.useState<PaletteMode>('dark');
// nav state
const [navState, updateNav] = React.useState<NavOptionType[]>(originalNavOptions);
// global / banner error messaging
const [globalError] = React.useState<string>();
// various APIs for Overview page
const [summaryOverview, setSummaryOverview] = React.useState<ApiState<SummaryOverviewResponse>>();
const [mixnodes, setMixnodes] = React.useState<ApiState<MixNodeResponse>>();
const [gateways, setGateways] = React.useState<ApiState<GatewayResponse>>();
const [validators, setValidators] = React.useState<ApiState<ValidatorsResponse>>();
const [block, setBlock] = React.useState<ApiState<BlockResponse>>();
const [countryData, setCountryData] = React.useState<ApiState<CountryDataResponse>>();
const [serviceProviders, setServiceProviders] = React.useState<ApiState<DirectoryServiceProvider[]>>();
const toggleMode = () => setMode((m) => (m !== 'light' ? 'light' : 'dark'));
const fetchOverviewSummary = async () => {
try {
const data = await Api.fetchOverviewSummary();
setSummaryOverview({ data, isLoading: false });
} catch (error) {
setSummaryOverview({
error: error instanceof Error ? error : new Error('Overview summary api fail'),
isLoading: false,
});
}
};
const fetchMixnodes = async (status?: MixnodeStatus) => {
let data;
setMixnodes((d) => ({ ...d, isLoading: true }));
try {
data = status ? await Api.fetchMixnodesActiveSetByStatus(status) : await Api.fetchMixnodes();
setMixnodes({ data, isLoading: false });
} catch (error) {
setMixnodes({
error: error instanceof Error ? error : new Error('Mixnode api fail'),
isLoading: false,
});
}
return data;
};
const filterMixnodes = async (filters: { [key in EnumFilterKey]: number[] }, status?: MixnodeStatus) => {
setMixnodes((d) => ({ ...d, isLoading: true }));
const mxns = status ? await Api.fetchMixnodesActiveSetByStatus(status) : await Api.fetchMixnodes();
const filtered = mxns?.filter(
(m) =>
+m.profit_margin_percent >= filters.profitMargin[0] / 100 &&
+m.profit_margin_percent <= filters.profitMargin[1] / 100 &&
m.stake_saturation >= filters.stakeSaturation[0] &&
m.stake_saturation <= filters.stakeSaturation[1] &&
m.avg_uptime >= filters.routingScore[0] &&
m.avg_uptime <= filters.routingScore[1],
);
setMixnodes({ data: filtered, isLoading: false });
};
const fetchGateways = async () => {
setGateways((d) => ({ ...d, isLoading: true }));
try {
const data = await Api.fetchGateways();
setGateways({ data, isLoading: false });
} catch (error) {
setGateways({
error: error instanceof Error ? error : new Error('Gateways api fail'),
isLoading: false,
});
}
};
const fetchValidators = async () => {
try {
const data = await Api.fetchValidators();
setValidators({ data, isLoading: false });
} catch (error) {
setValidators({
error: error instanceof Error ? error : new Error('Validators api fail'),
isLoading: false,
});
}
};
const fetchBlock = async () => {
try {
const data = await Api.fetchBlock();
setBlock({ data, isLoading: false });
} catch (error) {
setBlock({
error: error instanceof Error ? error : new Error('Block api fail'),
isLoading: false,
});
}
};
const fetchCountryData = async () => {
setCountryData({ data: undefined, isLoading: true });
try {
const res = await Api.fetchCountryData();
setCountryData({ data: res, isLoading: false });
} catch (error) {
setCountryData({
error: error instanceof Error ? error : new Error('Country Data api fail'),
isLoading: false,
});
}
};
const fetchServiceProviders = async () => {
setServiceProviders({ data: undefined, isLoading: true });
try {
const res = await Api.fetchServiceProviders();
const resWithRoutingScorePercentage = res.map((item) => ({
...item,
routing_score: item.routing_score
? `${toPercentIntegerString(item.routing_score.toString())}%`
: item.routing_score,
}));
setServiceProviders({ data: resWithRoutingScorePercentage, isLoading: false });
} catch (error) {
setServiceProviders({
error: error instanceof Error ? error : new Error('Service provider api fail'),
isLoading: false,
});
}
};
const updateNavState = (id: number) => {
const updated = navState.map((option) => ({
...option,
isActive: option.id === id,
}));
updateNav(updated);
};
React.useEffect(() => {
if (environment === 'mainnet') {
fetchServiceProviders();
}
}, [environment]);
React.useEffect(() => {
setEnvironment(getEnvironment());
Promise.all([fetchOverviewSummary(), fetchGateways(), fetchValidators(), fetchBlock(), fetchCountryData()]);
}, []);
const state = React.useMemo<State>(
() => ({
environment,
block,
countryData,
fetchMixnodes,
filterMixnodes,
gateways,
globalError,
mixnodes,
mode,
navState,
summaryOverview,
toggleMode,
updateNavState,
validators,
serviceProviders,
}),
[
environment,
block,
countryData,
gateways,
globalError,
mixnodes,
mode,
navState,
summaryOverview,
validators,
serviceProviders,
],
);
return <MainContext.Provider value={state}>{children}</MainContext.Provider>;
};