Dark theme

This commit is contained in:
Yana
2025-04-09 21:16:03 +03:00
parent d8d2f99a18
commit 46ebd84b02
58 changed files with 1782 additions and 714 deletions
+2 -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",
@@ -61,4 +62,4 @@
"lefthook": "^1.8.5",
"typescript": "^5"
}
}
}
@@ -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

+2 -2
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";
@@ -31,7 +31,7 @@ export default async function Home() {
size={{ xs: 12, sm: 6, lg: 3 }}
>
<Grid size={12}>
<RewardsCard />
<StakersNumberCard />
</Grid>
<Grid size={12}>
<CurrentEpochCard />
@@ -13,6 +13,7 @@ import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Typography from "@mui/material/Typography";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";
import * as React from "react";
import { TABLET_WIDTH } from "../../app/constants";
import { MultiSegmentProgressBar } from "../progressBars/MultiSegmentProgressBar";
@@ -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>
@@ -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);
@@ -27,20 +28,21 @@ const BlogArticlesCards = async ({
...blogArticle,
link: `/onboarding/${filename.replace(".json", "")}`,
};
}),
})
);
// --- End Data Fetching ---
const limitedOrFilteredBlogArticles = (
blogArticles: BlogArticleWithLink[],
limit?: number,
ids?: number[],
ids?: number[]
): BlogArticleWithLink[] => {
let filteredArticles = blogArticles;
// Filter by IDs if provided
if (ids && ids.length > 0) {
filteredArticles = filteredArticles.filter((article) =>
ids.includes(article.id),
ids.includes(article.id)
);
}
@@ -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,
useTheme,
type SxProps,
} 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>
@@ -5,6 +5,7 @@ import { Wrapper } from "../../components/wrapper";
import ConnectWallet from "../wallet/ConnectWallet";
import HeaderItem from "./HeaderItem";
import MENU_DATA from "./menuItems";
import { DarkLightSwitchDesktop } from "./Switch";
export const DesktopHeader = () => {
return (
@@ -50,6 +51,8 @@ export const DesktopHeader = () => {
))}
</Box>
<ConnectWallet size="small" />
<DarkLightSwitchDesktop defaultChecked />
</Wrapper>
<Divider variant="fullWidth" sx={{ width: "100%" }} />
</Box>
@@ -7,9 +7,12 @@ import { Wrapper } from "../../components/wrapper";
import NymLogo from "../icons/NymLogo";
import ConnectWallet from "../wallet/ConnectWallet";
import MENU_DATA from "./menuItems";
import { DarkLightSwitchDesktop } from "./Switch";
import { useTheme } from "@mui/material/styles";
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>
@@ -148,7 +168,19 @@ const MobileMenuHeader = ({
</IconButton>
</Box>
</Box>
{!drawerOpen && <ConnectWallet size="small" />}
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 2.5,
justifyContent: "center",
alignItems: "center",
width: "100%",
}}
>
{!drawerOpen && <DarkLightSwitchDesktop defaultChecked />}
{!drawerOpen && <ConnectWallet size="small" />}
</Box>
<Box height={40} />
</Wrapper>
);
@@ -0,0 +1,78 @@
"use client";
import * as React from "react";
import { styled } from "@mui/material/styles";
import Switch from "@mui/material/Switch";
import { Button } from "@mui/material";
import { PaletteMode } from "@mui/material";
import { useLocalStorage } from "@uidotdev/usehooks";
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>
);
};
@@ -0,0 +1,14 @@
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
const CopyFile = ({ className }: { className?: string }) => (
<Image
src={icons.copyFileDark}
alt="Copyfile"
width={16}
height={16}
className={className}
/>
);
export default CopyFile;
@@ -0,0 +1,8 @@
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
const CrossDark = () => (
<Image src={icons.crossDark} alt="cross" width={12.5} height={12.5} />
);
export default CrossDark;
@@ -0,0 +1,6 @@
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
export const ElipseDark = () => (
<Image src={icons.elipseDark} alt="Elips" width={20} height={20} />
);
+11 -1
View File
@@ -1,9 +1,19 @@
"use client";
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
import { PaletteMode } from "@mui/material";
import { useLocalStorage } from "@uidotdev/usehooks";
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;
@@ -0,0 +1,6 @@
import Image from "next/image";
import { icons } from "@/utils/getIconByName";
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);
@@ -38,7 +37,7 @@ export const CurrentEpochCard = () => {
const updateState = useCallback((data: NonNullable<EpochResponseData>) => {
const { startTime, endTime } = getStartEndTime(
data.current_epoch_start,
data.current_epoch_end,
data.current_epoch_end
);
const progress = calulateProgress(data.current_epoch_end);
@@ -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,
@@ -34,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} />
@@ -48,7 +56,6 @@ 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 data = packetsAndStakingData
@@ -60,9 +67,8 @@ export const NetworkStakeCard = () => {
.map((item: IPacketsAndStakingData) => ({
date_utc: item.date_utc,
numericData: item.total_stake / 1000000,
})).filter((item) => item.numericData >= 50_000_000)
}))
.filter((item) => item.numericData >= 50_000_000);
const stakeLineGraphData = {
color: "#00CA33",
@@ -77,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,6 +17,8 @@ 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,
@@ -32,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} />
@@ -46,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;
@@ -94,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)
})
.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,11 +1,11 @@
"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: nymNodes,
isLoading,
@@ -17,9 +17,11 @@ export const RewardsCard = () => {
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">
@@ -31,23 +33,30 @@ export const RewardsCard = () => {
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,
0
);
};
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>
@@ -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) => ({
@@ -83,7 +87,7 @@ export const LineChart = ({
},
ticks: {
text: {
fill: "#818386",
fill: isDarkMode ? "white" : "#818386",
},
},
legend: {
+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;
@@ -28,6 +32,7 @@ const SimpleModal = (props: SimpleModalProps) => {
}
const { title, children, Actions, onClose } = props;
const theme = useTheme();
return (
<Dialog open maxWidth="sm" fullWidth onClose={onClose}>
@@ -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,
@@ -99,7 +100,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
{ nodeId },
fee,
"Delegation from Nym Explorer V2",
uNymFunds,
uNymFunds
);
setSelectedNodeForStaking(undefined);
setInfoModalProps({
@@ -128,7 +129,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
}
setIsLoading(false);
},
[nymClient, handleRefetch],
[nymClient, handleRefetch]
);
const handleOnSelectStake = useCallback(
@@ -157,7 +158,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
identityKey: node.identity_key,
});
},
[isWalletConnected],
[isWalletConnected]
);
const columns: MRT_ColumnDef<MappedNymNode>[] = useMemo(
@@ -302,7 +303,7 @@ const NodeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
enableSorting: false,
},
],
[isWalletConnected, handleOnSelectStake, favorites],
[isWalletConnected, handleOnSelectStake, favorites]
);
const table = useMaterialReactTable({
columns,
@@ -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",
},
@@ -1,6 +1,6 @@
"use client";
import { Stack, Typography } from "@mui/material";
import { Stack, Typography, useTheme } from "@mui/material";
import {
type MRT_ColumnDef,
MaterialReactTable,
@@ -35,11 +35,9 @@ type Props = {
const DelegationsTable = ({ id }: Props) => {
const router = useRouter();
const theme = useTheme();
const {
data: delegations = [],
isError,
} = useQuery({
const { data: delegations = [], isError } = useQuery({
queryKey: ["nodeDelegations", id],
queryFn: () => fetchNodeDelegations(id),
staleTime: 10 * 60 * 1000, // 10 minutes
@@ -48,9 +46,6 @@ const DelegationsTable = ({ id }: Props) => {
refetchOnMount: false,
});
const columns: MRT_ColumnDef<NodeRewardDetails>[] = useMemo(
() => [
{
@@ -91,7 +86,7 @@ const DelegationsTable = ({ id }: Props) => {
),
},
],
[],
[]
);
const table = useMaterialReactTable({
columns,
@@ -130,7 +125,10 @@ const DelegationsTable = ({ id }: Props) => {
},
muiTableHeadRowProps: {
sx: {
bgcolor: "background.paper",
bgcolor:
theme.palette.mode === "dark"
? "rgba(255, 255, 255, 0.05)"
: "background.paper",
},
},
@@ -146,12 +144,24 @@ const DelegationsTable = ({ id }: Props) => {
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",
},
},
}),
});
@@ -1,7 +1,14 @@
"use client";
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";
@@ -24,7 +31,8 @@ type Props = {
};
export const NodeProfileCard = ({ paramId }: Props) => {
let nodeInfo: IObservatoryNode | undefined
let nodeInfo: IObservatoryNode | undefined;
const theme = useTheme();
const { isWalletConnected } = useChain(COSMOS_KIT_USE_CHAIN);
const { nymClient } = useNymClient();
@@ -51,13 +59,10 @@ export const NodeProfileCard = ({ paramId }: Props) => {
refetchOnMount: false,
});
// get node info based on wether it's dentity_key or node_id
// 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));
}
@@ -103,14 +108,18 @@ export const NodeProfileCard = ({ paramId }: Props) => {
if (isError || !nymNodes) {
return (
<ExplorerCard label="Nym Node" sx={{ height: "100%" }}>
<Typography variant="h3" sx={{ color: "pine.950" }}>
<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,
@@ -128,7 +137,7 @@ export const NodeProfileCard = ({ paramId }: Props) => {
{ nodeId },
fee,
"Delegation from Nym Explorer V2",
uNymFunds,
uNymFunds
);
setSelectedNodeForStaking(undefined);
setInfoModalProps({
@@ -153,16 +162,14 @@ export const NodeProfileCard = ({ paramId }: Props) => {
setIsLoading(false);
};
if (!nodeInfo) return null;
const cleanMoniker = DOMPurify.sanitize(
nodeInfo?.self_description.moniker,
nodeInfo?.self_description.moniker
).replace(/&amp;/g, "&");
const cleanDescription = DOMPurify.sanitize(
nodeInfo?.self_description.details,
nodeInfo?.self_description.details
).replace(/&amp;/g, "&");
// get full country name
@@ -182,26 +189,43 @@ export const NodeProfileCard = ({ paramId }: Props) => {
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
countryCode={nodeInfo.description.auxiliary_details.location}
countryName={countryName(
nodeInfo.description.auxiliary_details.location,
nodeInfo.description.auxiliary_details.location
)}
/>
</Box>
</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>
)}
@@ -16,6 +16,7 @@ const EpochProgressBar = ({
endTime,
progress,
}: IDynamicProgressBarProps) => {
return (
<Box sx={{ width: "100%" }}>
<ProgressBar value={progress} color="secondary" />
@@ -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 {
@@ -39,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>
@@ -50,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 />
+114 -37
View File
@@ -82,7 +82,7 @@ const ColumnHeading = ({
const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
const { nymClient, address, nymQueryClient } = useNymClient();
const [delegations, setDelegations] = useState<DelegationWithNodeDetails[]>(
[],
[]
);
const [isDataLoading, setIsLoading] = useState(false);
const [infoModalProps, setInfoModalProps] = useState<InfoModalProps>({
@@ -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]);
@@ -124,13 +121,13 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
const combineDelegationsWithNodeAndPendingEvents = (
delegations: Delegation[],
nodes: MappedNymNode[],
pendingEvents: PendingEvent[] | undefined,
pendingEvents: PendingEvent[] | undefined
) => {
// Combine delegations with node details
const delegationsWithNodeDetails = delegations.map((delegation) => {
const node = nodes.find((node) => node.nodeId === delegation.node_id);
const pendingEvent = pendingEvents?.find(
(event) => event?.mixId === delegation.node_id,
(event) => event?.mixId === delegation.node_id
);
return {
@@ -148,7 +145,7 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
!delegationsWithNodeDetails.find(
(item) =>
item.node?.nodeId === e.mixId ||
item.delegation.node_id === e.mixId,
item.delegation.node_id === e.mixId
)
) {
delegationsWithNodeDetails.push({
@@ -188,7 +185,7 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
combineDelegationsWithNodeAndPendingEvents(
delegations,
nodes,
pendingEvents,
pendingEvents
);
setDelegations(delegationsWithNodeDetails);
@@ -209,7 +206,7 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
{ nodeId },
fee,
"Delegation from Nym Explorer V2",
uNymFunds,
uNymFunds
);
setSelectedNodeForStaking(undefined);
@@ -238,7 +235,7 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
}
setIsLoading(false);
},
[nymClient, handleRefetch],
[nymClient, handleRefetch]
);
const handleOnSelectStake = useCallback(
@@ -269,7 +266,7 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
});
}
},
[isWalletConnected],
[isWalletConnected]
);
const handleUnstake = useCallback(
@@ -284,7 +281,7 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
nodeId,
},
fee,
`Explorer V2: Unstaking node ${nodeId}`,
`Explorer V2: Unstaking node ${nodeId}`
);
setIsLoading(false);
await handleRefetch();
@@ -309,7 +306,7 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
setIsLoading(false);
}
},
[address, nymClient, handleRefetch],
[address, nymClient, handleRefetch]
);
const handleActionSelect = useCallback(
@@ -325,7 +322,7 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
break;
}
},
[handleUnstake, handleOnSelectStake],
[handleUnstake, handleOnSelectStake]
);
const getTooltipTitle = useCallback(
@@ -336,13 +333,13 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
if (pending?.kind === "delegate") {
return `You have a delegation pending worth ${formatBigNum(
+pending.amount.amount / 1_000_000,
+pending.amount.amount / 1_000_000
)} NYM`;
}
return undefined;
},
[], // Add dependencies if necessary
[] // Add dependencies if necessary
);
const columns: MRT_ColumnDef<DelegationWithNodeDetails>[] = useMemo(
@@ -421,10 +418,10 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
sortingFn: (rowA, rowB) => {
const stakeA = Number.parseFloat(
rowA.original.delegation.amount.amount,
rowA.original.delegation.amount.amount
);
const stakeB = Number.parseFloat(
rowB.original.delegation.amount.amount,
rowB.original.delegation.amount.amount
);
return stakeA - stakeB;
},
@@ -469,10 +466,10 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
),
sortingFn: (rowA, rowB) => {
const isFavoriteA = favorites.includes(
rowA.original.node?.owner || "-",
rowA.original.node?.owner || "-"
);
const isFavoriteB = favorites.includes(
rowB.original.node?.owner || "-",
rowB.original.node?.owner || "-"
);
// Sort favorites first
@@ -512,7 +509,7 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
handleActionSelect(
action,
row.original.delegation?.node_id,
row.original.node?.identity_key || undefined,
row.original.node?.identity_key || undefined
);
}}
/>
@@ -522,7 +519,7 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
},
},
],
[handleActionSelect, favorites, getTooltipTitle],
[handleActionSelect, favorites, getTooltipTitle]
);
const table = useMaterialReactTable({
@@ -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,15 +655,42 @@ 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",
},
}),
renderEmptyRowsFallback: () => (
<Stack
gap={3}
sx={{ p: 5 }}
justifyContent={isMobile ? "flex-start" : "center"}
alignItems={isMobile ? "flex-start" : "center"}
>
<Typography variant="body3" width={isMobile ? 300 : "unset"}>
You haven&apos;t staked on any nodes yet. Stake on a node to start
earning rewards.
</Typography>
<Button
variant="contained"
size="large"
onClick={(e) => e.stopPropagation()}
>
<Link href="/table" underline="none" color="inherit">
Stake
</Link>
</Button>
</Stack>
),
});
if (!nymClient || !address) {
@@ -644,3 +720,4 @@ const StakeTable = ({ nodes }: { nodes: MappedNymNodes }) => {
};
export default StakeTable;
@@ -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,
@@ -39,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>
@@ -50,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 {
@@ -39,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>
@@ -50,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, PaletteMode } from "@mui/material";
import { ThemeProvider as MUIThemeProvider } from "@mui/material";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter";
import { lightTheme, darkTheme } from "../theme/theme";
import { useLocalStorage } from "@uidotdev/usehooks";
import { useEffect, useState } from "react";
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;
+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: {
+38 -15
View File
@@ -1,32 +1,45 @@
import type { StaticImageData } from "next/image";
import nymLogo from "@/../public/icons/nym-logo.svg";
import nymLogoWhite from "@/../public/icons/nym-logo-white.svg";
import token from "@/../public/icons/token.svg";
import tokenDark from "@/../public/icons/token-dark.svg";
import gateway from "@/../public/icons/gateway.svg";
import elips from "@/../public/icons/elips.svg";
import elipseDark from "@/../public/icons/elipse-dark.svg";
import cross from "@/../public/icons/cross.svg";
import crossDark from "@/../public/icons/cross-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 copyFileDark from "@/../public/icons/copy-file-dark.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 stakeCardDark from "@/../public/icons/stake-card-dark.svg";
import explorerCardDark from "@/../public/icons/explorer-card-dark.svg";
import gatewayDark from "@/../public/icons/gateway-dark.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"
}
}
+513 -424
View File
File diff suppressed because it is too large Load Diff