UI Integration

This commit is contained in:
Arnaud 2024-11-04 12:41:18 +01:00
parent 06699b3aee
commit 334dbafe4d
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
53 changed files with 1296 additions and 447 deletions

8
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "0.0.7",
"license": "MIT",
"dependencies": {
"@codex-storage/marketplace-ui-components": "^0.0.31",
"@codex-storage/marketplace-ui-components": "^0.0.32",
"@codex-storage/sdk-js": "^0.0.15",
"@sentry/browser": "^8.32.0",
"@sentry/react": "^8.31.0",
@ -379,9 +379,9 @@
"peer": true
},
"node_modules/@codex-storage/marketplace-ui-components": {
"version": "0.0.31",
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.31.tgz",
"integrity": "sha512-jMAlqVRxC+AP3Q+C4PfrU27mHZsDMtH2CMqzsGvcRMfy/W/e4Q/B0Nvk0PlSlbdvMcWkWI2CQ+2kbf6gmOietA==",
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.32.tgz",
"integrity": "sha512-6vJ9pacl2rzLE3GqBNtbaGEBSxZ6yxb5+ixY/Q6ORtgSgv54LPb6L9VOiwrjrRxD12HEVxosXEeyBLhek7C8tg==",
"dependencies": {
"lucide-react": "^0.453.0"
},

View File

@ -25,7 +25,7 @@
"React"
],
"dependencies": {
"@codex-storage/marketplace-ui-components": "^0.0.31",
"@codex-storage/marketplace-ui-components": "^0.0.32",
"@codex-storage/sdk-js": "^0.0.15",
"@sentry/browser": "^8.32.0",
"@sentry/react": "^8.31.0",

View File

@ -0,0 +1,10 @@
<svg
width="10"
height="6"
viewBox="0 0 10 6"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M4.99999 3.879L8.71249 0.166504L9.77299 1.227L4.99999 6L0.22699 1.227L1.28749 0.166504L4.99999 3.879Z"
fill="#969696" />
</svg>

After

Width:  |  Height:  |  Size: 251 B

32
public/icons/us-flag.svg Normal file
View File

@ -0,0 +1,32 @@
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_356_794)">
<path
d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16Z"
fill="#F0F0F0" />
<path
d="M7.65216 7.99981H16C16 7.27775 15.9037 6.57825 15.7244 5.91284H7.65216V7.99981Z"
fill="#D80027" />
<path
d="M7.65216 3.82598H14.8258C14.3361 3.02686 13.7099 2.32051 12.9798 1.73901H7.65216V3.82598Z"
fill="#D80027" />
<path
d="M7.99996 15.9999C9.88274 15.9999 11.6133 15.3491 12.9798 14.2607H3.02008C4.38664 15.3491 6.11717 15.9999 7.99996 15.9999Z"
fill="#D80027" />
<path
d="M1.17417 12.1739H14.8258C15.2189 11.5324 15.5239 10.8311 15.7244 10.0869H0.275574C0.476105 10.8311 0.781011 11.5324 1.17417 12.1739Z"
fill="#D80027" />
<path
d="M3.70575 1.24931H4.43478L3.75666 1.74197L4.01569 2.53912L3.33759 2.04647L2.6595 2.53912L2.88325 1.85047C2.28619 2.34781 1.76287 2.9305 1.33162 3.57975H1.56522L1.13356 3.89334C1.06631 4.00553 1.00181 4.1195 0.94 4.23516L1.14612 4.86956L0.761563 4.59016C0.665969 4.79269 0.578531 4.99978 0.499938 5.21119L0.727031 5.91019H1.56522L0.887094 6.40284L1.14612 7.2L0.468031 6.70734L0.0618437 7.00247C0.0211875 7.32928 0 7.66216 0 8H8C8 3.58175 8 3.06087 8 0C6.41963 0 4.94641 0.458438 3.70575 1.24931ZM4.01569 7.2L3.33759 6.70734L2.6595 7.2L2.91853 6.40284L2.24041 5.91019H3.07859L3.33759 5.11303L3.59659 5.91019H4.43478L3.75666 6.40284L4.01569 7.2ZM3.75666 4.07241L4.01569 4.86956L3.33759 4.37691L2.6595 4.86956L2.91853 4.07241L2.24041 3.57975H3.07859L3.33759 2.78259L3.59659 3.57975H4.43478L3.75666 4.07241ZM6.88525 7.2L6.20716 6.70734L5.52906 7.2L5.78809 6.40284L5.10997 5.91019H5.94816L6.20716 5.11303L6.46616 5.91019H7.30434L6.62622 6.40284L6.88525 7.2ZM6.62622 4.07241L6.88525 4.86956L6.20716 4.37691L5.52906 4.86956L5.78809 4.07241L5.10997 3.57975H5.94816L6.20716 2.78259L6.46616 3.57975H7.30434L6.62622 4.07241ZM6.62622 1.74197L6.88525 2.53912L6.20716 2.04647L5.52906 2.53912L5.78809 1.74197L5.10997 1.24931H5.94816L6.20716 0.452156L6.46616 1.24931H7.30434L6.62622 1.74197Z"
fill="#0052B4" />
</g>
<defs>
<clipPath id="clip0_356_794">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
public/img/wallet.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

BIN
public/img/welcome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

View File

@ -0,0 +1,15 @@
export function AccountIcon() {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M0.9 0.899902H17.1C17.3387 0.899902 17.5676 0.994723 17.7364 1.16351C17.9052 1.33229 18 1.56121 18 1.7999V16.1999C18 16.4386 17.9052 16.6675 17.7364 16.8363C17.5676 17.0051 17.3387 17.0999 17.1 17.0999H0.9C0.661305 17.0999 0.432387 17.0051 0.263604 16.8363C0.0948211 16.6675 0 16.4386 0 16.1999V1.7999C0 1.56121 0.0948211 1.33229 0.263604 1.16351C0.432387 0.994723 0.661305 0.899902 0.9 0.899902ZM16.2 8.0999H1.8V15.2999H16.2V8.0999ZM16.2 6.2999V2.6999H1.8V6.2999H16.2ZM10.8 11.6999H14.4V13.4999H10.8V11.6999Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -0,0 +1,15 @@
export function ArrowLeftIcon() {
return (
<svg
width="6"
height="8"
viewBox="0 0 6 8"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M2.36349 3.99931L5.33349 6.96931L4.48509 7.81771L0.666687 3.99931L4.48509 0.180908L5.33349 1.02931L2.36349 3.99931Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -0,0 +1,15 @@
export function ArrowRightIcon() {
return (
<svg
width="6"
height="8"
viewBox="0 0 6 8"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M3.63669 3.99931L0.666687 1.02931L1.51509 0.180908L5.33349 3.99931L1.51509 7.81771L0.666687 6.96931L3.63669 3.99931Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -2,7 +2,7 @@ import { CodexNodeSpace } from "@codex-storage/sdk-js";
import { AvailabilityState } from "./types";
import { SpaceAllocation } from "@codex-storage/marketplace-ui-components";
import "./AvailabilitySpaceAllocation.css";
import { nodeSpaceAllocationColors } from "../NodeSpaceAllocation/nodeSpaceAllocation.domain";
import { nodeSpaceAllocationColors } from "../NodeSpace/nodeSpace.domain";
type Props = {
space: CodexNodeSpace;

View File

@ -0,0 +1,7 @@
.connected-account {
border-radius: 8px;
border: 1px solid #96969633;
main {
}
}

View File

@ -0,0 +1,22 @@
import { Button } from "@codex-storage/marketplace-ui-components";
import { AccountIcon } from "../AccountIcon/AccountIcon";
import "./ConnectedAccount.css";
import { PlusIcon } from "../PlusIcon/PlusIcon";
import { WalletCard } from "./WalletCard";
export function ConnectedAccount() {
return (
<div className="card connected-account">
<header>
<div>
<AccountIcon></AccountIcon>
<h5>Connected Account</h5>
</div>
<Button Icon={PlusIcon} label="Add Wallet" variant="outline"></Button>
</header>
<main>
<WalletCard></WalletCard>
</main>
</div>
);
}

View File

@ -0,0 +1,168 @@
.wallet-card {
background-image: url(/img/wallet.png);
background-repeat: no-repeat;
background-size: cover;
border: 1px solid #96969633;
height: 286px;
border-radius: 16px;
padding: 16px;
box-sizing: border-box;
position: relative;
&::before {
content: " ";
width: 217px;
height: 148px;
position: absolute;
bottom: -1px;
right: 0;
background: transparent;
backdrop-filter: blur(3px);
}
header {
button {
background-color: #161616;
border: 1px solid #96969633;
height: 24px;
width: 24px;
cursor: pointer;
transition: box-shadow 0.35s;
&:hover {
box-shadow: 0 0 0 3px var(--codex-border-color);
}
&:first-child {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
&:nth-child(2) {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
}
}
h6 {
font-family: Inter;
font-size: 12px;
font-weight: 700;
line-height: 14.52px;
letter-spacing: 0.01em;
text-align: left;
text-transform: uppercase;
}
span {
font-family: Inter;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.006em;
text-align: left;
color: #ffffffb2;
}
var {
font-family: Inter;
font-weight: 500;
line-height: 40px;
letter-spacing: -0.005em;
color: var(--text-strong-950, #ffffff);
display: block;
font-style: normal;
}
main var {
font-size: 32px;
}
footer {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
&::after {
content: " ";
background-image: url(/icons/select-arrow.svg);
background-repeat: no-repeat;
position: absolute;
width: 16px;
height: 16px;
right: 0;
top: 22px;
}
var {
font-size: 20px;
line-height: 25px;
}
select {
background-color: #161616;
border-radius: 8px;
border: 1px solid #96969633;
padding: 6px 6px 6px 44px;
outline: none;
-moz-appearance: none; /* Firefox */
-webkit-appearance: none; /* Safari and Chrome */
appearance: none;
position: relative;
transition: box-shadow 0.35s;
&:hover {
box-shadow: 0 0 0 3px var(--codex-border-color);
}
&:has(option[value="US"]:checked) {
background-image: url(/icons/us-flag.svg);
background-position: 10px;
background-repeat: no-repeat;
background-size: 16px;
}
option {
border-radius: 32px;
}
}
div {
position: relative;
.row {
gap: 8px;
}
}
small {
color: #3ee089;
height: 20px;
width: 42px;
border-radius: 16px;
background-color: #1fc16b29;
display: flex;
align-items: center;
justify-content: center;
}
}
section:first-child {
margin-top: 12px;
}
section:nth-child(2) {
margin-top: 16px;
margin-bottom: 10px;
height: 90px;
position: relative;
.wallet-lines {
position: absolute;
left: 0;
top: 10px;
}
}
}

View File

@ -0,0 +1,48 @@
import { WalletChart } from "./WalletChart";
import { WalletLines } from "./WalletLines";
import "./WalletCard.css";
import { ArrowLeftIcon } from "../ArrowLeftIcon/ArrowLeftIcon";
import { ArrowRightIcon } from "../ArrowRightIcon/ArrowRightIcon";
export function WalletCard() {
return (
<div className="wallet-card">
<header>
<h6>Wallet</h6>
<div>
<button>
<ArrowLeftIcon></ArrowLeftIcon>
</button>
<button>
<ArrowRightIcon></ArrowRightIcon>
</button>
</div>
</header>
<main>
<section>
<span>TOKEN</span>
<var>123,223 CDX</var>
</section>
<section>
<WalletChart></WalletChart>
<WalletLines></WalletLines>
</section>
</main>
<footer>
<div>
<span>TOKEN</span>
<div className="row">
<var>$1,540 USD</var>
<small>+ 5%</small>
</div>
</div>
<select defaultValue={"US"}>
<option value={"US"}></option>
</select>
</footer>
</div>
);
}

View File

@ -0,0 +1,83 @@
export function WalletChart() {
return (
<svg
width="464"
height="91"
viewBox="0 0 464 91"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<line
x1="15.5"
x2="15.5"
y2="70.1744"
stroke="white"
strokeOpacity="0.61"
strokeDasharray="2 2"
/>
<path
d="M7.77365 80.1743H8.97678V85.8462C8.97678 86.3592 8.87521 86.798 8.67209 87.1626C8.47157 87.5272 8.18902 87.8058 7.82443 87.9985C7.45985 88.1886 7.03277 88.2837 6.54318 88.2837C6.09266 88.2837 5.68771 88.2017 5.32834 88.0376C4.97157 87.8735 4.68902 87.6353 4.48068 87.3228C4.27495 87.0076 4.17209 86.6248 4.17209 86.1743H5.37131C5.37131 86.3957 5.42209 86.5871 5.52365 86.7485C5.62782 86.91 5.76975 87.0363 5.94943 87.1274C6.13173 87.216 6.34006 87.2603 6.57443 87.2603C6.82964 87.2603 7.04579 87.2069 7.22287 87.1001C7.40256 86.9907 7.53928 86.8306 7.63303 86.6196C7.72678 86.4087 7.77365 86.1509 7.77365 85.8462V80.1743ZM11.6294 88.1743H10.3481L13.2271 80.1743H14.6216L17.5005 88.1743H16.2192L13.9575 81.6274H13.895L11.6294 88.1743ZM11.8442 85.0415H16.0005V86.0571H11.8442V85.0415ZM25.3328 80.1743V88.1743H24.2234L20.157 82.3071H20.0828V88.1743H18.8758V80.1743H19.9929L24.0633 86.0493H24.1375V80.1743H25.3328Z"
fill="white"
/>
<line
x1="103.7"
x2="103.7"
y2="70.1744"
stroke="white"
strokeOpacity="0.61"
strokeDasharray="2 2"
/>
<path
d="M93.7237 88.1743V80.1743H98.6846V81.2134H94.9307V83.6509H98.3291V84.686H94.9307V88.1743H93.7237ZM100.389 88.1743V80.1743H105.405V81.2134H101.596V83.6509H105.143V84.686H101.596V87.1353H105.451V88.1743H100.389ZM107.237 88.1743V80.1743H110.167C110.734 80.1743 111.204 80.2681 111.577 80.4556C111.949 80.6405 112.228 80.8918 112.413 81.2095C112.598 81.5246 112.69 81.88 112.69 82.2759C112.69 82.6092 112.629 82.8905 112.506 83.1196C112.384 83.3462 112.22 83.5285 112.014 83.6665C111.811 83.8019 111.587 83.9009 111.342 83.9634V84.0415C111.608 84.0545 111.867 84.1405 112.12 84.2993C112.375 84.4556 112.586 84.6782 112.753 84.9673C112.919 85.2563 113.003 85.6079 113.003 86.022C113.003 86.4308 112.906 86.798 112.713 87.1235C112.523 87.4465 112.229 87.703 111.831 87.8931C111.432 88.0806 110.923 88.1743 110.303 88.1743H107.237ZM108.444 87.1392H110.186C110.764 87.1392 111.178 87.0272 111.428 86.8032C111.678 86.5793 111.803 86.2993 111.803 85.9634C111.803 85.7108 111.74 85.479 111.612 85.2681C111.484 85.0571 111.302 84.8892 111.065 84.7642C110.831 84.6392 110.552 84.5767 110.229 84.5767H108.444V87.1392ZM108.444 83.6353H110.061C110.332 83.6353 110.575 83.5832 110.792 83.479C111.01 83.3748 111.184 83.229 111.311 83.0415C111.441 82.8514 111.506 82.6274 111.506 82.3696C111.506 82.0389 111.391 81.7616 111.159 81.5376C110.927 81.3136 110.572 81.2017 110.092 81.2017H108.444V83.6353Z"
fill="white"
/>
<line
x1="191.9"
x2="191.9"
y2="70.1744"
stroke="white"
strokeOpacity="0.61"
strokeDasharray="2 2"
/>
<path
d="M179.727 80.1743H181.192L183.739 86.3931H183.832L186.379 80.1743H187.844V88.1743H186.696V82.3853H186.621L184.262 88.1626H183.309L180.95 82.3813H180.875V88.1743H179.727V80.1743ZM190.495 88.1743H189.214L192.092 80.1743H193.487L196.366 88.1743H195.085L192.823 81.6274H192.76L190.495 88.1743ZM190.71 85.0415H194.866V86.0571H190.71V85.0415ZM197.741 88.1743V80.1743H200.593C201.212 80.1743 201.727 80.2811 202.136 80.4946C202.547 80.7082 202.854 81.0037 203.058 81.3813C203.261 81.7563 203.362 82.1899 203.362 82.6821C203.362 83.1717 203.259 83.6027 203.054 83.9751C202.85 84.3449 202.543 84.6326 202.132 84.8384C201.723 85.0441 201.209 85.147 200.589 85.147H198.429V84.1079H200.479C200.87 84.1079 201.188 84.0519 201.433 83.9399C201.68 83.828 201.861 83.6652 201.975 83.4517C202.09 83.2381 202.147 82.9816 202.147 82.6821C202.147 82.38 202.089 82.1183 201.972 81.897C201.857 81.6756 201.676 81.5063 201.429 81.3892C201.184 81.2694 200.862 81.2095 200.464 81.2095H198.948V88.1743H197.741ZM201.69 84.5649L203.667 88.1743H202.292L200.354 84.5649H201.69Z"
fill="white"
/>
<line
x1="279.6"
x2="279.6"
y2="70.1744"
stroke="white"
strokeOpacity="0.61"
strokeDasharray="2 2"
/>
<path
d="M269.521 88.1743H268.239L271.118 80.1743H272.513L275.392 88.1743H274.11L271.849 81.6274H271.786L269.521 88.1743ZM269.735 85.0415H273.892V86.0571H269.735V85.0415ZM276.767 88.1743V80.1743H279.619C280.241 80.1743 280.757 80.2876 281.165 80.5142C281.574 80.7407 281.88 81.0506 282.083 81.4438C282.286 81.8345 282.388 82.2746 282.388 82.7642C282.388 83.2563 282.285 83.6991 282.079 84.0923C281.876 84.4829 281.569 84.7928 281.158 85.022C280.749 85.2485 280.234 85.3618 279.615 85.3618H277.654V84.3384H279.505C279.898 84.3384 280.217 84.2707 280.462 84.1353C280.707 83.9972 280.887 83.8097 281.001 83.5728C281.116 83.3358 281.173 83.0662 281.173 82.7642C281.173 82.4621 281.116 82.1938 281.001 81.9595C280.887 81.7251 280.706 81.5415 280.458 81.4087C280.214 81.2759 279.891 81.2095 279.49 81.2095H277.974V88.1743H276.767ZM284.023 88.1743V80.1743H286.875C287.494 80.1743 288.009 80.2811 288.418 80.4946C288.829 80.7082 289.136 81.0037 289.34 81.3813C289.543 81.7563 289.644 82.1899 289.644 82.6821C289.644 83.1717 289.541 83.6027 289.336 83.9751C289.132 84.3449 288.825 84.6326 288.414 84.8384C288.005 85.0441 287.491 85.147 286.871 85.147H284.711V84.1079H286.761C287.152 84.1079 287.47 84.0519 287.715 83.9399C287.962 83.828 288.143 83.6652 288.257 83.4517C288.372 83.2381 288.429 82.9816 288.429 82.6821C288.429 82.38 288.371 82.1183 288.254 81.897C288.139 81.6756 287.958 81.5063 287.711 81.3892C287.466 81.2694 287.144 81.2095 286.746 81.2095H285.23V88.1743H284.023ZM287.972 84.5649L289.949 88.1743H288.574L286.636 84.5649H287.972Z"
fill="white"
/>
<line
x1="366.3"
x2="366.3"
y2="70.1744"
stroke="white"
strokeOpacity="0.61"
strokeDasharray="2 2"
/>
<path
d="M354.315 80.1743H355.78L358.327 86.3931H358.42L360.967 80.1743H362.432V88.1743H361.284V82.3853H361.209L358.85 88.1626H357.897L355.538 82.3813H355.463V88.1743H354.315V80.1743ZM365.083 88.1743H363.801L366.68 80.1743H368.075L370.954 88.1743H369.673L367.411 81.6274H367.348L365.083 88.1743ZM365.298 85.0415H369.454V86.0571H365.298V85.0415ZM370.895 80.1743H372.267L374.356 83.811H374.442L376.532 80.1743H377.903L375.001 85.0337V88.1743H373.798V85.0337L370.895 80.1743Z"
fill="white"
/>
<line
x1="452"
x2="452"
y2="70.1744"
stroke="white"
strokeOpacity="0.61"
strokeDasharray="2 2"
/>
<path
d="M444.07 80.1743H445.273V85.8462C445.273 86.3592 445.171 86.798 444.968 87.1626C444.767 87.5272 444.485 87.8058 444.12 87.9985C443.756 88.1886 443.329 88.2837 442.839 88.2837C442.389 88.2837 441.984 88.2017 441.624 88.0376C441.267 87.8735 440.985 87.6353 440.777 87.3228C440.571 87.0076 440.468 86.6248 440.468 86.1743H441.667C441.667 86.3957 441.718 86.5871 441.82 86.7485C441.924 86.91 442.066 87.0363 442.245 87.1274C442.428 87.216 442.636 87.2603 442.87 87.2603C443.126 87.2603 443.342 87.2069 443.519 87.1001C443.698 86.9907 443.835 86.8306 443.929 86.6196C444.023 86.4087 444.07 86.1509 444.07 85.8462V80.1743ZM452.394 80.1743H453.605V85.436C453.605 85.9959 453.473 86.492 453.21 86.9243C452.947 87.354 452.578 87.6925 452.101 87.9399C451.625 88.1847 451.066 88.3071 450.425 88.3071C449.787 88.3071 449.23 88.1847 448.753 87.9399C448.277 87.6925 447.907 87.354 447.644 86.9243C447.381 86.492 447.25 85.9959 447.25 85.436V80.1743H448.457V85.3384C448.457 85.7004 448.536 86.022 448.695 86.3032C448.856 86.5845 449.084 86.8058 449.378 86.9673C449.673 87.1261 450.022 87.2056 450.425 87.2056C450.832 87.2056 451.182 87.1261 451.476 86.9673C451.773 86.8058 452 86.5845 452.156 86.3032C452.315 86.022 452.394 85.7004 452.394 85.3384V80.1743ZM462.037 80.1743V88.1743H460.928L456.861 82.3071H456.787V88.1743H455.58V80.1743H456.697L460.767 86.0493H460.842V80.1743H462.037Z"
fill="white"
/>
</svg>
);
}

View File

@ -0,0 +1,36 @@
export function WalletLines() {
return (
<svg
className="wallet-lines"
width="464"
height="46"
viewBox="0 0 464 46"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
opacity="0.5"
d="M10.5131 44.5368H0.231579C0.103681 44.5368 0 44.6405 0 44.7684C0 44.8963 0.103681 45 0.231579 45H460C462.209 45 464 43.2091 464 41V5C464 2.79086 462.209 1 460 1H437.926C437.43 1 436.938 1.09246 436.475 1.27265L381.325 22.7489C380.862 22.9291 380.37 23.0215 379.874 23.0215H362.053C361.293 23.0215 360.548 22.8049 359.907 22.3971L327.232 1.62441C326.591 1.21659 325.846 1 325.086 1H291.727C290.845 1 289.988 1.29141 289.289 1.82891L262.803 22.1926C262.104 22.7301 261.254 23.0215 260.372 23.0215H216.323C215.661 23.0215 215.02 23.1857 214.437 23.4993L186.929 38.3042C186.346 38.6178 185.695 38.782 185.033 38.782H166.823C166.154 38.782 165.495 38.6138 164.907 38.2929L137.827 23.5106C137.24 23.1897 136.581 23.0215 135.911 23.0215H112.308C111.412 23.0215 110.542 22.7208 109.837 22.1676L83.9663 1.85392C83.2617 1.3007 82.3919 1 81.4961 1H50.9186C49.7391 1 48.6197 1.52056 47.8597 2.42254L13.572 43.1143C12.812 44.0163 11.6926 44.5368 10.5131 44.5368Z"
fill="url(#paint0_linear_346_1558)"
fillOpacity="0.06"
/>
<path
opacity="0.6"
d="M0 45H8.62753C11.0011 45 13.2521 43.946 14.772 42.123L46.6596 3.87702C48.1796 2.05398 50.4306 1 52.8041 1H80.0903C81.896 1 83.6486 1.6109 85.0631 2.73335L108.741 21.5225C110.155 22.6449 111.908 23.2558 113.714 23.2558H134.871C136.223 23.2558 137.552 23.5982 138.736 24.2511L163.999 38.1886C165.182 38.8415 166.512 39.1839 167.863 39.1839H184.007C185.342 39.1839 186.656 38.8496 187.829 38.2116L213.537 24.2281C214.71 23.5901 216.013 23.2558 217.348 23.2558C229.407 23.2558 251.34 23.2558 258.999 23.2558C260.776 23.2558 262.487 22.6637 263.891 21.5729L288.202 2.68288C289.605 1.59212 291.332 1 293.11 1H323.901C325.435 1 326.936 1.44066 328.226 2.26951L358.913 21.9863C360.203 22.8152 361.704 23.2558 363.237 23.2558H379.107C380.11 23.2558 381.104 23.0673 382.037 22.7L435.763 1.55577C436.696 1.18853 437.69 1 438.693 1L464 1"
stroke="#3EE089"
strokeWidth="2"
/>
<defs>
<linearGradient
id="paint0_linear_346_1558"
x1="232"
y1="17.617"
x2="232"
y2="45"
gradientUnits="userSpaceOnUse">
<stop stopColor="#CFD1D3" />
<stop offset="1" stopColor="#E4E5E7" stopOpacity="0" />
</linearGradient>
</defs>
</svg>
);
}

View File

@ -1,13 +1,13 @@
.download {
display: flex;
align-items: center;
gap: 0.5rem;
}
.input {
flex: 1;
.download-inputContainer {
flex: 1;
}
input {
width: 100%;
}
}
.download-input {
width: 100%;
.button {
width: 105px;
}
}

View File

@ -2,6 +2,7 @@ import { Button, Input } from "@codex-storage/marketplace-ui-components";
import "./Download.css";
import { ChangeEvent, useState } from "react";
import { CodexSdk } from "../../sdk/codex";
import { DownloadIcon } from "./DownloadIcon";
export function Download() {
const [cid, setCid] = useState("");
@ -14,15 +15,25 @@ export function Download() {
setCid(e.currentTarget.value);
return (
<div className="download">
<div className="download-inputContainer">
<div className="card download">
<header>
<div>
<DownloadIcon></DownloadIcon>
<h5>Download</h5>
</div>
</header>
<main className="row gap">
<Input
id="cid"
placeholder="CID"
inputClassName="download-input"
size={"medium" as any}
onChange={onCidChange}></Input>
</div>
<Button label="Download" onClick={onDownload}></Button>
<Button
label="Download"
onClick={onDownload}
variant="outline"></Button>
</main>
</div>
);
}

View File

@ -0,0 +1,15 @@
export function DownloadIcon() {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M9.89999 9H12.6L8.99999 12.6L5.39999 9H8.09999V5.4H9.89999V9ZM11.7 1.8H2.69999V16.2H15.3V5.4H11.7V1.8ZM0.899994 0.8928C0.899994 0.3996 1.30229 0 1.79909 0H12.6L17.1 4.5V17.0937C17.1008 17.2119 17.0784 17.3291 17.0339 17.4386C16.9894 17.5481 16.9238 17.6478 16.8409 17.7319C16.7579 17.8161 16.6591 17.8831 16.5502 17.9291C16.4414 17.9751 16.3245 17.9992 16.2063 18H1.79369C1.55733 17.9984 1.33111 17.9038 1.16389 17.7367C0.99667 17.5697 0.901879 17.3436 0.899994 17.1072V0.8928Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -279,7 +279,7 @@ export function Files() {
</div>
<div className="files-fileBody">
<Table headers={headers} rows={rows} defaultSortIndex={2} />
<Table headers={headers} rows={rows.slice(0, 4)} defaultSortIndex={2} />
</div>
<FileDetails onClose={onClose} details={details} />

View File

@ -1,14 +1,17 @@
.fetch {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 1rem;
}
.fetch-inputContainer {
.manifest-fetch {
flex: 1;
}
display: flex;
gap: 16px;
.fetch-input {
width: 100%;
.input {
flex: 1;
input {
width: 100%;
}
}
.button {
width: 105px;
}
}

View File

@ -43,16 +43,14 @@ export function ManifestFetch() {
setCid(e.currentTarget.value);
return (
<div className="fetch">
<div className="fetch-inputContainer">
<Input
id="cid"
value={cid}
placeholder="CID"
inputClassName="fetch-input"
onChange={onCidChange}></Input>
</div>
<Button label="Fetch" onClick={onDownload}></Button>
<div className="manifest-fetch">
<Input
id="cid"
value={cid}
placeholder="CID"
size={"medium" as any}
onChange={onCidChange}></Input>
<Button label="Fetch" onClick={onDownload} variant="outline"></Button>
</div>
);
}

View File

@ -0,0 +1,18 @@
import { ReloadManifest } from "./ReloadManifest";
import { ManifestFetch } from "./ManifestFetch";
export function ManifestFetchCard() {
return (
<div className="card">
<header>
<div>
<ReloadManifest></ReloadManifest>
<h5>Fetch Manifest</h5>
</div>
</header>
<main className="row gap">
<ManifestFetch />
</main>
</div>
);
}

View File

@ -0,0 +1,15 @@
export function ReloadManifest() {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M3.1167 2.18971C4.7498 0.774603 6.83909 -0.00300588 9 8.73239e-06C13.9707 8.73239e-06 18 4.02931 18 9.00001C18 10.9224 17.397 12.7044 16.371 14.166L13.5 9.00001H16.2C16.2001 7.58847 15.7853 6.20803 15.0072 5.03034C14.2291 3.85265 13.1219 2.92965 11.8235 2.37612C10.525 1.82259 9.09244 1.66294 7.70396 1.91702C6.31548 2.17111 5.03231 2.82772 4.014 3.80521L3.1167 2.18971ZM14.8833 15.8103C13.2502 17.2254 11.1609 18.003 9 18C4.0293 18 0 13.9707 0 9.00001C0 7.07761 0.603 5.29561 1.629 3.83401L4.5 9.00001H1.8C1.79988 10.4115 2.21468 11.792 2.9928 12.9697C3.77093 14.1474 4.87806 15.0704 6.17654 15.6239C7.47502 16.1774 8.90756 16.3371 10.296 16.083C11.6845 15.8289 12.9677 15.1723 13.986 14.1948L14.8833 15.8103Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -3,7 +3,7 @@ type Props = {
};
export function NodesIcon({ variant }: Props) {
let color = "currentColor";
let color = "#969696";
if (variant === "success") {
color = "#3EE089";

View File

@ -0,0 +1,13 @@
.node-space {
h6 {
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
text-align: left;
padding-top: 16px;
margin-bottom: 16px;
border-top: 1px solid #96969633;
}
}

View File

@ -1,9 +1,13 @@
import { useQuery } from "@tanstack/react-query";
import Loader from "../../assets/loader.svg";
import { CodexSdk } from "../../sdk/codex";
import { SpaceAllocation } from "@codex-storage/marketplace-ui-components";
import {
Button,
SpaceAllocation,
} from "@codex-storage/marketplace-ui-components";
import { Promises } from "../../utils/promises";
import { nodeSpaceAllocationColors } from "./nodeSpaceAllocation.domain";
import { NodesIcon } from "../Menu/NodesIcon";
import "./NodeSpace.css";
const defaultSpace = {
quotaMaxBytes: 0,
@ -12,7 +16,7 @@ const defaultSpace = {
totalBlocks: 0,
};
export function NodeSpaceAllocation() {
export function NodeSpace() {
const { data: space, isPending } = useQuery({
queryFn: () =>
CodexSdk.data()
@ -41,24 +45,36 @@ export function NodeSpaceAllocation() {
const { quotaMaxBytes, quotaReservedBytes, quotaUsedBytes } = space;
return (
<SpaceAllocation
data={[
{
title: "Maximum storage space used by the node",
size: quotaMaxBytes,
color: nodeSpaceAllocationColors[0],
},
{
title: "Amount of storage space currently in use",
size: quotaUsedBytes,
color: nodeSpaceAllocationColors[1],
},
{
title: "Amount of storage space reserved",
size: quotaReservedBytes,
color:
nodeSpaceAllocationColors[nodeSpaceAllocationColors.length - 1],
},
]}></SpaceAllocation>
<div className="card node-space">
<header>
<div>
<NodesIcon variant="default"></NodesIcon>
<h5>Storage</h5>
</div>
<Button label="Details" variant="outline"></Button>
</header>
<main>
<h6>Disk</h6>
<SpaceAllocation
data={[
{
title: "Allocated",
size: quotaUsedBytes,
color: "#FF6E61",
},
{
title: "Available",
size: quotaReservedBytes,
color: "#34A0FF",
},
{
title: "Free",
size: quotaMaxBytes - quotaReservedBytes - quotaUsedBytes,
color: "#6F6F6F",
},
]}></SpaceAllocation>
</main>
</div>
);
}

View File

@ -1,63 +1,23 @@
import { Cell } from "@codex-storage/marketplace-ui-components";
import { useQuery } from "@tanstack/react-query";
import "./PeerCountryCell.css";
import { useEffect } from "react";
import { PeerPin, PeerUtils } from "./peers.util";
import { PeerGeo, PeerNode, PeerUtils } from "./peers.util";
export type Props = {
address: string;
onPinAdd: (pin: PeerPin & { countryIso: string; ip: string }) => void;
node: PeerNode;
geo: PeerGeo | undefined;
};
export function PeerCountryCell({ address, onPinAdd }: Props) {
const { data } = useQuery({
queryFn: () => {
const [ip] = address.split(":");
return fetch(import.meta.env.VITE_GEO_IP_URL + "/json?ip=" + ip).then(
(res) => res.json()
);
},
refetchOnMount: true,
queryKey: [address],
// Enable only when the address exists
enabled: !!address,
// No need to retry because if the connection to the node
// is back again, all the queries will be invalidated.
retry: false,
// We can cache the data at Infinity because the relation between
// country and ip is fixed
staleTime: Infinity,
// Don't expect something new when coming back to the UI
refetchOnWindowFocus: false,
});
useEffect(() => {
if (data) {
onPinAdd({
lat: data.latitude,
lng: data.longitude,
countryIso: data.country_iso,
ip: data.ip,
});
}
}, [data, onPinAdd]);
export function PeerCountryCell({ geo }: Props) {
return (
<Cell>
<div className="peer-country">
{data ? (
{geo ? (
<>
<span> {!!data && PeerUtils.geCountryEmoji(data.country_iso)}</span>
<span>{data?.country}</span>
<span> {!!geo && PeerUtils.geCountryEmoji(geo.country_iso)}</span>
<span>{geo?.country}</span>
</>
) : (
<span>{address}</span>
<span></span>
)}
</div>
</Cell>

View File

@ -23,13 +23,6 @@
}
}
circle[fill="#d6ff79"] {
stroke: var(--codex-color-primary);
stroke-width: 0.6px;
fill: #141414;
animation: circle-pulse 3s infinite;
}
ul {
display: none;
@ -145,85 +138,9 @@
}
}
main {
> div {
position: relative;
width: 350px;
height: 175px;
overflow: hidden;
transform: scale(0.5);
margin: auto;
left: -32px;
@media (min-width: 1000px) {
& {
transform: scale(0.73);
}
}
*,
&::before {
box-sizing: border-box;
}
&::before,
&::after {
position: absolute;
}
&::before {
content: "";
width: inherit;
height: inherit;
border: 45px solid #323232;
border-bottom: none;
border-top-left-radius: 175px;
border-top-right-radius: 175px;
}
div {
position: absolute;
top: 100%;
width: inherit;
height: inherit;
border: 45px solid #323232;
border-top: none;
border-bottom-left-radius: 175px;
border-bottom-right-radius: 175px;
transform-origin: 50% 0;
}
div:nth-child(1) {
border-color: var(--codex-color-primary);
transform: rotate(calc(var(--codex-peers-degrees) * 1deg));
background-color: transparent;
}
}
span {
font-family: Inter;
font-size: 38.67px;
font-weight: 500;
line-height: 48.34px;
letter-spacing: -0.005em;
text-align: center;
position: absolute;
bottom: 0;
left: calc(50% - 24px);
}
}
footer {
border-top: 1px solid #96969633;
padding-top: 16px;
display: flex;
align-items: center;
gap: 8px;
font-family: Inter;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.006em;
}
}
}
@ -255,16 +172,3 @@
}
}
}
@keyframes circle-pulse {
0% {
opacity: 1; /* Fully opaque */
}
50% {
r: 2; /* Increased radius */
opacity: 1; /* Slightly transparent */
}
100% {
opacity: 1; /* Fully opaque */
}
}

View File

@ -4,64 +4,32 @@ import {
Cell,
Table,
} from "@codex-storage/marketplace-ui-components";
import DottedMap from "dotted-map/without-countries";
import { useRef, useState, useCallback } from "react";
import { useCallback, useState } from "react";
import { ErrorCircleIcon } from "../ErrorCircleIcon/ErrorCircleIcon";
import { PeersIcon } from "../Menu/PeersIcon";
import { PeerCountryCell } from "./PeerCountryCell";
import { SuccessCheckIcon } from "../SuccessCheckIcon/SuccessCheckIcon";
import { useDebug } from "../../hooks/useDebug";
import { getMapJSON } from "dotted-map";
import "./Peers.css";
import { PeerPin, PeerSortFn, PeerUtils } from "./peers.util";
// This function accepts the same arguments as DottedMap in the example above.
const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" });
type CustomCSSProperties = React.CSSProperties & {
"--codex-peers-degrees": number;
};
import { PeerGeo, PeerNode, PeerSortFn, PeerUtils } from "./peers.util";
import { PeersMap } from "./PeersMap";
import { useDebug } from "../../hooks/useDebug";
import { PeersQuality } from "./PeersQuality";
import { PeersChart } from "./PeersChart";
const throwOnError = true;
export const Peers = () => {
const ips = useRef<Record<string, string>>({});
const [pins, setPins] = useState<[PeerPin, number][]>([]);
const { data } = useDebug(throwOnError);
const [ips, setIps] = useState<Record<string, PeerGeo>>({});
const onPinAdd = useCallback((node: PeerNode, geo: PeerGeo) => {
const [ip = ""] = node.address.split(":");
setIps((ips) => ({ ...ips, [ip]: geo }));
}, []);
const [sortFn, setSortFn] = useState<PeerSortFn | null>(() =>
PeerUtils.sortByBoolean("desc")
);
const { data } = useDebug(throwOnError);
const onPinAdd = useCallback(
({
countryIso,
ip,
...pin
}: PeerPin & { countryIso: string; ip: string }) => {
setPins((val) => PeerUtils.incPin(val, pin));
ips.current[ip] = countryIso;
},
[]
);
// Its safe to re-create the map at each render, because of the
// pre-computation its super fast ⚡️
const map = new DottedMap({ map: JSON.parse(mapJsonString) });
pins.map(([pin, quantity]) =>
map.addPin({
lat: pin.lat,
lng: pin.lng,
svgOptions: { color: "#d6ff79", radius: 0.8 * quantity },
})
);
const svgMap = map.getSVG({
radius: 0.32,
color: "#969696",
shape: "circle",
backgroundColor: "#141414",
});
const onSortByCountry = (state: TabSortState) => {
if (!state) {
@ -69,7 +37,7 @@ export const Peers = () => {
return;
}
setSortFn(() => PeerUtils.sortByCountry(state, ips.current));
setSortFn(() => PeerUtils.sortByCountry(state, ips));
};
const onSortActive = (state: TabSortState) => {
@ -87,42 +55,41 @@ export const Peers = () => {
["Active", onSortActive],
] satisfies [string, ((state: TabSortState) => void)?][];
const nodes = data?.table?.nodes || [];
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 rows = sorted.map((node) => {
const [ip = ""] = node.address.split(":");
const geo = ips[ip];
return (
<Row
cells={[
<PeerCountryCell node={node} geo={geo}></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 = PeerUtils.countActives(sorted);
const degrees = PeerUtils.calculareDegrees(sorted);
const good = actives > 0;
const styles: CustomCSSProperties = {
"--codex-peers-degrees": degrees,
};
const good = PeerUtils.isGoodQuality(actives);
return (
<div className="peers">
<div>
<div dangerouslySetInnerHTML={{ __html: svgMap }}></div>
<PeersMap nodes={nodes} onPinAdd={onPinAdd} />
<div>
<ul>
<li>Legend</li>
@ -135,24 +102,11 @@ export const Peers = () => {
<PeersIcon></PeersIcon>
<span>Connections</span>
</header>
<main style={styles}>
<div>
<div></div>
<span>{actives}</span>
</div>
<main>
<PeersChart actives={actives} degrees={degrees}></PeersChart>
</main>
<footer>
{good ? (
<>
<SuccessCheckIcon variant="primary"></SuccessCheckIcon>
<span>Peer connections in good standing. </span>
</>
) : (
<>
<ErrorCircleIcon />
<span>No peer connection active. </span>
</>
)}
<PeersQuality good={good}></PeersQuality>
</footer>
</div>
</div>

View File

@ -0,0 +1,23 @@
.peers-card {
position: relative;
.peers-map {
border-top: 1px solid #96969633;
border-bottom: 1px solid #96969633;
width: 100%;
svg {
width: 100%;
}
}
.peers-chart {
position: absolute;
left: 0;
right: 0;
}
footer {
padding-top: 16px;
}
}

View File

@ -0,0 +1,38 @@
import { PeersIcon } from "../Menu/PeersIcon";
import { PeersMap } from "./PeersMap";
import "./PeersCard.css";
import { useDebug } from "../../hooks/useDebug";
import { PeerUtils } from "./peers.util";
import { PeersChart } from "./PeersChart";
import { PeersQuality } from "./PeersQuality";
import { Button } from "@codex-storage/marketplace-ui-components";
const throwOnError = true;
export function PeersCard() {
const { data } = useDebug(throwOnError);
const nodes = data?.table.nodes ?? [];
const actives = PeerUtils.countActives(nodes);
const degrees = PeerUtils.calculareDegrees(nodes);
const good = PeerUtils.isGoodQuality(actives);
return (
<div className="card peers-card">
<header>
<div>
<PeersIcon></PeersIcon>
<h5>Peers</h5>
</div>
<Button label="Details" variant="outline"></Button>
</header>
<main className="row gap">
<PeersMap nodes={data?.table.nodes || []}></PeersMap>
<PeersChart actives={actives} degrees={degrees}></PeersChart>
</main>
<footer>
<PeersQuality good={good}></PeersQuality>
</footer>
</div>
);
}

View File

@ -0,0 +1,65 @@
.peers-chart {
position: relative;
width: 350px;
height: 175px;
overflow: hidden;
transform: scale(0.5);
margin: auto;
left: -32px;
@media (min-width: 1000px) {
& {
transform: scale(0.73);
}
}
*,
&::before {
box-sizing: border-box;
}
&::before,
&::after {
position: absolute;
}
&::before {
content: "";
width: inherit;
height: inherit;
border: 45px solid #323232;
border-bottom: none;
border-top-left-radius: 175px;
border-top-right-radius: 175px;
}
div {
position: absolute;
top: 100%;
width: inherit;
height: inherit;
border: 45px solid #323232;
border-top: none;
border-bottom-left-radius: 175px;
border-bottom-right-radius: 175px;
transform-origin: 50% 0;
}
div:nth-child(1) {
border-color: var(--codex-color-primary);
transform: rotate(calc(var(--codex-peers-degrees) * 1deg));
background-color: transparent;
}
span {
font-family: Inter;
font-size: 38.67px;
font-weight: 500;
line-height: 48.34px;
letter-spacing: -0.005em;
text-align: center;
position: absolute;
bottom: 0;
left: calc(50% - 24px);
}
}

View File

@ -0,0 +1,23 @@
import "./PeersChart.css";
type Props = {
actives: number;
degrees: number;
};
type CustomCSSProperties = React.CSSProperties & {
"--codex-peers-degrees": number;
};
export function PeersChart({ actives, degrees }: Props) {
const style: CustomCSSProperties = {
"--codex-peers-degrees": degrees,
};
return (
<div style={style} className="peers-chart">
<div></div>
<span>{actives}</span>
</div>
);
}

View File

@ -0,0 +1,21 @@
.peers-map {
circle[fill="#d6ff79"] {
stroke: var(--codex-color-primary);
stroke-width: 0.6px;
fill: #141414;
animation: circle-pulse 3s infinite;
}
}
@keyframes circle-pulse {
0% {
opacity: 1; /* Fully opaque */
}
50% {
r: 2; /* Increased radius */
opacity: 1; /* Slightly transparent */
}
100% {
opacity: 1; /* Fully opaque */
}
}

View File

@ -0,0 +1,56 @@
import { getMapJSON } from "dotted-map";
import DottedMap from "dotted-map/without-countries";
import { PeerGeo, PeerNode, PeerUtils } from "./peers.util";
import { useCallback, useState } from "react";
import { PeersPin } from "./PeersPin";
import "./PeersMap.css";
// This function accepts the same arguments as DottedMap in the example above.
const mapJsonString = getMapJSON({ height: 60, grid: "diagonal" });
type Props = {
nodes: PeerNode[];
onPinAdd?: (node: PeerNode, geo: PeerGeo) => void;
};
export function PeersMap({ nodes, onPinAdd }: Props) {
// Its safe to re-create the map at each render, because of the
// pre-computation its super fast ⚡️
const map = new DottedMap({ map: JSON.parse(mapJsonString) });
const [pins, setPins] = useState<[PeerNode & PeerGeo, number][]>([]);
const onInternalPinAdd = useCallback(
(node: PeerNode, geo: PeerGeo) => {
setPins((val) => PeerUtils.incPin(val, { ...node, ...geo }));
onPinAdd?.(node, geo);
},
[setPins]
);
pins.map(([pin, quantity]) =>
map.addPin({
lat: pin.latitude,
lng: pin.longitude,
svgOptions: { color: "#d6ff79", radius: 0.4 * quantity },
})
);
const svgMap = map.getSVG({
radius: 0.32,
color: "#969696",
shape: "circle",
backgroundColor: "#141414",
});
return (
<>
{nodes.map((node, index) => (
<PeersPin key={index} node={node} onLoad={onInternalPinAdd} />
))}
<div
className="peers-map"
dangerouslySetInnerHTML={{ __html: svgMap }}></div>
</>
);
}

View File

@ -0,0 +1,44 @@
import { useQuery } from "@tanstack/react-query";
import { PeerGeo, PeerNode } from "./peers.util";
import { useEffect } from "react";
type Props = {
node: PeerNode
onLoad: (node: PeerNode, geo: PeerGeo) => void
}
export function PeersPin({ node, onLoad }: Props) {
const { data } = useQuery({
queryFn: () => {
const [ip] = node.address.split(":");
return fetch(import.meta.env.VITE_GEO_IP_URL + "/json?ip=" + ip).then(
(res) => res.json()
);
},
queryKey: ["peers", node.address],
// No need to retry because if the connection to the node
// is back again, all the queries will be invalidated.
retry: false,
// We can cache the data at Infinity because the relation between
// country and ip is fixed
staleTime: Infinity,
// Don't expect something new when coming back to the UI
refetchOnWindowFocus: false,
refetchOnMount: false,
});
useEffect(() => {
if (data) {
onLoad(node, data)
console.info(node.address)
}
}, [data, onLoad, node])
return ""
}

View File

@ -0,0 +1,10 @@
.peers-quality {
display: flex;
align-items: center;
gap: 8px;
font-family: Inter;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.006em;
}

View File

@ -0,0 +1,25 @@
import { ErrorCircleIcon } from "../ErrorCircleIcon/ErrorCircleIcon";
import { SuccessCheckIcon } from "../SuccessCheckIcon/SuccessCheckIcon";
import "./PeersQuality.css";
type Props = {
good: boolean;
};
export function PeersQuality({ good }: Props) {
if (good) {
return (
<div className="peers-quality">
<SuccessCheckIcon variant="primary"></SuccessCheckIcon>
<span>Peer connections in good standing. </span>
</div>
);
}
return (
<div className="peers-quality">
<ErrorCircleIcon />
<span>No peer connection active. </span>
</div>
);
}

View File

@ -1,5 +1,5 @@
import { assert, describe, it } from "vitest";
import { PeerUtils } from "./peers.util";
import { PeerGeo, PeerUtils } from "./peers.util";
describe("peers", () => {
it("sorts by boolean", async () => {
@ -19,10 +19,16 @@ describe("peers", () => {
it("sorts by country", async () => {
const a = { nodeId: "a", peerId: "", record: "", address: "127.0.0.1", seen: false }
const b = { nodeId: "a", peerId: "", record: "", address: "127.0.0.2", seen: true }
const table = {
"127.0.0.1": "US",
"127.0.0.2": "France",
"127.0.0.1": {
country: "United States"
} as PeerGeo,
"127.0.0.2": {
country: "France"
} as PeerGeo,
}
const items = [a, b,]
const descSorted = items.slice().sort(PeerUtils.sortByCountry("desc", table))
@ -35,14 +41,14 @@ describe("peers", () => {
});
it("adds a new pin", async () => {
const latLng = { lat: 0, lng: 0 }
const latLng = { latitude: 0, longitude: 0 } as any
const values = PeerUtils.incPin([], latLng)
assert.deepEqual(values, [[latLng, 1]]);
});
it("increments an existing pin", async () => {
const latLng = { lat: 0, lng: 0 }
const latLng = { lat: 0, lng: 0 } as any
const values = PeerUtils.incPin([[latLng, 1]], latLng)
assert.deepEqual(values, [[latLng, 2]]);

View File

@ -8,10 +8,12 @@ export type PeerNode = {
seen: boolean;
};
export type PeerPin = {
lat: number;
lng: number;
};
export type PeerGeo = {
latitude: number
longitude: number
country: string
country_iso: string
}
export type PeerSortFn = (a: PeerNode, b: PeerNode) => number;
@ -21,12 +23,12 @@ export const PeerUtils = {
return a?.seen === b?.seen ? 0 : b?.seen ? order : -order;
},
sortByCountry: (state: TabSortState, ipTable: Record<string, string>) =>
sortByCountry: (state: TabSortState, ipTable: Record<string, PeerGeo>) =>
(a: PeerNode, b: PeerNode) => {
const [ipA = ""] = a.address.split(":")
const [ipB = ""] = b.address.split(":")
const countryA = ipTable[ipA] || "";
const countryB = ipTable[ipB] || "";
const countryA = ipTable[ipA].country || "";
const countryB = ipTable[ipB].country || "";
return state === "desc"
? countryA.localeCompare(countryB)
@ -36,10 +38,10 @@ export const PeerUtils = {
/**
* Increments the number of pin for a location
*/
incPin(val: [PeerPin, number][], pin: PeerPin): [PeerPin, number][] {
incPin(val: [PeerNode & PeerGeo, number][], pin: PeerNode & PeerGeo): [PeerNode & PeerGeo, number][] {
const [, quantity = 0] =
val.find(([p]) => p.lat === pin.lat && p.lng == pin.lng) || [];
const rest = val.filter(([p]) => p.lat !== pin.lat || p.lng !== pin.lng)
val.find(([p]) => p.latitude === pin.latitude && p.longitude == pin.longitude) || [];
const rest = val.filter(([p]) => p.latitude !== pin.latitude || p.longitude !== pin.longitude)
return [...rest, [pin, quantity + 1]];
},
@ -53,6 +55,10 @@ export const PeerUtils = {
return (actives / total) * 180
},
isGoodQuality(actives: number) {
return actives > 0
},
geCountryEmoji: (countryCode: string) => {
const codePoints = countryCode
.toUpperCase()

View File

@ -0,0 +1,15 @@
export function PlusIcon() {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M5.25 5.25V0.75H6.75V5.25H11.25V6.75H6.75V11.25H5.25V6.75H0.75V5.25H5.25Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -0,0 +1,25 @@
import { Upload, UploadIcon } from "@codex-storage/marketplace-ui-components";
import { CodexSdk } from "../../sdk/codex";
import { useQueryClient } from "@tanstack/react-query";
export function UploadCard() {
const queryClient = useQueryClient();
const onSuccess = () => {
queryClient.invalidateQueries({ queryKey: ["cids"] });
};
return (
<div className="card node-space">
<header>
<div>
<UploadIcon width={18} fill="#969696"></UploadIcon>
<h5>Upload</h5>
</div>
</header>
<main>
<Upload multiple codexData={CodexSdk.data()} onSuccess={onSuccess} />
</main>
</div>
);
}

View File

@ -0,0 +1,15 @@
export function DiscordIcon() {
return (
<svg
width="14"
height="17"
viewBox="0 0 14 17"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M5.557 7.25C6.007 7.25 6.3715 7.5875 6.36325 8C6.36325 8.4125 6.00775 8.75 5.557 8.75C5.1145 8.75 4.75 8.4125 4.75 8C4.75 7.5875 5.10625 7.25 5.557 7.25ZM8.443 7.25C8.89375 7.25 9.25 7.5875 9.25 8C9.25 8.4125 8.89375 8.75 8.443 8.75C8.0005 8.75 7.63675 8.4125 7.63675 8C7.63675 7.5875 7.99225 7.25 8.443 7.25ZM12.1683 0.5C13.0405 0.5 13.75 1.2245 13.75 2.12225V16.25L12.0917 14.7537L11.158 13.8717L10.1702 12.9342L10.5798 14.3915H1.83175C0.9595 14.3915 0.25 13.667 0.25 12.7692V2.12225C0.25 1.2245 0.9595 0.5 1.83175 0.5H12.1675H12.1683ZM9.19075 10.7847C10.8955 10.73 11.5518 9.58775 11.5518 9.58775C11.5518 7.052 10.4403 4.99625 10.4403 4.99625C9.33025 4.1465 8.27275 4.16975 8.27275 4.16975L8.16475 4.29575C9.4765 4.70525 10.0855 5.29625 10.0855 5.29625C9.3693 4.89274 8.57998 4.63575 7.7635 4.54025C7.24558 4.48175 6.72245 4.48679 6.20575 4.55525C6.15925 4.55525 6.12025 4.5635 6.0745 4.571C5.8045 4.595 5.14825 4.697 4.32325 5.0675C4.03825 5.201 3.868 5.29625 3.868 5.29625C3.868 5.29625 4.5085 4.67375 5.89675 4.26425L5.8195 4.16975C5.8195 4.16975 4.76275 4.1465 3.652 4.997C3.652 4.997 2.54125 7.052 2.54125 9.58775C2.54125 9.58775 3.18925 10.7292 4.894 10.7847C4.894 10.7847 5.179 10.4307 5.4115 10.1315C4.43125 9.8315 4.0615 9.2015 4.0615 9.2015C4.0615 9.2015 4.138 9.257 4.27675 9.33575C4.28425 9.34325 4.29175 9.3515 4.3075 9.359C4.33075 9.3755 4.354 9.383 4.37725 9.39875C4.57 9.509 4.76275 9.59525 4.93975 9.6665C5.25625 9.7925 5.63425 9.9185 6.0745 10.0055C6.73399 10.1348 7.41206 10.1373 8.0725 10.013C8.45719 9.94434 8.8325 9.83081 9.19075 9.67475C9.46075 9.572 9.7615 9.422 10.078 9.20975C10.078 9.20975 9.6925 9.8555 8.6815 10.1473C8.91325 10.4465 9.1915 10.7847 9.1915 10.7847H9.19075Z"
fill="#6FCB94"
/>
</svg>
);
}

View File

@ -1,35 +0,0 @@
.welcome {
border-radius: var(--codex-border-radius);
border: 1px solid var(--codex-border-color);
background-color: var(--codex-background-secondary);
padding: 1rem 1.5rem;
display: flex;
align-items: flex-start;
flex-direction: column;
flex: 1;
}
.welcome-disclaimer {
margin: 1rem 0;
}
.welcome-title {
font-weight: bold;
font-size: 1.125rem;
line-height: 1.75rem;
margin-bottom: 0.75rem;
}
.welcome-body {
flex: 1;
}
.welcome-link {
display: flex;
align-items: center;
color: var(--codex-color-primary);
}
.welcome-link:hover {
text-decoration: underline;
}

View File

@ -1,23 +0,0 @@
import "./Welcome.css";
import { Link } from "@tanstack/react-router";
import { ChevronRight } from "lucide-react";
export function Welcome() {
return (
<div className="welcome">
<p className="welcome-title">Welcome to Codex Marketplace</p>
<div className="welcome-body">
<span>
Begin your journey with Codex by uploading new files for testing.
Experience the power of our decentralized data storage platform and
explore its features. Your feedback is invaluable as we continue to
improve!
</span>
</div>
<Link to="/dashboard/help" className="welcome-link">
Explore more content <ChevronRight size={"1.5rem"} />
</Link>
</div>
);
}

View File

@ -0,0 +1,102 @@
.welcome-card {
display: flex;
flex-direction: column;
> div {
padding: 16px;
background-color: #141414;
background-image: url(img/welcome.png);
background-repeat: no-repeat;
background-position: right;
flex: 1;
}
main {
max-width: 400px;
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
h6 {
font-family: Inter;
font-size: 20px;
font-weight: 400;
line-height: 24.2px;
color: var(--codex-color-primary);
}
p {
margin-top: 16px;
margin-bottom: 32px;
font-family: Azeret Mono;
font-size: 12px;
font-weight: 400;
line-height: 14px;
text-align: left;
color: #7f948d;
}
div {
display: flex;
align-items: center;
justify-content: space-between;
}
a {
font-family: Inter;
font-size: 16px;
font-weight: 600;
line-height: 19.36px;
letter-spacing: 0.01em;
text-align: left;
color: #7bfbaf;
display: flex;
align-items: center;
&:nth-child(2) {
gap: 12px;
}
}
}
footer {
margin-top: 32px;
}
}
.welcome {
border-radius: var(--codex-border-radius);
border: 1px solid var(--codex-border-color);
background-color: var(--codex-background-secondary);
padding: 1rem 1.5rem;
display: flex;
align-items: flex-start;
flex-direction: column;
flex: 1;
}
.welcome-disclaimer {
margin: 1rem 0;
}
.welcome-title {
font-weight: bold;
font-size: 1.125rem;
line-height: 1.75rem;
margin-bottom: 0.75rem;
}
.welcome-body {
flex: 1;
}
.welcome-link {
display: flex;
align-items: center;
color: var(--codex-color-primary);
}
.welcome-link:hover {
text-decoration: underline;
}

View File

@ -0,0 +1,47 @@
import "./WelcomeCard.css";
import { Link } from "@tanstack/react-router";
import { ArrowRight } from "lucide-react";
import { Logo } from "../Logo/Logo";
import { Logotype } from "../Logotype/Logotype";
import { DiscordIcon } from "./DiscordIcon";
import { Alert } from "@codex-storage/marketplace-ui-components";
export function WelcomeCard() {
return (
<div className="welcome-card card">
<div className="card">
<header>
<div>
<Logo height={48}></Logo>
<Logotype height={48}></Logotype>
</div>
</header>
<main>
<h6>
Begin your journey with Codex by uploading new files for testing.
</h6>
<p>
Experience the power of our decentralized data storage platform and
explore its features. Your feedback is invaluable as we continue to
improve!
</p>
<div>
<Link to="/dashboard/help" className="welcome-link">
Explore more content<ArrowRight></ArrowRight>
</Link>
<a href="">
<DiscordIcon></DiscordIcon>
Join Codex Discord
</a>
</div>
</main>
<footer>
<Alert variant="warning" title="Disclaimer">
The website and the content herein is not intended for public use
and is for informational and demonstration purposes only.
</Alert>
</footer>
</div>
</div>
);
}

View File

@ -30,7 +30,7 @@
--codex-color-on-primary: #333;
--codex-color-disabled: #717171;
--codex-color-light: rgb(150 150 150);
--codex-border-color: #2b303b;
--codex-border-color: #96969633;
--codex-input-border-color: #494949;
--codex-background-secondary: rgb(38 38 38);
--codex-highlight-color: #2f2f2f;
@ -130,7 +130,7 @@ ul {
padding: 0;
}
main {
body > main {
flex: 1;
display: flex;
flex-direction: column;
@ -181,3 +181,36 @@ input:-webkit-autofill:active {
.gap {
gap: var(--codex-row-gap);
}
.card {
border: 1px solid #96969633;
border-radius: 16px;
padding: 16px;
background-color: #232323;
> header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 16px;
svg {
color: #969696;
}
> div {
display: flex;
align-items: center;
gap: 12px;
}
h5 {
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
}
}
}

View File

@ -2,19 +2,16 @@ import { createFileRoute } from "@tanstack/react-router";
import { Files } from "../../components/Files/Files";
import { ErrorBoundary } from "@sentry/react";
import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder";
import { Card } from "@codex-storage/marketplace-ui-components";
export const Route = createFileRoute("/dashboard/favorites")({
component: () => (
<>
<ErrorBoundary
fallback={({ error }) => (
<Card title="Error">
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the favorites."
/>
</Card>
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the favorites."
/>
)}>
<div className="container">
<Files />

View File

@ -1,6 +1,7 @@
.dashboard {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
h3 {
font-family: Inter;
@ -32,3 +33,31 @@
font-size: 26px;
}
}
.dashboard-body {
display: flex;
flex-wrap: wrap;
gap: 16px;
}
.welcome-card,
.connected-account {
min-width: 550px;
display: flex;
flex-direction: column;
flex: 1;
}
.column {
min-width: 400px;
}
.files {
flex: 1;
min-width: 100%;
}
@media (min-width: 1800px) {
.dashboard-body {
}
}

View File

@ -1,28 +1,23 @@
import { createFileRoute } from "@tanstack/react-router";
import { Files } from "../../components/Files/Files.tsx";
import { Alert, Card, Upload } from "@codex-storage/marketplace-ui-components";
import { CodexSdk } from "../../sdk/codex";
import { Welcome } from "../../components/Welcome/Welcome.tsx";
import { WelcomeCard } from "../../components/Welcome/WelcomeCard.tsx";
import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder.tsx";
import { ErrorBoundary } from "@sentry/react";
import { useQueryClient } from "@tanstack/react-query";
import { Download } from "../../components/Download/Download.tsx";
import { ManifestFetch } from "../../components/ManifestFetch/ManifestFetch.tsx";
import "./index.css";
import { Versions } from "../../components/Versions/Versions.tsx";
import { WebStorage } from "../../utils/web-storage.ts";
import { ConnectedAccount } from "../../components/ConnectedAccount/ConnectedAccount.tsx";
import { NodeSpace } from "../../components/NodeSpace/NodeSpace.tsx";
import { UploadCard } from "../../components/UploadCard/UploadCard.tsx";
import { ManifestFetchCard } from "../../components/ManifestFetch/ManifestFetchCard.tsx";
import { PeersCard } from "../../components/Peers/PeersCard.tsx";
export const Route = createFileRoute("/dashboard/")({
component: Dashboard,
});
function Dashboard() {
const queryClient = useQueryClient();
const onSuccess = () => {
queryClient.invalidateQueries({ queryKey: ["cids"] });
};
const username = WebStorage.onBoarding.getDisplayName();
const emoji = WebStorage.onBoarding.getEmoji();
@ -40,22 +35,20 @@ function Dashboard() {
<Versions />
</div>
<div>
<div>
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
)}>
<Card title="Upload a file">
<Upload
multiple
codexData={CodexSdk.data()}
onSuccess={onSuccess}
/>
</Card>
</ErrorBoundary>
<div className="dashboard-body">
<ConnectedAccount></ConnectedAccount>
<div className="column">
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
)}>
<NodeSpace></NodeSpace>
</ErrorBoundary>
<PeersCard></PeersCard>
</div>
<ErrorBoundary
fallback={({ error }) => (
@ -64,11 +57,23 @@ function Dashboard() {
subtitle="Cannot retrieve the data."
/>
)}>
<Card title="Download a file" className="dashboard-download">
<Download></Download>
</Card>
<WelcomeCard />
</ErrorBoundary>
<div className="column">
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
)}>
<UploadCard />
</ErrorBoundary>
<Download></Download>
<ManifestFetchCard />
</div>
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
@ -76,45 +81,9 @@ function Dashboard() {
subtitle="Cannot retrieve the data."
/>
)}>
<Card title="Fetch a manifest" className="dashboard-fetch">
<ManifestFetch></ManifestFetch>
</Card>
<Files />
</ErrorBoundary>
</div>
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
)}>
<div className="dashboard-welcome">
<Welcome />
<Alert
variant="warning"
title="Disclaimer"
className="welcome-disclaimer dashboard-alert">
The website and the content herein is not intended for public use
and is for informational and demonstration purposes only.
</Alert>
</div>
</ErrorBoundary>
</div>
<div className="container-fluid">
<ErrorBoundary
fallback={({ error }) => (
<Card title="Error">
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
</Card>
)}>
<Files />
</ErrorBoundary>
</div>
</>
);