fix next js explorer

This commit is contained in:
fmtabbara
2024-07-11 21:45:41 +01:00
parent ce99ccea44
commit b4a719dbab
15 changed files with 373 additions and 533 deletions
+6 -6
View File
@@ -1,11 +1,11 @@
import React from 'react'
import { Navbar } from './components/Nav/Navbar'
import { Providers } from './providers'
import React from "react";
import { Navbar } from "./components/Nav/Navbar";
import { Providers } from "./providers";
const App = ({ children }: { children: React.ReactNode }) => (
const App = ({ children }: { children: any }) => (
<Providers>
<Navbar>{children}</Navbar>
</Providers>
)
);
export { App }
export { App };
@@ -1,9 +1,9 @@
import { Typography } from '@mui/material';
import * as React from 'react';
import { Typography } from "@mui/material";
import * as React from "react";
export const ComponentError: FCWithChildren<{ text: string }> = ({ text }) => (
export const ComponentError = ({ text }: { text: string }) => (
<Typography
sx={{ marginTop: 2, color: 'primary.main', fontSize: 10 }}
sx={{ marginTop: 2, color: "primary.main", fontSize: 10 }}
variant="body1"
data-testid="delegation-total-amount"
>
+16 -15
View File
@@ -1,16 +1,17 @@
import { Card, CardHeader, CardContent, Typography } from '@mui/material'
import React, { ReactEventHandler } from 'react'
import React, { ReactEventHandler } from "react";
import { Card, CardHeader, CardContent, Typography } from "@mui/material";
type ContentCardProps = {
title?: React.ReactNode
subtitle?: string
Icon?: React.ReactNode
Action?: React.ReactNode
errorMsg?: string
onClick?: ReactEventHandler
}
title?: React.ReactNode;
subtitle?: string;
Icon?: React.ReactNode;
Action?: React.ReactNode;
errorMsg?: string;
children: React.ReactNode;
onClick?: ReactEventHandler;
};
export const ContentCard: FCWithChildren<ContentCardProps> = ({
export const ContentCard = ({
title,
Icon,
Action,
@@ -18,11 +19,11 @@ export const ContentCard: FCWithChildren<ContentCardProps> = ({
errorMsg,
children,
onClick,
}) => (
<Card onClick={onClick} sx={{ height: '100%' }}>
}: ContentCardProps) => (
<Card onClick={onClick} sx={{ height: "100%" }}>
{title && (
<CardHeader
title={title || ''}
title={title || ""}
avatar={Icon}
action={Action}
subheader={subtitle}
@@ -30,9 +31,9 @@ export const ContentCard: FCWithChildren<ContentCardProps> = ({
)}
{children && <CardContent>{children}</CardContent>}
{errorMsg && (
<Typography variant="body2" sx={{ color: 'danger', padding: 2 }}>
<Typography variant="body2" sx={{ color: "danger", padding: 2 }}>
{errorMsg}
</Typography>
)}
</Card>
)
);
+24 -23
View File
@@ -1,23 +1,24 @@
import React from 'react'
import Box from '@mui/material/Box'
import MuiLink from '@mui/material/Link'
import Typography from '@mui/material/Typography'
import { useIsMobile } from '../hooks/useIsMobile'
import { NymVpnIcon } from '../icons/NymVpn'
import { Socials } from './Socials'
import Link from 'next/link'
import React from "react";
import Box from "@mui/material/Box";
import MuiLink from "@mui/material/Link";
import Typography from "@mui/material/Typography";
import { useIsMobile } from "../hooks/useIsMobile";
import { NymVpnIcon } from "../icons/NymVpn";
import { Socials } from "./Socials";
import Link from "next/link";
export const Footer: FCWithChildren = () => {
const isMobile = useIsMobile()
const isMobile = useIsMobile();
return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
width: '100%',
height: 'auto',
display: "flex",
flexDirection: "column",
justifyContent: "center",
width: "100%",
height: "auto",
bgcolor: "background.default",
mt: 3,
pt: 3,
pb: 3,
@@ -25,11 +26,11 @@ export const Footer: FCWithChildren = () => {
>
<Box
sx={{
display: 'flex',
flexDirection: 'row',
width: 'auto',
justifyContent: 'center',
alignItems: 'center',
display: "flex",
flexDirection: "row",
width: "auto",
justifyContent: "center",
alignItems: "center",
mb: 2,
}}
>
@@ -45,12 +46,12 @@ export const Footer: FCWithChildren = () => {
<Typography
sx={{
fontSize: 12,
textAlign: isMobile ? 'center' : 'end',
color: 'nym.muted.onDarkBg',
textAlign: isMobile ? "center" : "end",
color: "nym.muted.onDarkBg",
}}
>
© {new Date().getFullYear()} Nym Technologies SA, all rights reserved
</Typography>
</Box>
)
}
);
};
@@ -88,7 +88,7 @@ type ExpandableButtonType = {
fixDrawerClose?: () => void;
};
export const ExpandableButton: FCWithChildren<ExpandableButtonType> = ({
export const ExpandableButton = ({
title,
url,
drawIsTempOpen,
@@ -101,7 +101,7 @@ export const ExpandableButton: FCWithChildren<ExpandableButtonType> = ({
openDrawer,
closeDrawer,
fixDrawerClose,
}) => {
}: ExpandableButtonType) => {
const { palette } = useTheme();
const pathname = usePathname();
const router = useRouter();
@@ -183,7 +183,7 @@ export const ExpandableButton: FCWithChildren<ExpandableButtonType> = ({
);
};
export const Nav: FCWithChildren = ({ children }) => {
export const Nav = ({ children }: { children: React.ReactNode }) => {
const { environment } = useMainContext();
const [drawerIsOpen, setDrawerToOpen] = React.useState(false);
const [fixedOpen, setFixedOpen] = React.useState(false);
@@ -364,7 +364,7 @@ export const Nav: FCWithChildren = ({ children }) => {
</Drawer>
<Box
style={{ width: `calc(100% - ${drawerWidth}px` }}
sx={{ py: 5, px: 6, mt: 7 }}
sx={{ py: 5, px: 6, mt: 7, bgcolor: "background.default" }}
>
{children}
<Footer />
@@ -14,7 +14,6 @@ import {
Toolbar,
} from "@mui/material";
import { Menu } from "@mui/icons-material";
import { MaintenanceBanner } from "@nymproject/react";
import { useIsMobile } from "@/app/hooks/useIsMobile";
import { MobileDrawerClose } from "@/app/icons/MobileDrawerClose";
import { Footer } from "../Footer";
@@ -23,7 +22,9 @@ import { ConnectKeplrWallet } from "../Wallet/ConnectKeplrWallet";
import { NetworkTitle } from "../NetworkTitle";
import { originalNavOptions } from "@/app/context/nav";
export const MobileNav: FCWithChildren = ({ children }) => {
export const MobileNav: FCWithChildren<{ children: React.ReactNode }> = ({
children,
}) => {
const theme = useTheme();
const [drawerOpen, setDrawerOpen] = React.useState(false);
// Set maintenance banner to false by default to don't display it
@@ -46,10 +47,6 @@ export const MobileNav: FCWithChildren = ({ children }) => {
borderRadius: 0,
}}
>
<MaintenanceBanner
open={openMaintenance}
onClick={() => setOpenMaintenance(false)}
/>
<Toolbar
sx={{
display: "flex",
@@ -126,8 +123,7 @@ export const MobileNav: FCWithChildren = ({ children }) => {
</List>
</Box>
</Drawer>
<Box sx={{ width: "100%", p: 4, mt: 7 }}>
<Box sx={{ width: "100%", p: 4, mt: 7, bgcolor: "background.default" }}>
{children}
<Footer />
</Box>
+3 -2
View File
@@ -1,11 +1,12 @@
import * as React from 'react';
import { Typography } from '@mui/material';
import * as React from "react";
import { Typography } from "@mui/material";
export const Title: FCWithChildren<{ text: string }> = ({ text }) => (
<Typography
variant="h5"
sx={{
fontWeight: 600,
color: "text.primary",
}}
data-testid={text}
>
+95 -93
View File
@@ -1,7 +1,7 @@
'use client'
"use client";
import * as React from 'react'
import { PaletteMode } from '@mui/material'
import * as React from "react";
import { PaletteMode } from "@mui/material";
import {
ApiState,
BlockResponse,
@@ -13,110 +13,112 @@ import {
ValidatorsResponse,
Environment,
DirectoryServiceProvider,
} from '@/app/typeDefs/explorer-api'
import { EnumFilterKey } from '@/app/typeDefs/filters'
import { Api, getEnvironment } from '@/app/api'
import { toPercentIntegerString } from '@/app/utils'
import { NavOptionType, originalNavOptions } from './nav'
} from "@/app/typeDefs/explorer-api";
import { EnumFilterKey } from "@/app/typeDefs/filters";
import { Api, getEnvironment } from "@/app/api";
import { toPercentIntegerString } from "@/app/utils";
import { NavOptionType, originalNavOptions } from "./nav";
interface StateData {
summaryOverview?: ApiState<SummaryOverviewResponse>
block?: ApiState<BlockResponse>
countryData?: ApiState<CountryDataResponse>
gateways?: ApiState<GatewayResponse>
globalError?: string | undefined
mixnodes?: ApiState<MixNodeResponse>
mode: PaletteMode
validators?: ApiState<ValidatorsResponse>
environment?: Environment
serviceProviders?: ApiState<DirectoryServiceProvider[]>
summaryOverview?: ApiState<SummaryOverviewResponse>;
block?: ApiState<BlockResponse>;
countryData?: ApiState<CountryDataResponse>;
gateways?: ApiState<GatewayResponse>;
globalError?: string | undefined;
mixnodes?: ApiState<MixNodeResponse>;
mode: PaletteMode;
validators?: ApiState<ValidatorsResponse>;
environment?: Environment;
serviceProviders?: ApiState<DirectoryServiceProvider[]>;
}
interface StateApi {
fetchMixnodes: (
status?: MixnodeStatus
) => Promise<MixNodeResponse | undefined>
filterMixnodes: (filters: any, status: any) => void
toggleMode: () => void
) => Promise<MixNodeResponse | undefined>;
filterMixnodes: (filters: any, status: any) => void;
toggleMode: () => void;
}
type State = StateData & StateApi
type State = StateData & StateApi;
export const MainContext = React.createContext<State>({
mode: 'dark',
mode: "dark",
toggleMode: () => undefined,
filterMixnodes: () => null,
fetchMixnodes: () => Promise.resolve(undefined),
})
});
export const useMainContext = (): React.ContextType<typeof MainContext> =>
React.useContext<State>(MainContext)
React.useContext<State>(MainContext);
export const MainContextProvider: FCWithChildren = ({ children }) => {
export const MainContextProvider: FCWithChildren<{
children: React.ReactNode;
}> = ({ children }) => {
// network explorer environment
const [environment, setEnvironment] = React.useState<Environment>()
const [environment, setEnvironment] = React.useState<Environment>();
// light/dark mode
const [mode, setMode] = React.useState<PaletteMode>('dark')
const [mode, setMode] = React.useState<PaletteMode>("dark");
// global / banner error messaging
const [globalError] = React.useState<string>()
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>>()
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>>()
React.useState<ApiState<ValidatorsResponse>>();
const [block, setBlock] = React.useState<ApiState<BlockResponse>>();
const [countryData, setCountryData] =
React.useState<ApiState<CountryDataResponse>>()
React.useState<ApiState<CountryDataResponse>>();
const [serviceProviders, setServiceProviders] =
React.useState<ApiState<DirectoryServiceProvider[]>>()
React.useState<ApiState<DirectoryServiceProvider[]>>();
const toggleMode = () => setMode((m) => (m !== 'light' ? 'light' : 'dark'))
const toggleMode = () => setMode((m) => (m !== "light" ? "light" : "dark"));
const fetchOverviewSummary = async () => {
try {
const data = await Api.fetchOverviewSummary()
setSummaryOverview({ data, isLoading: false })
const data = await Api.fetchOverviewSummary();
setSummaryOverview({ data, isLoading: false });
} catch (error) {
setSummaryOverview({
error:
error instanceof Error
? error
: new Error('Overview summary api fail'),
: new Error("Overview summary api fail"),
isLoading: false,
})
});
}
}
};
const fetchMixnodes = async (status?: MixnodeStatus) => {
let data
setMixnodes((d) => ({ ...d, isLoading: true }))
let data;
setMixnodes((d) => ({ ...d, isLoading: true }));
try {
data = status
? await Api.fetchMixnodesActiveSetByStatus(status)
: await Api.fetchMixnodes()
setMixnodes({ data, isLoading: false })
: await Api.fetchMixnodes();
setMixnodes({ data, isLoading: false });
} catch (error) {
setMixnodes({
error: error instanceof Error ? error : new Error('Mixnode api fail'),
error: error instanceof Error ? error : new Error("Mixnode api fail"),
isLoading: false,
})
});
}
return data
}
return data;
};
const filterMixnodes = async (
filters: { [key in EnumFilterKey]: number[] },
status?: MixnodeStatus
) => {
setMixnodes((d) => ({ ...d, isLoading: true }))
setMixnodes((d) => ({ ...d, isLoading: true }));
const mxns = status
? await Api.fetchMixnodesActiveSetByStatus(status)
: await Api.fetchMixnodes()
: await Api.fetchMixnodes();
const filtered = mxns?.filter(
(m) =>
@@ -126,101 +128,101 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
m.stake_saturation <= filters.stakeSaturation[1] &&
m.avg_uptime >= filters.routingScore[0] &&
m.avg_uptime <= filters.routingScore[1]
)
);
setMixnodes({ data: filtered, isLoading: false })
}
setMixnodes({ data: filtered, isLoading: false });
};
const fetchGateways = async () => {
setGateways((d) => ({ ...d, isLoading: true }))
setGateways((d) => ({ ...d, isLoading: true }));
try {
const data = await Api.fetchGateways()
setGateways({ data, isLoading: false })
const data = await Api.fetchGateways();
setGateways({ data, isLoading: false });
} catch (error) {
setGateways({
error: error instanceof Error ? error : new Error('Gateways api fail'),
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 })
const data = await Api.fetchValidators();
setValidators({ data, isLoading: false });
} catch (error) {
setValidators({
error:
error instanceof Error ? error : new Error('Validators api fail'),
error instanceof Error ? error : new Error("Validators api fail"),
isLoading: false,
})
});
}
}
};
const fetchBlock = async () => {
try {
const data = await Api.fetchBlock()
setBlock({ data, isLoading: false })
const data = await Api.fetchBlock();
setBlock({ data, isLoading: false });
} catch (error) {
setBlock({
error: error instanceof Error ? error : new Error('Block api fail'),
error: error instanceof Error ? error : new Error("Block api fail"),
isLoading: false,
})
});
}
}
};
const fetchCountryData = async () => {
setCountryData({ data: undefined, isLoading: true })
setCountryData({ data: undefined, isLoading: true });
try {
const res = await Api.fetchCountryData()
setCountryData({ data: res, isLoading: false })
const res = await Api.fetchCountryData();
setCountryData({ data: res, isLoading: false });
} catch (error) {
setCountryData({
error:
error instanceof Error ? error : new Error('Country Data api fail'),
error instanceof Error ? error : new Error("Country Data api fail"),
isLoading: false,
})
});
}
}
};
const fetchServiceProviders = async () => {
setServiceProviders({ data: undefined, isLoading: true })
setServiceProviders({ data: undefined, isLoading: true });
try {
const res = await Api.fetchServiceProviders()
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'),
: new Error("Service provider api fail"),
isLoading: false,
})
});
}
}
};
React.useEffect(() => {
if (environment === 'mainnet') {
fetchServiceProviders()
if (environment === "mainnet") {
fetchServiceProviders();
}
}, [environment])
}, [environment]);
React.useEffect(() => {
setEnvironment(getEnvironment())
setEnvironment(getEnvironment());
Promise.all([
fetchOverviewSummary(),
fetchGateways(),
fetchValidators(),
fetchBlock(),
fetchCountryData(),
])
}, [])
]);
}, []);
const state = React.useMemo<State>(
() => ({
@@ -250,7 +252,7 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
validators,
serviceProviders,
]
)
);
return <MainContext.Provider value={state}>{children}</MainContext.Provider>
}
return <MainContext.Provider value={state}>{children}</MainContext.Provider>;
};
+9 -9
View File
@@ -1,21 +1,21 @@
import type { Metadata } from 'next'
import '@interchain-ui/react/styles'
import { App } from './App'
import type { Metadata } from "next";
import "@interchain-ui/react/styles";
import { App } from "./App";
export const metadata: Metadata = {
title: 'Nym Network Explorer',
}
title: "Nym Network Explorer",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<App>{children}</App>
</body>
</html>
)
);
}
+8 -8
View File
@@ -1,8 +1,8 @@
import React from 'react'
import CosmosKitProvider from '@/app/context/cosmos-kit'
import { WalletProvider } from '@/app/context/wallet'
import { NetworkExplorerThemeProvider } from '@/app/theme'
import { MainContextProvider } from '@/app/context/main'
import React from "react";
import CosmosKitProvider from "@/app/context/cosmos-kit";
import { WalletProvider } from "@/app/context/wallet";
import { NetworkExplorerThemeProvider } from "@/app/theme";
import { MainContextProvider } from "@/app/context/main";
const Providers = ({ children }: { children: React.ReactNode }) => {
return (
@@ -13,7 +13,7 @@ const Providers = ({ children }: { children: React.ReactNode }) => {
</CosmosKitProvider>
</NetworkExplorerThemeProvider>
</MainContextProvider>
)
}
);
};
export { Providers }
export { Providers };
+12 -8
View File
@@ -1,15 +1,19 @@
'use client'
"use client";
import * as React from 'react'
import { NymNetworkExplorerThemeProvider } from '@nymproject/mui-theme'
import { useMainContext } from '../context/main'
import * as React from "react";
import { NymNetworkExplorerThemeProvider } from "@nymproject/mui-theme";
import { useMainContext } from "../context/main";
export const NetworkExplorerThemeProvider: FCWithChildren = ({ children }) => {
const { mode } = useMainContext()
export const NetworkExplorerThemeProvider = ({
children,
}: {
children: React.ReactNode;
}) => {
const { mode } = useMainContext();
return (
<NymNetworkExplorerThemeProvider mode={mode}>
{children}
</NymNetworkExplorerThemeProvider>
)
}
);
};
+1 -1
View File
@@ -1 +1 @@
declare type FCWithChildren<P = {}> = React.FC<React.PropsWithChildren<P>>;
declare type FCWithChildren<P = object> = React.FC<React.PropsWithChildren<P>>;
+1 -1
View File
@@ -4,7 +4,7 @@
"scripts": {
"dev": "next dev",
"build": "next build",
"build:prod": "yarn --cwd .. build && next build",
"build:prod": "pnpm i && pnpm -C .. build && next build",
"start": "next start",
"lint": "next lint"
},
+3 -3
View File
@@ -40,11 +40,11 @@
"@mui/system": "^5.0.1",
"@mui/x-data-grid": "^5.0.0-beta.5",
"@nymproject/contract-clients": "1.2.4-rc.1",
"@nymproject/mui-theme": "^1.0.0",
"@nymproject/mui-theme": "workspace: ^1.0.0",
"@nymproject/node-tester": "^1.0.0",
"@nymproject/nym-validator-client": "^0.18.0",
"@nymproject/react": "^1.0.0",
"@nymproject/types": "^1.0.0",
"@nymproject/react": "workspace:^1.0.0",
"@nymproject/types": "workspace:^1.0.0",
"@tauri-apps/api": "^1.5.1",
"big.js": "^6.2.1",
"bs58": "^5.0.0",
+183 -348
View File
File diff suppressed because it is too large Load Diff