Improve design and merge folders

This commit is contained in:
Arnaud 2024-10-31 19:18:21 +01:00
parent 9ce4e55593
commit 635b115a4d
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
26 changed files with 718 additions and 529 deletions

10
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "0.0.7",
"license": "MIT",
"dependencies": {
"@codex-storage/marketplace-ui-components": "^0.0.28",
"@codex-storage/marketplace-ui-components": "^0.0.29",
"@codex-storage/sdk-js": "^0.0.12",
"@sentry/browser": "^8.32.0",
"@sentry/react": "^8.31.0",
@ -378,9 +378,9 @@
"peer": true
},
"node_modules/@codex-storage/marketplace-ui-components": {
"version": "0.0.28",
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.28.tgz",
"integrity": "sha512-C2Wj6Rb4RzdkXRtXsj+dnzP5NwnJbmlHfQ1R2jxqgaJ4DXBDnAnEGiiljHzHE2oP0P4Exx7CQBjrP7vWChMpNg==",
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.29.tgz",
"integrity": "sha512-ctbjE/m2bOu3SXvlHcB8hulZyzFFymizjKPonNjHHriyCwlGMgryLeKLty2XpzAWw/JR/WZPSZTwlWJ7qOy0hQ==",
"dependencies": {
"lucide-react": "^0.453.0"
},
@ -4841,4 +4841,4 @@
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
}
}
}
}

View File

@ -24,7 +24,7 @@
"React"
],
"dependencies": {
"@codex-storage/marketplace-ui-components": "^0.0.28",
"@codex-storage/marketplace-ui-components": "^0.0.29",
"@codex-storage/sdk-js": "^0.0.12",
"@sentry/browser": "^8.32.0",
"@sentry/react": "^8.31.0",

View File

@ -4,25 +4,37 @@ import { classnames } from "../../utils/classnames";
import { useNetwork } from "../../network/useNetwork";
import { NetworkFlashIcon } from "../NetworkFlashIcon/NetworkFlashIcon";
import { useQueryClient } from "@tanstack/react-query";
import { useEffect } from "react";
import { ReactElement, useEffect } from "react";
import { useCodexConnection } from "../../hooks/useCodexConnection";
import { NodesIcon } from "../Menu/NodesIcon";
import { usePersistence } from "../../hooks/usePersistence";
import { useNavigate, useRouterState } from "@tanstack/react-router";
import { PeersIcon } from "../Menu/PeersIcon";
import { SettingsIcon } from "../Menu/SettingsIcon";
type Props = {
/**
* Event triggered when the menu is expanding, after a click on the
* menu button.
*/
onExpand: () => void;
onIconClick: () => void;
};
export function AppBar(_: Props) {
console.debug(_);
const icons: Record<string, ReactElement> = {
dashboard: <DashboardIcon />,
peers: <PeersIcon />,
settings: <SettingsIcon />,
};
const descriptions: Record<string, string> = {
dashboard: "Get Overview of your Codex Vault",
peers: "Monitor your Codex peer connections",
settings: "Manage your Codex Vault",
};
export function AppBar({ onIconClick }: Props) {
const online = useNetwork();
const queryClient = useQueryClient();
const codex = useCodexConnection();
const persistence = usePersistence(codex.enabled);
const router = useRouterState();
const navigate = useNavigate({ from: router.location.pathname });
useEffect(() => {
queryClient.invalidateQueries({
@ -31,39 +43,45 @@ export function AppBar(_: Props) {
});
}, [queryClient, codex.enabled]);
const onNodeClick = () => navigate({ to: "/dashboard/settings" });
const offline = !online || !codex.enabled;
const title =
router.location.pathname.split("/")[2] ||
router.location.pathname.split("/")[1];
return (
<div
className={classnames(
["app-bar"],
["app-bar--offline", offline],
["app-bar--no-persistence", !persistence.enabled]
)}>
<div className="row gap">
{/* <a className="appBar-burger" onClick={onExpand}>
<>
<div
className={classnames(
["app-bar"],
["app-bar--offline", offline],
["app-bar--no-persistence", !persistence.enabled]
)}>
<div className="row gap">
{/* <a className="appBar-burger" onClick={onExpand}>
<Menu size={"1.25rem"} />
</a> */}
<span>
<DashboardIcon />
</span>
<span onClick={onIconClick}>{icons[title]}</span>
<div>
<h1>Dashboard</h1>
<h2>Get Overview of your Codex Vault</h2>
<div>
<h1>{title}</h1>
<h2>{descriptions[title]}</h2>
</div>
</div>
<aside className="row gap">
<div className="row gap">
<NetworkFlashIcon />
<span>Network</span>
</div>
<div className="row gap" onClick={onNodeClick}>
<NodesIcon variant={codex.enabled ? "success" : "failure"} />
<span>Node</span>
</div>
</aside>
</div>
<aside className="row gap">
<div className="row gap">
<NetworkFlashIcon />
<span>Network</span>
</div>
<div className="row gap">
<NodesIcon variant={codex.enabled ? "success" : "failure"} />
<span>Node</span>
</div>
</aside>
</div>
</>
);
}

View File

@ -3,10 +3,7 @@
justify-content: space-between;
border-bottom: 1px solid var(--codex-border-color);
display: flex;
padding-right: 48px;
padding-left: 48px;
padding-top: 20px;
padding-bottom: 20px;
padding: 16px;
border-bottom: 1px solid #96969633;
box-sizing: border-box;
background-color: #1c1c1c;
@ -15,6 +12,12 @@
top: 0;
z-index: 1;
@media (min-width: 1000px) {
& {
padding: 20px 48px;
}
}
&:not(.app-bar--offline):not(.app-bar--no-persistence) {
border-right-color: #6ccc93;
}
@ -33,6 +36,7 @@
sans-serif;
letter-spacing: -0.015em;
color: white;
text-transform: capitalize;
}
h2 {
@ -53,6 +57,13 @@
justify-content: center;
border: 1px solid #353639;
border-radius: 50%;
color: #969696;
}
@media (max-width: 999px) {
& {
cursor: pointer;
}
}
}
@ -69,6 +80,16 @@
sans-serif;
letter-spacing: -0.006em;
color: #8d8d8d;
@media (max-width: 999px) {
& {
display: none;
}
}
}
div:last-child {
cursor: pointer;
}
}
}

View File

@ -1,5 +1,5 @@
.background-img {
position: absolute;
position: fixed;
right: -40px;
max-height: 90%;
width: auto;
@ -7,7 +7,7 @@
transition: opacity 0.35s;
opacity: 0.3;
@media (min-width: 1200px) {
@media (min-width: 1580px) {
& {
opacity: 1;
}

View File

@ -1,8 +1,4 @@
import {
Backdrop,
ButtonIcon,
SimpleText,
} from "@codex-storage/marketplace-ui-components";
import { Backdrop, ButtonIcon } from "@codex-storage/marketplace-ui-components";
import { CheckCircle, Folder } from "lucide-react";
import "./FolderButton.css";
import { useState } from "react";
@ -49,9 +45,9 @@ export function FolderButton({ folders, onFolderToggle }: Props) {
<div>{folder}</div>
<div>
{isActive && (
<SimpleText variant="primary">
<span className="text--primary">
<CheckCircle size={"1rem"}></CheckCircle>
</SimpleText>
</span>
)}
</div>
</div>

View File

@ -1,85 +1,124 @@
.address {
display: flex;
align-items: center;
position: relative;
gap: 16px;
> div {
.health-checks {
.address {
display: flex;
position: relative;
}
gap: 16px;
flex-direction: column;
align-items: flex-start;
svg {
position: absolute;
top: 68px;
bottom: 0;
right: 18px;
}
@media (min-width: 1000px) {
& {
flex-direction: row;
align-items: center;
}
}
.refresh {
position: relative;
top: 24px;
cursor: pointer;
> div {
position: relative;
@media (max-width: 999px) {
&:not(.refresh) {
width: 100%;
}
}
}
svg {
position: initial;
position: absolute;
top: 68px;
bottom: 0;
right: 18px;
}
&.address--fetching .refresh svg {
animation: rotate 2s linear infinite;
.refresh {
position: relative;
cursor: pointer;
cursor: pointer;
@media (max-width: 999px) {
& {
top: 0px;
left: 0;
transform: scale(1.5);
right: 0;
margin: auto;
}
}
@media (min-width: 1000px) {
& {
top: 18px;
}
}
svg {
position: initial;
}
&.address--fetching .refresh svg {
animation: rotate 2s linear infinite;
}
}
input[type="number"] {
width: 150px;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
-moz-appearance: textfield;
}
@media (max-width: 999px) {
input[type="number"],
input {
width: 100%;
}
}
}
input[type="number"] {
width: 150px;
p {
font-family: Azeret Mono;
font-size: 12px;
font-weight: 400;
line-height: 14px;
color: #828282;
padding-left: 1.25rem;
margin-top: 1.75rem;
margin-bottom: 3rem;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
ul {
margin-bottom: 32px;
input[type="number"] {
-moz-appearance: textfield;
}
}
.helper {
font-family: Azeret Mono;
font-size: 12px;
font-weight: 400;
line-height: 14px;
color: #828282;
padding-left: 1.25rem;
margin-top: 1.75rem;
margin-bottom: 3rem;
}
.health-checks {
margin-bottom: 32px;
li {
display: flex;
align-items: center;
padding: 16px 0;
gap: 16px;
border-top: 1px solid #96969633;
border-bottom: 1px solid #96969633;
&:first-child {
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
}
span {
li {
display: flex;
align-items: center;
width: 20px;
height: 20px;
justify-content: center;
padding: 16px 0;
gap: 16px;
border-top: 1px solid #96969633;
border-bottom: 1px solid #96969633;
&:first-child {
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
}
span {
display: flex;
align-items: center;
width: 20px;
height: 20px;
justify-content: center;
}
}
}
}

View File

@ -1,5 +1,5 @@
import { useQueryClient } from "@tanstack/react-query";
import { useEffect, useState, ClipboardEvent } from "react";
import { useEffect, useState } from "react";
import { useDebug } from "../../hooks/useDebug";
import { usePersistence } from "../../hooks/usePersistence";
import { usePortForwarding } from "../../hooks/usePortForwarding";
@ -11,7 +11,6 @@ import { HealthCheckIcon } from "./HealthCheckIcon";
import { Input } from "@codex-storage/marketplace-ui-components";
import { classnames } from "../../utils/classnames";
import { DebugUtils } from "../../utils/debug";
import { Strings } from "../../utils/strings";
import { RefreshIcon } from "../RefreshIcon/RefreshIcon";
import "./HealthChecks.css";
@ -27,8 +26,14 @@ export function HealthChecks({ online, onStepValid }: Props) {
const codex = useDebug(throwOnError);
const portForwarding = usePortForwarding(codex.data);
const persistence = usePersistence(codex.isSuccess);
const [isInvalid, setIsInvalid] = useState(false);
const [url, setUrl] = useState(CodexSdk.url);
const [isAddressInvalid, setIsAddressInvalid] = useState(false);
const [isPortInvalid, setIsPortInvalid] = useState(false);
const [address, setAddress] = useState(
CodexSdk.url().split(":")[0] + ":" + CodexSdk.url().split(":")[1]
);
const [port, setPort] = useState(
parseInt(CodexSdk.url().split(":")[2] || "80", 10)
);
const queryClient = useQueryClient();
useEffect(() => {
@ -46,48 +51,38 @@ export function HealthChecks({ online, onStepValid }: Props) {
]);
const onAddressChange = (e: React.FormEvent<HTMLInputElement>) => {
const [, port] = Strings.splitURLAndPort(url);
const element = e.currentTarget;
const value = e.currentTarget.value;
const parts = e.currentTarget.value.split(":");
if (
value.startsWith("http://") === false &&
value.startsWith("https://") === false
) {
setIsInvalid(true);
return;
}
setIsAddressInvalid(!element.checkValidity());
setIsInvalid(!element.checkValidity());
setUrl(value + ":" + port);
};
if (parts.length > 2) {
const [protocol, addr, port] = parts;
setAddress(protocol + ":" + addr);
const onPaste = (e: ClipboardEvent) => {
const text = e.clipboardData?.getData("text") || "";
try {
new URL(text);
setUrl(text);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_) {
// Nothing to do here
const p = parseInt(port, 10);
if (!isNaN(p)) {
setPort(p);
}
} else {
setAddress(parts.join(":"));
}
};
const onPortChange = (e: React.FormEvent<HTMLInputElement>) => {
const [address] = Strings.splitURLAndPort(url);
const element = e.currentTarget;
const value = element.value;
setUrl(address + ":" + value);
setIsPortInvalid(!element.checkValidity());
setPort(parseInt(value, 10));
};
const onSave = () => {
if (isInvalid === true) {
if (isAddressInvalid || isPortInvalid) {
return;
}
CodexSdk.updateURL(url)
CodexSdk.updateURL(address + ":" + port)
.then(() => queryClient.invalidateQueries())
.then(() => codex.refetch());
};
@ -100,10 +95,9 @@ export function HealthChecks({ online, onStepValid }: Props) {
forwardingPortValue = port.data;
}
}
const [address, port] = Strings.splitURLAndPort(url);
return (
<>
<div className="health-checks">
<div
className={classnames(
["address"],
@ -111,15 +105,15 @@ export function HealthChecks({ online, onStepValid }: Props) {
)}>
<div>
<Input
onPaste={onPaste}
id="url"
type="url"
label="Address"
isInvalid={isInvalid}
required
isInvalid={isAddressInvalid}
onChange={onAddressChange}
value={address}
placeholder="127.0.0.1"></Input>
{isInvalid ? (
{isAddressInvalid ? (
<ErrorCircleIcon />
) : (
<SuccessCheckIcon variant="default" />
@ -133,6 +127,7 @@ export function HealthChecks({ online, onStepValid }: Props) {
type="number"
onChange={onPortChange}
value={port}
isInvalid={isPortInvalid}
placeholder="8080"></Input>
<SuccessCheckIcon variant="default"></SuccessCheckIcon>
</div>
@ -142,11 +137,14 @@ export function HealthChecks({ online, onStepValid }: Props) {
</div>
</div>
<ul className="helper">
<li>Port forwarding should be default {forwardingPortValue}.</li>
</ul>
<p>
<li>
Port forwarding should be {forwardingPortValue} for TCP and 8090 by
default for UDP.
</li>
</p>
<ul className="health-checks">
<ul>
<li>
<span>
<HealthCheckIcon />
@ -194,6 +192,6 @@ export function HealthChecks({ online, onStepValid }: Props) {
Marketplace
</li>
</ul>
</>
</div>
);
}

View File

@ -44,7 +44,7 @@ export function Logotype({ height, width, className }: Props) {
<path
d="M63.4017 39.9316C63.4017 36.8129 65.93 34.2846 69.0487 34.2846H114.931C118.05 34.2846 120.578 36.8129 120.578 39.9316C120.578 43.0504 118.05 45.5787 114.931 45.5787H69.0487C65.93 45.5787 63.4017 43.0504 63.4017 39.9316Z"
stroke="#7F948D"
stroke-width="0.705882"
strokeWidth="0.705882"
/>
<path
d="M113.049 42.0462H109.012L108.46 43.1081H106.814L110.134 36.7551H111.863L115.284 43.1081H113.619L113.049 42.0462ZM112.378 40.7847L111.017 38.2798L109.674 40.7847H112.378Z"

View File

@ -1,7 +1,6 @@
import { Backdrop } from "@codex-storage/marketplace-ui-components";
import { attributes } from "../../utils/attributes";
import "./menu.css";
import { ComponentType, useEffect, useState } from "react";
import { ComponentType, useState } from "react";
import { Logo } from "../Logo/Logo";
import { Logotype } from "../Logotype/Logotype";
import { ExpandIcon } from "./ExpandIcon";
@ -38,25 +37,12 @@ export type MenuItem =
};
type Props = {
/**
* If true, the menu will be displayed
*/
expanded: boolean;
onClose: () => void;
onOpen?: () => void;
isMobileMenuDisplayed: boolean;
};
export function Menu({ expanded, onClose, onOpen }: Props) {
export function Menu({ isMobileMenuDisplayed }: Props) {
const [isExpanded, setIsExpanded] = useState<boolean | null>(null);
useEffect(() => {
if (expanded && onOpen) {
onOpen();
}
}, [expanded, onOpen]);
const onLogoClick = () => {
if (isExpanded === false) {
setIsExpanded(true);
@ -67,15 +53,18 @@ export function Menu({ expanded, onClose, onOpen }: Props) {
return (
<>
<Backdrop onClose={onClose} open={expanded} />
<aside
className={classnames(
[`menu`],
["menu--expanded", isExpanded === true],
["menu--unexpanded", isExpanded === false]
)}
{...attributes({ "aria-expanded": expanded })}>
className={classnames([`menu`])}
{...attributes(
isExpanded === null
? {
"aria-hidden": (!isMobileMenuDisplayed).toString(),
}
: {
"aria-expanded": isExpanded.toString(),
"aria-hidden": (!isMobileMenuDisplayed).toString(),
}
)}>
<div>
<header>
<Logo onClick={onLogoClick} width={30} />

View File

@ -3,25 +3,34 @@
flex-direction: column;
background-color: #1c1c1c;
border-radius: var(--codex-border-radius);
transform: translatex(-500px);
transition: transform 0.25s;
transition: left 0.25s;
position: sticky;
z-index: 10;
view-transition-name: main-menu;
height: 100%;
top: 0;
width: 80px;
transition: width 0.5s;
transition:
width 0.5s,
font-size 0.5s,
left 0.05s;
min-width: 0;
width: 272px;
transform: translatex(0px);
&[aria-expanded] {
transform: translatex(0);
min-width: 200px;
@media (max-width: 999px) {
&,
&[aria-hidden] {
width: 272px;
position: fixed;
z-index: 12;
left: -300px;
}
&[aria-hidden="false"] {
left: 0px;
}
}
&:not(.menu--unexpanded) a[data-title]:hover::after {
&:not([aria-expanded="false"]) a[data-title]:hover::after {
content: attr(data-title);
background-color: #2f2f2f;
color: #fff;
@ -36,11 +45,11 @@
overflow: visible;
}
&.menu--expanded {
&[aria-expanded="true"] {
width: 272px;
}
&.menu--unexpanded {
&[aria-expanded="false"] {
width: 80px;
.items {

View File

@ -1,16 +1,25 @@
.onboarding {
width: 100%;
padding: 3rem 6rem;
padding: 16px;
display: flex;
@media (min-width: 1000px) {
& {
padding: 3rem 6rem;
}
}
> section {
display: flex;
flex-direction: column;
justify-content: space-between;
z-index: 1;
}
> section:first-child {
max-width: 500px;
@media (min-width: 1000px) {
> section:first-child {
max-width: 500px;
}
}
section {
@ -177,12 +186,19 @@
.navigation {
cursor: pointer;
position: absolute;
right: 6rem;
bottom: 3rem;
right: 16px;
bottom: 16px;
border-bottom: none;
text-decoration: none;
z-index: 1;
@media (min-width: 1000px) {
& {
right: 6rem;
bottom: 3rem;
}
}
&:hover {
animation-name: example;
animation-duration: 2.5s;

View File

@ -6,7 +6,7 @@ import { useEffect } from "react";
export type Props = {
address: string;
onPinAdd: (pin: PeerPin) => void;
onPinAdd: (pin: PeerPin & { countryIso: string; ip: string }) => void;
};
const getFlagEmoji = (countryCode: string) => {
@ -50,6 +50,8 @@ export function PeerCountryCell({ address, onPinAdd }: Props) {
onPinAdd({
lat: data.latitude,
lng: data.longitude,
countryIso: data.country_iso,
ip: data.ip,
});
}
}, [data, onPinAdd]);

View File

@ -1,7 +1,16 @@
.user-info {
display: flex;
align-items: center;
gap: 32px;
flex-direction: column;
align-items: flex-start;
gap: 16px;
@media (min-width: 1000px) {
& {
flex-direction: row;
gap: 32px;
align-items: center;
}
}
.emoji {
position: relative;
@ -9,8 +18,14 @@
aside {
position: absolute;
top: -140px;
left: 116px;
left: 0px;
z-index: 1;
@media (min-width: 1000px) {
& {
left: 116px;
}
}
}
.input input {
@ -19,4 +34,14 @@
cursor: pointer;
}
}
.input input {
width: 100%;
@media (min-width: 1000px) {
& {
width: inherit;
}
}
}
}

View File

@ -39,6 +39,7 @@ export function UserInfo({ onNameChange }: Props) {
<div className="emoji">
{areEmojiVisible && (
<EmojiPicker
width={"auto"}
emojiStyle={EmojiStyle.NATIVE}
theme={Theme.DARK}
lazyLoadEmojis={true}

View File

@ -40,6 +40,7 @@ if (import.meta.env.PROD && !import.meta.env.CI) {
// Create a new router instance
const router = createRouter({
routeTree,
defaultPreload: "viewport",
defaultNotFoundComponent: () => {
return (
<Failure

View File

@ -22,7 +22,6 @@ import { Route as DashboardWalletImport } from './routes/dashboard/wallet'
import { Route as DashboardSettingsImport } from './routes/dashboard/settings'
import { Route as DashboardRequestsImport } from './routes/dashboard/requests'
import { Route as DashboardPurchasesImport } from './routes/dashboard/purchases'
import { Route as DashboardPeersImport } from './routes/dashboard/peers'
import { Route as DashboardNodesImport } from './routes/dashboard/nodes'
import { Route as DashboardLogsImport } from './routes/dashboard/logs'
import { Route as DashboardHelpImport } from './routes/dashboard/help'
@ -30,7 +29,6 @@ import { Route as DashboardFilesImport } from './routes/dashboard/files'
import { Route as DashboardFavoritesImport } from './routes/dashboard/favorites'
import { Route as DashboardDisclaimerImport } from './routes/dashboard/disclaimer'
import { Route as DashboardDeviceImport } from './routes/dashboard/device'
import { Route as DashboardAvailabilitiesImport } from './routes/dashboard/availabilities'
import { Route as DashboardAnalyticsImport } from './routes/dashboard/analytics'
import { Route as DashboardAboutImport } from './routes/dashboard/about'
@ -73,11 +71,6 @@ const DashboardIndexRoute = DashboardIndexImport.update({
getParentRoute: () => DashboardRoute,
} as any)
const DashboardWalletRoute = DashboardWalletImport.update({
id: '/wallet',
path: '/wallet',
getParentRoute: () => DashboardRoute,
} as any)
const DashboardPeersLazyRoute = DashboardPeersLazyImport.update({
id: '/peers',
path: '/peers',
@ -95,6 +88,12 @@ const DashboardAvailabilitiesLazyRoute =
import('./routes/dashboard/availabilities.lazy').then((d) => d.Route),
)
const DashboardWalletRoute = DashboardWalletImport.update({
id: '/wallet',
path: '/wallet',
getParentRoute: () => DashboardRoute,
} as any)
const DashboardSettingsRoute = DashboardSettingsImport.update({
id: '/settings',
path: '/settings',
@ -113,12 +112,6 @@ const DashboardPurchasesRoute = DashboardPurchasesImport.update({
getParentRoute: () => DashboardRoute,
} as any)
const DashboardPeersRoute = DashboardPeersImport.update({
id: '/peers',
path: '/peers',
getParentRoute: () => DashboardRoute,
} as any)
const DashboardNodesRoute = DashboardNodesImport.update({
id: '/nodes',
path: '/nodes',
@ -161,12 +154,6 @@ const DashboardDeviceRoute = DashboardDeviceImport.update({
getParentRoute: () => DashboardRoute,
} as any)
const DashboardAvailabilitiesRoute = DashboardAvailabilitiesImport.update({
id: '/availabilities',
path: '/availabilities',
getParentRoute: () => DashboardRoute,
} as any)
const DashboardAnalyticsRoute = DashboardAnalyticsImport.update({
id: '/analytics',
path: '/analytics',
@ -225,13 +212,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardAnalyticsImport
parentRoute: typeof DashboardImport
}
'/dashboard/availabilities': {
id: '/dashboard/availabilities'
path: '/availabilities'
fullPath: '/dashboard/availabilities'
preLoaderRoute: typeof DashboardAvailabilitiesImport
parentRoute: typeof DashboardImport
}
'/dashboard/device': {
id: '/dashboard/device'
path: '/device'
@ -281,13 +261,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardNodesImport
parentRoute: typeof DashboardImport
}
'/dashboard/peers': {
id: '/dashboard/peers'
path: '/peers'
fullPath: '/dashboard/peers'
preLoaderRoute: typeof DashboardPeersImport
parentRoute: typeof DashboardImport
}
'/dashboard/purchases': {
id: '/dashboard/purchases'
path: '/purchases'
@ -314,178 +287,154 @@ declare module '@tanstack/react-router' {
path: '/wallet'
fullPath: '/dashboard/wallet'
preLoaderRoute: typeof DashboardWalletImport
'/dashboard/availabilities': {
id: '/dashboard/availabilities'
path: '/availabilities'
fullPath: '/dashboard/availabilities'
preLoaderRoute: typeof DashboardAvailabilitiesLazyImport
parentRoute: typeof DashboardImport
}
'/dashboard/peers': {
id: '/dashboard/peers'
path: '/peers'
fullPath: '/dashboard/peers'
preLoaderRoute: typeof DashboardPeersLazyImport
parentRoute: typeof DashboardImport
}
'/dashboard/': {
id: '/dashboard/'
path: '/'
fullPath: '/dashboard/'
preLoaderRoute: typeof DashboardIndexImport
parentRoute: typeof DashboardImport
}
parentRoute: typeof DashboardImport
}
'/dashboard/availabilities': {
id: '/dashboard/availabilities'
path: '/availabilities'
fullPath: '/dashboard/availabilities'
preLoaderRoute: typeof DashboardAvailabilitiesLazyImport
parentRoute: typeof DashboardImport
}
'/dashboard/peers': {
id: '/dashboard/peers'
path: '/peers'
fullPath: '/dashboard/peers'
preLoaderRoute: typeof DashboardPeersLazyImport
parentRoute: typeof DashboardImport
}
'/dashboard/': {
id: '/dashboard/'
path: '/'
fullPath: '/dashboard/'
preLoaderRoute: typeof DashboardIndexImport
parentRoute: typeof DashboardImport
}
}
}
// Create and export the route tree
// Create and export the route tree
interface DashboardRouteChildren {
DashboardAboutRoute: typeof DashboardAboutRoute
DashboardAnalyticsRoute: typeof DashboardAnalyticsRoute
DashboardAvailabilitiesRoute: typeof DashboardAvailabilitiesRoute
DashboardDeviceRoute: typeof DashboardDeviceRoute
DashboardDisclaimerRoute: typeof DashboardDisclaimerRoute
DashboardFavoritesRoute: typeof DashboardFavoritesRoute
DashboardFilesRoute: typeof DashboardFilesRoute
DashboardHelpRoute: typeof DashboardHelpRoute
DashboardLogsRoute: typeof DashboardLogsRoute
DashboardNodesRoute: typeof DashboardNodesRoute
DashboardPeersRoute: typeof DashboardPeersRoute
DashboardPurchasesRoute: typeof DashboardPurchasesRoute
DashboardRequestsRoute: typeof DashboardRequestsRoute
DashboardSettingsRoute: typeof DashboardSettingsRoute
DashboardWalletRoute: typeof DashboardWalletRoute
DashboardPurchasesRoute: typeof DashboardPurchasesRoute
DashboardRequestsRoute: typeof DashboardRequestsRoute
DashboardSettingsRoute: typeof DashboardSettingsRoute
DashboardAvailabilitiesLazyRoute: typeof DashboardAvailabilitiesLazyRoute
DashboardPeersLazyRoute: typeof DashboardPeersLazyRoute
DashboardIndexRoute: typeof DashboardIndexRoute
}
interface DashboardRouteChildren {
DashboardAboutRoute: typeof DashboardAboutRoute
DashboardAnalyticsRoute: typeof DashboardAnalyticsRoute
DashboardDeviceRoute: typeof DashboardDeviceRoute
DashboardDisclaimerRoute: typeof DashboardDisclaimerRoute
DashboardFavoritesRoute: typeof DashboardFavoritesRoute
DashboardFilesRoute: typeof DashboardFilesRoute
DashboardHelpRoute: typeof DashboardHelpRoute
DashboardLogsRoute: typeof DashboardLogsRoute
DashboardNodesRoute: typeof DashboardNodesRoute
DashboardPurchasesRoute: typeof DashboardPurchasesRoute
DashboardRequestsRoute: typeof DashboardRequestsRoute
DashboardSettingsRoute: typeof DashboardSettingsRoute
DashboardWalletRoute: typeof DashboardWalletRoute
DashboardAvailabilitiesLazyRoute: typeof DashboardAvailabilitiesLazyRoute
DashboardPeersLazyRoute: typeof DashboardPeersLazyRoute
DashboardIndexRoute: typeof DashboardIndexRoute
}
const DashboardRouteChildren: DashboardRouteChildren = {
DashboardAboutRoute: DashboardAboutRoute,
DashboardAnalyticsRoute: DashboardAnalyticsRoute,
DashboardAvailabilitiesRoute: DashboardAvailabilitiesRoute,
DashboardDeviceRoute: DashboardDeviceRoute,
DashboardDisclaimerRoute: DashboardDisclaimerRoute,
DashboardFavoritesRoute: DashboardFavoritesRoute,
DashboardFilesRoute: DashboardFilesRoute,
DashboardHelpRoute: DashboardHelpRoute,
DashboardLogsRoute: DashboardLogsRoute,
DashboardNodesRoute: DashboardNodesRoute,
DashboardPeersRoute: DashboardPeersRoute,
DashboardPurchasesRoute: DashboardPurchasesRoute,
DashboardRequestsRoute: DashboardRequestsRoute,
DashboardSettingsRoute: DashboardSettingsRoute,
DashboardWalletRoute: DashboardWalletRoute,
DashboardPurchasesRoute: DashboardPurchasesRoute,
DashboardRequestsRoute: DashboardRequestsRoute,
DashboardSettingsRoute: DashboardSettingsRoute,
DashboardAvailabilitiesLazyRoute: DashboardAvailabilitiesLazyRoute,
DashboardPeersLazyRoute: DashboardPeersLazyRoute,
DashboardIndexRoute: DashboardIndexRoute,
}
const DashboardRouteChildren: DashboardRouteChildren = {
DashboardAboutRoute: DashboardAboutRoute,
DashboardAnalyticsRoute: DashboardAnalyticsRoute,
DashboardDeviceRoute: DashboardDeviceRoute,
DashboardDisclaimerRoute: DashboardDisclaimerRoute,
DashboardFavoritesRoute: DashboardFavoritesRoute,
DashboardFilesRoute: DashboardFilesRoute,
DashboardHelpRoute: DashboardHelpRoute,
DashboardLogsRoute: DashboardLogsRoute,
DashboardNodesRoute: DashboardNodesRoute,
DashboardPurchasesRoute: DashboardPurchasesRoute,
DashboardRequestsRoute: DashboardRequestsRoute,
DashboardSettingsRoute: DashboardSettingsRoute,
DashboardWalletRoute: DashboardWalletRoute,
DashboardAvailabilitiesLazyRoute: DashboardAvailabilitiesLazyRoute,
DashboardPeersLazyRoute: DashboardPeersLazyRoute,
DashboardIndexRoute: DashboardIndexRoute,
}
const DashboardRouteWithChildren = DashboardRoute._addFileChildren(
DashboardRouteChildren,
)
const DashboardRouteWithChildren = DashboardRoute._addFileChildren(
DashboardRouteChildren,
)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/dashboard': typeof DashboardRouteWithChildren
'/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesRoute
'/dashboard/device': typeof DashboardDeviceRoute
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/files': typeof DashboardFilesRoute
'/dashboard/help': typeof DashboardHelpRoute
'/dashboard/logs': typeof DashboardLogsRoute
'/dashboard/nodes': typeof DashboardNodesRoute
'/dashboard/peers': typeof DashboardPeersRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/wallet': typeof DashboardWalletRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard/': typeof DashboardIndexRoute
}
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/dashboard': typeof DashboardRouteWithChildren
'/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/device': typeof DashboardDeviceRoute
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/files': typeof DashboardFilesRoute
'/dashboard/help': typeof DashboardHelpRoute
'/dashboard/logs': typeof DashboardLogsRoute
'/dashboard/nodes': typeof DashboardNodesRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/wallet': typeof DashboardWalletRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard/': typeof DashboardIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesRoute
'/dashboard/device': typeof DashboardDeviceRoute
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/files': typeof DashboardFilesRoute
'/dashboard/help': typeof DashboardHelpRoute
'/dashboard/logs': typeof DashboardLogsRoute
'/dashboard/nodes': typeof DashboardNodesRoute
'/dashboard/peers': typeof DashboardPeersRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/wallet': typeof DashboardWalletRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard': typeof DashboardIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/device': typeof DashboardDeviceRoute
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/files': typeof DashboardFilesRoute
'/dashboard/help': typeof DashboardHelpRoute
'/dashboard/logs': typeof DashboardLogsRoute
'/dashboard/nodes': typeof DashboardNodesRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/wallet': typeof DashboardWalletRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard': typeof DashboardIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/dashboard': typeof DashboardRouteWithChildren
'/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesRoute
'/dashboard/device': typeof DashboardDeviceRoute
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/files': typeof DashboardFilesRoute
'/dashboard/help': typeof DashboardHelpRoute
'/dashboard/logs': typeof DashboardLogsRoute
'/dashboard/nodes': typeof DashboardNodesRoute
'/dashboard/peers': typeof DashboardPeersRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/wallet': typeof DashboardWalletRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard/': typeof DashboardIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/dashboard': typeof DashboardRouteWithChildren
'/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/device': typeof DashboardDeviceRoute
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/files': typeof DashboardFilesRoute
'/dashboard/help': typeof DashboardHelpRoute
'/dashboard/logs': typeof DashboardLogsRoute
'/dashboard/nodes': typeof DashboardNodesRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/wallet': typeof DashboardWalletRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard/': typeof DashboardIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/dashboard'
| '/onboarding-checks'
| '/onboarding-name'
| '/dashboard/about'
| '/dashboard/analytics'
| '/dashboard/availabilities'
| '/dashboard/device'
| '/dashboard/disclaimer'
| '/dashboard/favorites'
@ -493,25 +442,20 @@ declare module '@tanstack/react-router' {
| '/dashboard/help'
| '/dashboard/logs'
| '/dashboard/nodes'
| '/dashboard/peers'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
| '/dashboard/wallet'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
| '/dashboard/availabilities'
| '/dashboard/peers'
| '/dashboard/'
fileRoutesByTo: FileRoutesByTo
to:
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/onboarding-checks'
| '/onboarding-name'
| '/dashboard/about'
| '/dashboard/analytics'
| '/dashboard/availabilities'
| '/dashboard/device'
| '/dashboard/disclaimer'
| '/dashboard/favorites'
@ -519,18 +463,14 @@ declare module '@tanstack/react-router' {
| '/dashboard/help'
| '/dashboard/logs'
| '/dashboard/nodes'
| '/dashboard/peers'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
| '/dashboard/wallet'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
| '/dashboard/availabilities'
| '/dashboard/peers'
| '/dashboard'
id:
id:
| '__root__'
| '/'
| '/dashboard'
@ -538,7 +478,6 @@ declare module '@tanstack/react-router' {
| '/onboarding-name'
| '/dashboard/about'
| '/dashboard/analytics'
| '/dashboard/availabilities'
| '/dashboard/device'
| '/dashboard/disclaimer'
| '/dashboard/favorites'
@ -546,37 +485,33 @@ declare module '@tanstack/react-router' {
| '/dashboard/help'
| '/dashboard/logs'
| '/dashboard/nodes'
| '/dashboard/peers'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
| '/dashboard/wallet'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
| '/dashboard/availabilities'
| '/dashboard/peers'
| '/dashboard/'
fileRoutesById: FileRoutesById
}
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
DashboardRoute: typeof DashboardRouteWithChildren
OnboardingChecksRoute: typeof OnboardingChecksRoute
OnboardingNameRoute: typeof OnboardingNameRoute
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
DashboardRoute: typeof DashboardRouteWithChildren
OnboardingChecksRoute: typeof OnboardingChecksRoute
OnboardingNameRoute: typeof OnboardingNameRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
DashboardRoute: DashboardRouteWithChildren,
OnboardingChecksRoute: OnboardingChecksRoute,
OnboardingNameRoute: OnboardingNameRoute,
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
DashboardRoute: DashboardRouteWithChildren,
OnboardingChecksRoute: OnboardingChecksRoute,
OnboardingNameRoute: OnboardingNameRoute,
}
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
/* prettier-ignore-end */
@ -600,7 +535,6 @@ declare module '@tanstack/react-router' {
"children": [
"/dashboard/about",
"/dashboard/analytics",
"/dashboard/availabilities",
"/dashboard/device",
"/dashboard/disclaimer",
"/dashboard/favorites",
@ -608,14 +542,10 @@ declare module '@tanstack/react-router' {
"/dashboard/help",
"/dashboard/logs",
"/dashboard/nodes",
"/dashboard/peers",
"/dashboard/purchases",
"/dashboard/requests",
"/dashboard/settings",
"/dashboard/wallet",
"/dashboard/purchases",
"/dashboard/requests",
"/dashboard/settings",
"/dashboard/availabilities",
"/dashboard/peers",
"/dashboard/"
@ -635,10 +565,6 @@ declare module '@tanstack/react-router' {
"filePath": "dashboard/analytics.tsx",
"parent": "/dashboard"
},
"/dashboard/availabilities": {
"filePath": "dashboard/availabilities.tsx",
"parent": "/dashboard"
},
"/dashboard/device": {
"filePath": "dashboard/device.tsx",
"parent": "/dashboard"
@ -667,10 +593,6 @@ declare module '@tanstack/react-router' {
"filePath": "dashboard/nodes.tsx",
"parent": "/dashboard"
},
"/dashboard/peers": {
"filePath": "dashboard/peers.tsx",
"parent": "/dashboard"
},
"/dashboard/purchases": {
"filePath": "dashboard/purchases.tsx",
"parent": "/dashboard"
@ -685,6 +607,8 @@ declare module '@tanstack/react-router' {
},
"/dashboard/wallet": {
"filePath": "dashboard/wallet.tsx",
"parent": "/dashboard"
},
"/dashboard/availabilities": {
"filePath": "dashboard/availabilities.lazy.tsx",
"parent": "/dashboard"

View File

@ -1,24 +1,45 @@
import { createFileRoute, Outlet } from "@tanstack/react-router";
import {
createFileRoute,
Outlet,
useRouterState,
} from "@tanstack/react-router";
import "./layout.css";
import { Menu } from "../components/Menu/Menu";
import { useState } from "react";
import { useEffect, useState } from "react";
import { AppBar } from "../components/AppBar/AppBar";
import { Backdrop } from "@codex-storage/marketplace-ui-components";
const Layout = () => {
const [open, setOpen] = useState(false);
const [hasMobileMenu, setHasMobileMenu] = useState(false);
const router = useRouterState();
const onClose = () => setOpen(false);
const onIconClick = () => {
if (window.innerWidth <= 999) {
setHasMobileMenu(true);
}
};
const onExpand = () => setOpen(true);
useEffect(() => {
setHasMobileMenu(false);
}, [router.location.pathname]);
const onClose = () => setHasMobileMenu(false);
const isMobileMenuDisplayed =
hasMobileMenu === true && window.innerWidth <= 999;
return (
<div className="layout">
<Menu expanded={open} onClose={onClose}></Menu>
<Menu isMobileMenuDisplayed={isMobileMenuDisplayed}></Menu>
<main>
<AppBar onExpand={onExpand} />
<Outlet />
<AppBar onIconClick={onIconClick} />
<div>
<Outlet />
</div>
</main>
<Backdrop onClose={onClose} open={hasMobileMenu}></Backdrop>
</div>
);
};

View File

@ -1,5 +1,4 @@
.dashboard {
padding: 24px 48px;
display: flex;
justify-content: space-between;

View File

@ -2,34 +2,46 @@
max-width: 1320px;
margin-left: auto;
margin-right: auto;
width: calc(100% - 2 * 24px);
padding: 24px;
> div {
max-width: 1320px;
}
> div:first-child {
width: calc(100% - 128px - 16px);
width: calc(100% - 16px);
border: 1px solid #96969633;
padding: 16px 16px 16px 128px;
padding: 16px;
border-radius: 16px;
position: relative;
height: 600px;
@media (min-width: 1000px) {
& {
width: calc(100% - 128px - 16px);
padding: 16px 16px 16px 128px;
height: 600px;
}
}
circle[fill="#d6ff79"] {
stroke: var(--codex-color-primary);
stroke-width: 0.6px;
fill: #141414;
animation: circle-pulse 1.5s infinite;
animation: circle-pulse 3s infinite;
}
ul {
list-style-type: none;
width: 71px;
position: absolute;
left: 16px;
top: 16px;
display: none;
@media (min-width: 1000px) {
& {
list-style-type: none;
width: 71px;
position: absolute;
left: 16px;
top: 16px;
display: inline-block;
}
}
li {
border-bottom: 1px solid #969696cc;
@ -86,11 +98,33 @@
background-color: #232323;
border: 1px solid #96969633;
border-radius: 16px;
position: absolute;
bottom: 16px;
left: 16px;
width: 280px;
max-width: 280px;
padding: 16px;
transform: scale(0.7);
width: 280px;
@media (max-width: 999px) {
& {
position: relative;
bottom: -32px;
left: -32px;
}
}
@media (min-width: 1000px) {
& {
transform: scale(1);
}
}
@media (min-width: 1000px) {
& {
position: absolute;
bottom: 16px;
left: 16px;
width: 280px;
}
}
header {
display: flex;
@ -116,10 +150,16 @@
width: 350px;
height: 175px;
overflow: hidden;
transform: scale(0.73);
transform: scale(0.5);
margin: auto;
left: -32px;
@media (min-width: 1000px) {
& {
transform: scale(0.73);
}
}
*,
&::before {
box-sizing: border-box;
@ -188,7 +228,8 @@
}
> div:nth-child(2) {
width: calc(100% - 64px);
margin-top: 32px;
width: 100%;
}
table {

View File

@ -1,10 +1,15 @@
import { Cell, Row, Table } from "@codex-storage/marketplace-ui-components";
import {
Cell,
Row,
Table,
TabSortState,
} from "@codex-storage/marketplace-ui-components";
import { getMapJSON } from "dotted-map";
import DottedMap from "dotted-map/without-countries";
import { Promises } from "../../utils/promises";
import { useQuery } from "@tanstack/react-query";
import { PeerCountryCell } from "../../components/Peers/PeerCountryCell";
import { useCallback, useState } from "react";
import { useCallback, useRef, useState } from "react";
import { PeerPin } from "../../components/Peers/types";
import "./peers.css";
import { CodexSdk } from "../../sdk/codex";
@ -14,6 +19,7 @@ import { PeersIcon } from "../../components/Menu/PeersIcon";
import { SuccessCheckIcon } from "../../components/SuccessCheckIcon/SuccessCheckIcon";
import { ErrorCircleIcon } from "../../components/ErrorCircleIcon/ErrorCircleIcon";
import { createLazyFileRoute } from "@tanstack/react-router";
import { Network } from "../../utils/network";
// This function accepts the same arguments as DottedMap in the example above.
const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" });
@ -22,8 +28,29 @@ type CustomCSSProperties = React.CSSProperties & {
"--codex-peers-percent": number;
};
type Node = {
nodeId: string;
peerId: string;
record: string;
address: string;
seen: boolean;
};
type SortFn = (a: Node, b: Node) => number;
const sortByBooleanValue = (state: TabSortState) => {
return (a: Node, b: Node) => {
const order = state === "desc" ? 1 : -1;
return a?.seen === b?.seen ? 0 : b?.seen ? order : -order;
};
};
const Peers = () => {
const ips = useRef<Record<string, string>>({});
const [pins, setPins] = useState<[PeerPin, number][]>([]);
const [sortFn, setSortFn] = useState<SortFn | null>(() =>
sortByBooleanValue("desc")
);
const { data } = useQuery({
queryFn: () =>
CodexSdk.debug()
@ -47,13 +74,21 @@ const Peers = () => {
throwOnError: true,
});
const onPinAdd = useCallback((pin: PeerPin) => {
setPins((val) => {
const [, quantity = 0] =
val.find(([p]) => p.lat === pin.lat && p.lng == pin.lng) || [];
return [...val, [pin, quantity + 1]];
});
}, []);
const onPinAdd = useCallback(
({
countryIso,
ip,
...pin
}: PeerPin & { countryIso: string; ip: string }) => {
setPins((val) => {
const [, quantity = 0] =
val.find(([p]) => p.lat === pin.lat && p.lng == pin.lng) || [];
return [...val, [pin, quantity + 1]];
});
ips.current[ip] = countryIso;
},
[]
);
// Its safe to re-create the map at each render, because of the
// pre-computation its super fast ⚡️
@ -74,67 +109,111 @@ const Peers = () => {
backgroundColor: "#141414",
});
const headers = ["Country", "PeerId", "Active"];
const onSortByCountry = (state: TabSortState) => {
if (!state) {
setSortFn(null);
return;
}
const rows =
(data?.table?.nodes || []).map((node) => (
<Row
cells={[
<PeerCountryCell
onPinAdd={onPinAdd}
address={node.address}></PeerCountryCell>,
<Cell>{node.peerId}</Cell>,
<Cell>
{node.seen ? (
<div className="status--active">
<SuccessCheckIcon variant="primary"></SuccessCheckIcon> Active
</div>
) : (
<div className="status--inactive">
<ErrorCircleIcon></ErrorCircleIcon> Inactive
</div>
)}
</Cell>,
]}></Row>
)) || [];
setSortFn(() => (a: Node, b: Node) => {
const countryA = ips.current[Network.getIp(a.address)] || "";
const countryB = ips.current[Network.getIp(b.address)] || "";
const actives =
data?.table.nodes.reduce((acc, cur) => acc + (cur.seen ? 1 : 0), 0) || 0;
return state === "desc"
? countryA.localeCompare(countryB)
: countryB.localeCompare(countryA);
});
};
const onSortActive = (state: TabSortState) => {
console.info("fdf");
if (!state) {
setSortFn(null);
return;
}
setSortFn(() => sortByBooleanValue(state));
};
const headers = [
["Country", onSortByCountry],
["PeerId"],
["Active", onSortActive],
] satisfies [string, ((state: TabSortState) => void)?][];
const nodes = data?.table?.nodes || [];
const sorted = sortFn ? nodes.slice().sort(sortFn) : nodes;
const rows = sorted.map((node) => (
<Row
cells={[
<PeerCountryCell
onPinAdd={onPinAdd}
address={node.address}></PeerCountryCell>,
<Cell>{node.peerId}</Cell>,
<Cell>
{node.seen ? (
<div className="status--active">
<SuccessCheckIcon variant="primary"></SuccessCheckIcon> Active
</div>
) : (
<div className="status--inactive">
<ErrorCircleIcon></ErrorCircleIcon> Inactive
</div>
)}
</Cell>,
]}></Row>
));
const actives = sorted.reduce((acc, cur) => acc + (cur.seen ? 1 : 0), 0) || 0;
const total = data?.table.nodes.length || 1;
const styles: CustomCSSProperties = {
"--codex-peers-percent": (actives / total) * 180,
};
const good = actives > 0;
return (
<div className="peers">
<div>
<ul>
<li>Legend</li>
<li>1-3</li>
<li>3-5</li>
<li>5 +</li>
</ul>
<div className="connections">
<header>
<PeersIcon></PeersIcon>
<span>Connections</span>
</header>
<main style={styles}>
<div>
<div></div>
<span>{actives}</span>
</div>
</main>
<footer>
<SuccessCheckIcon variant="primary"></SuccessCheckIcon>{" "}
<span>Peer connections in Good standing. </span>
</footer>
</div>
<div dangerouslySetInnerHTML={{ __html: svgMap }}></div>
<div>
<ul>
<li>Legend</li>
<li>1-3</li>
<li>3-5</li>
<li>5 +</li>
</ul>
<div className="connections">
<header>
<PeersIcon></PeersIcon>
<span>Connections</span>
</header>
<main style={styles}>
<div>
<div></div>
<span>{actives}</span>
</div>
</main>
<footer>
{good ? (
<>
<SuccessCheckIcon variant="primary"></SuccessCheckIcon>
<span>Peer connections in good standing. </span>
</>
) : (
<>
<ErrorCircleIcon />
<span>No peer connection active. </span>
</>
)}
</footer>
</div>
</div>
</div>
<Table headers={headers} rows={rows} />
<Table headers={headers} rows={rows} defaultSortIndex={2} />
</div>
);
};

View File

@ -1,6 +1,4 @@
.settings {
padding: 24px 48px;
header {
display: flex;
align-items: center;

View File

@ -6,6 +6,14 @@
> main {
flex: 1;
background-color: #141414;
> div {
padding: 16px;
@media (min-width: 1000px) {
padding: 24px 48px;
}
}
}
}

View File

@ -13,3 +13,5 @@ export const EXPLORER_URL = "https://explorer.testnet.codex.storage/tx";
export const GB = 1_073_741_824;
export const TB = 1_099_511_627_776;
export const MOBILE_MAX_WIDTH = 999

6
src/utils/network.ts Normal file
View File

@ -0,0 +1,6 @@
export const Network = {
getIp(address: string) {
const [ip] = address.split(":")
return ip
}
}

View File

@ -15,6 +15,7 @@ export default defineConfig({
output: {
manualChunks: {
"@sentry/react": ["@sentry/react"],
"emoji-picker-react": ["emoji-picker-react"]
}
},
onwarn(warning, defaultHandler) {
@ -24,11 +25,6 @@ export default defineConfig({
defaultHandler(warning);
},
output: {
manualChunks: {
"emoji-picker-react": ["emoji-picker-react"]
}
}
},
},