Merge pull request #5669 from nymtech/yana/explorer-caching

Yana/explorer caching
This commit is contained in:
Yana Matrosova
2025-04-10 18:41:31 +03:00
committed by GitHub
88 changed files with 2581 additions and 1481 deletions
+2 -2
View File
@@ -16,8 +16,8 @@ const nextConfig: NextConfig = {
basePath: false,
permanent: true,
},
]
}
];
},
};
export default nextConfig;
+1
View File
@@ -19,6 +19,7 @@
"@emotion/react": "^11.13.5",
"@emotion/styled": "^11.13.5",
"@interchain-ui/react": "^1.26.1",
"@keplr-wallet/types": "^0.12.211",
"@mui/icons-material": "^5.16.11",
"@mui/material": "^6.1.10",
"@mui/material-nextjs": "^6.1.9",
@@ -0,0 +1,35 @@
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.6668 13.3333V14.6667H10.0002V15.3333H2.00016V14.6667H1.3335V3.99999H2.00016V3.33333H4.00016V13.3333H10.6668Z"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
<path
d="M10.6665 4.66667V0.666672H5.33317V1.33334H4.6665V12H5.33317V12.6667H13.9998V12H14.6665V4.66667H10.6665ZM13.3332 11.3333H5.99984V2.00001H9.33317V6.00001H13.3332V11.3333Z"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
<path
d="M14.6668 3.33334V4H11.3335V0.666672H12.0002V1.33334H12.6668V2.00001H13.3335V2.66667H14.0002V3.33334H14.6668Z"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
</svg>

After

Width:  |  Height:  |  Size: 922 B

+118
View File
@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="15"
height="15"
viewBox="0 0 15 15"
fill="none"
>
<g clip-path="url(#clip0_8428_65000)">
<rect
y="3"
width="3"
height="3"
transform="rotate(-90 0 3)"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
<rect
y="15"
width="3"
height="3"
transform="rotate(-90 0 15)"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
<rect
x="12"
y="3"
width="3"
height="3"
transform="rotate(-90 12 3)"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
<rect
x="9"
y="12"
width="3"
height="3"
transform="rotate(-90 9 12)"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
<rect
x="12"
y="15"
width="3"
height="3"
transform="rotate(-90 12 15)"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
<rect
x="3"
y="6"
width="3"
height="3"
transform="rotate(-90 3 6)"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
<rect
x="3"
y="12"
width="3"
height="3"
transform="rotate(-90 3 12)"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
<rect
x="9"
y="6"
width="3"
height="3"
transform="rotate(-90 9 6)"
fill="#FFFFFF"
style="
fill: #FFFFFF;
fill: color(display-p3 1 1 1);
fill-opacity: 1;
"
/>
</g>
<defs>
<clipPath id="clip0_8428_65000">
<rect width="15" height="15" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+22
View File
@@ -0,0 +1,22 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="25"
viewBox="0 0 24 25"
fill="none"
>
<circle cx="12" cy="12.5" r="10" fill="#ffffff" />
<defs>
<radialGradient
id="paint0_angular_2549_7570"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(12 12.5) rotate(90) scale(12)"
>
<stop stopColor="#ffffff" />
<stop offset="1" stopColor="#ffffff" />
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 492 B

@@ -0,0 +1,17 @@
<svg width="85" height="84" viewBox="0 0 85 84" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_11897_1105)">
<path d="M42.3334 28V35H49.3334V42H56.3334C56.3334 34.268 50.0654 28 42.3334 28Z" fill="#242B2D"/>
<path d="M42.3334 56L42.3334 49L35.3334 49L35.3334 42L28.3334 42C28.3334 49.732 34.6014 56 42.3334 56Z" fill="#242B2D"/>
<path d="M28.3334 42L35.3334 42L35.3334 35L42.3334 35L42.3334 28C34.6014 28 28.3334 34.268 28.3334 42Z" fill="#242B2D"/>
<path d="M56.3334 42L49.3334 42L49.3334 49L42.3334 49L42.3334 56C50.0654 56 56.3334 49.732 56.3334 42Z" fill="#242B2D"/>
<path d="M28.3334 84L28.3334 77L7.33337 77L7.33337 56L0.333372 56L0.333372 70C0.333371 77.732 6.60138 84 14.3334 84L28.3334 84Z" fill="#242B2D"/>
<path d="M56.3334 5.72205e-06L56.3334 7.00001L77.3334 7.00001L77.3334 28L84.3334 28L84.3334 14C84.3334 6.26802 78.0654 5.9812e-06 70.3334 5.88899e-06L56.3334 5.72205e-06Z" fill="#242B2D"/>
<path d="M84.3334 56L77.3334 56L77.3334 77L56.3334 77L56.3334 84L70.3334 84C78.0654 84 84.3334 77.732 84.3334 70L84.3334 56Z" fill="#242B2D"/>
<path d="M0.33338 28L7.33338 28L7.33338 7L28.3334 7L28.3334 -1.90735e-06L14.3334 -3.13127e-06C6.60139 -3.80722e-06 0.333382 6.26801 0.333381 14L0.33338 28Z" fill="#242B2D"/>
</g>
<defs>
<clipPath id="clip0_11897_1105">
<rect width="84" height="84" fill="white" transform="translate(0.333374)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -0,0 +1,9 @@
<svg width="84" height="84" viewBox="0 0 84 84" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="7" height="56" transform="matrix(-2.36041e-07 1 1 7.28523e-08 28 77)" fill="#242B2D"/>
<rect width="7" height="56" transform="matrix(1 0 -1.63189e-07 -1 0 56)" fill="#242B2D"/>
<path d="M63 0L63 21L84 21L84 28L63 28C59.134 28 56 24.866 56 21L56 0H63Z" fill="#242B2D"/>
<rect width="7" height="42" transform="matrix(1 0 -1.63189e-07 -1 28 42)" fill="#242B2D"/>
<rect width="7" height="35" transform="matrix(-2.36041e-07 1 1 7.28523e-08 49 49)" fill="#242B2D"/>
<path d="M28 84L28 77L7 77L7 56L-1.90735e-06 56L-2.82529e-06 77C-2.99428e-06 80.866 3.134 84 7 84L28 84Z" fill="#242B2D"/>
<path d="M56 56L56 49L35 49L35 28L28 28L28 49C28 52.866 31.134 56 35 56L56 56Z" fill="#242B2D"/>
</svg>

After

Width:  |  Height:  |  Size: 796 B

@@ -0,0 +1,5 @@
<svg width="89" height="25" viewBox="0 0 89 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 2.73999C0 1.50288 1.00579 0.5 2.2465 0.5H10.4256C11.8792 0.5 13.1513 1.47412 13.5263 2.87439L18.7506 22.3815C18.8474 22.7432 19.3816 22.6733 19.3816 22.299V0.5H25.8001V22.2599C25.8001 23.497 24.7943 24.4999 23.5536 24.4999H15.314C13.8643 24.4999 12.5946 23.5308 12.216 22.1355L7.04928 3.0892C6.95141 2.72844 6.41855 2.79902 6.41855 3.17275V24.4999H0V2.73999Z" fill="white"/>
<path d="M55.5159 0.500025C54.2752 0.500025 53.2694 1.5029 53.2694 2.74002V24.4999H59.6879V3.18233C59.6879 2.79548 60.2483 2.74066 60.3237 3.12013L64.0601 21.9219C64.3579 23.4203 65.6762 24.4999 67.2082 24.4999L74.6942 24.4999C76.2294 24.4999 77.5496 23.4158 77.8439 21.9135L81.5171 3.16662C81.5916 2.78643 82.153 2.84061 82.153 3.22798V24.4999H88.5714V2.74002C88.5714 1.5029 87.5656 0.500025 86.3249 0.500025L78.3813 0.50002C76.859 0.500019 75.5461 1.56646 75.2383 3.05306L71.2361 22.3844C71.1655 22.7251 70.6773 22.7246 70.6074 22.3838L66.6405 3.05837C66.3349 1.56926 65.0208 0.500019 63.4964 0.50002L55.5159 0.500025Z" fill="white"/>
<path d="M26.5569 0.50009H33.3531L39.2253 13.0169C39.4563 13.5093 40.1589 13.5085 40.3888 13.0155L46.2237 0.50009H53.1458L41.8949 24.5H34.9858L39.4713 14.9H35.3564C34.1107 14.9 32.9775 14.1813 32.4496 13.0563L26.5569 0.50009Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,38 @@
<svg width="84" height="84" viewBox="0 0 84 84" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 56L21 56L21 49L28 49L28 42C20.268 42 14 48.268 14 56Z" fill="#242B2D"/>
<path d="M42 56L35 56L35 63L28 63L28 70C35.732 70 42 63.732 42 56Z" fill="#242B2D"/>
<path d="M28 42V49H35V56H42C42 48.268 35.732 42 28 42Z" fill="#242B2D"/>
<path d="M28 70L28 63L21 63L21 56L14 56C14 63.732 20.268 70 28 70Z" fill="#242B2D"/>
<path d="M14 28L21 28L21 21L28 21L28 14C20.268 14 14 20.268 14 28Z" fill="#242B2D"/>
<path d="M42 28L35 28L35 35L28 35L28 42C35.732 42 42 35.732 42 28Z" fill="#242B2D"/>
<path d="M28 14V21H35V28H42C42 20.268 35.732 14 28 14Z" fill="#242B2D"/>
<path d="M28 42L28 35L21 35L21 28L14 28C14 35.732 20.268 42 28 42Z" fill="#242B2D"/>
<path d="M42 56L49 56L49 49L56 49L56 42C48.268 42 42 48.268 42 56Z" fill="#242B2D"/>
<path d="M70 56L63 56L63 63L56 63L56 70C63.732 70 70 63.732 70 56Z" fill="#242B2D"/>
<path d="M56 42V49H63V56H70C70 48.268 63.732 42 56 42Z" fill="#242B2D"/>
<path d="M56 70L56 63L49 63L49 56L42 56C42 63.732 48.268 70 56 70Z" fill="#242B2D"/>
<path d="M42 28L49 28L49 21L56 21L56 14C48.268 14 42 20.268 42 28Z" fill="#242B2D"/>
<path d="M70 28L63 28L63 35L56 35L56 42C63.732 42 70 35.732 70 28Z" fill="#242B2D"/>
<path d="M56 14V21H63V28H70C70 20.268 63.732 14 56 14Z" fill="#242B2D"/>
<path d="M56 42L56 35L49 35L49 28L42 28C42 35.732 48.268 42 56 42Z" fill="#242B2D"/>
<path d="M70 0L70 7L77 7L77 14L84 14C84 6.26801 77.732 0 70 0Z" fill="#242B2D"/>
<rect x="63" width="7" height="7" fill="#242B2D"/>
<rect x="56" width="7" height="7" fill="#242B2D"/>
<rect x="77" y="14" width="7" height="7" fill="#242B2D"/>
<rect x="77" y="21" width="7" height="7" fill="#242B2D"/>
<path d="M14 84L14 77L7 77L7 70L1.22392e-06 70C5.47967e-07 77.732 6.26801 84 14 84Z" fill="#242B2D"/>
<rect x="21" y="84" width="7" height="7" transform="rotate(-180 21 84)" fill="#242B2D"/>
<rect x="28" y="84" width="7" height="7" transform="rotate(-180 28 84)" fill="#242B2D"/>
<rect x="7" y="70" width="7" height="7" transform="rotate(-180 7 70)" fill="#242B2D"/>
<rect x="7" y="63" width="7" height="7" transform="rotate(-180 7 63)" fill="#242B2D"/>
<path d="M84 70L77 70L77 77L70 77L70 84C77.732 84 84 77.732 84 70Z" fill="#242B2D"/>
<rect x="84" y="63" width="7" height="7" transform="rotate(90 84 63)" fill="#242B2D"/>
<rect x="84" y="56" width="7" height="7" transform="rotate(90 84 56)" fill="#242B2D"/>
<rect x="70" y="77" width="7" height="7" transform="rotate(90 70 77)" fill="#242B2D"/>
<rect x="63" y="77" width="7" height="7" transform="rotate(90 63 77)" fill="#242B2D"/>
<path d="M0 14L7 14L7 7L14 7L14 1.83588e-06C6.26802 8.21951e-07 1.01393e-06 6.26801 0 14Z" fill="#242B2D"/>
<rect y="21" width="7" height="7" transform="rotate(-90 0 21)" fill="#242B2D"/>
<rect y="28" width="7" height="7" transform="rotate(-90 0 28)" fill="#242B2D"/>
<rect x="14" y="7" width="7" height="7" transform="rotate(-90 14 7)" fill="#242B2D"/>
<rect x="21" y="7" width="7" height="7" transform="rotate(-90 21 7)" fill="#242B2D"/>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

+23
View File
@@ -0,0 +1,23 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="25"
viewBox="0 0 24 25"
fill="none"
>
<g clipPath="url(#clip0_2549_7563)">
<path
d="M20.4841 4.01607C15.8041 -0.67593 8.19607 -0.67593 3.51607 4.01607C-1.17593 8.70807 -1.17593 16.3041 3.51607 20.9841C8.20807 25.6761 15.8041 25.6761 20.4841 20.9841C25.1761 16.3041 25.1761 8.69607 20.4841 4.01607ZM19.4521 19.9521C15.3361 24.0681 8.65207 24.0681 4.53607 19.9521C0.42007 15.8361 0.42007 9.15207 4.53607 5.03607C8.65207 0.92007 15.3361 0.92007 19.4521 5.03607C23.5801 9.16407 23.5801 15.8361 19.4521 19.9521Z"
fill="white"
/>
<path
d="M18.48 19.4965V5.50447C17.868 4.92847 17.184 4.42447 16.452 4.02847V17.4085L7.62002 3.98047C6.85202 4.38847 6.14402 4.89247 5.52002 5.49247V19.4965C6.13202 20.0725 6.81602 20.5765 7.54802 20.9725V7.59247L16.38 21.0205C17.148 20.6125 17.856 20.0965 18.48 19.4965Z"
fill="white"
/>
</g>
<defs>
<clipPath id="clip0_2549_7563">
<rect width="24" height="24" fill="black" transform="translate(0 0.5)" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,12 +1,10 @@
import { fetchObservatoryNodes } from "@/app/api";
import type { IObservatoryNode } from "@/app/api/types";
import AccountPageButtonGroup from "@/components/accountPageComponents/AccountPageButtonGroup";
import { Box, Typography } from "@mui/material";
import Grid from "@mui/material/Grid2";
import { AccountBalancesCard } from "../../../../components/accountPageComponents/AccountBalancesCard";
import { AccountInfoCard } from "../../../../components/accountPageComponents/AccountInfoCard";
import { ContentLayout } from "../../../../components/contentLayout/ContentLayout";
import SectionHeading from "../../../../components/headings/SectionHeading";
import ExplorerButtonGroup from "../../../../components/toggleButton/ToggleButton";
export default async function Account({
params,
@@ -16,12 +14,6 @@ export default async function Account({
try {
const address = (await params).address;
const nymNodes: IObservatoryNode[] = await fetchObservatoryNodes();
const nymNode = nymNodes.find(
(node) => node.bonding_address === address,
);
return (
<ContentLayout>
<Grid container columnSpacing={5} rowSpacing={5}>
@@ -31,23 +23,7 @@ export default async function Account({
<Grid size={6} justifyContent="flex-end">
<Box sx={{ display: "flex", justifyContent: "end" }}>
<ExplorerButtonGroup
onPage="Account"
options={[
{
label: "Nym Node",
isSelected: false,
link: nymNode
? `/nym-node/${nymNode.node_id}`
: `/account/${address}/not-found`,
},
{
label: "Account",
isSelected: true,
link: `/account/${address}`,
},
]}
/>
<AccountPageButtonGroup address={address} />
</Box>
</Grid>
@@ -1,14 +1,13 @@
import { fetchNodeIdByIdentityKey, fetchNodeInfo } from "@/app/api";
import { ContentLayout } from "@/components/contentLayout/ContentLayout";
import SectionHeading from "@/components/headings/SectionHeading";
import { BasicInfoCard } from "@/components/nymNodePageComponents/BasicInfoCard";
import { NodeDataCard } from "@/components/nymNodePageComponents/NodeDataCard";
// import { NodeChatCard } from "@/components/nymNodePageComponents/ChatCard";
import NodeDelegationsCard from "@/components/nymNodePageComponents/NodeDelegationsCard";
import NodePageButtonGroup from "@/components/nymNodePageComponents/NodePageButtonGroup";
import { NodeParametersCard } from "@/components/nymNodePageComponents/NodeParametersCard";
import { NodeProfileCard } from "@/components/nymNodePageComponents/NodeProfileCard";
import { NodeRoleCard } from "@/components/nymNodePageComponents/NodeRoleCard";
import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton";
import { Box } from "@mui/material";
import Grid from "@mui/material/Grid2";
@@ -18,22 +17,7 @@ export default async function NymNode({
params: Promise<{ id: string }>; // node_id or identity_key
}) {
try {
let id: string | number;
const paramsId = (await params).id;
// check if the params id is a node_id or identity_key
if (paramsId.length > 10) {
id = await fetchNodeIdByIdentityKey(paramsId);
} else {
id = Number(paramsId);
}
const observatoryNymNode = await fetchNodeInfo(id);
if (!observatoryNymNode) {
return null;
}
const paramId = (await params).id;
return (
<ContentLayout>
@@ -41,23 +25,7 @@ export default async function NymNode({
<Grid size={12}>
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<SectionHeading title="Nym Node Details" />
{observatoryNymNode.bonding_address && (
<ExplorerButtonGroup
onPage="Nym Node"
options={[
{
label: "Nym Node",
isSelected: true,
link: `/nym-node/${id}`,
},
{
label: "Account",
isSelected: false,
link: `/account/${observatoryNymNode.bonding_address}`,
},
]}
/>
)}
<NodePageButtonGroup paramId={paramId} />
</Box>
</Grid>
<Grid
@@ -66,7 +34,7 @@ export default async function NymNode({
md: 4,
}}
>
<NodeProfileCard id={id} />
<NodeProfileCard paramId={paramId} />
</Grid>
<Grid
size={{
@@ -74,7 +42,7 @@ export default async function NymNode({
md: 4,
}}
>
<BasicInfoCard id={id} />
<BasicInfoCard paramId={paramId} />
</Grid>
<Grid
size={{
@@ -82,7 +50,7 @@ export default async function NymNode({
md: 4,
}}
>
<NodeRoleCard id={id} />
<NodeRoleCard paramId={paramId} />
</Grid>
<Grid
size={{
@@ -90,7 +58,7 @@ export default async function NymNode({
md: 6,
}}
>
<NodeParametersCard id={id} />
<NodeParametersCard paramId={paramId} />
</Grid>
<Grid
size={{
@@ -98,14 +66,14 @@ export default async function NymNode({
md: 6,
}}
>
<NodeDataCard id={id} />
<NodeDataCard paramId={paramId} />
</Grid>
<Grid
size={{
xs: 12,
}}
>
<NodeDelegationsCard id={id} />
<NodeDelegationsCard paramId={paramId} />
</Grid>
{/*
<Grid
@@ -70,13 +70,13 @@ export default async function BlogPage({
<Typography key={author} variant="subtitle3">
{author}
</Typography>
)
),
)}
</Box>
<time dateTime={blogArticle?.attributes?.date.toString()}>
{format(
new Date(blogArticle?.attributes?.date),
"MMMM dd, yyyy"
"MMMM dd, yyyy",
)}
</time>
</Typography>
-18
View File
@@ -53,21 +53,6 @@ export const fetchGatewayStatus = async (
return response.json();
};
export const fetchNodeInfo = async (
id: number,
): Promise<IObservatoryNode | undefined> => {
const nodes = await fetchObservatoryNodes();
return nodes?.find((node) => node.node_id === id);
};
export const fetchNodeIdByIdentityKey = async (
identity_key: string,
): Promise<number> => {
const nodes = await fetchObservatoryNodes();
const node = nodes?.find((node) => node.identity_key === identity_key);
return node?.node_id || 0;
};
export const fetchNodeDelegations = async (
id: number,
): Promise<NodeRewardDetails[]> => {
@@ -207,8 +192,6 @@ export const fetchAccountBalance = async (
return data;
};
export const fetchObservatoryNodes = async (): Promise<IObservatoryNode[]> => {
const allNodes: IObservatoryNode[] = [];
let page = 1;
@@ -239,7 +222,6 @@ export const fetchObservatoryNodes = async (): Promise<IObservatoryNode[]> => {
page++; // Move to the next page
}
}
return allNodes;
};
-1
View File
@@ -1,4 +1,3 @@
export const CURRENT_EPOCH =
"https://validator.nymtech.net/api/v1/epoch/current";
export const CURRENT_EPOCH_REWARDS =
+6 -7
View File
@@ -6,7 +6,7 @@ import SectionHeading from "../components/headings/SectionHeading";
import { CurrentEpochCard } from "../components/landingPageComponents/CurrentEpochCard";
import { NetworkStakeCard } from "../components/landingPageComponents/NetworkStakeCard";
import { NoiseCard } from "../components/landingPageComponents/NoiseCard";
import { RewardsCard } from "../components/landingPageComponents/StakersNumberCard";
import { StakersNumberCard } from "../components/landingPageComponents/StakersNumberCard";
import { TokenomicsCard } from "../components/landingPageComponents/TokenomicsCard";
import NodeTable from "../components/nodeTable/NodeTableWithAction";
import NodeAndAddressSearch from "../components/search/NodeAndAddressSearch";
@@ -15,34 +15,33 @@ export default async function Home() {
return (
<ContentLayout>
<Stack gap={5}>
<NodeAndAddressSearch />
</Stack>
<Grid container columnSpacing={5} rowSpacing={5}>
<Grid size={12}>
<SectionHeading title="Noise Generating Network Overview" />
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
<NoiseCard />
</Grid>
<Grid
container
columnSpacing={5}
rowSpacing={5}
size={{ xs: 12, sm: 6, md: 3 }}
size={{ xs: 12, sm: 6, lg: 3 }}
>
<Grid size={12}>
<RewardsCard />
<StakersNumberCard />
</Grid>
<Grid size={12}>
<CurrentEpochCard />
</Grid>
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
<NetworkStakeCard />
</Grid>
<Grid size={{ xs: 12, sm: 6, md: 3 }}>
<Grid size={{ xs: 12, sm: 6, lg: 3 }}>
<TokenomicsCard />
</Grid>
</Grid>
+10
View File
@@ -0,0 +1,10 @@
import { QueryClient } from "@tanstack/react-query";
let queryClient: QueryClient | null = null;
export function getQueryClient() {
if (!queryClient) {
queryClient = new QueryClient();
}
return queryClient;
}
@@ -70,6 +70,9 @@ export const AccountBalancesCard = (props: IAccountBalancesCardProps) => {
queryKey: ["accountBalance", address],
queryFn: () => fetchAccountBalance(address),
enabled: !!address,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
});
const {
@@ -79,6 +82,9 @@ export const AccountBalancesCard = (props: IAccountBalancesCardProps) => {
} = useQuery({
queryKey: ["nymPrice"],
queryFn: fetchNymPrice,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
});
if (isLoading || isLoadingPrice) {
@@ -12,6 +12,7 @@ import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Typography from "@mui/material/Typography";
import { useTheme } from "@mui/material/styles";
import useMediaQuery from "@mui/material/useMediaQuery";
import * as React from "react";
import { TABLET_WIDTH } from "../../app/constants";
@@ -38,6 +39,7 @@ const progressBarColours = [
const Row = (props: IAccontStatsRowProps) => {
const tablet = useMediaQuery(TABLET_WIDTH);
const theme = useTheme();
const {
type,
@@ -60,11 +62,17 @@ const Row = (props: IAccontStatsRowProps) => {
sx={{
borderBottom: isLastRow
? "none"
: "1px solid rgba(224, 224, 224, 1)",
: `1px solid ${theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(224, 224, 224, 1)"}`,
width: "25%",
}}
>
<Typography variant="body4" sx={{ color: "pine.950" }}>
<Typography
variant="body4"
sx={{
color:
theme.palette.mode === "dark" ? "base.white" : "pine.950",
}}
>
{type}
</Typography>
</TableCell>
@@ -73,12 +81,18 @@ const Row = (props: IAccontStatsRowProps) => {
sx={{
borderBottom: isLastRow
? "none"
: "1px solid rgba(224, 224, 224, 1)",
: `1px solid ${theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(224, 224, 224, 1)"}`,
width: "25%",
}}
>
<Box>
<Typography variant="body4" sx={{ color: "pine.950" }}>
<Typography
variant="body4"
sx={{
color:
theme.palette.mode === "dark" ? "base.white" : "pine.950",
}}
>
{allocation}%
</Typography>
<StaticProgressBar
@@ -92,11 +106,17 @@ const Row = (props: IAccontStatsRowProps) => {
sx={{
borderBottom: isLastRow
? "none"
: "1px solid rgba(224, 224, 224, 1)",
: `1px solid ${theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(224, 224, 224, 1)"}`,
width: "20%",
}}
>
<Typography variant="body4" sx={{ color: "pine.950" }}>
<Typography
variant="body4"
sx={{
color:
theme.palette.mode === "dark" ? "base.white" : "pine.950",
}}
>
{amount.toFixed(4)} NYM
</Typography>
</TableCell>
@@ -105,13 +125,17 @@ const Row = (props: IAccontStatsRowProps) => {
sx={{
borderBottom: isLastRow
? "none"
: "1px solid rgba(224, 224, 224, 1)",
: `1px solid ${theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(224, 224, 224, 1)"}`,
width: "20%",
}}
>
<Typography
variant="subtitle2"
sx={{ color: "pine.950", fontWeight: 700 }}
sx={{
color:
theme.palette.mode === "dark" ? "base.white" : "pine.950",
fontWeight: 700,
}}
>
${value}
</Typography>
@@ -120,7 +144,7 @@ const Row = (props: IAccontStatsRowProps) => {
sx={{
borderBottom: isLastRow
? "none"
: "1px solid rgba(224, 224, 224, 1)",
: `1px solid ${theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(224, 224, 224, 1)"}`,
width: "10%",
}}
>
@@ -142,13 +166,20 @@ const Row = (props: IAccontStatsRowProps) => {
sx={{
borderBottom: isLastRow
? "none"
: "1px solid rgba(224, 224, 224, 1)",
: `1px solid ${theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(224, 224, 224, 1)"}`,
width: "45%",
}}
>
<Box display={"flex"} gap={1} alignItems={"center"}>
<CircleIcon sx={{ color: progressBarColor, fontSize: 12 }} />
{type}
<Typography
sx={{
color:
theme.palette.mode === "dark" ? "base.white" : "inherit",
}}
>
{type}
</Typography>
</Box>
</TableCell>
@@ -157,19 +188,31 @@ const Row = (props: IAccontStatsRowProps) => {
sx={{
borderBottom: isLastRow
? "none"
: "1px solid rgba(224, 224, 224, 1)",
: `1px solid ${theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(224, 224, 224, 1)"}`,
width: "45%",
}}
>
<Typography>{amount.toFixed(4)} NYM</Typography>
<Typography>$ {value}</Typography>
<Typography
sx={{
color: theme.palette.mode === "dark" ? "base.white" : "inherit",
}}
>
{amount.toFixed(4)} NYM
</Typography>
<Typography
sx={{
color: theme.palette.mode === "dark" ? "base.white" : "inherit",
}}
>
$ {value}
</Typography>
</TableCell>
<TableCell
sx={{
borderBottom: isLastRow
? "none"
: "1px solid rgba(224, 224, 224, 1)",
: `1px solid ${theme.palette.mode === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(224, 224, 224, 1)"}`,
width: "10%",
}}
>
@@ -199,7 +242,13 @@ const Row = (props: IAccontStatsRowProps) => {
borderBottom: "none", // Explicitly remove border
}}
>
<Typography variant="body4" sx={{ color: "pine.950" }}>
<Typography
variant="body4"
sx={{
color:
theme.palette.mode === "dark" ? "base.white" : "pine.950",
}}
>
<span style={{ marginRight: 8 }}></span>
{historyRow.type}
</Typography>
@@ -218,7 +267,13 @@ const Row = (props: IAccontStatsRowProps) => {
borderBottom: "none", // Explicitly remove border
}}
>
<Typography variant="body4" sx={{ color: "pine.950" }}>
<Typography
variant="body4"
sx={{
color:
theme.palette.mode === "dark" ? "base.white" : "pine.950",
}}
>
{historyRow.amount.toFixed(4)} NYM
</Typography>
</TableCell>
@@ -241,6 +296,8 @@ export interface IAccountBalancesTableProps {
export const AccountBalancesTable = (props: IAccountBalancesTableProps) => {
const { rows } = props;
const tablet = useMediaQuery(TABLET_WIDTH);
const theme = useTheme();
const progressBarPercentages = () => {
return rows.map((row) => row.allocation);
};
@@ -267,22 +324,54 @@ export const AccountBalancesTable = (props: IAccountBalancesTableProps) => {
{tablet ? (
<TableRow>
<TableCell>
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
<Typography
variant="subtitle2"
sx={{
color:
theme.palette.mode === "dark"
? "base.white"
: "pine.600",
}}
>
Type
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
<Typography
variant="subtitle2"
sx={{
color:
theme.palette.mode === "dark"
? "base.white"
: "pine.600",
}}
>
Allocation
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
<Typography
variant="subtitle2"
sx={{
color:
theme.palette.mode === "dark"
? "base.white"
: "pine.600",
}}
>
Amount
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
<Typography
variant="subtitle2"
sx={{
color:
theme.palette.mode === "dark"
? "base.white"
: "pine.600",
}}
>
Value
</Typography>
</TableCell>
@@ -291,12 +380,28 @@ export const AccountBalancesTable = (props: IAccountBalancesTableProps) => {
) : (
<TableRow>
<TableCell>
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
<Typography
variant="subtitle2"
sx={{
color:
theme.palette.mode === "dark"
? "base.white"
: "pine.600",
}}
>
Type
</Typography>
</TableCell>
<TableCell align="right">
<Typography variant="subtitle2" sx={{ color: "pine.600" }}>
<Typography
variant="subtitle2"
sx={{
color:
theme.palette.mode === "dark"
? "base.white"
: "pine.600",
}}
>
Amount / Value
</Typography>
</TableCell>
@@ -18,6 +18,9 @@ export const AccountInfoCard = (props: IAccountInfoCardProps) => {
queryKey: ["accountBalance", address],
queryFn: () => fetchAccountBalance(address),
enabled: !!address,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
});
if (isLoading) {
@@ -0,0 +1,47 @@
"use client";
import { fetchObservatoryNodes } from "@/app/api";
import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton";
import { useQuery } from "@tanstack/react-query";
type Props = {
address: string;
};
export default function AccountPageButtonGroup({ address }: Props) {
const { data: nymNodes, isError } = useQuery({
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
if (!nymNodes || isError) return null;
const nymNode = nymNodes.find((node) => node.bonding_address === address);
if (!nymNode) return null;
if (nymNode.bonding_address)
return (
<ExplorerButtonGroup
onPage="Account"
options={[
{
label: "Nym Node",
isSelected: false,
link: nymNode
? `/nym-node/${nymNode.node_id}`
: `/account/${address}/not-found`,
},
{
label: "Account",
isSelected: true,
link: `/account/${address}`,
},
]}
/>
);
}
@@ -1,9 +1,9 @@
import fs from "node:fs/promises";
import path from "node:path";
import { type IconName, icons } from "@/utils/getIconByName";
import Grid from "@mui/material/Grid2";
import ExplorerHeroCard from "../cards/ExplorerHeroCard";
import type { BlogArticleWithLink } from "./types";
import { icons, IconName } from "@/utils/getIconByName";
// TODO: Articles should be sorted by date
@@ -14,6 +14,7 @@ const BlogArticlesCards = async ({
limit?: number;
ids?: Array<number>;
}) => {
// --- Data Fetching ---
const blogsDir = path.join(process.cwd(), "/src/data");
const blogsDirFilenames = await fs.readdir(blogsDir);
@@ -29,6 +30,7 @@ const BlogArticlesCards = async ({
};
}),
);
// --- End Data Fetching ---
const limitedOrFilteredBlogArticles = (
blogArticles: BlogArticleWithLink[],
@@ -62,6 +64,9 @@ const BlogArticlesCards = async ({
);
})
.map((blogArticle) => {
const iconLightSrc = icons[blogArticle.iconLight as IconName]?.src;
const iconDarkSrc = icons[blogArticle.iconDark as IconName]?.src;
return (
<Grid
size={{
@@ -74,7 +79,8 @@ const BlogArticlesCards = async ({
label={blogArticle.label}
title={blogArticle.title}
description={blogArticle.description}
icon={icons[blogArticle.icon as IconName]?.src}
iconLightSrc={iconLightSrc}
iconDarkSrc={iconDarkSrc ?? iconLightSrc}
link={blogArticle.link || ""}
sx={{ height: "100%" }}
/>
+2 -1
View File
@@ -6,7 +6,8 @@ type BlogArticle = {
label: string;
description: string;
image: string;
icon: string;
iconLight: string;
iconDark: string;
attributes: {
blogAuthors: string[];
date: Date;
@@ -1,18 +1,14 @@
import { Card, CardContent, CardHeader, type SxProps } from "@mui/material";
import {
Card,
CardContent,
CardHeader,
type SxProps,
useTheme,
} from "@mui/material";
const cardStyles = {
p: 2,
};
const cardTitleStyles: SxProps = {
variant: "h5",
color: "pine.600",
letterSpacing: 0.7,
};
const cardSubtitleStyles: SxProps = {
variant: "h2",
mt: 3,
color: "pine.400",
bgcolor: "background.paper",
};
const ExplorerCard = ({
@@ -26,13 +22,27 @@ const ExplorerCard = ({
children: React.ReactNode;
sx?: SxProps;
}) => {
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
const cardLabelStyles: SxProps = {
variant: "h5",
color: isDarkMode ? "pine.300" : "pine.600",
letterSpacing: 0.7,
};
const cardTitleStyles: SxProps = {
variant: "h2",
mt: 3,
color: isDarkMode ? "base.white" : "pine.950",
};
return (
<Card elevation={0} sx={{ ...cardStyles, ...sx }}>
<CardHeader
title={label}
titleTypographyProps={cardTitleStyles}
subheader={title}
subheaderTypographyProps={cardSubtitleStyles}
slotProps={{ subheader: cardTitleStyles, title: cardLabelStyles }}
/>
<CardContent>{children}</CardContent>
</Card>
@@ -1,10 +1,13 @@
"use client";
import {
Box,
Card,
CardContent,
CardHeader,
Stack,
type SxProps,
Typography,
useTheme,
} from "@mui/material";
import Image from "next/image";
import ArrowUpRight from "../../components/icons/ArrowUpRight";
@@ -14,7 +17,7 @@ const cardStyles = {
p: 3,
cursor: "pointer",
"&:hover": {
bgcolor: "accent.main",
// Hover style adjusted based on theme mode below
},
};
const cardContentStyles = {
@@ -28,40 +31,71 @@ const ExplorerHeroCard = ({
title,
label,
description,
icon,
iconLightSrc,
iconDarkSrc,
link,
sx,
}: {
title: string;
label: string;
description: string;
icon: string;
iconLightSrc: string;
iconDarkSrc: string;
link: string;
sx?: SxProps;
}) => {
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
const dynamicCardStyles = {
...cardStyles,
bgcolor: isDarkMode ? "pine.300" : "background.paper",
"&:hover": {
bgcolor: isDarkMode ? "pine.600" : "accent.main",
},
...sx,
};
const dynamicTitleStyles = {
...titleStyles,
color: isDarkMode ? "pine.950" : "inherit",
};
const dynamicDescriptionStyles = {
color: isDarkMode ? "pine.950" : "inherit",
};
const iconSrc = isDarkMode ? iconDarkSrc : iconLightSrc;
return (
<Link href={link} sx={{ textDecoration: "none", height: "100%" }}>
<Card sx={{ ...cardStyles, ...sx }} elevation={0}>
<Card sx={dynamicCardStyles} elevation={0}>
<CardHeader
title={
<Stack direction="row" justifyContent="space-between">
<Typography variant="body4" sx={titleStyles}>
<Typography variant="body4" sx={dynamicTitleStyles}>
{label}
</Typography>
<ArrowUpRight />
<Box sx={{ color: isDarkMode ? "pine.950" : "inherit" }}>
<ArrowUpRight />
</Box>
</Stack>
}
/>
<CardContent sx={cardContentStyles}>
<Stack spacing={4}>
<Image
src={icon}
src={iconSrc}
alt={"explorer-blog-image"}
width={84}
height={84}
/>
<Typography variant="h2">{title}</Typography>
<Typography variant="body3">{description}</Typography>
<Typography variant="h2" sx={dynamicTitleStyles}>
{title}
</Typography>
<Typography variant="body3" sx={dynamicDescriptionStyles}>
{description}
</Typography>
</Stack>
</CardContent>
</Card>
@@ -1,9 +1,10 @@
"use client";
import { IconButton, Typography } from "@mui/material";
import { IconButton, Typography, useTheme } from "@mui/material";
import { useCopyToClipboard } from "@uidotdev/usehooks";
import { useEffect } from "react";
import CopyFile from "../icons/CopyFile";
import CopyFileDark from "../icons/CopyFileDark";
const CLEAR_AFTER_MS = 10_000;
@@ -18,6 +19,7 @@ const CopyToClipboard = ({
}) => {
const [copiedText, setCopiedText] = useCopyToClipboard();
const hasCopied = Boolean(copiedText);
const theme = useTheme();
useEffect(() => {
if (hasCopied) {
@@ -39,7 +41,8 @@ const CopyToClipboard = ({
return (
<IconButton size={size} onClick={() => setCopiedText(text)}>
{Icon || <CopyFile />}
{Icon ||
(theme.palette.mode === "dark" ? <CopyFileDark /> : <CopyFile />)}
</IconButton>
);
};
@@ -1,4 +1,4 @@
import { Stack, Typography } from "@mui/material";
import { Stack, Typography, useTheme } from "@mui/material";
import Flag from "react-world-flags";
interface ICountryFlag {
@@ -7,11 +7,17 @@ interface ICountryFlag {
}
const CountryFlag = ({ countryCode, countryName }: ICountryFlag) => {
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
return (
<Stack direction="row" gap={1}>
<Flag code={countryCode} width="19" />
<Typography variant="h6" sx={{ color: "pine.950" }}>
<Typography
variant="h6"
sx={{ color: isDarkMode ? "white" : "pine.900" }}
>
{countryName}
</Typography>
</Stack>
@@ -1,6 +1,6 @@
"use client";
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
// import { useMainContext } from "@/context";
export const SocialIcon = ({ channel }: { channel: string }): JSX.Element => {
@@ -4,6 +4,7 @@ import { Link } from "../../components/muiLink";
import { Wrapper } from "../../components/wrapper";
import ConnectWallet from "../wallet/ConnectWallet";
import HeaderItem from "./HeaderItem";
import { DarkLightSwitchDesktop } from "./Switch";
import MENU_DATA from "./menuItems";
export const DesktopHeader = () => {
@@ -50,6 +51,7 @@ export const DesktopHeader = () => {
))}
</Box>
<ConnectWallet size="small" />
<DarkLightSwitchDesktop defaultChecked />
</Wrapper>
<Divider variant="fullWidth" sx={{ width: "100%" }} />
</Box>
@@ -1,15 +1,18 @@
"use client";
import { Close as CloseIcon, Menu as MenuIcon } from "@mui/icons-material";
import { Box, Drawer, IconButton, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useState } from "react";
import { Link } from "../../components/muiLink";
import { Wrapper } from "../../components/wrapper";
import NymLogo from "../icons/NymLogo";
import ConnectWallet from "../wallet/ConnectWallet";
import { DarkLightSwitchDesktop } from "./Switch";
import MENU_DATA from "./menuItems";
export const MobileHeader = () => {
const [drawerOpen, setDrawerOpen] = useState(false);
const theme = useTheme();
// Mobile menu handlers
const toggleDrawer = (open: boolean) => {
@@ -31,10 +34,14 @@ export const MobileHeader = () => {
sx: {
width: "100%",
height: "100%",
bgcolor: "base.white",
bgcolor:
theme.palette.mode === "dark" ? "pine.900" : "base.white",
overflow: "hidden",
position: "relative",
color: "background.main",
color:
theme.palette.mode === "dark"
? "base.white"
: "background.main",
},
}}
>
@@ -65,7 +72,10 @@ export const MobileHeader = () => {
display: "flex",
width: "100%",
padding: 3.75,
color: "background.main",
color:
theme.palette.mode === "dark"
? "base.white"
: "background.main",
justifyContent: "space-between",
alignItems: "center",
}}
@@ -84,10 +94,20 @@ export const MobileHeader = () => {
width: "10px",
height: "10px",
borderRadius: "100%",
bgcolor: "primary.main",
bgcolor:
theme.palette.mode === "dark"
? "base.white"
: "primary.main",
}}
/>
<Typography color="primary" variant="h4">
<Typography
color={
theme.palette.mode === "dark"
? "base.white"
: "primary"
}
variant="h4"
>
{menu.title}
</Typography>
</Box>
@@ -139,16 +159,27 @@ const MobileMenuHeader = ({
<Box
sx={{
display: "flex",
gap: 2.5,
alignItems: "center",
}}
>
{!drawerOpen && <DarkLightSwitchDesktop defaultChecked />}
<IconButton sx={{}} onClick={() => toggleDrawer(!drawerOpen)}>
{drawerOpen ? <CloseIcon /> : <MenuIcon />}
</IconButton>
</Box>
</Box>
{!drawerOpen && <ConnectWallet size="small" />}
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 2.5,
justifyContent: "center",
alignItems: "center",
width: "100%",
}}
>
{!drawerOpen && <ConnectWallet size="small" />}
</Box>
<Box height={40} />
</Wrapper>
);
@@ -0,0 +1,78 @@
"use client";
import { Button } from "@mui/material";
import type { PaletteMode } from "@mui/material";
import Switch from "@mui/material/Switch";
import { styled } from "@mui/material/styles";
import { useLocalStorage } from "@uidotdev/usehooks";
import * as React from "react";
export const DarkLightSwitch = styled(Switch)(({ theme }) => ({
width: 55,
height: 34,
padding: 7,
"& .MuiSwitch-switchBase": {
margin: 1,
padding: 2,
transform: "translateX(4px)",
"&.Mui-checked": {
color: "#fff",
transform: "translateX(22px)",
"& .MuiSwitch-thumb:before": {
backgroundImage:
'url(\'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="black" d="M4.2 2.5l-.7 1.8-1.8.7 1.8.7.7 1.8.6-1.8L6.7 5l-1.9-.7-.6-1.8zm15 8.3a6.7 6.7 0 11-6.6-6.6 5.8 5.8 0 006.6 6.6z"/></svg>\')',
},
"& + .MuiSwitch-track": {
opacity: 1,
backgroundColor: theme.palette.mode === "dark" ? "#8796A5" : "#aab4be",
},
},
},
"& .MuiSwitch-thumb": {
backgroundColor: theme.palette.text.primary,
width: 25,
height: 25,
marginTop: "2px",
"&:before": {
content: "''",
position: "absolute",
width: "100%",
height: "100%",
left: 0,
top: 0,
backgroundRepeat: "no-repeat",
backgroundPosition: "center",
backgroundImage:
'url(\'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="20" width="20" viewBox="0 0 20 20"><path fill="black" d="M9.305 1.667V3.75h1.389V1.667h-1.39zm-4.707 1.95l-.982.982L5.09 6.072l.982-.982-1.473-1.473zm10.802 0L13.927 5.09l.982.982 1.473-1.473-.982-.982zM10 5.139a4.872 4.872 0 00-4.862 4.86A4.872 4.872 0 0010 14.862 4.872 4.872 0 0014.86 10 4.872 4.872 0 0010 5.139zm0 1.389A3.462 3.462 0 0113.471 10a3.462 3.462 0 01-3.473 3.472A3.462 3.462 0 016.527 10 3.462 3.462 0 0110 6.528zM1.665 9.305v1.39h2.083v-1.39H1.666zm14.583 0v1.39h2.084v-1.39h-2.084zM5.09 13.928L3.616 15.4l.982.982 1.473-1.473-.982-.982zm9.82 0l-.982.982 1.473 1.473.982-.982-1.473-1.473zM9.305 16.25v2.083h1.389V16.25h-1.39z"/></svg>\')',
},
},
"& .MuiSwitch-track": {
opacity: 1,
backgroundColor: theme.palette.mode === "dark" ? "#8796A5" : "#aab4be",
borderRadius: 20 / 2,
},
}));
// export const DarkLightSwitchMobile: FCWithChildren = () => {
// const { toggleMode } = useMainContext();
// return (
// <Button onClick={() => toggleMode()} data-testid="switch-button" sx={{ p: 0, minWidth: 0 }}>
// <LightSwitchSVG />
// </Button>
// );
// };
export const DarkLightSwitchDesktop: FCWithChildren<{
defaultChecked: boolean;
}> = ({ defaultChecked }) => {
const [mode, setMode] = useLocalStorage<PaletteMode>("mode", "dark");
console.log("mode", mode);
const toggleMode = () => setMode((m) => (m !== "light" ? "light" : "dark"));
return (
<Button onClick={() => toggleMode()} data-testid="switch-button">
<DarkLightSwitch defaultChecked={defaultChecked} />
</Button>
);
};
@@ -19,8 +19,7 @@ const MENU_DATA: MenuItem[] = [
id: 3,
title: "Onboarding",
url: "/onboarding",
}
},
];
export default MENU_DATA;
@@ -3,12 +3,7 @@ import Image from "next/image";
import { icons } from "@/utils/getIconByName";
const ArrowUpRight = () => (
<Image
src={icons.arrowUpRight}
alt="Arrow Up Right"
width={32}
height={32}
/>
<Image src={icons.arrowUpRight} alt="Arrow Up Right" width={32} height={32} />
);
export default ArrowUpRight;
+2 -7
View File
@@ -1,14 +1,9 @@
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
const ChevronMenu = () => {
return (
<Image
src={icons.chevronMenu}
alt="Chevron Menu"
width={24}
height={24}
/>
<Image src={icons.chevronMenu} alt="Chevron Menu" width={24} height={24} />
);
};
@@ -1,6 +1,5 @@
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
const CopyFile = ({ className }: { className?: string }) => (
<Image
@@ -0,0 +1,14 @@
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
const CopyFile = ({ className }: { className?: string }) => (
<Image
src={icons.copyFileDark}
alt="Copyfile"
width={16}
height={16}
className={className}
/>
);
export default CopyFile;
+1 -1
View File
@@ -1,5 +1,5 @@
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
const Cross = () => (
<Image src={icons.cross} alt="cross" width={12.5} height={12.5} />
@@ -0,0 +1,8 @@
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
const CrossDark = () => (
<Image src={icons.crossDark} alt="cross" width={12.5} height={12.5} />
);
export default CrossDark;
+1 -1
View File
@@ -1,5 +1,5 @@
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
export const Elips = () => (
<Image src={icons.elips} alt="Elips" width={20} height={20} />
@@ -0,0 +1,6 @@
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
export const ElipseDark = () => (
<Image src={icons.elipseDark} alt="Elips" width={20} height={20} />
);
+1 -1
View File
@@ -1,5 +1,5 @@
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
const Gateway = () => {
return (
+12 -2
View File
@@ -1,9 +1,19 @@
import Image from "next/image";
"use client";
import { icons } from "@/utils/getIconByName";
import type { PaletteMode } from "@mui/material";
import { useLocalStorage } from "@uidotdev/usehooks";
import Image from "next/image";
const NymLogo = () => {
const [mode] = useLocalStorage<PaletteMode>("mode", "dark");
return (
<Image src={icons.nymLogo} alt="Nym Logo" width={90} height={25} />
<Image
src={mode === "dark" ? icons.nymLogoWhite : icons.nymLogo}
alt="Nym Logo"
width={90}
height={25}
/>
);
};
export default NymLogo;
+1 -1
View File
@@ -1,5 +1,5 @@
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
export const Token = () => {
return <Image src={icons.token} alt="Token" width={20} height={20} />;
@@ -0,0 +1,6 @@
import { icons } from "@/utils/getIconByName";
import Image from "next/image";
export const TokenDark = () => {
return <Image src={icons.tokenDark} alt="Token" width={20} height={20} />;
};
@@ -24,6 +24,15 @@ const Input = ({
value={value}
onChange={onChange}
onKeyDown={onKeyDown}
sx={{
"& .MuiInputBase-input": {
color: "#575D63 !important",
"&::placeholder": {
color: "#575D63 !important",
opacity: "1 !important",
},
},
}}
slotProps={{
input: {
sx: {
@@ -30,7 +30,6 @@ const getStartEndTime = (start: string, end: string) => {
export const CurrentEpochCard = () => {
const { data, isError, isLoading, epochStatus } = useEpochContext();
const [startTime, setStartTime] = useState("");
const [endTime, setEndTime] = useState("");
const [progress, setProgress] = useState(0);
@@ -1,6 +1,6 @@
"use client";
import { fetchNoise } from "@/app/api";
import { Box, Skeleton, Stack, Typography } from "@mui/material";
import { Box, Skeleton, Stack, Typography, useTheme } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import type { ExplorerData, IPacketsAndStakingData } from "../../app/api/types";
import { formatBigNum } from "../../utils/formatBigNumbers";
@@ -8,6 +8,8 @@ import ExplorerCard from "../cards/ExplorerCard";
import { LineChart } from "../lineChart";
export const NetworkStakeCard = () => {
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
const {
data: packetsAndStaking,
isLoading: isStakingLoading,
@@ -15,6 +17,9 @@ export const NetworkStakeCard = () => {
} = useQuery({
queryKey: ["noise"],
queryFn: fetchNoise,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
});
if (isStakingLoading) {
@@ -31,7 +36,13 @@ export const NetworkStakeCard = () => {
if (isStakingError || !packetsAndStaking) {
return (
<ExplorerCard label="Current network stake">
<Typography variant="h5" sx={{ color: "pine.600", letterSpacing: 0.7 }}>
<Typography
variant="h5"
sx={{
color: isDarkMode ? "base.white" : "pine.950",
letterSpacing: 0.7,
}}
>
Failed to load data
</Typography>
<Skeleton variant="text" height={238} />
@@ -45,21 +56,19 @@ export const NetworkStakeCard = () => {
const lastTotalStake =
packetsAndStaking[packetsAndStaking.length - 1]?.total_stake / 1_000_000;
const startDate = new Date("2025-02-26").getTime(); // Convert to timestamp
const startDate = new Date("2025-02-26").getTime(); // Convert to timestamp
const data = packetsAndStakingData
.slice(0, -1) // Exclude the last element
.filter((item: IPacketsAndStakingData) => {
const itemDate = new Date(item.date_utc).getTime(); // Convert each date to timestamp
return itemDate >= startDate; // Compare timestamps
})
.map((item: IPacketsAndStakingData) => ({
date_utc: item.date_utc,
numericData: item.total_stake / 1000000,
}));
const data = packetsAndStakingData
.slice(0, -1) // Exclude the last element
.filter((item: IPacketsAndStakingData) => {
const itemDate = new Date(item.date_utc).getTime(); // Convert each date to timestamp
return itemDate >= startDate; // Compare timestamps
})
.map((item: IPacketsAndStakingData) => ({
date_utc: item.date_utc,
numericData: item.total_stake / 1000000,
}))
.filter((item) => item.numericData >= 50_000_000);
const stakeLineGraphData = {
color: "#00CA33",
@@ -74,7 +83,11 @@ export const NetworkStakeCard = () => {
<Stack>
<Typography
variant="h3"
sx={{ color: "pine.950", wordWrap: "break-word", maxWidth: "95%" }}
sx={{
color: isDarkMode ? "base.white" : "pine.950",
wordWrap: "break-word",
maxWidth: "95%",
}}
>
{title}
</Typography>
@@ -1,7 +1,14 @@
"use client";
import { fetchNoise } from "@/app/api";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import { Box, Skeleton, Stack, Tooltip, Typography } from "@mui/material";
import {
Box,
Skeleton,
Stack,
Tooltip,
Typography,
useTheme,
} from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import type { IPacketsAndStakingData } from "../../app/api/types";
import { formatBigNum } from "../../utils/formatBigNumbers";
@@ -10,9 +17,14 @@ import { LineChart } from "../lineChart";
import { UpDownPriceIndicator } from "../price/UpDownPriceIndicator";
export const NoiseCard = () => {
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
const { data, isLoading, isError } = useQuery({
queryKey: ["noise"],
queryFn: fetchNoise,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
});
if (isLoading) {
@@ -29,7 +41,13 @@ export const NoiseCard = () => {
if (isError || !data) {
return (
<ExplorerCard label="Mixnet traffic">
<Typography variant="h5" sx={{ color: "pine.600", letterSpacing: 0.7 }}>
<Typography
variant="h5"
sx={{
color: isDarkMode ? "base.white" : "pine.950",
letterSpacing: 0.7,
}}
>
Failed to load data
</Typography>
<Skeleton variant="text" height={238} />
@@ -43,8 +61,6 @@ export const NoiseCard = () => {
const noiseLast24H =
todaysData.total_packets_sent + todaysData.total_packets_received;
const noisePrevious24H =
yesterdaysData.total_packets_sent + yesterdaysData.total_packets_received;
@@ -91,15 +107,19 @@ export const NoiseCard = () => {
date_utc: item.date_utc,
numericData: item.total_packets_sent + item.total_packets_received,
};
});
})
.filter((item) => item.numericData >= 2_500_000_000);
return (
<ExplorerCard label="Mixnet traffic" sx={{ height: "100%" }}>
<Box display={"flex"} gap={2} flexDirection={{ xs: "column", sm: "row" }}>
<Typography
variant="h4"
sx={{ color: "pine.950", wordWrap: "break-word", maxWidth: "95%" }}
sx={{
color: isDarkMode ? "base.white" : "pine.950",
wordWrap: "break-word",
maxWidth: "95%",
}}
>
{noiseLast24HFormatted}
</Typography>
@@ -1,20 +1,27 @@
"use client";
import { fetchObservatoryNodes } from "@/app/api";
import type { IObservatoryNode } from "@/app/api/types";
import { Skeleton, Typography } from "@mui/material";
import { Skeleton, Typography, useTheme } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import ExplorerCard from "../cards/ExplorerCard";
export const RewardsCard = () => {
export const StakersNumberCard = () => {
const {
data: observatoryNodes,
data: nymNodes,
isLoading,
isError,
} = useQuery({
queryKey: ["observatoryNodes"],
queryKey: ["nymNodes"],
queryFn: () => fetchObservatoryNodes(),
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
if (isLoading) {
return (
<ExplorerCard label="Number of delegations">
@@ -23,26 +30,33 @@ export const RewardsCard = () => {
);
}
if (isError || !observatoryNodes) {
if (isError || !nymNodes) {
return (
<ExplorerCard label="Number of delegations">
<Typography variant="h3" sx={{ color: "pine.950" }}>
<Typography
variant="h3"
sx={{ color: isDarkMode ? "base.white" : "pine.950" }}
>
Failed to load node data.
</Typography>
</ExplorerCard>
);
}
const getActiveStakersNumber = (nodes: IObservatoryNode[]): number => {
return nodes.reduce(
(sum, node) => sum + node.rewarding_details.unique_delegations,
0,
);
};
const allStakers = getActiveStakersNumber(observatoryNodes);
const allStakers = getActiveStakersNumber(nymNodes);
return (
<ExplorerCard label="Number of delegations">
<Typography variant="h3" sx={{ color: "pine.950" }}>
<Typography
variant="h3"
sx={{ color: isDarkMode ? "base.white" : "pine.950" }}
>
{allStakers}
</Typography>
</ExplorerCard>
@@ -16,6 +16,10 @@ export const TokenomicsCard = () => {
} = useQuery({
queryKey: ["nymPrice"],
queryFn: fetchNymPrice,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
const {
@@ -25,6 +29,10 @@ export const TokenomicsCard = () => {
} = useQuery({
queryKey: ["epochRewards"],
queryFn: fetchEpochRewards,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
const {
@@ -34,6 +42,10 @@ export const TokenomicsCard = () => {
} = useQuery({
queryKey: ["noise"],
queryFn: fetchNoise,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
if (isLoading || isEpochLoading || isStakingLoading) {
+40 -4
View File
@@ -1,4 +1,5 @@
"use client";
import { useTheme } from "@mui/material";
import { ResponsiveLine } from "@nivo/line";
export interface ILineChartData {
@@ -25,6 +26,9 @@ export const LineChart = ({
color: string;
label: string;
}) => {
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
const chartData: ILineAxes = {
id: label,
data: data.map((item) => ({
@@ -55,6 +59,7 @@ export const LineChart = ({
return (
<ResponsiveLine
key={`line-chart-${label}`}
curve="basis"
colors={[color]}
data={[chartData]}
@@ -71,10 +76,14 @@ export const LineChart = ({
grid: { line: { strokeWidth: 0 } },
tooltip: {
container: {
color: "black",
color: isDarkMode ? "white" : "black",
fontSize: 10,
maxWidth: 250,
maxWidth: 200,
lineHeight: 1,
background: isDarkMode ? "#1E1E1E" : "white",
padding: "9px 12px",
border: `1px solid ${isDarkMode ? "rgba(255, 255, 255, 0.1)" : "#E5E7EB"}`,
borderRadius: "4px",
},
},
axis: {
@@ -83,7 +92,7 @@ export const LineChart = ({
},
ticks: {
text: {
fill: "#818386",
fill: isDarkMode ? "white" : "#818386",
},
},
legend: {
@@ -112,7 +121,34 @@ export const LineChart = ({
axisBottom={{
format: "%b %d",
legendOffset: -12,
tickValues: chartData.data.length > 7 ? "every 5 days" : "every 2 days",
tickValues: chartData.data.length > 7 ? "every 6 days" : "every 2 days",
}}
sliceTooltip={(slice) => {
const point = slice.slice.points[0];
const value = point.data.y as number;
const date = point.data.x as Date;
return (
<div
style={{
background: isDarkMode ? "#1E1E1E" : "white",
color: isDarkMode ? "white" : "black",
padding: "9px 12px",
border: `1px solid ${isDarkMode ? "rgba(255, 255, 255, 0.1)" : "#E5E7EB"}`,
borderRadius: "4px",
fontSize: "10px",
lineHeight: 1,
maxWidth: "170px",
}}
>
<div>
<strong>{date.toLocaleDateString()}</strong>
</div>
<div>
{label}: {yformat(value)}
</div>
</div>
);
}}
/>
);
+22 -13
View File
@@ -1,16 +1,10 @@
import { Divider, Stack, type SxProps, Typography } from "@mui/material";
const listItemLabelStyle: SxProps = {
color: "pine.600",
letterSpacing: 0.7,
flexGrow: 1,
};
const listItemValueStyle: SxProps = {
fontSize: "small",
fontWeight: 300,
color: "pine.950",
};
import {
Divider,
Stack,
type SxProps,
Typography,
useTheme,
} from "@mui/material";
const ExplorerListItem = ({
label,
@@ -23,6 +17,21 @@ const ExplorerListItem = ({
row?: boolean;
divider?: boolean;
}) => {
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
const listItemLabelStyle: SxProps = {
color: isDarkMode ? "pine.300" : "pine.600",
letterSpacing: 0.7,
flexGrow: 1,
};
const listItemValueStyle: SxProps = {
fontSize: "small",
fontWeight: 300,
color: isDarkMode ? "base.white" : "pine.950",
};
return (
<>
<Stack
@@ -1,3 +1,5 @@
"use client";
import {
Dialog,
DialogActions,
@@ -5,8 +7,10 @@ import {
IconButton,
Stack,
Typography,
useTheme,
} from "@mui/material";
import Cross from "../../components/icons/Cross";
import CrossDark from "../../components/icons/CrossDark";
type SimpleModalPropsClosed = {
open: false;
@@ -23,6 +27,7 @@ type SimpleModalPropsOpen = {
export type SimpleModalProps = SimpleModalPropsClosed | SimpleModalPropsOpen;
const SimpleModal = (props: SimpleModalProps) => {
const theme = useTheme();
if (!props.open) {
return null;
}
@@ -41,7 +46,7 @@ const SimpleModal = (props: SimpleModalProps) => {
{title}
</Typography>
<IconButton aria-label="close" size="large" onClick={onClose}>
<Cross />
{theme.palette.mode === "dark" ? <CrossDark /> : <Cross />}
</IconButton>
</Stack>
<DialogContent sx={{ p: 4, pt: 0 }}>{children}</DialogContent>
@@ -71,6 +71,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
const queryClient = useQueryClient();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const isDarkMode = theme.palette.mode === "dark";
const [infoModalProps, setInfoModalProps] = useState<InfoModalProps>({
open: false,
@@ -337,25 +338,75 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
},
muiTablePaperProps: {
elevation: 0,
sx: {
bgcolor: isDarkMode ? "#0F1720" : "background.paper",
},
},
muiTableHeadRowProps: {
sx: {
bgcolor: "background.paper",
bgcolor: isDarkMode ? "#374042" : "background.paper",
},
},
muiTableHeadCellProps: {
sx: {
alignItems: "center",
paddingRight: 0,
color: isDarkMode ? "#FFFFFF" : "inherit",
},
},
muiSearchTextFieldProps: {
InputProps: {
style: {
color: isDarkMode ? "#475569" : "inherit",
},
},
sx: {
backgroundColor: isDarkMode ? "#374042" : "white",
"& .MuiOutlinedInput-root": {
color: isDarkMode ? "#475569" : "inherit",
backgroundColor: isDarkMode ? "#374042" : "white",
},
"& .MuiOutlinedInput-notchedOutline": {
borderColor: isDarkMode ? "#334155" : "inherit",
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: isDarkMode ? "#475569" : "inherit",
},
},
variant: "outlined",
size: "small",
},
muiFilterTextFieldProps: {
InputProps: {
sx: {
color: isDarkMode ? "#FFFFFF" : "inherit",
},
},
sx: {
"& .MuiInputBase-root": {
backgroundColor: isDarkMode ? "#1C2A2E" : "white",
},
"& .MuiInputBase-input::placeholder": {
color: isDarkMode ? "#94A3B8" : "inherit",
opacity: 1,
},
"& .MuiOutlinedInput-notchedOutline": {
borderColor: isDarkMode ? "#334155" : "inherit",
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: isDarkMode ? "#475569" : "inherit",
},
},
variant: "outlined",
size: "small",
},
muiTableBodyCellProps: {
sx: {
border: "none",
whiteSpace: "unset", // Allow text wrapping in body cells
wordBreak: "break-word", // Ensure long text breaks correctly
whiteSpace: "unset",
wordBreak: "break-word",
paddingRight: 0,
color: isDarkMode ? "#FFFFFF" : "inherit",
},
},
muiTableBodyRowProps: ({ row }) => ({
@@ -364,11 +415,16 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
},
hover: true,
sx: {
":nth-child(odd)": {
bgcolor: "#F3F7FB !important",
},
":nth-child(even)": {
bgcolor: "white !important",
backgroundColor: isDarkMode
? row.index % 2 === 0
? "#3E4A4C !important"
: "#374042 !important"
: row.index % 2 === 0
? "#F3F7FB"
: "white",
"&:hover": {
backgroundColor: `${isDarkMode ? "#2A3436" : "#E5E7EB"} !important`,
transition: "background-color 0.2s ease",
},
cursor: "pointer",
},
@@ -66,6 +66,10 @@ const NodeTableWithAction = () => {
} = useQuery({
queryKey: ["epochRewards"],
queryFn: fetchEpochRewards,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
// Use React Query to fetch Nym nodes
@@ -76,6 +80,10 @@ const NodeTableWithAction = () => {
} = useQuery({
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
// Handle loading state
@@ -1,25 +1,32 @@
"use client";
import type { IObservatoryNode } from "@/app/api/types";
import { Skeleton, Stack, Typography } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import { fetchNodeInfo } from "../../app/api";
import { fetchObservatoryNodes } from "../../app/api";
import { formatBigNum } from "../../utils/formatBigNumbers";
import ExplorerCard from "../cards/ExplorerCard";
import CopyToClipboard from "../copyToClipboard/CopyToClipboard";
import ExplorerListItem from "../list/ListItem";
interface IBasicInfoCardProps {
id: number;
}
type Props = {
paramId: string;
};
export const BasicInfoCard = ({ paramId }: Props) => {
let nodeInfo: IObservatoryNode | undefined;
export const BasicInfoCard = ({ id }: IBasicInfoCardProps) => {
const {
data: nodeInfo,
data: nymNodes,
isLoading,
isError,
} = useQuery({
queryKey: ["nodeInfo", id],
queryFn: () => fetchNodeInfo(id),
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
if (isLoading) {
@@ -35,7 +42,7 @@ export const BasicInfoCard = ({ id }: IBasicInfoCardProps) => {
);
}
if (isError || !nodeInfo) {
if (isError || !nymNodes) {
return (
<ExplorerCard label="Basic info">
<Typography variant="h3" sx={{ color: "pine.950" }}>
@@ -45,6 +52,16 @@ export const BasicInfoCard = ({ id }: IBasicInfoCardProps) => {
);
}
// get node info based on wether it's dentity_key or node_id
if (paramId.length > 10) {
nodeInfo = nymNodes.find((node) => node.identity_key === paramId);
} else {
nodeInfo = nymNodes.find((node) => node.node_id === Number(paramId));
}
if (!nodeInfo) return null;
const selfBond = formatBigNum(
Number(nodeInfo.rewarding_details.operator) / 1_000_000,
);
@@ -1,6 +1,8 @@
"use client";
import { Stack, Typography } from "@mui/material";
import { fetchNodeDelegations } from "@/app/api";
import { Stack, Typography, useTheme } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import {
type MRT_ColumnDef,
MaterialReactTable,
@@ -27,12 +29,22 @@ const getNymsFormated = (unyms: string) => {
return balance.toFixed();
};
const DelegationsTable = ({
delegations,
}: {
delegations: NodeRewardDetails[];
}) => {
type Props = {
id: number;
};
const DelegationsTable = ({ id }: Props) => {
const router = useRouter();
const theme = useTheme();
const { data: delegations = [], isError } = useQuery({
queryKey: ["nodeDelegations", id],
queryFn: () => fetchNodeDelegations(id),
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
const columns: MRT_ColumnDef<NodeRewardDetails>[] = useMemo(
() => [
@@ -113,7 +125,10 @@ const DelegationsTable = ({
},
muiTableHeadRowProps: {
sx: {
bgcolor: "background.paper",
bgcolor:
theme.palette.mode === "dark"
? "rgba(255, 255, 255, 0.05)"
: "background.paper",
},
},
@@ -129,15 +144,30 @@ const DelegationsTable = ({
hover: true,
sx: {
":nth-child(odd)": {
bgcolor: "#F3F7FB !important",
bgcolor:
theme.palette.mode === "dark"
? "rgba(255, 255, 255, 0.05) !important"
: "#F3F7FB !important",
},
":nth-child(even)": {
bgcolor: "white !important",
bgcolor:
theme.palette.mode === "dark"
? "transparent !important"
: "white !important",
},
cursor: "pointer",
"&:hover": {
bgcolor:
theme.palette.mode === "dark"
? "rgba(255, 255, 255, 0.1) !important"
: "rgba(0, 0, 0, 0.04) !important",
},
},
}),
});
if (isError) return null;
return <MaterialReactTable table={table} />;
};
@@ -1,18 +1,20 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { fetchEpochRewards, fetchNodeInfo } from "../../app/api";
import type { IObservatoryNode } from "@/app/api/types";
import { Skeleton, Typography } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import { format } from "date-fns";
import { fetchEpochRewards, fetchObservatoryNodes } from "../../app/api";
import ExplorerCard from "../cards/ExplorerCard";
import ExplorerListItem from "../list/ListItem";
interface INodeMetricsCardProps {
id: number; // Node ID
}
type Props = {
paramId: string;
};
export const NodeDataCard = ({ paramId }: Props) => {
let nodeInfo: IObservatoryNode | undefined;
export const NodeDataCard = ({ id }: INodeMetricsCardProps) => {
const {
data: epochRewardsData,
isLoading: isEpochLoading,
@@ -20,19 +22,27 @@ export const NodeDataCard = ({ id }: INodeMetricsCardProps) => {
} = useQuery({
queryKey: ["epochRewards"],
queryFn: fetchEpochRewards,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
// Fetch node information
const {
data: nodeInfo,
isLoading: isNodeLoading,
isError: isNodeError,
data: nymNodes,
isLoading,
isError,
} = useQuery({
queryKey: ["nodeInfo", id],
queryFn: () => fetchNodeInfo(id),
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
if (isEpochLoading || isNodeLoading) {
if (isEpochLoading || isLoading) {
return (
<ExplorerCard label="Nym node data" sx={{ height: "100%" }}>
<Skeleton variant="text" height={50} />
@@ -43,7 +53,7 @@ export const NodeDataCard = ({ id }: INodeMetricsCardProps) => {
);
}
if (isEpochError || isNodeError || !nodeInfo || !epochRewardsData) {
if (isEpochError || isError || !nymNodes || !epochRewardsData) {
return (
<ExplorerCard label="Nym node data" sx={{ height: "100%" }}>
<Typography variant="h3" sx={{ color: "pine.950" }}>
@@ -53,6 +63,16 @@ export const NodeDataCard = ({ id }: INodeMetricsCardProps) => {
);
}
// get node info based on wether it's dentity_key or node_id
if (paramId.length > 10) {
nodeInfo = nymNodes.find((node) => node.identity_key === paramId);
} else {
nodeInfo = nymNodes.find((node) => node.node_id === Number(paramId));
}
if (!nodeInfo) return null;
const softwareUpdateTime = format(
new Date(nodeInfo.description.build_information.build_timestamp),
"dd/MM/yyyy",
@@ -1,25 +1,42 @@
"use client";
import { fetchNodeDelegations } from "@/app/api";
import { fetchObservatoryNodes } from "@/app/api";
import type { IObservatoryNode } from "@/app/api/types";
import { Skeleton, Typography } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import ExplorerCard from "../cards/ExplorerCard";
import DelegationsTable from "./DelegationsTable";
interface NodeDelegationsCardProps {
id: number; // Node ID
}
type Props = {
paramId: string;
};
const NodeDelegationsCard = ({ paramId }: Props) => {
let nodeInfo: IObservatoryNode | undefined;
const NodeDelegationsCard = ({ id }: NodeDelegationsCardProps) => {
const {
data: delegations = [],
isLoading,
data: nymNodes,
isError,
isLoading,
} = useQuery({
queryKey: ["nodeDelegations", id],
queryFn: () => fetchNodeDelegations(id),
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
if (paramId.length > 10) {
nodeInfo = nymNodes?.find((node) => node.identity_key === paramId);
} else {
nodeInfo = nymNodes?.find((node) => node.node_id === Number(paramId));
}
if (!nodeInfo) return null;
const id = nodeInfo.node_id;
if (isLoading) {
return (
<ExplorerCard label="Delegations" sx={{ height: "100%" }}>
@@ -43,7 +60,7 @@ const NodeDelegationsCard = ({ id }: NodeDelegationsCardProps) => {
return (
<ExplorerCard label="Delegations" sx={{ height: "100%" }}>
<DelegationsTable delegations={delegations} />
<DelegationsTable id={id} />
</ExplorerCard>
);
};
@@ -0,0 +1,54 @@
"use client";
import { fetchObservatoryNodes } from "@/app/api";
import type { IObservatoryNode } from "@/app/api/types";
import ExplorerButtonGroup from "@/components/toggleButton/ToggleButton";
import { useQuery } from "@tanstack/react-query";
type Props = {
paramId: string;
};
export default function NodePageButtonGroup({ paramId }: Props) {
let nodeInfo: IObservatoryNode | undefined;
const { data: nymNodes, isError } = useQuery({
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
if (!nymNodes || isError) return null;
// get node info based on wether it's dentity_key or node_id
if (paramId.length > 10) {
nodeInfo = nymNodes.find((node) => node.identity_key === paramId);
} else {
nodeInfo = nymNodes.find((node) => node.node_id === Number(paramId));
}
if (!nodeInfo) return null;
if (nodeInfo.bonding_address)
return (
<ExplorerButtonGroup
onPage="Nym Node"
options={[
{
label: "Nym Node",
isSelected: true,
link: `/nym-node/${nodeInfo.node_id}`,
},
{
label: "Account",
isSelected: false,
link: `/account/${nodeInfo.bonding_address}`,
},
]}
/>
);
}
@@ -3,16 +3,18 @@
import { formatBigNum } from "@/utils/formatBigNumbers";
import { Skeleton, Typography } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import { fetchEpochRewards, fetchNodeInfo } from "../../app/api";
import type { RewardingDetails } from "../../app/api/types";
import { fetchEpochRewards, fetchObservatoryNodes } from "../../app/api";
import type { IObservatoryNode, RewardingDetails } from "../../app/api/types";
import ExplorerCard from "../cards/ExplorerCard";
import ExplorerListItem from "../list/ListItem";
interface INodeParametersCardProps {
id: number; // Node ID
}
type Props = {
paramId: string;
};
export const NodeParametersCard = ({ paramId }: Props) => {
let nodeInfo: IObservatoryNode | undefined;
export const NodeParametersCard = ({ id }: INodeParametersCardProps) => {
// Fetch epoch rewards
const {
data: epochRewardsData,
@@ -21,19 +23,27 @@ export const NodeParametersCard = ({ id }: INodeParametersCardProps) => {
} = useQuery({
queryKey: ["epochRewards"],
queryFn: fetchEpochRewards,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
// Fetch node information
const {
data: nodeInfo,
isLoading: isNodeLoading,
isError: isNodeError,
data: nymNodes,
isLoading,
isError,
} = useQuery({
queryKey: ["nodeInfo", id],
queryFn: () => fetchNodeInfo(id),
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
if (isEpochLoading || isNodeLoading) {
if (isEpochLoading || isLoading) {
return (
<ExplorerCard label="Node parameters" sx={{ height: "100%" }}>
<Skeleton variant="text" height={50} />
@@ -44,7 +54,7 @@ export const NodeParametersCard = ({ id }: INodeParametersCardProps) => {
);
}
if (isEpochError || isNodeError || !nodeInfo || !epochRewardsData) {
if (isEpochError || isError || !nymNodes || !epochRewardsData) {
return (
<ExplorerCard label="Node parameters" sx={{ height: "100%" }}>
<Typography variant="h3" sx={{ color: "pine.950" }}>
@@ -53,6 +63,15 @@ export const NodeParametersCard = ({ id }: INodeParametersCardProps) => {
</ExplorerCard>
);
}
// get node info based on wether it's dentity_key or node_id
if (paramId.length > 10) {
nodeInfo = nymNodes.find((node) => node.identity_key === paramId);
} else {
nodeInfo = nymNodes.find((node) => node.node_id === Number(paramId));
}
if (!nodeInfo) return null;
const totalStake = formatBigNum(Number(nodeInfo.total_stake) / 1_000_000);
const totalStakeFormatted = `${totalStake} NYM`;
@@ -1,12 +1,20 @@
"use client";
import type { IObservatoryNode } from "@/app/api/types";
import { useChain } from "@cosmos-kit/react";
import { Box, Button, Skeleton, Stack, Typography } from "@mui/material";
import {
Box,
Button,
Skeleton,
Stack,
Typography,
useTheme,
} from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import DOMPurify from "isomorphic-dompurify";
import { useCallback, useState } from "react";
import { RandomAvatar } from "react-random-avatars";
import { fetchNodeInfo } from "../../app/api";
import { fetchObservatoryNodes } from "../../app/api";
import { COSMOS_KIT_USE_CHAIN } from "../../config";
import { useNymClient } from "../../hooks/useNymClient";
import ExplorerCard from "../cards/ExplorerCard";
@@ -18,11 +26,14 @@ import StakeModal from "../staking/StakeModal";
import { fee } from "../staking/schemas";
import ConnectWallet from "../wallet/ConnectWallet";
interface INodeProfileCardProps {
id: number; // Node ID
}
type Props = {
paramId: string;
};
export const NodeProfileCard = ({ paramId }: Props) => {
let nodeInfo: IObservatoryNode | undefined;
const theme = useTheme();
export const NodeProfileCard = ({ id }: INodeProfileCardProps) => {
const { isWalletConnected } = useChain(COSMOS_KIT_USE_CHAIN);
const { nymClient } = useNymClient();
const [infoModalProps, setInfoModalProps] = useState<InfoModalProps>({
@@ -36,14 +47,79 @@ export const NodeProfileCard = ({ id }: INodeProfileCardProps) => {
// Fetch node info
const {
data: nodeInfo,
isLoading: isNodeLoading,
isError: isNodeError,
data: nymNodes,
isLoading: isLoadingNymNodes,
isError,
} = useQuery({
queryKey: ["nodeInfo", id],
queryFn: () => fetchNodeInfo(id),
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
// get node info based on wether it's dentity_key or node_id
if (paramId.length > 10) {
nodeInfo = nymNodes?.find((node) => node.identity_key === paramId);
} else {
nodeInfo = nymNodes?.find((node) => node.node_id === Number(paramId));
}
const handleOnSelectStake = useCallback(() => {
if (!nodeInfo) return;
if (!isWalletConnected) {
setInfoModalProps({
open: true,
title: "Connect Wallet",
message: "Connect your wallet to stake",
Action: (
<ConnectWallet
fullWidth
onClick={() =>
setInfoModalProps({
open: false,
})
}
/>
),
onClose: () => setInfoModalProps({ open: false }),
});
return;
}
if (nodeInfo) {
setSelectedNodeForStaking({
nodeId: nodeInfo.node_id,
identityKey: nodeInfo.identity_key,
});
}
}, [isWalletConnected, nodeInfo]);
if (isLoadingNymNodes) {
return (
<ExplorerCard label="Nym Node" sx={{ height: "100%" }}>
<Skeleton variant="rectangular" height={80} width={80} />
<Skeleton variant="text" />
<Skeleton variant="text" height={200} />
</ExplorerCard>
);
}
if (isError || !nymNodes) {
return (
<ExplorerCard label="Nym Node" sx={{ height: "100%" }}>
<Typography
variant="h3"
sx={{
color: theme.palette.mode === "dark" ? "base.white" : "pine.950",
}}
>
Failed to load node data.
</Typography>
</ExplorerCard>
);
}
const handleStakeOnNode = async ({
nodeId,
amount,
@@ -86,53 +162,8 @@ export const NodeProfileCard = ({ id }: INodeProfileCardProps) => {
setIsLoading(false);
};
const handleOnSelectStake = useCallback(() => {
if (!isWalletConnected) {
setInfoModalProps({
open: true,
title: "Connect Wallet",
message: "Connect your wallet to stake",
Action: (
<ConnectWallet
fullWidth
onClick={() =>
setInfoModalProps({
open: false,
})
}
/>
),
onClose: () => setInfoModalProps({ open: false }),
});
return;
}
if (nodeInfo) {
setSelectedNodeForStaking({
nodeId: nodeInfo.node_id,
identityKey: nodeInfo.identity_key,
});
}
}, [isWalletConnected, nodeInfo]);
if (!nodeInfo) return null;
if (isNodeLoading) {
return (
<ExplorerCard label="Nym Node" sx={{ height: "100%" }}>
<Skeleton variant="rectangular" height={80} width={80} />
<Skeleton variant="text" />
<Skeleton variant="text" height={200} />
</ExplorerCard>
);
}
if (isNodeError || !nodeInfo) {
return (
<ExplorerCard label="Nym Node" sx={{ height: "100%" }}>
<Typography variant="h3" sx={{ color: "pine.950" }}>
Failed to load node data.
</Typography>
</ExplorerCard>
);
}
const cleanMoniker = DOMPurify.sanitize(
nodeInfo?.self_description.moniker,
).replace(/&amp;/g, "&");
@@ -158,13 +189,24 @@ export const NodeProfileCard = ({ id }: INodeProfileCardProps) => {
variant="h3"
mt={3}
mb={1}
sx={{ color: "pine.950", wordWrap: "break-word", maxWidth: "95%" }}
sx={{
color: theme.palette.mode === "dark" ? "base.white" : "pine.950",
wordWrap: "break-word",
maxWidth: "95%",
}}
>
{cleanMoniker || "Moniker"}
</Typography>
{nodeInfo.description.auxiliary_details.location && (
<Box display={"flex"} gap={1}>
<Typography variant="h6">Location:</Typography>
<Typography
variant="h6"
sx={{
color: theme.palette.mode === "dark" ? "base.white" : "inherit",
}}
>
Location:
</Typography>
<Box>
<CountryFlag
@@ -177,7 +219,13 @@ export const NodeProfileCard = ({ id }: INodeProfileCardProps) => {
</Box>
)}
{nodeInfo && (
<Typography variant="body4" sx={{ color: "pine.950" }} mt={2}>
<Typography
variant="body4"
sx={{
color: theme.palette.mode === "dark" ? "base.white" : "pine.950",
}}
mt={2}
>
{cleanDescription}
</Typography>
)}
@@ -5,16 +5,20 @@ import { useQuery } from "@tanstack/react-query";
import {
fetchEpochRewards,
fetchGatewayStatus,
fetchNodeInfo,
fetchObservatoryNodes,
} from "../../app/api";
import type { LastProbeResult, NodeDescription } from "../../app/api/types";
import type {
IObservatoryNode,
LastProbeResult,
NodeDescription,
} from "../../app/api/types";
import ExplorerCard from "../cards/ExplorerCard";
import ExplorerListItem from "../list/ListItem";
import StarRating from "../starRating/StarRating";
interface INodeRoleCardProps {
id: number; // Node ID
}
type Props = {
paramId: string;
};
type NodeDescriptionNotNull = NonNullable<NodeDescription>;
type DeclaredRoleKey = keyof NodeDescriptionNotNull["declared_role"];
@@ -143,15 +147,21 @@ function calculateWireguardPerformance(probeResult: LastProbeResult): number {
}
}
export const NodeRoleCard = ({ id }: INodeRoleCardProps) => {
export const NodeRoleCard = ({ paramId }: Props) => {
let nodeInfo: IObservatoryNode | undefined;
// Fetch node info
const {
data: nodeInfo,
data: nymNodes,
isLoading,
isError,
} = useQuery({
queryKey: ["nodeInfo", id],
queryFn: () => fetchNodeInfo(id),
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
const {
data: epochRewardsData,
@@ -160,9 +170,17 @@ export const NodeRoleCard = ({ id }: INodeRoleCardProps) => {
} = useQuery({
queryKey: ["epochRewards"],
queryFn: fetchEpochRewards,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
// Extract node roles once `nodeInfo` is available
if (paramId.length > 10) {
nodeInfo = nymNodes?.find((node) => node.identity_key === paramId);
} else {
nodeInfo = nymNodes?.find((node) => node.node_id === Number(paramId));
} // Extract node roles once `nodeInfo` is available
const nodeRoles = nodeInfo
? getNodeRoles(nodeInfo.description.declared_role)
: [];
@@ -177,6 +195,9 @@ export const NodeRoleCard = ({ id }: INodeRoleCardProps) => {
queryKey: ["gatewayStatus", nodeInfo?.identity_key],
queryFn: () => fetchGatewayStatus(nodeInfo?.identity_key || ""),
enabled: !!nodeInfo?.identity_key && shouldFetchGatewayStatus, // ✅ Only fetch if needed
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
});
if (isLoading || isEpochLoading) {
@@ -189,7 +210,7 @@ export const NodeRoleCard = ({ id }: INodeRoleCardProps) => {
);
}
if (isError || !nodeInfo || !epochRewardsData || isEpochError) {
if (isError || !nymNodes || !epochRewardsData || isEpochError) {
return (
<ExplorerCard label="Node role & performance">
<Typography variant="h3" sx={{ color: "pine.950" }}>
@@ -205,6 +226,8 @@ export const NodeRoleCard = ({ id }: INodeRoleCardProps) => {
</Stack>
));
if (!nodeInfo) return null;
const qualityOfServiceStars = nodeInfo?.uptime
? calculateQualityOfServiceStars(nodeInfo.uptime)
: gatewayStatus
@@ -1,6 +1,6 @@
"use client";
import { Skeleton, Typography } from "@mui/material";
import { Skeleton, Typography, useTheme } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import { fetchOriginalStake } from "../../app/api";
import { useNymClient } from "../../hooks/useNymClient";
@@ -9,6 +9,8 @@ import ExplorerCard from "../cards/ExplorerCard";
const OriginalStakeCard = () => {
const { address } = useNymClient();
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
// Use React Query to fetch original stake
const {
@@ -19,6 +21,9 @@ const OriginalStakeCard = () => {
queryKey: ["originalStake", address],
queryFn: () => fetchOriginalStake(address || ""),
enabled: !!address, // Only fetch if address exists
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
});
if (!address) {
@@ -36,7 +41,10 @@ const OriginalStakeCard = () => {
if (isError) {
return (
<ExplorerCard label="Original Stake">
<Typography variant="h3" sx={{ color: "pine.950" }}>
<Typography
variant="h3"
sx={{ color: isDarkMode ? "base.white" : "pine.950" }}
>
Failed to load original stake.
</Typography>
</ExplorerCard>
@@ -47,7 +55,11 @@ const OriginalStakeCard = () => {
<ExplorerCard label="Original Stake">
<Typography
variant="h3"
sx={{ color: "pine.950", wordWrap: "break-word", maxWidth: "95%" }}
sx={{
color: isDarkMode ? "base.white" : "pine.950",
wordWrap: "break-word",
maxWidth: "95%",
}}
>
{`${formatBigNum(originalStake / 1_000_000)} NYM`}
</Typography>
@@ -99,6 +99,17 @@ const StakeModal = ({
initialValue={identityKey}
readOnly
showTickOnValid={false}
sx={{
"& .MuiInputBase-input": {
color: "rgba(0, 0, 0, 0.87)",
"&::placeholder": {
color: "rgba(0, 0, 0, 0.54)",
},
},
"& .Mui-disabled": {
color: "rgba(0, 0, 0, 0.38)",
},
}}
/>
</Stack>
<Stack spacing={0.5}>
@@ -113,6 +124,17 @@ const StakeModal = ({
onChanged={setAmount}
denom={"nym"}
validationError={errorAmount}
sx={{
"& .MuiInputBase-input": {
color: "rgba(0, 0, 0, 0.87)",
"&::placeholder": {
color: "rgba(0, 0, 0, 0.54)",
},
},
"& .Mui-disabled": {
color: "rgba(0, 0, 0, 0.38)",
},
}}
/>
</Stack>
<ExplorerListItem row label="Account balance" value={balance} divider />
@@ -98,13 +98,10 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const isDarkMode = theme.palette.mode === "dark";
const router = useRouter();
const queryClient = useQueryClient();
// Custom Hook for fetching pending events
const handleRefetch = useCallback(async () => {
await queryClient.invalidateQueries();
}, [queryClient]);
@@ -563,41 +560,93 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
sx: {
fontFamily: "labGrotesqueMono",
fontSize: "14px",
color: isDarkMode ? "#FFFFFF" : "inherit",
},
},
color: "primary",
shape: "circular",
},
initialState: {
columnPinning: isMobile ? {} : { right: ["Action", "Favorite"] }, // No pinning on mobile
columnPinning: isMobile ? {} : { right: ["Action", "Favorite"] },
},
muiColumnActionsButtonProps: {
sx: {
color: "red",
color: isDarkMode ? "#FFFFFF" : "inherit",
},
size: "small",
},
muiTablePaperProps: {
elevation: 0,
},
muiTableHeadCellProps: {
sx: {
alignItems: "center",
bgcolor: isDarkMode ? "#0F1720" : "background.paper",
},
},
muiTableHeadRowProps: {
sx: {
bgcolor: "background.paper",
bgcolor: isDarkMode ? "#374042" : "background.paper",
},
},
muiTableHeadCellProps: {
sx: {
alignItems: "center",
paddingRight: 0,
color: isDarkMode ? "#FFFFFF" : "inherit",
},
},
muiSearchTextFieldProps: {
InputProps: {
style: {
color: isDarkMode ? "#475569" : "inherit",
},
},
sx: {
backgroundColor: isDarkMode ? "#374042" : "white",
"& .MuiOutlinedInput-root": {
color: isDarkMode ? "#475569" : "inherit",
backgroundColor: isDarkMode ? "#374042" : "white",
},
"& .MuiOutlinedInput-notchedOutline": {
borderColor: isDarkMode ? "#334155" : "inherit",
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: isDarkMode ? "#475569" : "inherit",
},
},
variant: "outlined",
size: "small",
},
muiFilterTextFieldProps: {
InputProps: {
sx: {
color: isDarkMode ? "#FFFFFF" : "inherit",
},
},
sx: {
"& .MuiInputBase-root": {
backgroundColor: isDarkMode ? "#1C2A2E" : "white",
},
"& .MuiInputBase-input::placeholder": {
color: isDarkMode ? "#94A3B8" : "inherit",
opacity: 1,
},
"& .MuiOutlinedInput-notchedOutline": {
borderColor: isDarkMode ? "#334155" : "inherit",
},
"&:hover .MuiOutlinedInput-notchedOutline": {
borderColor: isDarkMode ? "#475569" : "inherit",
},
},
variant: "outlined",
size: "small",
},
muiTableBodyCellProps: {
sx: {
border: "none",
whiteSpace: "unset", // Allow text wrapping in body cells
whiteSpace: "unset",
wordBreak: "break-word",
paddingRight: 0,
color: isDarkMode ? "#FFFFFF" : "inherit",
},
},
muiTableBodyRowProps: ({ row }) => ({
@@ -606,11 +655,16 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
},
hover: true,
sx: {
":nth-child(odd)": {
bgcolor: "#F3F7FB !important",
},
":nth-child(even)": {
bgcolor: "white !important",
backgroundColor: isDarkMode
? row.index % 2 === 0
? "#3E4A4C !important"
: "#374042 !important"
: row.index % 2 === 0
? "#F3F7FB"
: "white",
"&:hover": {
backgroundColor: `${isDarkMode ? "#2A3436" : "#E5E7EB"} !important`,
transition: "background-color 0.2s ease",
},
cursor: "pointer",
},
@@ -64,6 +64,10 @@ const StakeTableWithAction = () => {
} = useQuery({
queryKey: ["epochRewards"],
queryFn: fetchEpochRewards,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
// Use React Query to fetch Nym nodes
@@ -74,6 +78,10 @@ const StakeTableWithAction = () => {
} = useQuery({
queryKey: ["nymNodes"],
queryFn: fetchObservatoryNodes,
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
refetchOnMount: false,
});
// Handle loading state
@@ -1,5 +1,7 @@
"use client";
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { GasPrice } from "@cosmjs/stargate";
import { useChain } from "@cosmos-kit/react";
import { Button, Stack } from "@mui/material";
import type { Delegation } from "@nymproject/contract-clients/Mixnet.types";
@@ -13,8 +15,6 @@ import Loading from "../loading";
import InfoModal, { type InfoModalProps } from "../modal/InfoModal";
import RedeemRewardsModal from "../redeemRewards/RedeemRewardsModal";
const fee = { gas: "1000000", amount: [{ amount: "1000000", denom: "unym" }] };
// Fetch delegations
const fetchDelegations = async (
address: string,
@@ -34,7 +34,7 @@ const SubHeaderRowActions = () => {
});
const { address, nymClient } = useNymClient();
const { getSigningCosmWasmClient } = useChain(COSMOS_KIT_USE_CHAIN);
const { getOfflineSigner } = useChain(COSMOS_KIT_USE_CHAIN);
const queryClient = useQueryClient();
@@ -59,6 +59,9 @@ const SubHeaderRowActions = () => {
queryKey: ["totalStakerRewards", address],
queryFn: () => fetchTotalStakerRewards(address || ""),
enabled: !!address, // Only fetch if address is available
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
});
const handleRefetch = useCallback(async () => {
@@ -75,6 +78,15 @@ const SubHeaderRowActions = () => {
throw new Error("Wallet, client, or delegations not available.");
}
const signer = await getOfflineSigner();
const gasPrice = GasPrice.fromString("0.025unym");
const client = await SigningCosmWasmClient.connectWithSigner(
"https://rpc.nymtech.net/",
signer,
{ gasPrice },
);
const messages = delegations.map((delegation: NodeRewardDetails) => ({
contractAddress: NYM_MIXNET_CONTRACT,
funds: [],
@@ -85,12 +97,10 @@ const SubHeaderRowActions = () => {
},
}));
const cosmWasmSigningClient = await getSigningCosmWasmClient();
const result = await cosmWasmSigningClient.executeMultiple(
const result = await client.executeMultiple(
address,
messages,
fee,
"auto",
"Redeeming all rewards",
);
// Success state
@@ -118,13 +128,7 @@ const SubHeaderRowActions = () => {
} finally {
setIsLoading(false);
}
}, [
address,
nymClient,
delegations,
getSigningCosmWasmClient,
handleRefetch,
]);
}, [address, nymClient, delegations, handleRefetch, getOfflineSigner]);
const handleRedeemRewardsButtonClick = () => {
setOpenRedeemRewardsModal(true);
@@ -1,6 +1,6 @@
"use client";
import { Skeleton, Typography } from "@mui/material";
import { Skeleton, Typography, useTheme } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import { fetchTotalStakerRewards } from "../../app/api";
import { useNymClient } from "../../hooks/useNymClient";
@@ -9,8 +9,10 @@ import ExplorerCard from "../cards/ExplorerCard";
const TotalRewardsCard = () => {
const { address } = useNymClient();
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
// Use React Query to fetch total staker rewards
// Use React Query to fetch total rewards
const {
data: totalStakerRewards = 0,
isLoading,
@@ -19,6 +21,9 @@ const TotalRewardsCard = () => {
queryKey: ["totalStakerRewards", address],
queryFn: () => fetchTotalStakerRewards(address || ""),
enabled: !!address, // Only fetch if address exists
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
});
if (!address) {
@@ -36,7 +41,10 @@ const TotalRewardsCard = () => {
if (isError) {
return (
<ExplorerCard label="Total Rewards">
<Typography variant="h3" sx={{ color: "pine.950" }}>
<Typography
variant="h3"
sx={{ color: isDarkMode ? "base.white" : "pine.950" }}
>
Failed to load total rewards.
</Typography>
</ExplorerCard>
@@ -47,7 +55,11 @@ const TotalRewardsCard = () => {
<ExplorerCard label="Total Rewards">
<Typography
variant="h3"
sx={{ color: "pine.950", wordWrap: "break-word", maxWidth: "95%" }}
sx={{
color: isDarkMode ? "base.white" : "pine.950",
wordWrap: "break-word",
maxWidth: "95%",
}}
>
{`${formatBigNum(totalStakerRewards / 1_000_000)} NYM`}
</Typography>
@@ -1,6 +1,6 @@
"use client";
import { Skeleton, Typography } from "@mui/material";
import { Skeleton, Typography, useTheme } from "@mui/material";
import { useQuery } from "@tanstack/react-query";
import { fetchBalances } from "../../app/api";
import { useNymClient } from "../../hooks/useNymClient";
@@ -9,6 +9,8 @@ import ExplorerCard from "../cards/ExplorerCard";
const TotalStakeCard = () => {
const { address } = useNymClient();
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
// Use React Query to fetch total stake
const {
@@ -19,6 +21,9 @@ const TotalStakeCard = () => {
queryKey: ["totalStake", address],
queryFn: () => fetchBalances(address || ""),
enabled: !!address, // Only fetch if address exists
staleTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false, // Prevents unnecessary refetching
refetchOnReconnect: false,
});
if (!address) {
@@ -36,7 +41,10 @@ const TotalStakeCard = () => {
if (isError) {
return (
<ExplorerCard label="Total Stake">
<Typography variant="h3" color="error">
<Typography
variant="h3"
sx={{ color: isDarkMode ? "base.white" : "pine.950" }}
>
Failed to load total stake.
</Typography>
</ExplorerCard>
@@ -47,7 +55,11 @@ const TotalStakeCard = () => {
<ExplorerCard label="Total Stake">
<Typography
variant="h3"
sx={{ color: "pine.950", wordWrap: "break-word", maxWidth: "95%" }}
sx={{
color: isDarkMode ? "base.white" : "pine.950",
wordWrap: "break-word",
maxWidth: "95%",
}}
>
{`${formatBigNum(totalStake / 1_000_000)} NYM`}
</Typography>
@@ -7,9 +7,11 @@ import {
type ButtonProps,
IconButton,
Typography,
useTheme,
} from "@mui/material";
import { COSMOS_KIT_USE_CHAIN } from "../../config";
import Cross from "../icons/Cross";
import CrossDark from "../icons/CrossDark";
import { WalletAddress } from "./WalletAddress";
import { WalletBalance } from "./WalletBalance";
@@ -21,6 +23,7 @@ interface ButtonPropsWithOnClick extends ButtonProps {
const ConnectWallet = ({ ...buttonProps }: ButtonPropsWithOnClick) => {
const { connect, disconnect, address, isWalletConnected } =
useChain(COSMOS_KIT_USE_CHAIN);
const theme = useTheme();
const handleConnectWallet = async () => {
buttonProps.onClick?.();
@@ -58,7 +61,7 @@ const ConnectWallet = ({ ...buttonProps }: ButtonPropsWithOnClick) => {
>
Disconnect
</Typography>
<Cross />
{theme.palette.mode === "dark" ? <CrossDark /> : <Cross />}
</Box>
</IconButton>
</Box>
@@ -1,11 +1,16 @@
import { Stack, Typography } from "@mui/material";
"use client";
import { Stack, Typography, useTheme } from "@mui/material";
import React from "react";
import { Elips } from "../../components/icons/Elips";
import { ElipseDark } from "../../components/icons/ElipseDark";
export const trimAddress = (address = "", trimBy = 6) =>
`${address.slice(0, trimBy)}...${address.slice(-trimBy)}`;
export const WalletAddress = ({ address }: { address?: string }) => {
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
if (!address) {
return null;
}
@@ -14,7 +19,7 @@ export const WalletAddress = ({ address }: { address?: string }) => {
return (
<Stack direction="row" alignItems="center" gap={0.5}>
<Elips />
{isDarkMode ? <ElipseDark /> : <Elips />}
<Typography variant="h5" fontWeight={400}>
{displayAddress}
</Typography>
@@ -1,20 +1,23 @@
"use client";
import { COSMOS_KIT_USE_CHAIN } from "@/config";
import { useChain } from "@cosmos-kit/react";
import { Stack, Typography } from "@mui/material";
import { Stack, Typography, useTheme } from "@mui/material";
import React from "react";
import { Token } from "../../components/icons/Token";
import { TokenDark } from "../../components/icons/TokenDark";
import useGetWalletBalance from "../../hooks/useGetWalletBalance";
export const WalletBalance = () => {
const { isWalletConnected } = useChain(COSMOS_KIT_USE_CHAIN);
const { formattedBalance, isLoading, isError, refetch } =
useGetWalletBalance();
const theme = useTheme();
const isDarkMode = theme.palette.mode === "dark";
if (isLoading) {
return (
<Stack direction="row" alignItems="center" gap={1}>
<Token />
{isDarkMode ? <TokenDark /> : <Token />}
<Typography variant="h5" fontWeight={400}>
Loading...
</Typography>
@@ -30,7 +33,7 @@ export const WalletBalance = () => {
return (
<Stack direction="row" alignItems="center" gap={1}>
<Token />
{isDarkMode ? <TokenDark /> : <Token />}
<Typography variant="h5" fontWeight={400}>
{formattedBalance} NYM
</Typography>
@@ -8,7 +8,8 @@
"date": "Mon Jan 20 2025 10:26:09 GMT+0000 (Greenwich Mean Time)",
"readingTime": 5
},
"icon": "explorerCard",
"iconLight": "explorerCard",
"iconDark": "explorerCardDark",
"image": "/explorer/images/Network.webp",
"overview": {
"content": [
+2 -1
View File
@@ -8,7 +8,8 @@
"date": "Mon Jan 05 2025 10:26:09 GMT+0000 (Greenwich Mean Time)",
"readingTime": 5
},
"icon": "gateway",
"iconLight": "gateway",
"iconDark": "gatewayDark",
"image": "/explorer/images/placeholder.webp",
"overview": {
"content": [
+2 -1
View File
@@ -8,7 +8,8 @@
"date": "Mon Jan 05 2025 10:26:09 GMT+0000 (Greenwich Mean Time)",
"readingTime": 5
},
"icon": "gateway",
"iconLight": "gateway",
"iconDark": "gatewayDark",
"image": "/explorer/images/placeholder.webp",
"overview": {
"content": [
+2 -1
View File
@@ -8,7 +8,8 @@
"date": "Mon Jan 05 2025 10:26:09 GMT+0000 (Greenwich Mean Time)",
"readingTime": 5
},
"icon": "stakeCard",
"iconLight": "stakeCard",
"iconDark": "stakeCardDark",
"image": "/explorer/images/stake-article.webp",
"overview": {
"content": [
@@ -0,0 +1,30 @@
"use client";
import { CssBaseline, type PaletteMode } from "@mui/material";
import { ThemeProvider as MUIThemeProvider } from "@mui/material";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter";
import { useLocalStorage } from "@uidotdev/usehooks";
import { useEffect, useState } from "react";
import { darkTheme, lightTheme } from "../theme/theme";
const ClientThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [isMounted, setIsMounted] = useState(false);
const [mode] = useLocalStorage<PaletteMode>("mode", "dark");
useEffect(() => {
setIsMounted(true);
}, []);
if (!isMounted) return null; // or a loading spinner if you prefer
return (
<AppRouterCacheProvider>
<MUIThemeProvider theme={mode === "light" ? lightTheme : darkTheme}>
<CssBaseline />
{children}
</MUIThemeProvider>
</AppRouterCacheProvider>
);
};
export default ClientThemeProvider;
@@ -0,0 +1,13 @@
"use client";
import dynamic from "next/dynamic";
const ClientThemeProvider = dynamic(() => import("./ClientThemeProvider"), {
ssr: false,
});
const ClientThemeWrapper = ({ children }: { children: React.ReactNode }) => {
return <ClientThemeProvider>{children}</ClientThemeProvider>;
};
export default ClientThemeWrapper;
@@ -70,6 +70,7 @@ const CosmosKitProvider = ({ children }: { children: React.ReactNode }) => {
chains={chainsWithNyx}
assetLists={assetsWithNyx}
wallets={[...keplr]}
throwErrors
>
{children}
</ChainProvider>
+5 -4
View File
@@ -1,16 +1,17 @@
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { getQueryClient } from "@/app/react-query";
import { QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { type ReactNode, useState } from "react";
import type { ReactNode } from "react";
interface QueryProviderProps {
children: ReactNode;
}
export const QueryProvider = ({ children }: QueryProviderProps) => {
// Create QueryClient instance
const [queryClient] = useState(() => new QueryClient());
// Get QueryClient instance from lib
const queryClient = getQueryClient();
return (
<QueryClientProvider client={queryClient}>
+2 -13
View File
@@ -1,18 +1,7 @@
"use client";
import { CssBaseline } from "@mui/material";
import { ThemeProvider as MUIThemeProvider } from "@mui/material";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter";
import { lightTheme } from "../theme/theme";
import ClientThemeWrapper from "./ClientThemeWrapper";
const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
return (
<AppRouterCacheProvider>
<MUIThemeProvider theme={lightTheme}>
<CssBaseline />
{children}
</MUIThemeProvider>
</AppRouterCacheProvider>
);
return <ClientThemeWrapper>{children}</ClientThemeWrapper>;
};
export default ThemeProvider;
+2
View File
@@ -15,8 +15,10 @@ export const colours = {
pine: {
25: "#F9FBFB",
200: "#CAD6D7",
300: "#A6B9BA",
600: "#4C666A",
800: "#3E4A4C",
900: "#374042",
950: "#242B2D",
},
base: {
+28
View File
@@ -13,6 +13,20 @@ declare module "@mui/material/styles" {
medium: Palette["primary"];
light: Palette["primary"];
gray: Palette["primary"];
pine: {
25: string;
200: string;
300: string;
600: string;
800: string;
900: string;
950: string;
};
base: {
white: string;
black: string;
transparent: string;
};
}
interface PaletteOptions {
accent?: PaletteOptions["primary"];
@@ -20,6 +34,20 @@ declare module "@mui/material/styles" {
medium?: PaletteOptions["primary"];
light?: PaletteOptions["primary"];
gray?: PaletteOptions["primary"];
pine?: {
25?: string;
200?: string;
300?: string;
600?: string;
800?: string;
900?: string;
950?: string;
};
base?: {
white?: string;
black?: string;
transparent?: string;
};
}
// Apply CustomTypography to TypographyVariants
+5 -2
View File
@@ -2,6 +2,7 @@ import { labGrotesque } from "@/fonts";
import { colours } from "@/theme/colours";
import { body, headings, subtitles } from "@/theme/typography";
import { type ThemeOptions, createTheme } from "@mui/material/styles";
const lightMode = {
mode: "light" as const,
accent: colours.green[500],
@@ -9,6 +10,7 @@ const lightMode = {
background: colours.gray[200],
medium: colours.haze[200],
light: colours.base.white,
paper: colours.base.white,
};
const darkMode = {
@@ -18,6 +20,7 @@ const darkMode = {
background: colours.pine[950],
medium: colours.pine[800],
light: colours.pine[200],
paper: colours.pine[900],
};
// createDesignTokens function to generate design tokens based on mode
@@ -33,13 +36,13 @@ const createDesignTokens = (mode: "light" | "dark") => {
background: {
default: palette.background,
main: palette.background,
paper: palette.light,
paper: palette.paper,
},
secondary: {
main: palette.accent,
},
text: {
primary: palette.primary,
primary: mode === "dark" ? colours.base.white : palette.primary,
secondary: palette.medium,
},
success: {
+43 -20
View File
@@ -1,32 +1,45 @@
import type { StaticImageData } from "next/image";
import nymLogo from "@/../public/icons/nym-logo.svg";
import token from "@/../public/icons/token.svg";
import gateway from "@/../public/icons/gateway.svg";
import elips from "@/../public/icons/elips.svg";
import cross from "@/../public/icons/cross.svg";
import arrowUpRight from "@/../public/icons/arrow-up-right.svg";
import arrow from "@/../public/icons/arrow-up-right.svg";
import chevronMenu from "@/../public/icons/chevronMenu.svg";
import copyFileDark from "@/../public/icons/copy-file-dark.svg";
import copyFile from "@/../public/icons/copy-file.svg";
import chevronMenu from "@/../public/icons/chevronMenu.svg"
import document from "@/../public/icons/document.svg"
import download from "@/../public/icons/download.svg"
import discord from "@/../public/icons/discord.svg"
import github from "@/../public/icons/github.svg"
import telegram from "@/../public/icons/telegram.svg"
import twitter from "@/../public/icons/twitter.svg"
import youtube from "@/../public/icons/youtube.svg"
import youTubeInverted from "@/../public/icons/youtubeInverted.svg"
import arrowUpRight from "@/../public/icons/arrow-up-right.svg"
import arrow from "@/../public/icons/arrow-up-right.svg"
import explorerCard from "@/../public/icons/explorer-card.svg"
import stakeCard from "@/../public/icons/stake-card.svg"
import crossDark from "@/../public/icons/cross-dark.svg";
import cross from "@/../public/icons/cross.svg";
import discord from "@/../public/icons/discord.svg";
import document from "@/../public/icons/document.svg";
import download from "@/../public/icons/download.svg";
import elips from "@/../public/icons/elips.svg";
import elipseDark from "@/../public/icons/elipse-dark.svg";
import explorerCardDark from "@/../public/icons/explorer-card-dark.svg";
import explorerCard from "@/../public/icons/explorer-card.svg";
import gatewayDark from "@/../public/icons/gateway-dark.svg";
import gateway from "@/../public/icons/gateway.svg";
import github from "@/../public/icons/github.svg";
import nymLogoWhite from "@/../public/icons/nym-logo-white.svg";
import nymLogo from "@/../public/icons/nym-logo.svg";
import stakeCardDark from "@/../public/icons/stake-card-dark.svg";
import stakeCard from "@/../public/icons/stake-card.svg";
import telegram from "@/../public/icons/telegram.svg";
import tokenDark from "@/../public/icons/token-dark.svg";
import token from "@/../public/icons/token.svg";
import twitter from "@/../public/icons/twitter.svg";
import youtube from "@/../public/icons/youtube.svg";
import youTubeInverted from "@/../public/icons/youtubeInverted.svg";
export type IconName =
| "nymLogo"
| "nymLogoWhite"
| "token"
| "tokenDark"
| "gateway"
| "elips"
| "elipseDark"
| "cross"
| "crossDark"
| "copyFile"
| "copyFileDark"
| "chevronMenu"
| "document"
| "download"
@@ -40,6 +53,9 @@ export type IconName =
| "arrow"
| "stakeCard"
| "explorerCard"
| "explorerCardDark"
| "stakeCardDark"
| "gatewayDark";
export type Icons = {
[key in IconName]: StaticImageData;
@@ -47,11 +63,16 @@ export type Icons = {
export const icons: Icons = {
nymLogo,
nymLogoWhite,
token,
tokenDark,
gateway,
elips,
elipseDark,
cross,
crossDark,
copyFile,
copyFileDark,
chevronMenu,
document,
download,
@@ -64,6 +85,8 @@ export const icons: Icons = {
arrowUpRight,
arrow,
stakeCard,
explorerCard
explorerCard,
stakeCardDark,
explorerCardDark,
gatewayDark,
};
+66 -59
View File
@@ -1,60 +1,67 @@
{
"name": "@nymproject/nymsphere",
"version": "1.0.0",
"private": true,
"license": "Apache 2.0",
"workspaces": [
"dist/wasm/**",
"dist/node/**",
"dist/ts/**",
"sdk/typescript/packages/mui-theme",
"sdk/typescript/packages/react-components",
"sdk/typescript/packages/validator-client",
"ts-packages/*",
"nym-wallet",
"explorer-nextjs",
"explorer-v2",
"types",
"clients/validator"
],
"scripts": {
"nuke": "npx rimraf **/node_modules node_modules",
"scrub": "npx rimraf **/dist dist",
"clean": "lerna run clean",
"build": "run-s build:types build:packages",
"build:wasm": "make sdk-wasm-build",
"build:types": "lerna run --scope @nymproject/types build --stream",
"build:packages": "run-s build:packages:theme build:packages:react",
"build:packages:theme": "lerna run --scope @nymproject/mui-theme build",
"build:packages:react": "lerna run --scope @nymproject/react build",
"build:react-example": "lerna run --scope @nymproject/react-webpack-with-theme-example build --stream",
"build:playground": "lerna run --scope @nymproject/react storybook:build --stream",
"build:ci:storybook": "yarn build && yarn dev:on && run-p build:react-example build:playground && yarn build:ci:storybook:collect-artifacts",
"build:ci:storybook:collect-artifacts": "mkdir -p ts-packages/dist && mv sdk/typescript/packages/react-components/storybook-static ts-packages/dist/storybook && mv sdk/typescript/examples/react/mui-theme/dist ts-packages/dist/example",
"prebuild:ci": "yarn dev:on && yarn",
"build:ci": "run-s build:types build:packages build:wasm build:ci:sdk",
"postbuild:ci": "yarn dev:off",
"build:ci:sdk": "lerna run --scope '{@nymproject/sdk,@nymproject/node-tester,@nymproject/contract-clients,@nymproject/sdk-react,@nymproject/mix-fetch,@nymproject/nodejs-client,@nymproject/mix-fetch-node}' build --stream",
"docs:prod:build": "run-s docs:prod:build:ws",
"docs:prod:build:ws": "lerna run docs:prod:build --stream",
"sdk:build": "./sdk/typescript/scripts/build-prod-sdk.sh",
"sdk:publish": "./sdk/typescript/scripts/publish.sh",
"lint": "lerna run lint --stream",
"lint:fix": "lerna run lint:fix --stream",
"tsc": "lerna run tsc --stream",
"types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app",
"audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run",
"dev:on": "node sdk/typescript/scripts/dev-mode-add.mjs",
"dev:off": "node sdk/typescript/scripts/dev-mode-remove.mjs"
},
"devDependencies": {
"@npmcli/node-gyp": "^3.0.0",
"lerna": "^7.3.0",
"node-gyp": "^9.3.1",
"npm-run-all": "^4.1.5",
"tslog": "3.3.3"
},
"dependencies": {
"lucide-react": "^0.453.0"
}
}
"name": "@nymproject/nymsphere",
"version": "1.0.0",
"private": true,
"license": "Apache 2.0",
"workspaces": [
"dist/wasm/**",
"dist/node/**",
"dist/ts/**",
"sdk/typescript/packages/mui-theme",
"sdk/typescript/packages/react-components",
"sdk/typescript/packages/validator-client",
"ts-packages/*",
"nym-wallet",
"explorer-nextjs",
"explorer-v2",
"types",
"clients/validator"
],
"scripts": {
"nuke": "npx rimraf **/node_modules node_modules",
"scrub": "npx rimraf **/dist dist",
"clean": "lerna run clean",
"build": "run-s build:types build:packages",
"build:wasm": "make sdk-wasm-build",
"build:types": "lerna run --scope @nymproject/types build --stream",
"build:packages": "run-s build:packages:theme build:packages:react",
"build:packages:theme": "lerna run --scope @nymproject/mui-theme build",
"build:packages:react": "lerna run --scope @nymproject/react build",
"build:react-example": "lerna run --scope @nymproject/react-webpack-with-theme-example build --stream",
"build:playground": "lerna run --scope @nymproject/react storybook:build --stream",
"build:ci:storybook": "yarn build && yarn dev:on && run-p build:react-example build:playground && yarn build:ci:storybook:collect-artifacts",
"build:ci:storybook:collect-artifacts": "mkdir -p ts-packages/dist && mv sdk/typescript/packages/react-components/storybook-static ts-packages/dist/storybook && mv sdk/typescript/examples/react/mui-theme/dist ts-packages/dist/example",
"prebuild:ci": "yarn dev:on && yarn",
"build:ci": "run-s build:types build:packages build:wasm build:ci:sdk",
"postbuild:ci": "yarn dev:off",
"build:ci:sdk": "lerna run --scope '{@nymproject/sdk,@nymproject/node-tester,@nymproject/contract-clients,@nymproject/sdk-react,@nymproject/mix-fetch,@nymproject/nodejs-client,@nymproject/mix-fetch-node}' build --stream",
"docs:prod:build": "run-s docs:prod:build:ws",
"docs:prod:build:ws": "lerna run docs:prod:build --stream",
"sdk:build": "./sdk/typescript/scripts/build-prod-sdk.sh",
"sdk:publish": "./sdk/typescript/scripts/publish.sh",
"lint": "lerna run lint --stream",
"lint:fix": "lerna run lint:fix --stream",
"tsc": "lerna run tsc --stream",
"types:lint:fix": "lerna run lint:fix --scope @nymproject/types --scope @nymproject/nym-wallet-app",
"audit:fix": "npm_config_yes=true npx yarn-audit-fix -- --dry-run",
"dev:on": "node sdk/typescript/scripts/dev-mode-add.mjs",
"dev:off": "node sdk/typescript/scripts/dev-mode-remove.mjs"
},
"devDependencies": {
"@npmcli/node-gyp": "^3.0.0",
"lerna": "^7.3.0",
"node-gyp": "^9.3.1",
"npm-run-all": "^4.1.5",
"tslog": "3.3.3"
},
"dependencies": {
"lucide-react": "^0.453.0"
},
"resolutions": {
"@cosmjs/amino": "^0.32.4",
"@cosmjs/proto-signing": "^0.32.4",
"@cosmjs/stargate": "^0.32.4",
"@cosmjs/cosmwasm-stargate": "^0.32.4",
"cosmjs-types": "^0.9.0"
}
}
+853 -964
View File
File diff suppressed because it is too large Load Diff