242 lines
7.4 KiB
TypeScript
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>;
|
|
};
|