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

View File

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

View File

@ -4,25 +4,37 @@ import { classnames } from "../../utils/classnames";
import { useNetwork } from "../../network/useNetwork"; import { useNetwork } from "../../network/useNetwork";
import { NetworkFlashIcon } from "../NetworkFlashIcon/NetworkFlashIcon"; import { NetworkFlashIcon } from "../NetworkFlashIcon/NetworkFlashIcon";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useEffect } from "react"; import { ReactElement, useEffect } from "react";
import { useCodexConnection } from "../../hooks/useCodexConnection"; import { useCodexConnection } from "../../hooks/useCodexConnection";
import { NodesIcon } from "../Menu/NodesIcon"; import { NodesIcon } from "../Menu/NodesIcon";
import { usePersistence } from "../../hooks/usePersistence"; import { usePersistence } from "../../hooks/usePersistence";
import { useNavigate, useRouterState } from "@tanstack/react-router";
import { PeersIcon } from "../Menu/PeersIcon";
import { SettingsIcon } from "../Menu/SettingsIcon";
type Props = { type Props = {
/** onIconClick: () => void;
* Event triggered when the menu is expanding, after a click on the
* menu button.
*/
onExpand: () => void;
}; };
export function AppBar(_: Props) { const icons: Record<string, ReactElement> = {
console.debug(_); 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 online = useNetwork();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const codex = useCodexConnection(); const codex = useCodexConnection();
const persistence = usePersistence(codex.enabled); const persistence = usePersistence(codex.enabled);
const router = useRouterState();
const navigate = useNavigate({ from: router.location.pathname });
useEffect(() => { useEffect(() => {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
@ -31,39 +43,45 @@ export function AppBar(_: Props) {
}); });
}, [queryClient, codex.enabled]); }, [queryClient, codex.enabled]);
const onNodeClick = () => navigate({ to: "/dashboard/settings" });
const offline = !online || !codex.enabled; const offline = !online || !codex.enabled;
const title =
router.location.pathname.split("/")[2] ||
router.location.pathname.split("/")[1];
return ( return (
<div <>
className={classnames( <div
["app-bar"], className={classnames(
["app-bar--offline", offline], ["app-bar"],
["app-bar--no-persistence", !persistence.enabled] ["app-bar--offline", offline],
)}> ["app-bar--no-persistence", !persistence.enabled]
<div className="row gap"> )}>
{/* <a className="appBar-burger" onClick={onExpand}> <div className="row gap">
{/* <a className="appBar-burger" onClick={onExpand}>
<Menu size={"1.25rem"} /> <Menu size={"1.25rem"} />
</a> */} </a> */}
<span> <span onClick={onIconClick}>{icons[title]}</span>
<DashboardIcon />
</span>
<div> <div>
<h1>Dashboard</h1> <h1>{title}</h1>
<h2>Get Overview of your Codex Vault</h2> <h2>{descriptions[title]}</h2>
</div>
</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> </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; justify-content: space-between;
border-bottom: 1px solid var(--codex-border-color); border-bottom: 1px solid var(--codex-border-color);
display: flex; display: flex;
padding-right: 48px; padding: 16px;
padding-left: 48px;
padding-top: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #96969633; border-bottom: 1px solid #96969633;
box-sizing: border-box; box-sizing: border-box;
background-color: #1c1c1c; background-color: #1c1c1c;
@ -15,6 +12,12 @@
top: 0; top: 0;
z-index: 1; z-index: 1;
@media (min-width: 1000px) {
& {
padding: 20px 48px;
}
}
&:not(.app-bar--offline):not(.app-bar--no-persistence) { &:not(.app-bar--offline):not(.app-bar--no-persistence) {
border-right-color: #6ccc93; border-right-color: #6ccc93;
} }
@ -33,6 +36,7 @@
sans-serif; sans-serif;
letter-spacing: -0.015em; letter-spacing: -0.015em;
color: white; color: white;
text-transform: capitalize;
} }
h2 { h2 {
@ -53,6 +57,13 @@
justify-content: center; justify-content: center;
border: 1px solid #353639; border: 1px solid #353639;
border-radius: 50%; border-radius: 50%;
color: #969696;
}
@media (max-width: 999px) {
& {
cursor: pointer;
}
} }
} }
@ -69,6 +80,16 @@
sans-serif; sans-serif;
letter-spacing: -0.006em; letter-spacing: -0.006em;
color: #8d8d8d; color: #8d8d8d;
@media (max-width: 999px) {
& {
display: none;
}
}
}
div:last-child {
cursor: pointer;
} }
} }
} }

View File

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

View File

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

View File

@ -1,85 +1,124 @@
.address { .health-checks {
display: flex; .address {
align-items: center; display: flex;
position: relative;
gap: 16px;
> div {
position: relative; position: relative;
} gap: 16px;
flex-direction: column;
align-items: flex-start;
svg { @media (min-width: 1000px) {
position: absolute; & {
top: 68px; flex-direction: row;
bottom: 0; align-items: center;
right: 18px; }
} }
.refresh { > div {
position: relative; position: relative;
top: 24px;
cursor: pointer; @media (max-width: 999px) {
&:not(.refresh) {
width: 100%;
}
}
}
svg { svg {
position: initial; position: absolute;
top: 68px;
bottom: 0;
right: 18px;
} }
&.address--fetching .refresh svg { .refresh {
animation: rotate 2s linear infinite; 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"] { p {
width: 150px; 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, ul {
input[type="number"]::-webkit-inner-spin-button { margin-bottom: 32px;
-webkit-appearance: none;
margin: 0;
}
input[type="number"] { li {
-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 {
display: flex; display: flex;
align-items: center; align-items: center;
width: 20px; padding: 16px 0;
height: 20px; gap: 16px;
justify-content: center; 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 { useQueryClient } from "@tanstack/react-query";
import { useEffect, useState, ClipboardEvent } from "react"; import { useEffect, useState } from "react";
import { useDebug } from "../../hooks/useDebug"; import { useDebug } from "../../hooks/useDebug";
import { usePersistence } from "../../hooks/usePersistence"; import { usePersistence } from "../../hooks/usePersistence";
import { usePortForwarding } from "../../hooks/usePortForwarding"; import { usePortForwarding } from "../../hooks/usePortForwarding";
@ -11,7 +11,6 @@ import { HealthCheckIcon } from "./HealthCheckIcon";
import { Input } from "@codex-storage/marketplace-ui-components"; import { Input } from "@codex-storage/marketplace-ui-components";
import { classnames } from "../../utils/classnames"; import { classnames } from "../../utils/classnames";
import { DebugUtils } from "../../utils/debug"; import { DebugUtils } from "../../utils/debug";
import { Strings } from "../../utils/strings";
import { RefreshIcon } from "../RefreshIcon/RefreshIcon"; import { RefreshIcon } from "../RefreshIcon/RefreshIcon";
import "./HealthChecks.css"; import "./HealthChecks.css";
@ -27,8 +26,14 @@ export function HealthChecks({ online, onStepValid }: Props) {
const codex = useDebug(throwOnError); const codex = useDebug(throwOnError);
const portForwarding = usePortForwarding(codex.data); const portForwarding = usePortForwarding(codex.data);
const persistence = usePersistence(codex.isSuccess); const persistence = usePersistence(codex.isSuccess);
const [isInvalid, setIsInvalid] = useState(false); const [isAddressInvalid, setIsAddressInvalid] = useState(false);
const [url, setUrl] = useState(CodexSdk.url); 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(); const queryClient = useQueryClient();
useEffect(() => { useEffect(() => {
@ -46,48 +51,38 @@ export function HealthChecks({ online, onStepValid }: Props) {
]); ]);
const onAddressChange = (e: React.FormEvent<HTMLInputElement>) => { const onAddressChange = (e: React.FormEvent<HTMLInputElement>) => {
const [, port] = Strings.splitURLAndPort(url);
const element = e.currentTarget; const element = e.currentTarget;
const value = e.currentTarget.value; const parts = e.currentTarget.value.split(":");
if ( setIsAddressInvalid(!element.checkValidity());
value.startsWith("http://") === false &&
value.startsWith("https://") === false
) {
setIsInvalid(true);
return;
}
setIsInvalid(!element.checkValidity()); if (parts.length > 2) {
setUrl(value + ":" + port); const [protocol, addr, port] = parts;
}; setAddress(protocol + ":" + addr);
const onPaste = (e: ClipboardEvent) => { const p = parseInt(port, 10);
const text = e.clipboardData?.getData("text") || ""; if (!isNaN(p)) {
setPort(p);
try { }
new URL(text); } else {
setUrl(text); setAddress(parts.join(":"));
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (_) {
// Nothing to do here
} }
}; };
const onPortChange = (e: React.FormEvent<HTMLInputElement>) => { const onPortChange = (e: React.FormEvent<HTMLInputElement>) => {
const [address] = Strings.splitURLAndPort(url);
const element = e.currentTarget; const element = e.currentTarget;
const value = element.value; const value = element.value;
setUrl(address + ":" + value); setIsPortInvalid(!element.checkValidity());
setPort(parseInt(value, 10));
}; };
const onSave = () => { const onSave = () => {
if (isInvalid === true) { if (isAddressInvalid || isPortInvalid) {
return; return;
} }
CodexSdk.updateURL(url) CodexSdk.updateURL(address + ":" + port)
.then(() => queryClient.invalidateQueries()) .then(() => queryClient.invalidateQueries())
.then(() => codex.refetch()); .then(() => codex.refetch());
}; };
@ -100,10 +95,9 @@ export function HealthChecks({ online, onStepValid }: Props) {
forwardingPortValue = port.data; forwardingPortValue = port.data;
} }
} }
const [address, port] = Strings.splitURLAndPort(url);
return ( return (
<> <div className="health-checks">
<div <div
className={classnames( className={classnames(
["address"], ["address"],
@ -111,15 +105,15 @@ export function HealthChecks({ online, onStepValid }: Props) {
)}> )}>
<div> <div>
<Input <Input
onPaste={onPaste}
id="url" id="url"
type="url" type="url"
label="Address" label="Address"
isInvalid={isInvalid} required
isInvalid={isAddressInvalid}
onChange={onAddressChange} onChange={onAddressChange}
value={address} value={address}
placeholder="127.0.0.1"></Input> placeholder="127.0.0.1"></Input>
{isInvalid ? ( {isAddressInvalid ? (
<ErrorCircleIcon /> <ErrorCircleIcon />
) : ( ) : (
<SuccessCheckIcon variant="default" /> <SuccessCheckIcon variant="default" />
@ -133,6 +127,7 @@ export function HealthChecks({ online, onStepValid }: Props) {
type="number" type="number"
onChange={onPortChange} onChange={onPortChange}
value={port} value={port}
isInvalid={isPortInvalid}
placeholder="8080"></Input> placeholder="8080"></Input>
<SuccessCheckIcon variant="default"></SuccessCheckIcon> <SuccessCheckIcon variant="default"></SuccessCheckIcon>
</div> </div>
@ -142,11 +137,14 @@ export function HealthChecks({ online, onStepValid }: Props) {
</div> </div>
</div> </div>
<ul className="helper"> <p>
<li>Port forwarding should be default {forwardingPortValue}.</li> <li>
</ul> Port forwarding should be {forwardingPortValue} for TCP and 8090 by
default for UDP.
</li>
</p>
<ul className="health-checks"> <ul>
<li> <li>
<span> <span>
<HealthCheckIcon /> <HealthCheckIcon />
@ -194,6 +192,6 @@ export function HealthChecks({ online, onStepValid }: Props) {
Marketplace Marketplace
</li> </li>
</ul> </ul>
</> </div>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,16 @@
.user-info { .user-info {
display: flex; display: flex;
align-items: center; flex-direction: column;
gap: 32px; align-items: flex-start;
gap: 16px;
@media (min-width: 1000px) {
& {
flex-direction: row;
gap: 32px;
align-items: center;
}
}
.emoji { .emoji {
position: relative; position: relative;
@ -9,8 +18,14 @@
aside { aside {
position: absolute; position: absolute;
top: -140px; top: -140px;
left: 116px; left: 0px;
z-index: 1; z-index: 1;
@media (min-width: 1000px) {
& {
left: 116px;
}
}
} }
.input input { .input input {
@ -19,4 +34,14 @@
cursor: pointer; 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"> <div className="emoji">
{areEmojiVisible && ( {areEmojiVisible && (
<EmojiPicker <EmojiPicker
width={"auto"}
emojiStyle={EmojiStyle.NATIVE} emojiStyle={EmojiStyle.NATIVE}
theme={Theme.DARK} theme={Theme.DARK}
lazyLoadEmojis={true} lazyLoadEmojis={true}

View File

@ -40,6 +40,7 @@ if (import.meta.env.PROD && !import.meta.env.CI) {
// Create a new router instance // Create a new router instance
const router = createRouter({ const router = createRouter({
routeTree, routeTree,
defaultPreload: "viewport",
defaultNotFoundComponent: () => { defaultNotFoundComponent: () => {
return ( return (
<Failure <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 DashboardSettingsImport } from './routes/dashboard/settings'
import { Route as DashboardRequestsImport } from './routes/dashboard/requests' import { Route as DashboardRequestsImport } from './routes/dashboard/requests'
import { Route as DashboardPurchasesImport } from './routes/dashboard/purchases' 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 DashboardNodesImport } from './routes/dashboard/nodes'
import { Route as DashboardLogsImport } from './routes/dashboard/logs' import { Route as DashboardLogsImport } from './routes/dashboard/logs'
import { Route as DashboardHelpImport } from './routes/dashboard/help' 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 DashboardFavoritesImport } from './routes/dashboard/favorites'
import { Route as DashboardDisclaimerImport } from './routes/dashboard/disclaimer' import { Route as DashboardDisclaimerImport } from './routes/dashboard/disclaimer'
import { Route as DashboardDeviceImport } from './routes/dashboard/device' 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 DashboardAnalyticsImport } from './routes/dashboard/analytics'
import { Route as DashboardAboutImport } from './routes/dashboard/about' import { Route as DashboardAboutImport } from './routes/dashboard/about'
@ -73,11 +71,6 @@ const DashboardIndexRoute = DashboardIndexImport.update({
getParentRoute: () => DashboardRoute, getParentRoute: () => DashboardRoute,
} as any) } as any)
const DashboardWalletRoute = DashboardWalletImport.update({
id: '/wallet',
path: '/wallet',
getParentRoute: () => DashboardRoute,
} as any)
const DashboardPeersLazyRoute = DashboardPeersLazyImport.update({ const DashboardPeersLazyRoute = DashboardPeersLazyImport.update({
id: '/peers', id: '/peers',
path: '/peers', path: '/peers',
@ -95,6 +88,12 @@ const DashboardAvailabilitiesLazyRoute =
import('./routes/dashboard/availabilities.lazy').then((d) => d.Route), 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({ const DashboardSettingsRoute = DashboardSettingsImport.update({
id: '/settings', id: '/settings',
path: '/settings', path: '/settings',
@ -113,12 +112,6 @@ const DashboardPurchasesRoute = DashboardPurchasesImport.update({
getParentRoute: () => DashboardRoute, getParentRoute: () => DashboardRoute,
} as any) } as any)
const DashboardPeersRoute = DashboardPeersImport.update({
id: '/peers',
path: '/peers',
getParentRoute: () => DashboardRoute,
} as any)
const DashboardNodesRoute = DashboardNodesImport.update({ const DashboardNodesRoute = DashboardNodesImport.update({
id: '/nodes', id: '/nodes',
path: '/nodes', path: '/nodes',
@ -161,12 +154,6 @@ const DashboardDeviceRoute = DashboardDeviceImport.update({
getParentRoute: () => DashboardRoute, getParentRoute: () => DashboardRoute,
} as any) } as any)
const DashboardAvailabilitiesRoute = DashboardAvailabilitiesImport.update({
id: '/availabilities',
path: '/availabilities',
getParentRoute: () => DashboardRoute,
} as any)
const DashboardAnalyticsRoute = DashboardAnalyticsImport.update({ const DashboardAnalyticsRoute = DashboardAnalyticsImport.update({
id: '/analytics', id: '/analytics',
path: '/analytics', path: '/analytics',
@ -225,13 +212,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardAnalyticsImport preLoaderRoute: typeof DashboardAnalyticsImport
parentRoute: typeof DashboardImport parentRoute: typeof DashboardImport
} }
'/dashboard/availabilities': {
id: '/dashboard/availabilities'
path: '/availabilities'
fullPath: '/dashboard/availabilities'
preLoaderRoute: typeof DashboardAvailabilitiesImport
parentRoute: typeof DashboardImport
}
'/dashboard/device': { '/dashboard/device': {
id: '/dashboard/device' id: '/dashboard/device'
path: '/device' path: '/device'
@ -281,13 +261,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof DashboardNodesImport preLoaderRoute: typeof DashboardNodesImport
parentRoute: typeof DashboardImport parentRoute: typeof DashboardImport
} }
'/dashboard/peers': {
id: '/dashboard/peers'
path: '/peers'
fullPath: '/dashboard/peers'
preLoaderRoute: typeof DashboardPeersImport
parentRoute: typeof DashboardImport
}
'/dashboard/purchases': { '/dashboard/purchases': {
id: '/dashboard/purchases' id: '/dashboard/purchases'
path: '/purchases' path: '/purchases'
@ -314,178 +287,154 @@ declare module '@tanstack/react-router' {
path: '/wallet' path: '/wallet'
fullPath: '/dashboard/wallet' fullPath: '/dashboard/wallet'
preLoaderRoute: typeof DashboardWalletImport preLoaderRoute: typeof DashboardWalletImport
'/dashboard/availabilities': { parentRoute: typeof DashboardImport
id: '/dashboard/availabilities' }
path: '/availabilities' '/dashboard/availabilities': {
fullPath: '/dashboard/availabilities' id: '/dashboard/availabilities'
preLoaderRoute: typeof DashboardAvailabilitiesLazyImport path: '/availabilities'
parentRoute: typeof DashboardImport fullPath: '/dashboard/availabilities'
} preLoaderRoute: typeof DashboardAvailabilitiesLazyImport
'/dashboard/peers': { parentRoute: typeof DashboardImport
id: '/dashboard/peers' }
path: '/peers' '/dashboard/peers': {
fullPath: '/dashboard/peers' id: '/dashboard/peers'
preLoaderRoute: typeof DashboardPeersLazyImport path: '/peers'
parentRoute: typeof DashboardImport fullPath: '/dashboard/peers'
} preLoaderRoute: typeof DashboardPeersLazyImport
'/dashboard/': { parentRoute: typeof DashboardImport
id: '/dashboard/' }
path: '/' '/dashboard/': {
fullPath: '/dashboard/' id: '/dashboard/'
preLoaderRoute: typeof DashboardIndexImport path: '/'
parentRoute: typeof DashboardImport fullPath: '/dashboard/'
} preLoaderRoute: typeof DashboardIndexImport
parentRoute: typeof DashboardImport
} }
} }
}
// Create and export the route tree // Create and export the route tree
interface DashboardRouteChildren { interface DashboardRouteChildren {
DashboardAboutRoute: typeof DashboardAboutRoute DashboardAboutRoute: typeof DashboardAboutRoute
DashboardAnalyticsRoute: typeof DashboardAnalyticsRoute DashboardAnalyticsRoute: typeof DashboardAnalyticsRoute
DashboardAvailabilitiesRoute: typeof DashboardAvailabilitiesRoute DashboardDeviceRoute: typeof DashboardDeviceRoute
DashboardDeviceRoute: typeof DashboardDeviceRoute DashboardDisclaimerRoute: typeof DashboardDisclaimerRoute
DashboardDisclaimerRoute: typeof DashboardDisclaimerRoute DashboardFavoritesRoute: typeof DashboardFavoritesRoute
DashboardFavoritesRoute: typeof DashboardFavoritesRoute DashboardFilesRoute: typeof DashboardFilesRoute
DashboardFilesRoute: typeof DashboardFilesRoute DashboardHelpRoute: typeof DashboardHelpRoute
DashboardHelpRoute: typeof DashboardHelpRoute DashboardLogsRoute: typeof DashboardLogsRoute
DashboardLogsRoute: typeof DashboardLogsRoute DashboardNodesRoute: typeof DashboardNodesRoute
DashboardNodesRoute: typeof DashboardNodesRoute DashboardPurchasesRoute: typeof DashboardPurchasesRoute
DashboardPeersRoute: typeof DashboardPeersRoute DashboardRequestsRoute: typeof DashboardRequestsRoute
DashboardPurchasesRoute: typeof DashboardPurchasesRoute DashboardSettingsRoute: typeof DashboardSettingsRoute
DashboardRequestsRoute: typeof DashboardRequestsRoute DashboardWalletRoute: typeof DashboardWalletRoute
DashboardSettingsRoute: typeof DashboardSettingsRoute DashboardAvailabilitiesLazyRoute: typeof DashboardAvailabilitiesLazyRoute
DashboardWalletRoute: typeof DashboardWalletRoute DashboardPeersLazyRoute: typeof DashboardPeersLazyRoute
DashboardPurchasesRoute: typeof DashboardPurchasesRoute DashboardIndexRoute: typeof DashboardIndexRoute
DashboardRequestsRoute: typeof DashboardRequestsRoute }
DashboardSettingsRoute: typeof DashboardSettingsRoute
DashboardAvailabilitiesLazyRoute: typeof DashboardAvailabilitiesLazyRoute
DashboardPeersLazyRoute: typeof DashboardPeersLazyRoute
DashboardIndexRoute: typeof DashboardIndexRoute
}
const DashboardRouteChildren: DashboardRouteChildren = { const DashboardRouteChildren: DashboardRouteChildren = {
DashboardAboutRoute: DashboardAboutRoute, DashboardAboutRoute: DashboardAboutRoute,
DashboardAnalyticsRoute: DashboardAnalyticsRoute, DashboardAnalyticsRoute: DashboardAnalyticsRoute,
DashboardAvailabilitiesRoute: DashboardAvailabilitiesRoute, DashboardDeviceRoute: DashboardDeviceRoute,
DashboardDeviceRoute: DashboardDeviceRoute, DashboardDisclaimerRoute: DashboardDisclaimerRoute,
DashboardDisclaimerRoute: DashboardDisclaimerRoute, DashboardFavoritesRoute: DashboardFavoritesRoute,
DashboardFavoritesRoute: DashboardFavoritesRoute, DashboardFilesRoute: DashboardFilesRoute,
DashboardFilesRoute: DashboardFilesRoute, DashboardHelpRoute: DashboardHelpRoute,
DashboardHelpRoute: DashboardHelpRoute, DashboardLogsRoute: DashboardLogsRoute,
DashboardLogsRoute: DashboardLogsRoute, DashboardNodesRoute: DashboardNodesRoute,
DashboardNodesRoute: DashboardNodesRoute, DashboardPurchasesRoute: DashboardPurchasesRoute,
DashboardPeersRoute: DashboardPeersRoute, DashboardRequestsRoute: DashboardRequestsRoute,
DashboardPurchasesRoute: DashboardPurchasesRoute, DashboardSettingsRoute: DashboardSettingsRoute,
DashboardRequestsRoute: DashboardRequestsRoute, DashboardWalletRoute: DashboardWalletRoute,
DashboardSettingsRoute: DashboardSettingsRoute, DashboardAvailabilitiesLazyRoute: DashboardAvailabilitiesLazyRoute,
DashboardWalletRoute: DashboardWalletRoute, DashboardPeersLazyRoute: DashboardPeersLazyRoute,
DashboardPurchasesRoute: DashboardPurchasesRoute, DashboardIndexRoute: DashboardIndexRoute,
DashboardRequestsRoute: DashboardRequestsRoute, }
DashboardSettingsRoute: DashboardSettingsRoute,
DashboardAvailabilitiesLazyRoute: DashboardAvailabilitiesLazyRoute,
DashboardPeersLazyRoute: DashboardPeersLazyRoute,
DashboardIndexRoute: DashboardIndexRoute,
}
const DashboardRouteWithChildren = DashboardRoute._addFileChildren( const DashboardRouteWithChildren = DashboardRoute._addFileChildren(
DashboardRouteChildren, DashboardRouteChildren,
) )
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/dashboard': typeof DashboardRouteWithChildren '/dashboard': typeof DashboardRouteWithChildren
'/onboarding-checks': typeof OnboardingChecksRoute '/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute '/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute '/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute '/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesRoute '/dashboard/device': typeof DashboardDeviceRoute
'/dashboard/device': typeof DashboardDeviceRoute '/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute '/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute '/dashboard/files': typeof DashboardFilesRoute
'/dashboard/files': typeof DashboardFilesRoute '/dashboard/help': typeof DashboardHelpRoute
'/dashboard/help': typeof DashboardHelpRoute '/dashboard/logs': typeof DashboardLogsRoute
'/dashboard/logs': typeof DashboardLogsRoute '/dashboard/nodes': typeof DashboardNodesRoute
'/dashboard/nodes': typeof DashboardNodesRoute '/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/peers': typeof DashboardPeersRoute '/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute '/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/requests': typeof DashboardRequestsRoute '/dashboard/wallet': typeof DashboardWalletRoute
'/dashboard/settings': typeof DashboardSettingsRoute '/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/wallet': typeof DashboardWalletRoute '/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute '/dashboard/': typeof DashboardIndexRoute
'/dashboard/requests': typeof DashboardRequestsRoute }
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard/': typeof DashboardIndexRoute
}
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/onboarding-checks': typeof OnboardingChecksRoute '/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute '/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute '/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute '/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesRoute '/dashboard/device': typeof DashboardDeviceRoute
'/dashboard/device': typeof DashboardDeviceRoute '/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute '/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute '/dashboard/files': typeof DashboardFilesRoute
'/dashboard/files': typeof DashboardFilesRoute '/dashboard/help': typeof DashboardHelpRoute
'/dashboard/help': typeof DashboardHelpRoute '/dashboard/logs': typeof DashboardLogsRoute
'/dashboard/logs': typeof DashboardLogsRoute '/dashboard/nodes': typeof DashboardNodesRoute
'/dashboard/nodes': typeof DashboardNodesRoute '/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/peers': typeof DashboardPeersRoute '/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute '/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/requests': typeof DashboardRequestsRoute '/dashboard/wallet': typeof DashboardWalletRoute
'/dashboard/settings': typeof DashboardSettingsRoute '/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/wallet': typeof DashboardWalletRoute '/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute '/dashboard': typeof DashboardIndexRoute
'/dashboard/requests': typeof DashboardRequestsRoute }
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard': typeof DashboardIndexRoute
}
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRoute __root__: typeof rootRoute
'/': typeof IndexRoute '/': typeof IndexRoute
'/dashboard': typeof DashboardRouteWithChildren '/dashboard': typeof DashboardRouteWithChildren
'/onboarding-checks': typeof OnboardingChecksRoute '/onboarding-checks': typeof OnboardingChecksRoute
'/onboarding-name': typeof OnboardingNameRoute '/onboarding-name': typeof OnboardingNameRoute
'/dashboard/about': typeof DashboardAboutRoute '/dashboard/about': typeof DashboardAboutRoute
'/dashboard/analytics': typeof DashboardAnalyticsRoute '/dashboard/analytics': typeof DashboardAnalyticsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesRoute '/dashboard/device': typeof DashboardDeviceRoute
'/dashboard/device': typeof DashboardDeviceRoute '/dashboard/disclaimer': typeof DashboardDisclaimerRoute
'/dashboard/disclaimer': typeof DashboardDisclaimerRoute '/dashboard/favorites': typeof DashboardFavoritesRoute
'/dashboard/favorites': typeof DashboardFavoritesRoute '/dashboard/files': typeof DashboardFilesRoute
'/dashboard/files': typeof DashboardFilesRoute '/dashboard/help': typeof DashboardHelpRoute
'/dashboard/help': typeof DashboardHelpRoute '/dashboard/logs': typeof DashboardLogsRoute
'/dashboard/logs': typeof DashboardLogsRoute '/dashboard/nodes': typeof DashboardNodesRoute
'/dashboard/nodes': typeof DashboardNodesRoute '/dashboard/purchases': typeof DashboardPurchasesRoute
'/dashboard/peers': typeof DashboardPeersRoute '/dashboard/requests': typeof DashboardRequestsRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute '/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/requests': typeof DashboardRequestsRoute '/dashboard/wallet': typeof DashboardWalletRoute
'/dashboard/settings': typeof DashboardSettingsRoute '/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/wallet': typeof DashboardWalletRoute '/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard/purchases': typeof DashboardPurchasesRoute '/dashboard/': typeof DashboardIndexRoute
'/dashboard/requests': typeof DashboardRequestsRoute }
'/dashboard/settings': typeof DashboardSettingsRoute
'/dashboard/availabilities': typeof DashboardAvailabilitiesLazyRoute
'/dashboard/peers': typeof DashboardPeersLazyRoute
'/dashboard/': typeof DashboardIndexRoute
}
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: fullPaths:
| '/' | '/'
| '/dashboard' | '/dashboard'
| '/onboarding-checks' | '/onboarding-checks'
| '/onboarding-name' | '/onboarding-name'
| '/dashboard/about' | '/dashboard/about'
| '/dashboard/analytics' | '/dashboard/analytics'
| '/dashboard/availabilities'
| '/dashboard/device' | '/dashboard/device'
| '/dashboard/disclaimer' | '/dashboard/disclaimer'
| '/dashboard/favorites' | '/dashboard/favorites'
@ -493,25 +442,20 @@ declare module '@tanstack/react-router' {
| '/dashboard/help' | '/dashboard/help'
| '/dashboard/logs' | '/dashboard/logs'
| '/dashboard/nodes' | '/dashboard/nodes'
| '/dashboard/peers'
| '/dashboard/purchases' | '/dashboard/purchases'
| '/dashboard/requests' | '/dashboard/requests'
| '/dashboard/settings' | '/dashboard/settings'
| '/dashboard/wallet' | '/dashboard/wallet'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
| '/dashboard/availabilities' | '/dashboard/availabilities'
| '/dashboard/peers' | '/dashboard/peers'
| '/dashboard/' | '/dashboard/'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: to:
| '/' | '/'
| '/onboarding-checks' | '/onboarding-checks'
| '/onboarding-name' | '/onboarding-name'
| '/dashboard/about' | '/dashboard/about'
| '/dashboard/analytics' | '/dashboard/analytics'
| '/dashboard/availabilities'
| '/dashboard/device' | '/dashboard/device'
| '/dashboard/disclaimer' | '/dashboard/disclaimer'
| '/dashboard/favorites' | '/dashboard/favorites'
@ -519,18 +463,14 @@ declare module '@tanstack/react-router' {
| '/dashboard/help' | '/dashboard/help'
| '/dashboard/logs' | '/dashboard/logs'
| '/dashboard/nodes' | '/dashboard/nodes'
| '/dashboard/peers'
| '/dashboard/purchases' | '/dashboard/purchases'
| '/dashboard/requests' | '/dashboard/requests'
| '/dashboard/settings' | '/dashboard/settings'
| '/dashboard/wallet' | '/dashboard/wallet'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
| '/dashboard/availabilities' | '/dashboard/availabilities'
| '/dashboard/peers' | '/dashboard/peers'
| '/dashboard' | '/dashboard'
id: id:
| '__root__' | '__root__'
| '/' | '/'
| '/dashboard' | '/dashboard'
@ -538,7 +478,6 @@ declare module '@tanstack/react-router' {
| '/onboarding-name' | '/onboarding-name'
| '/dashboard/about' | '/dashboard/about'
| '/dashboard/analytics' | '/dashboard/analytics'
| '/dashboard/availabilities'
| '/dashboard/device' | '/dashboard/device'
| '/dashboard/disclaimer' | '/dashboard/disclaimer'
| '/dashboard/favorites' | '/dashboard/favorites'
@ -546,37 +485,33 @@ declare module '@tanstack/react-router' {
| '/dashboard/help' | '/dashboard/help'
| '/dashboard/logs' | '/dashboard/logs'
| '/dashboard/nodes' | '/dashboard/nodes'
| '/dashboard/peers'
| '/dashboard/purchases' | '/dashboard/purchases'
| '/dashboard/requests' | '/dashboard/requests'
| '/dashboard/settings' | '/dashboard/settings'
| '/dashboard/wallet' | '/dashboard/wallet'
| '/dashboard/purchases'
| '/dashboard/requests'
| '/dashboard/settings'
| '/dashboard/availabilities' | '/dashboard/availabilities'
| '/dashboard/peers' | '/dashboard/peers'
| '/dashboard/' | '/dashboard/'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
DashboardRoute: typeof DashboardRouteWithChildren DashboardRoute: typeof DashboardRouteWithChildren
OnboardingChecksRoute: typeof OnboardingChecksRoute OnboardingChecksRoute: typeof OnboardingChecksRoute
OnboardingNameRoute: typeof OnboardingNameRoute OnboardingNameRoute: typeof OnboardingNameRoute
} }
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
DashboardRoute: DashboardRouteWithChildren, DashboardRoute: DashboardRouteWithChildren,
OnboardingChecksRoute: OnboardingChecksRoute, OnboardingChecksRoute: OnboardingChecksRoute,
OnboardingNameRoute: OnboardingNameRoute, OnboardingNameRoute: OnboardingNameRoute,
} }
export const routeTree = rootRoute export const routeTree = rootRoute
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>() ._addFileTypes<FileRouteTypes>()
/* prettier-ignore-end */ /* prettier-ignore-end */
@ -600,7 +535,6 @@ declare module '@tanstack/react-router' {
"children": [ "children": [
"/dashboard/about", "/dashboard/about",
"/dashboard/analytics", "/dashboard/analytics",
"/dashboard/availabilities",
"/dashboard/device", "/dashboard/device",
"/dashboard/disclaimer", "/dashboard/disclaimer",
"/dashboard/favorites", "/dashboard/favorites",
@ -608,14 +542,10 @@ declare module '@tanstack/react-router' {
"/dashboard/help", "/dashboard/help",
"/dashboard/logs", "/dashboard/logs",
"/dashboard/nodes", "/dashboard/nodes",
"/dashboard/peers",
"/dashboard/purchases", "/dashboard/purchases",
"/dashboard/requests", "/dashboard/requests",
"/dashboard/settings", "/dashboard/settings",
"/dashboard/wallet", "/dashboard/wallet",
"/dashboard/purchases",
"/dashboard/requests",
"/dashboard/settings",
"/dashboard/availabilities", "/dashboard/availabilities",
"/dashboard/peers", "/dashboard/peers",
"/dashboard/" "/dashboard/"
@ -635,10 +565,6 @@ declare module '@tanstack/react-router' {
"filePath": "dashboard/analytics.tsx", "filePath": "dashboard/analytics.tsx",
"parent": "/dashboard" "parent": "/dashboard"
}, },
"/dashboard/availabilities": {
"filePath": "dashboard/availabilities.tsx",
"parent": "/dashboard"
},
"/dashboard/device": { "/dashboard/device": {
"filePath": "dashboard/device.tsx", "filePath": "dashboard/device.tsx",
"parent": "/dashboard" "parent": "/dashboard"
@ -667,10 +593,6 @@ declare module '@tanstack/react-router' {
"filePath": "dashboard/nodes.tsx", "filePath": "dashboard/nodes.tsx",
"parent": "/dashboard" "parent": "/dashboard"
}, },
"/dashboard/peers": {
"filePath": "dashboard/peers.tsx",
"parent": "/dashboard"
},
"/dashboard/purchases": { "/dashboard/purchases": {
"filePath": "dashboard/purchases.tsx", "filePath": "dashboard/purchases.tsx",
"parent": "/dashboard" "parent": "/dashboard"
@ -685,6 +607,8 @@ declare module '@tanstack/react-router' {
}, },
"/dashboard/wallet": { "/dashboard/wallet": {
"filePath": "dashboard/wallet.tsx", "filePath": "dashboard/wallet.tsx",
"parent": "/dashboard"
},
"/dashboard/availabilities": { "/dashboard/availabilities": {
"filePath": "dashboard/availabilities.lazy.tsx", "filePath": "dashboard/availabilities.lazy.tsx",
"parent": "/dashboard" "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 "./layout.css";
import { Menu } from "../components/Menu/Menu"; import { Menu } from "../components/Menu/Menu";
import { useState } from "react"; import { useEffect, useState } from "react";
import { AppBar } from "../components/AppBar/AppBar"; import { AppBar } from "../components/AppBar/AppBar";
import { Backdrop } from "@codex-storage/marketplace-ui-components";
const Layout = () => { 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 ( return (
<div className="layout"> <div className="layout">
<Menu expanded={open} onClose={onClose}></Menu> <Menu isMobileMenuDisplayed={isMobileMenuDisplayed}></Menu>
<main> <main>
<AppBar onExpand={onExpand} /> <AppBar onIconClick={onIconClick} />
<Outlet /> <div>
<Outlet />
</div>
</main> </main>
<Backdrop onClose={onClose} open={hasMobileMenu}></Backdrop>
</div> </div>
); );
}; };

View File

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

View File

@ -2,34 +2,46 @@
max-width: 1320px; max-width: 1320px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
width: calc(100% - 2 * 24px);
padding: 24px;
> div { > div {
max-width: 1320px; max-width: 1320px;
} }
> div:first-child { > div:first-child {
width: calc(100% - 128px - 16px); width: calc(100% - 16px);
border: 1px solid #96969633; border: 1px solid #96969633;
padding: 16px 16px 16px 128px; padding: 16px;
border-radius: 16px; border-radius: 16px;
position: relative; position: relative;
height: 600px;
@media (min-width: 1000px) {
& {
width: calc(100% - 128px - 16px);
padding: 16px 16px 16px 128px;
height: 600px;
}
}
circle[fill="#d6ff79"] { circle[fill="#d6ff79"] {
stroke: var(--codex-color-primary); stroke: var(--codex-color-primary);
stroke-width: 0.6px; stroke-width: 0.6px;
fill: #141414; fill: #141414;
animation: circle-pulse 1.5s infinite; animation: circle-pulse 3s infinite;
} }
ul { ul {
list-style-type: none; display: none;
width: 71px;
position: absolute; @media (min-width: 1000px) {
left: 16px; & {
top: 16px; list-style-type: none;
width: 71px;
position: absolute;
left: 16px;
top: 16px;
display: inline-block;
}
}
li { li {
border-bottom: 1px solid #969696cc; border-bottom: 1px solid #969696cc;
@ -86,11 +98,33 @@
background-color: #232323; background-color: #232323;
border: 1px solid #96969633; border: 1px solid #96969633;
border-radius: 16px; border-radius: 16px;
position: absolute; max-width: 280px;
bottom: 16px;
left: 16px;
width: 280px;
padding: 16px; 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 { header {
display: flex; display: flex;
@ -116,10 +150,16 @@
width: 350px; width: 350px;
height: 175px; height: 175px;
overflow: hidden; overflow: hidden;
transform: scale(0.73); transform: scale(0.5);
margin: auto; margin: auto;
left: -32px; left: -32px;
@media (min-width: 1000px) {
& {
transform: scale(0.73);
}
}
*, *,
&::before { &::before {
box-sizing: border-box; box-sizing: border-box;
@ -188,7 +228,8 @@
} }
> div:nth-child(2) { > div:nth-child(2) {
width: calc(100% - 64px); margin-top: 32px;
width: 100%;
} }
table { 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 { getMapJSON } from "dotted-map";
import DottedMap from "dotted-map/without-countries"; import DottedMap from "dotted-map/without-countries";
import { Promises } from "../../utils/promises"; import { Promises } from "../../utils/promises";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { PeerCountryCell } from "../../components/Peers/PeerCountryCell"; import { PeerCountryCell } from "../../components/Peers/PeerCountryCell";
import { useCallback, useState } from "react"; import { useCallback, useRef, useState } from "react";
import { PeerPin } from "../../components/Peers/types"; import { PeerPin } from "../../components/Peers/types";
import "./peers.css"; import "./peers.css";
import { CodexSdk } from "../../sdk/codex"; import { CodexSdk } from "../../sdk/codex";
@ -14,6 +19,7 @@ import { PeersIcon } from "../../components/Menu/PeersIcon";
import { SuccessCheckIcon } from "../../components/SuccessCheckIcon/SuccessCheckIcon"; import { SuccessCheckIcon } from "../../components/SuccessCheckIcon/SuccessCheckIcon";
import { ErrorCircleIcon } from "../../components/ErrorCircleIcon/ErrorCircleIcon"; import { ErrorCircleIcon } from "../../components/ErrorCircleIcon/ErrorCircleIcon";
import { createLazyFileRoute } from "@tanstack/react-router"; import { createLazyFileRoute } from "@tanstack/react-router";
import { Network } from "../../utils/network";
// This function accepts the same arguments as DottedMap in the example above. // This function accepts the same arguments as DottedMap in the example above.
const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" }); const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" });
@ -22,8 +28,29 @@ type CustomCSSProperties = React.CSSProperties & {
"--codex-peers-percent": number; "--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 Peers = () => {
const ips = useRef<Record<string, string>>({});
const [pins, setPins] = useState<[PeerPin, number][]>([]); const [pins, setPins] = useState<[PeerPin, number][]>([]);
const [sortFn, setSortFn] = useState<SortFn | null>(() =>
sortByBooleanValue("desc")
);
const { data } = useQuery({ const { data } = useQuery({
queryFn: () => queryFn: () =>
CodexSdk.debug() CodexSdk.debug()
@ -47,13 +74,21 @@ const Peers = () => {
throwOnError: true, throwOnError: true,
}); });
const onPinAdd = useCallback((pin: PeerPin) => { const onPinAdd = useCallback(
setPins((val) => { ({
const [, quantity = 0] = countryIso,
val.find(([p]) => p.lat === pin.lat && p.lng == pin.lng) || []; ip,
return [...val, [pin, quantity + 1]]; ...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 // Its safe to re-create the map at each render, because of the
// pre-computation its super fast ⚡️ // pre-computation its super fast ⚡️
@ -74,67 +109,111 @@ const Peers = () => {
backgroundColor: "#141414", backgroundColor: "#141414",
}); });
const headers = ["Country", "PeerId", "Active"]; const onSortByCountry = (state: TabSortState) => {
if (!state) {
setSortFn(null);
return;
}
const rows = setSortFn(() => (a: Node, b: Node) => {
(data?.table?.nodes || []).map((node) => ( const countryA = ips.current[Network.getIp(a.address)] || "";
<Row const countryB = ips.current[Network.getIp(b.address)] || "";
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 = return state === "desc"
data?.table.nodes.reduce((acc, cur) => acc + (cur.seen ? 1 : 0), 0) || 0; ? 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 total = data?.table.nodes.length || 1;
const styles: CustomCSSProperties = { const styles: CustomCSSProperties = {
"--codex-peers-percent": (actives / total) * 180, "--codex-peers-percent": (actives / total) * 180,
}; };
const good = actives > 0;
return ( return (
<div className="peers"> <div className="peers">
<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>
<SuccessCheckIcon variant="primary"></SuccessCheckIcon>{" "}
<span>Peer connections in Good standing. </span>
</footer>
</div>
<div dangerouslySetInnerHTML={{ __html: svgMap }}></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> </div>
<Table headers={headers} rows={rows} /> <Table headers={headers} rows={rows} defaultSortIndex={2} />
</div> </div>
); );
}; };

View File

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

View File

@ -6,6 +6,14 @@
> main { > main {
flex: 1; flex: 1;
background-color: #141414; 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 GB = 1_073_741_824;
export const TB = 1_099_511_627_776; 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: { output: {
manualChunks: { manualChunks: {
"@sentry/react": ["@sentry/react"], "@sentry/react": ["@sentry/react"],
"emoji-picker-react": ["emoji-picker-react"]
} }
}, },
onwarn(warning, defaultHandler) { onwarn(warning, defaultHandler) {
@ -24,11 +25,6 @@ export default defineConfig({
defaultHandler(warning); defaultHandler(warning);
}, },
output: {
manualChunks: {
"emoji-picker-react": ["emoji-picker-react"]
}
}
}, },
}, },