e52fe65985
* Add identicons package * Tidy up styling and move methods into component directories with better naming * Add mixnode status colours to theme * Mixnode status and icon components * Add status to mixnode types * Add API method to get mixnode details * Add mixnode details to state * Add status and name+description section to mixnode detail page * Wrap with div instead of p * Limit width of description and link to new tab * Limit length of link button and truncate with elipsis * Replace `filter` with `find` * Move mix node detail components to a location that is better named * Refactor mixnode detail state and separate into an independent context from main state. This prevents the mixnode detail page from showing stale data when switching between mix nodes. * Tidy up mixnode detail page adding new state provider and a guard component to handle loading, error and not found states * Layout changes to mixnode description header section * Add methods to Explorer API client to get a mixnode by id, active set by status and overview summary * Add color prop to StatsCard and make count optional * Add optional start and end children to TableToolbar * Tidy up naming * Add summary overview and getting mixnodes by active set status to main state * Add mix node status overview cards * Add mix node status to routes * Mixnode list has a dropdown component to select the active set status * Clean up caching code * Add resource to get a single mixnode by id * Add API resources to get `active`, `inactive` and `standby` mixnodes * Add mixnode summary to API * Add overview summary endpoint to API * Fix OpenAPI/swagger base url * Make clippy happy * Add method to get validators * Add methods to get active and rewarded mixnodes * Fix naming * Move client creation to crate root * Move cache to module * Delete unused files * Add validators API resource * Add gateways API resource * Move tasks to crate root * Add new HTTP resources for validators and gateways to routes * Tidy up naming and locations for mixnodes * Add validator and gateways to state, and tidy up naming * Add gateways and validator modules to main * Overview shows validator and gateway summaries from state * Bundle variable weight Open Sans fonts * Fix up font weights and sizes * Fix up typing * Fix up social icons * Fix navbar colour * Fix paper colour in dark mode and border radius * Fix up stats card * Tidy up Nym icons * Fix up overview * Fix up spacing and padding for overview * Add light mode shades that are darker for mixnode status values * Review feedback
175 lines
5.0 KiB
TypeScript
175 lines
5.0 KiB
TypeScript
import * as React from 'react';
|
|
import {
|
|
ApiState,
|
|
DelegationsResponse,
|
|
MixNodeDescriptionResponse,
|
|
MixNodeResponseItem,
|
|
StatsResponse,
|
|
StatusResponse,
|
|
UptimeStoryResponse,
|
|
} from 'src/typeDefs/explorer-api';
|
|
import { Api } from '../api';
|
|
import {
|
|
mixNodeResponseItemToMixnodeRowType,
|
|
MixnodeRowType,
|
|
} from '../components/MixNodes';
|
|
|
|
/**
|
|
* This context provides the state for a single mixnode by identity key.
|
|
*/
|
|
|
|
interface MixnodeState {
|
|
delegations?: ApiState<DelegationsResponse>;
|
|
description?: ApiState<MixNodeDescriptionResponse>;
|
|
mixNode?: ApiState<MixNodeResponseItem | undefined>;
|
|
mixNodeRow?: MixnodeRowType;
|
|
stats?: ApiState<StatsResponse>;
|
|
status?: ApiState<StatusResponse>;
|
|
uptimeStory?: ApiState<UptimeStoryResponse>;
|
|
}
|
|
|
|
export const MixnodeContext = React.createContext<MixnodeState>({});
|
|
|
|
export const useMixnodeContext = (): React.ContextType<typeof MixnodeContext> =>
|
|
React.useContext<MixnodeState>(MixnodeContext);
|
|
|
|
interface MixnodeContextProviderProps {
|
|
mixNodeIdentityKey: string;
|
|
}
|
|
|
|
/**
|
|
* Provides a state context for a mixnode by identity
|
|
* @param mixNodeIdentityKey The identity key of the mixnode
|
|
*/
|
|
export const MixnodeContextProvider: React.FC<MixnodeContextProviderProps> = ({
|
|
mixNodeIdentityKey,
|
|
children,
|
|
}) => {
|
|
const [mixNode, fetchMixnodeById, clearMixnodeById] = useApiState<
|
|
MixNodeResponseItem | undefined
|
|
>(mixNodeIdentityKey, Api.fetchMixnodeByID, 'Failed to fetch mixnode by id');
|
|
|
|
const [mixNodeRow, setMixnodeRow] = React.useState<
|
|
MixnodeRowType | undefined
|
|
>();
|
|
|
|
const [delegations, fetchDelegations, clearDelegations] =
|
|
useApiState<DelegationsResponse>(
|
|
mixNodeIdentityKey,
|
|
Api.fetchDelegationsById,
|
|
'Failed to fetch delegations for mixnode',
|
|
);
|
|
|
|
const [status, fetchStatus, clearStatus] = useApiState<StatusResponse>(
|
|
mixNodeIdentityKey,
|
|
Api.fetchStatusById,
|
|
'Failed to fetch mixnode status',
|
|
);
|
|
|
|
const [stats, fetchStats, clearStats] = useApiState<StatsResponse>(
|
|
mixNodeIdentityKey,
|
|
Api.fetchStatsById,
|
|
'Failed to fetch mixnode stats',
|
|
);
|
|
|
|
const [description, fetchDescription, clearDescription] =
|
|
useApiState<MixNodeDescriptionResponse>(
|
|
mixNodeIdentityKey,
|
|
Api.fetchMixnodeDescriptionById,
|
|
'Failed to fetch mixnode description',
|
|
);
|
|
|
|
const [uptimeStory, fetchUptimeHistory, clearUptimeHistory] =
|
|
useApiState<UptimeStoryResponse>(
|
|
mixNodeIdentityKey,
|
|
Api.fetchUptimeStoryById,
|
|
'Failed to fetch mixnode uptime history',
|
|
);
|
|
|
|
React.useEffect(() => {
|
|
// when the identity key changes, remove all previous data
|
|
clearMixnodeById();
|
|
clearDelegations();
|
|
clearStatus();
|
|
clearStats();
|
|
clearDescription();
|
|
clearUptimeHistory();
|
|
|
|
// fetch the mixnode, then get all the other stuff
|
|
fetchMixnodeById().then((value) => {
|
|
if (!value.data || value.error) {
|
|
setMixnodeRow(undefined);
|
|
return;
|
|
}
|
|
setMixnodeRow(mixNodeResponseItemToMixnodeRowType(value.data));
|
|
Promise.all([
|
|
fetchDelegations(),
|
|
fetchStatus(),
|
|
fetchStats(),
|
|
fetchDescription(),
|
|
fetchUptimeHistory(),
|
|
]);
|
|
});
|
|
}, [mixNodeIdentityKey]);
|
|
|
|
return (
|
|
<MixnodeContext.Provider
|
|
value={{
|
|
delegations,
|
|
mixNode,
|
|
mixNodeRow,
|
|
description,
|
|
stats,
|
|
status,
|
|
uptimeStory,
|
|
}}
|
|
>
|
|
{children}
|
|
</MixnodeContext.Provider>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Custom hook to get data from the API by passing an id to a delegate method that fetches the data asynchronously
|
|
* @param id The id to fetch
|
|
* @param fn Delegate the fetching to this method (must take `(id: string)` as a parameter)
|
|
* @param errorMessage A static error message, to use when no dynamic error message is returned
|
|
*/
|
|
function useApiState<T>(
|
|
id: string,
|
|
fn: (argId: string) => Promise<T>,
|
|
errorMessage: string,
|
|
): [ApiState<T> | undefined, () => Promise<ApiState<T>>, () => void] {
|
|
// stores the state
|
|
const [value, setValue] = React.useState<ApiState<T> | undefined>();
|
|
|
|
// clear the value
|
|
const clearValueFn = () => setValue(undefined);
|
|
|
|
// this provides a method to trigger the delegate to fetch data
|
|
const wrappedFetchFn = React.useCallback(async () => {
|
|
try {
|
|
// keep previous state and set to loading
|
|
setValue((prevState) => ({ ...prevState, isLoading: true }));
|
|
|
|
// delegate to user function to get data and set if successful
|
|
const data = await fn(id);
|
|
const newValue: ApiState<T> = {
|
|
isLoading: false,
|
|
data,
|
|
};
|
|
setValue(newValue);
|
|
return newValue;
|
|
} catch (error) {
|
|
// return the caught error or create a new error with the static error message
|
|
const newValue: ApiState<T> = {
|
|
error: error instanceof Error ? error : new Error(errorMessage),
|
|
isLoading: false,
|
|
};
|
|
setValue(newValue);
|
|
return newValue;
|
|
}
|
|
}, [setValue, fn]);
|
|
return [value || { isLoading: true }, wrappedFetchFn, clearValueFn];
|
|
}
|