mirror of
https://github.com/codex-storage/codex-marketplace-ui.git
synced 2025-02-22 12:48:32 +00:00
Merge branch 'releases/v0.0.13' into mock
This commit is contained in:
commit
4602feb486
6
.github/workflows/playwright.yml
vendored
6
.github/workflows/playwright.yml
vendored
@ -8,9 +8,9 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
codex_version: v0.1.8
|
||||
circuit_version: v0.1.8
|
||||
marketplace_address: "0x5Bd66fA15Eb0E546cd26808248867a572cFF5706"
|
||||
codex_version: v0.1.9
|
||||
circuit_version: v0.1.9
|
||||
marketplace_address: "0xAB03b6a58C5262f530D54146DA2a552B1C0F7648"
|
||||
eth_provider: "https://rpc.testnet.codex.storage"
|
||||
VITE_CODEX_API_URL: ${{ secrets.VITE_CODEX_API_URL }}
|
||||
VITE_GEO_IP_URL: ${{ secrets.VITE_GEO_IP_URL }}
|
||||
|
@ -1,11 +1,16 @@
|
||||
import test, { expect } from "@playwright/test";
|
||||
import { Bytes } from "../src/utils/bytes"
|
||||
import { GB } from "../src/utils/constants"
|
||||
|
||||
test('create an availability', async ({ page }) => {
|
||||
await page.goto('/dashboard/availabilities');
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('.availability-edit button').first().click();
|
||||
await page.getByLabel('Total size').click();
|
||||
await page.getByLabel('Total size').fill('0.50');
|
||||
|
||||
const value = (Math.random() * 0.5) + 0.1;
|
||||
|
||||
await page.getByLabel('Total size').fill(value.toFixed(1));
|
||||
await page.getByLabel('Duration').click();
|
||||
await page.getByLabel('Duration').fill('30');
|
||||
await page.getByLabel('Min price').click();
|
||||
@ -20,7 +25,7 @@ test('create an availability', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.getByText('Success', { exact: true })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
await expect(page.getByText('512.0 MB allocated for the').first()).toBeVisible();
|
||||
await expect(page.getByText(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()).toBeVisible();
|
||||
})
|
||||
|
||||
test('availability navigation buttons', async ({ page }) => {
|
||||
@ -54,3 +59,63 @@ test('availability navigation buttons', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
await expect(page.locator('.modal--open')).not.toBeVisible();
|
||||
})
|
||||
|
||||
test('create an availability with changing the duration to months', async ({ page }) => {
|
||||
await page.goto('/dashboard/availabilities');
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('.availability-edit button').first().click();
|
||||
await page.getByLabel('Total size').click();
|
||||
|
||||
await page.getByLabel('Total size').fill("0.1");
|
||||
await page.getByLabel('Duration').click();
|
||||
await page.getByLabel('Duration').fill("3");
|
||||
await page.getByRole('combobox').nth(1).selectOption('months');
|
||||
|
||||
await page.getByLabel('Min price').click();
|
||||
await page.getByLabel('Min price').fill('5');
|
||||
await page.getByLabel('Max collateral').click();
|
||||
await page.getByLabel('Max collateral').fill('30');
|
||||
await page.getByLabel('Min price').fill('5');
|
||||
await page.getByLabel('Nickname').click();
|
||||
await page.getByLabel('Nickname').fill('test');
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.getByText('Confirm your new sale')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.getByText('Success', { exact: true })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
await expect(page.getByText("3 months").first()).toBeVisible();
|
||||
})
|
||||
|
||||
|
||||
test('create an availability after checking max size and invalid input', async ({ page }) => {
|
||||
await page.goto('/dashboard/availabilities');
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('.availability-edit button').first().click();
|
||||
await page.getByLabel('Total size').click();
|
||||
|
||||
|
||||
await page.getByLabel('Total size').fill("9999");
|
||||
await expect(page.getByLabel('Total size')).toHaveAttribute("aria-invalid");
|
||||
|
||||
await page.getByText("Use max size").click()
|
||||
await expect(page.getByLabel('Total size')).not.toHaveAttribute("aria-invalid");
|
||||
|
||||
const value = (Math.random() * 0.5);
|
||||
await page.getByLabel('Total size').fill(value.toFixed(1));
|
||||
|
||||
await page.getByLabel('Duration').click();
|
||||
await page.getByLabel('Duration').fill('30');
|
||||
await page.getByLabel('Min price').click();
|
||||
await page.getByLabel('Min price').fill('5');
|
||||
await page.getByLabel('Max collateral').click();
|
||||
await page.getByLabel('Max collateral').fill('30');
|
||||
await page.getByLabel('Min price').fill('5');
|
||||
await page.getByLabel('Nickname').click();
|
||||
await page.getByLabel('Nickname').fill('test');
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.getByText('Confirm your new sale')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.getByText('Success', { exact: true })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
await expect(page.getByText(Bytes.pretty(parseFloat(value.toFixed(1)) * GB)).first()).toBeVisible();
|
||||
})
|
@ -76,3 +76,58 @@ test('remove the CID when the file is deleted', async ({ page }) => {
|
||||
await page.locator('.button-icon--small').nth(1).click();
|
||||
await expect(page.locator('#cid')).toBeEmpty()
|
||||
})
|
||||
|
||||
test('create a storage request by using decimal values', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||
|
||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
||||
await expect(page.getByText('Success, the CID has been')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
|
||||
await page.getByLabel("Full period of the contract").fill("10")
|
||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
||||
|
||||
await page.getByLabel("Full period of the contract").fill("1")
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
|
||||
await page.getByLabel("Full period of the contract").fill("0")
|
||||
await expect(page.locator('footer .button--primary')).toHaveAttribute("disabled");
|
||||
|
||||
const value = (Math.random() * 7);
|
||||
await page.getByLabel("Full period of the contract").fill(value.toFixed(1))
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.getByText('Your request is being processed.')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
await expect(page.getByText('No data.')).not.toBeVisible();
|
||||
await expect(page.getByText(value.toFixed(1) + " days").first()).toBeVisible();
|
||||
})
|
||||
|
||||
// test('create a storage request by using months', async ({ page }) => {
|
||||
// await page.goto('/dashboard');
|
||||
// await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||
// await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||
|
||||
// await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
// path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
// ]);
|
||||
// await expect(page.locator('#cid')).not.toBeEmpty()
|
||||
// await expect(page.getByText('Success, the CID has been')).toBeVisible();
|
||||
// await page.getByRole('button', { name: 'Next' }).click();
|
||||
|
||||
// await page.getByLabel("Full period of the contract").fill("3")
|
||||
// await page.getByRole('combobox').selectOption('months');
|
||||
// await expect(page.getByLabel("Full period of the contract")).toHaveValue("3")
|
||||
|
||||
// await page.getByRole('button', { name: 'Next' }).click();
|
||||
// await expect(page.getByText('Your request is being processed.')).toBeVisible();
|
||||
// await page.getByRole('button', { name: 'Finish' }).click();
|
||||
// await expect(page.getByText('No data.')).not.toBeVisible();
|
||||
// await expect(page.getByText("3 months").first()).toBeVisible();
|
||||
// })
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -9,7 +9,7 @@
|
||||
"version": "0.0.13",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codex-storage/marketplace-ui-components": "^0.0.50",
|
||||
"@codex-storage/marketplace-ui-components": "^0.0.51",
|
||||
"@codex-storage/sdk-js": "^0.0.16",
|
||||
"@sentry/browser": "^8.32.0",
|
||||
"@sentry/react": "^8.31.0",
|
||||
@ -395,9 +395,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@codex-storage/marketplace-ui-components": {
|
||||
"version": "0.0.50",
|
||||
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.50.tgz",
|
||||
"integrity": "sha512-pijYw8Wd/ns64Q7iSLQ444U5Q7Ql0U88OuxBycuxIOVp3obITJk6SBgdZpxHIj5kWShsUvXU/nJl0V0oKt9ieQ==",
|
||||
"version": "0.0.51",
|
||||
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.51.tgz",
|
||||
"integrity": "sha512-KPPFlcpx3a83WCBSLbRONrF/yr4J/ctyTfFPxMaRSMTRD1LtfIE0uPy3QxtHs6tigOts2h4DEz6Kn2ynHdfKPg==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
|
@ -26,7 +26,7 @@
|
||||
"React"
|
||||
],
|
||||
"dependencies": {
|
||||
"@codex-storage/marketplace-ui-components": "^0.0.50",
|
||||
"@codex-storage/marketplace-ui-components": "^0.0.51",
|
||||
"@codex-storage/sdk-js": "^0.0.16",
|
||||
"@sentry/browser": "^8.32.0",
|
||||
"@sentry/react": "^8.31.0",
|
||||
|
85
public/icons/aud-flag.svg
Normal file
85
public/icons/aud-flag.svg
Normal file
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 512 512"
|
||||
style="enable-background: new 0 0 512 512"
|
||||
xml:space="preserve">
|
||||
<path
|
||||
style="fill: #0052b4"
|
||||
d="M512,256c0,141.384-114.616,256-256,256S0,397.384,0,256C0,256.06,256,0.029,256,0
|
||||
C397.384,0,512,114.616,512,256z" />
|
||||
<g>
|
||||
<path
|
||||
style="fill: #f0f0f0"
|
||||
d="M256,0c-0.014,0-0.029,0.001-0.043,0.001L256,0L256,0z" />
|
||||
<path
|
||||
style="fill: #f0f0f0"
|
||||
d="M255.315,256H256c0-0.232,0-0.454,0-0.685C255.772,255.544,255.544,255.772,255.315,256z" />
|
||||
<path
|
||||
style="fill: #f0f0f0"
|
||||
d="M256,133.566c0-45.045,0-74.562,0-133.565h-0.043C114.592,0.024,0,114.629,0,256h133.565v-75.212
|
||||
L208.777,256h46.539c0.229-0.228,0.457-0.456,0.685-0.685c0-17.247,0-32.636,0-46.536l-75.213-75.213H256z" />
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
style="fill: #d80027"
|
||||
d="M129.515,33.391C89.476,56.19,56.19,89.476,33.391,129.515V256h66.783V100.175v-0.001H256
|
||||
c0-21.063,0-41.129,0-66.783H129.515z" />
|
||||
<path
|
||||
style="fill: #d80027"
|
||||
d="M256,224.519l-90.953-90.952h-31.481c0-0.001,0,0,0,0L255.999,256H256
|
||||
C256,256,256,234.295,256,224.519z" />
|
||||
</g>
|
||||
<g>
|
||||
<polygon
|
||||
style="fill: #f0f0f0"
|
||||
points="154.395,300.522 168.445,329.9 200.172,322.567 185.964,351.869 211.478,372.102
|
||||
179.711,379.262 179.8,411.826 154.395,391.453 128.991,411.826 129.08,379.262 97.312,372.102 122.827,351.869 108.617,322.567
|
||||
140.346,329.9 " />
|
||||
<polygon
|
||||
style="fill: #f0f0f0"
|
||||
points="383.284,356.174 390.309,370.863 406.173,367.196 399.068,381.847 411.826,391.964
|
||||
395.942,395.544 395.986,411.826 383.284,401.639 370.582,411.826 370.626,395.544 354.743,391.964 367.5,381.847 360.396,367.196
|
||||
376.259,370.863 " />
|
||||
<polygon
|
||||
style="fill: #f0f0f0"
|
||||
points="317.933,200.348 324.957,215.038 340.821,211.37 333.717,226.021 346.474,236.138
|
||||
330.591,239.718 330.634,256 317.933,245.813 305.231,256 305.274,239.718 289.391,236.138 302.148,226.021 295.044,211.37
|
||||
310.908,215.038 " />
|
||||
<polygon
|
||||
style="fill: #f0f0f0"
|
||||
points="383.284,111.304 390.309,125.994 406.173,122.327 399.069,136.978 411.825,147.094
|
||||
395.942,150.675 395.986,166.957 383.284,156.77 370.582,166.957 370.626,150.675 354.743,147.094 367.499,136.978
|
||||
360.396,122.327 376.259,125.994 " />
|
||||
<polygon
|
||||
style="fill: #f0f0f0"
|
||||
points="440.368,178.087 447.392,192.777 463.256,189.109 456.152,203.76 468.909,213.877
|
||||
453.025,217.458 453.069,233.739 440.368,223.553 427.666,233.739 427.709,217.458 411.826,213.877 424.583,203.76
|
||||
417.479,189.109 433.342,192.777 " />
|
||||
<polygon
|
||||
style="fill: #f0f0f0"
|
||||
points="399.55,256 405.075,273.006 422.957,273.006 408.49,283.517 414.017,300.522
|
||||
399.55,290.012 385.084,300.522 390.609,283.517 376.143,273.006 394.024,273.006 " />
|
||||
</g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
<g></g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.1 KiB |
28
public/icons/btc-flag.svg
Normal file
28
public/icons/btc-flag.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xml:space="preserve"
|
||||
width="100%"
|
||||
height="100%"
|
||||
version="1.1"
|
||||
shape-rendering="geometricPrecision"
|
||||
text-rendering="geometricPrecision"
|
||||
image-rendering="optimizeQuality"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
viewBox="0 0 4091.27 4091.73"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<g id="Layer_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer" />
|
||||
<g id="_1421344023328">
|
||||
<path
|
||||
fill="#F7931A"
|
||||
fill-rule="nonzero"
|
||||
d="M4030.06 2540.77c-273.24,1096.01 -1383.32,1763.02 -2479.46,1489.71 -1095.68,-273.24 -1762.69,-1383.39 -1489.33,-2479.31 273.12,-1096.13 1383.2,-1763.19 2479,-1489.95 1096.06,273.24 1763.03,1383.51 1489.76,2479.57l0.02 -0.02z" />
|
||||
<path
|
||||
fill="white"
|
||||
fill-rule="nonzero"
|
||||
d="M2947.77 1754.38c40.72,-272.26 -166.56,-418.61 -450,-516.24l91.95 -368.8 -224.5 -55.94 -89.51 359.09c-59.02,-14.72 -119.63,-28.59 -179.87,-42.34l90.16 -361.46 -224.36 -55.94 -92 368.68c-48.84,-11.12 -96.81,-22.11 -143.35,-33.69l0.26 -1.16 -309.59 -77.31 -59.72 239.78c0,0 166.56,38.18 163.05,40.53 90.91,22.69 107.35,82.87 104.62,130.57l-104.74 420.15c6.26,1.59 14.38,3.89 23.34,7.49 -7.49,-1.86 -15.46,-3.89 -23.73,-5.87l-146.81 588.57c-11.11,27.62 -39.31,69.07 -102.87,53.33 2.25,3.26 -163.17,-40.72 -163.17,-40.72l-111.46 256.98 292.15 72.83c54.35,13.63 107.61,27.89 160.06,41.3l-92.9 373.03 224.24 55.94 92 -369.07c61.26,16.63 120.71,31.97 178.91,46.43l-91.69 367.33 224.51 55.94 92.89 -372.33c382.82,72.45 670.67,43.24 791.83,-303.02 97.63,-278.78 -4.86,-439.58 -206.26,-544.44 146.69,-33.83 257.18,-130.31 286.64,-329.61l-0.07 -0.05zm-512.93 719.26c-69.38,278.78 -538.76,128.08 -690.94,90.29l123.28 -494.2c152.17,37.99 640.17,113.17 567.67,403.91zm69.43 -723.3c-63.29,253.58 -453.96,124.75 -580.69,93.16l111.77 -448.21c126.73,31.59 534.85,90.55 468.94,355.05l-0.02 0z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
24
public/icons/cad-flag.svg
Normal file
24
public/icons/cad-flag.svg
Normal file
@ -0,0 +1,24 @@
|
||||
<svg
|
||||
height="800px"
|
||||
width="800px"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512 512"
|
||||
xml:space="preserve">
|
||||
<circle style="fill: #f0f0f0" cx="256" cy="256" r="256" />
|
||||
<g>
|
||||
<path
|
||||
style="fill: #d80027"
|
||||
d="M512,256c0-101.494-59.065-189.19-144.696-230.598v461.195C452.935,445.19,512,357.494,512,256z" />
|
||||
<path
|
||||
style="fill: #d80027"
|
||||
d="M0,256c0,101.494,59.065,189.19,144.696,230.598V25.402C59.065,66.81,0,154.506,0,256z" />
|
||||
<polygon
|
||||
style="fill: #d80027"
|
||||
points="300.522,289.391 345.043,267.13 322.783,256 322.783,233.739 278.261,256 300.522,211.478
|
||||
278.261,211.478 256,178.087 233.739,211.478 211.478,211.478 233.739,256 189.217,233.739 189.217,256 166.957,267.13
|
||||
211.478,289.391 200.348,311.652 244.87,311.652 244.87,345.043 267.13,345.043 267.13,311.652 311.652,311.652 " />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 936 B |
45
public/icons/cny-flag.svg
Normal file
45
public/icons/cny-flag.svg
Normal file
@ -0,0 +1,45 @@
|
||||
<svg
|
||||
version="1.1"
|
||||
id="圖層_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="311.81px"
|
||||
height="311.81px"
|
||||
viewBox="0 0 311.81 311.81"
|
||||
enable-background="new 0 0 311.81 311.81"
|
||||
xml:space="preserve">
|
||||
<circle
|
||||
fill="#DC2E27"
|
||||
stroke="#000000"
|
||||
stroke-miterlimit="10"
|
||||
cx="157"
|
||||
cy="157"
|
||||
r="150" />
|
||||
<path
|
||||
id="path3374_1_"
|
||||
fill="#F7DC15"
|
||||
d="M191.928,83.868l7.479,12.46l-9.544,10.964l14.164-3.26l7.478,12.459l1.279-14.476
|
||||
l14.159-3.26l-13.371-5.685l1.271-14.479l-9.538,10.964L191.928,83.868z" />
|
||||
<path
|
||||
id="path3433_1_"
|
||||
fill="#F7DC15"
|
||||
d="M107.009,97l-13.46,41.46H49.94l35.271,25.628L71.743,205.54l35.276-25.616l35.265,25.612
|
||||
l-13.473-41.444l35.264-25.64l-43.6,0.012L107.009,97z" />
|
||||
<path
|
||||
id="path3447_1_"
|
||||
fill="#F7DC15"
|
||||
d="M238.217,119.048l2.061,14.38l-13.047,6.399l14.314,2.492L243.6,156.7l6.797-12.844
|
||||
l14.313,2.488l-10.12-10.424l6.788-12.848l-13.045,6.404L238.217,119.048z" />
|
||||
<path
|
||||
id="path3453_1_"
|
||||
fill="#F7DC15"
|
||||
d="M214.076,218.288L205,229.636l-13.6-5.128l7.982,12.141l-9.074,11.344l14.012-3.84
|
||||
l7.984,12.132l0.672-14.507l14.012-3.85l-13.586-5.123L214.076,218.288z" />
|
||||
<path
|
||||
id="path3475_1_"
|
||||
fill="#F7DC15"
|
||||
d="M246.305,177.012l-3.99,13.973l-14.527,0.521l12.057,8.116l-3.99,13.968l11.442-8.956
|
||||
l12.054,8.108l-4.984-13.644l11.439-8.965l-14.523,0.524L246.305,177.012z" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
46
public/icons/eth-flag.svg
Normal file
46
public/icons/eth-flag.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xml:space="preserve"
|
||||
width="100%"
|
||||
height="100%"
|
||||
version="1.1"
|
||||
shape-rendering="geometricPrecision"
|
||||
text-rendering="geometricPrecision"
|
||||
image-rendering="optimizeQuality"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
viewBox="0 0 784.37 1277.39"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<g id="Layer_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer" />
|
||||
<g id="_1421394342400">
|
||||
<g>
|
||||
<polygon
|
||||
fill="#343434"
|
||||
fill-rule="nonzero"
|
||||
points="392.07,0 383.5,29.11 383.5,873.74 392.07,882.29 784.13,650.54 " />
|
||||
<polygon
|
||||
fill="#8C8C8C"
|
||||
fill-rule="nonzero"
|
||||
points="392.07,0 -0,650.54 392.07,882.29 392.07,472.33 " />
|
||||
<polygon
|
||||
fill="#3C3C3B"
|
||||
fill-rule="nonzero"
|
||||
points="392.07,956.52 387.24,962.41 387.24,1263.28 392.07,1277.38 784.37,724.89 " />
|
||||
<polygon
|
||||
fill="#8C8C8C"
|
||||
fill-rule="nonzero"
|
||||
points="392.07,1277.38 392.07,956.52 -0,724.89 " />
|
||||
<polygon
|
||||
fill="#141414"
|
||||
fill-rule="nonzero"
|
||||
points="392.07,882.29 784.13,650.54 392.07,472.33 " />
|
||||
<polygon
|
||||
fill="#393939"
|
||||
fill-rule="nonzero"
|
||||
points="0,650.54 392.07,882.29 392.07,472.33 " />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
14
src/assets/icons/dots.svg
Normal file
14
src/assets/icons/dots.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="#969696"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="1" />
|
||||
<circle cx="12" cy="5" r="1" />
|
||||
<circle cx="12" cy="19" r="1" />
|
||||
</svg>
|
After Width: | Height: | Size: 306 B |
@ -28,7 +28,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const icons: Record<string, ReactElement> = {
|
||||
dashboard: <DashboardIcon />,
|
||||
dashboard: <DashboardIcon width={24} />,
|
||||
peers: <PeersIcon width={24} />,
|
||||
settings: <SettingsIcon width={24} />,
|
||||
files: <FilesIcon width={24} />,
|
||||
@ -74,16 +74,16 @@ export function AppBar({ onIconClick, onExpanded }: Props) {
|
||||
|
||||
const title =
|
||||
location.pathname.split("/")[2] || location.pathname.split("/")[1];
|
||||
const networkIconColor = online ? "#3EE089" : "var(-codex-color-error)";
|
||||
const networkIconColor = online ? "#3EE089" : "var(--codex-color-error)";
|
||||
const nodesIconColor =
|
||||
codex.enabled === false
|
||||
? "var(-codex-color-error)"
|
||||
? "var(--codex-color-error)"
|
||||
: persistence.enabled
|
||||
? "#3EE089"
|
||||
: "var(--codex-input-color-warning)";
|
||||
|
||||
const icon = isMobile ? (
|
||||
<Logo onClick={() => onExpanded(true)}></Logo>
|
||||
<Logo onClick={() => onExpanded(true)} width={30}></Logo>
|
||||
) : (
|
||||
icons[title]
|
||||
);
|
||||
|
@ -45,6 +45,12 @@
|
||||
sans-serif;
|
||||
letter-spacing: -0.006em;
|
||||
color: rgba(150, 150, 150, 0.8);
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
@ -88,7 +94,7 @@
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
aside {
|
||||
.wallet-login {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
Table,
|
||||
TabSortState,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { PrettyBytes } from "../../utils/bytes";
|
||||
import { Bytes } from "../../utils/bytes";
|
||||
import { AvailabilityActionsCell } from "./AvailabilityActionsCell";
|
||||
import { CodexAvailability, CodexNodeSpace } from "@codex-storage/sdk-js/async";
|
||||
import { Times } from "../../utils/times";
|
||||
@ -86,7 +86,7 @@ export function AvailabilitiesTable({ availabilities, space }: Props) {
|
||||
)}
|
||||
</Cell>,
|
||||
<AvailabilityIdCell value={a} />,
|
||||
<Cell>{PrettyBytes(a.totalSize)}</Cell>,
|
||||
<Cell>{Bytes.pretty(a.totalSize)}</Cell>,
|
||||
<Cell>{Times.pretty(a.duration)}</Cell>,
|
||||
<Cell>{a.minPrice.toString()}</Cell>,
|
||||
<Cell>{a.maxCollateral.toString()}</Cell>,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Cell, Row } from "@codex-storage/marketplace-ui-components";
|
||||
import { PrettyBytes } from "../../utils/bytes";
|
||||
import { Bytes } from "../../utils/bytes";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import HardriveIcon from "../../assets/icons/hardrive.svg?react";
|
||||
|
||||
@ -19,7 +19,7 @@ export function AvailabilityDiskRow({ bytes }: Props) {
|
||||
<HardriveIcon />
|
||||
<div>
|
||||
<b>Node</b>
|
||||
<small>{PrettyBytes(bytes)} allocated for the node</small>
|
||||
<small>{Bytes.pretty(bytes)} allocated for the node</small>
|
||||
</div>
|
||||
</div>
|
||||
</Cell>,
|
||||
|
@ -10,7 +10,7 @@ import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { AvailabilityConfirm } from "./AvailabilityConfirmation";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import { AvailabilityState } from "./types";
|
||||
import { GB, STEPPER_DURATION } from "../../utils/constants";
|
||||
import { STEPPER_DURATION } from "../../utils/constants";
|
||||
import { useAvailabilityMutation } from "./useAvailabilityMutation";
|
||||
import { AvailabilitySuccess } from "./AvailabilitySuccess";
|
||||
import { AvailabilityError } from "./AvailabilityError";
|
||||
@ -28,8 +28,8 @@ type Props = {
|
||||
const CONFIRM_STATE = 2;
|
||||
|
||||
const defaultAvailabilityData: AvailabilityState = {
|
||||
totalSize: 0.5 * GB,
|
||||
duration: Times.unitValue("days"),
|
||||
totalSize: 0.5,
|
||||
duration: 1,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
totalSizeUnit: "gb",
|
||||
@ -52,7 +52,7 @@ export function AvailabilityEdit({
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
WebStorage.get<number>("availability-step"),
|
||||
WebStorage.get<AvailabilityState>("availability"),
|
||||
WebStorage.get<AvailabilityState>("availability-1"),
|
||||
]).then(([s, a]) => {
|
||||
if (s) {
|
||||
dispatch({
|
||||
@ -62,31 +62,11 @@ export function AvailabilityEdit({
|
||||
}
|
||||
|
||||
if (a) {
|
||||
setAvailability(a);
|
||||
setAvailability({ ...defaultAvailabilityData, ...a });
|
||||
}
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
// We use a custom event to not re render the sunburst component
|
||||
// useEffect(() => {
|
||||
// const onAvailabilityIdChange = (e: Event) => {
|
||||
// const custom = e as CustomEvent;
|
||||
// setAvailabilityId(custom.detail);
|
||||
// };
|
||||
|
||||
// document.addEventListener(
|
||||
// "codexavailabilityid",
|
||||
// onAvailabilityIdChange,
|
||||
// false
|
||||
// );
|
||||
|
||||
// return () =>
|
||||
// document.removeEventListener(
|
||||
// "codexavailabilityid",
|
||||
// onAvailabilityIdChange
|
||||
// );
|
||||
// }, []);
|
||||
|
||||
const components = [
|
||||
AvailabilityForm,
|
||||
AvailabilityConfirm,
|
||||
@ -157,7 +137,7 @@ export function AvailabilityEdit({
|
||||
|
||||
editAvailabilityValue.current = a.totalSize;
|
||||
WebStorage.set("availability-step", 0);
|
||||
WebStorage.set("availability", a);
|
||||
WebStorage.set("availability-1", a);
|
||||
|
||||
const unit = Times.unit(a.duration);
|
||||
|
||||
|
@ -11,7 +11,6 @@ import NodesIcon from "../../assets/icons/nodes.svg?react";
|
||||
import InfoIcon from "../../assets/icons/info.svg?react";
|
||||
import { attributes } from "../../utils/attributes";
|
||||
import { AvailabilityUtils } from "./availability.utils";
|
||||
import { Times } from "../../utils/times";
|
||||
|
||||
export function AvailabilityForm({
|
||||
dispatch,
|
||||
@ -39,27 +38,24 @@ export function AvailabilityForm({
|
||||
const element = e.currentTarget;
|
||||
|
||||
onAvailabilityChange({
|
||||
totalSize: 0,
|
||||
totalSizeUnit: element.value as "tb" | "gb",
|
||||
});
|
||||
};
|
||||
|
||||
const onDurationChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const unitValue = Times.unitValue(availability.durationUnit);
|
||||
|
||||
onAvailabilityChange({
|
||||
duration: parseInt(element.value) * unitValue,
|
||||
duration: parseInt(element.value),
|
||||
});
|
||||
};
|
||||
|
||||
const onDurationUnitChange = async (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const unit = element.value as "hours" | "days" | "months";
|
||||
const unitValue = Times.unitValue(unit);
|
||||
|
||||
onAvailabilityChange({
|
||||
duration: unitValue,
|
||||
duration: availability.duration,
|
||||
durationUnit: unit,
|
||||
});
|
||||
};
|
||||
@ -67,10 +63,9 @@ export function AvailabilityForm({
|
||||
const onAvailablityChange = async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const element = e.currentTarget;
|
||||
const v = element.value;
|
||||
const unit = AvailabilityUtils.unitValue(availability.totalSizeUnit);
|
||||
|
||||
onAvailabilityChange({
|
||||
totalSize: parseFloat(v) * unit,
|
||||
totalSize: parseFloat(v),
|
||||
});
|
||||
};
|
||||
|
||||
@ -87,7 +82,12 @@ export function AvailabilityForm({
|
||||
const available = AvailabilityUtils.maxValue(space);
|
||||
|
||||
onAvailabilityChange({
|
||||
totalSize: available,
|
||||
totalSize:
|
||||
Math.floor(
|
||||
((available - 1) /
|
||||
AvailabilityUtils.unitValue(availability.totalSizeUnit)) *
|
||||
10
|
||||
) / 10,
|
||||
});
|
||||
};
|
||||
|
||||
@ -96,20 +96,17 @@ export function AvailabilityForm({
|
||||
available += editAvailabilityValue;
|
||||
}
|
||||
|
||||
const isValid =
|
||||
availability.totalSize > 0 && available >= availability.totalSize;
|
||||
const totalSizeInBytes =
|
||||
availability.totalSize *
|
||||
AvailabilityUtils.unitValue(availability.totalSizeUnit);
|
||||
|
||||
const isValid = totalSizeInBytes > 0 && available >= totalSizeInBytes;
|
||||
|
||||
const helper = isValid
|
||||
? "Total size of sale's storage in bytes"
|
||||
: "The total size cannot exceed the space available.";
|
||||
|
||||
const value = AvailabilityUtils.toUnit(
|
||||
availability.totalSize,
|
||||
availability.totalSizeUnit
|
||||
).toFixed(2);
|
||||
|
||||
const unitValue = Times.unitValue(availability.durationUnit);
|
||||
const duration = availability.duration / unitValue;
|
||||
const duration = availability.duration;
|
||||
|
||||
return (
|
||||
<div className="availability-form">
|
||||
@ -143,13 +140,12 @@ export function AvailabilityForm({
|
||||
name="totalSize"
|
||||
type="number"
|
||||
label="Total size"
|
||||
min={0.01}
|
||||
isInvalid={!isValid}
|
||||
max={available.toFixed(2)}
|
||||
onChange={onAvailablityChange}
|
||||
onGroupChange={onTotalSizeUnitChange}
|
||||
step={"0.01"}
|
||||
value={value}
|
||||
value={availability.totalSize.toString()}
|
||||
min={"0"}
|
||||
group={[
|
||||
["gb", "GB"],
|
||||
// ["tb", "TB"],
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Strings } from "../../utils/strings";
|
||||
import { Cell } from "@codex-storage/marketplace-ui-components";
|
||||
import { PrettyBytes } from "../../utils/bytes";
|
||||
import { Bytes } from "../../utils/bytes";
|
||||
import { AvailabilityWithSlots } from "./types";
|
||||
import AvailbilityIcon from "../../assets/icons/availability.svg?react";
|
||||
|
||||
@ -18,7 +18,7 @@ export function AvailabilityIdCell({ value }: Props) {
|
||||
<b>{value.name || Strings.shortId(value.id)}</b>
|
||||
</div>
|
||||
<small className="text--light">
|
||||
{PrettyBytes(value.totalSize)} allocated for the availability
|
||||
{Bytes.pretty(value.totalSize)} allocated for the availability
|
||||
</small>
|
||||
<br />
|
||||
<small className="text--light">
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Cell, Row } from "@codex-storage/marketplace-ui-components";
|
||||
import { PrettyBytes } from "../../utils/bytes";
|
||||
import { Bytes } from "../../utils/bytes";
|
||||
import "./SlotRow.css";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import { attributes } from "../../utils/attributes";
|
||||
@ -47,7 +47,7 @@ export function SlotRow({ bytes, active, id }: Props) {
|
||||
<SlotIcon />
|
||||
<div>
|
||||
<b>Slot {id}</b>
|
||||
<small>{PrettyBytes(bytes)} allocated for the slot</small>
|
||||
<small>{Bytes.pretty(bytes)} allocated for the slot</small>
|
||||
</div>
|
||||
</div>
|
||||
</Cell>,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { Times } from "../../utils/times";
|
||||
import { Strings } from "../../utils/strings";
|
||||
import { PrettyBytes } from "../../utils/bytes";
|
||||
import { Bytes } from "../../utils/bytes";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { CallbackDataParams, ECBasicOption } from "echarts/types/dist/shared";
|
||||
import * as echarts from "echarts/core";
|
||||
@ -36,6 +36,16 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
}
|
||||
}, [chart, div]);
|
||||
|
||||
useEffect(() => {
|
||||
const refresh = () => chart.current?.resize();
|
||||
|
||||
window.addEventListener("resize", refresh);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", refresh);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const data = availabilities.map((a, index) => {
|
||||
return {
|
||||
name: Strings.shortId(a.id),
|
||||
@ -65,7 +75,7 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
a.minPrice +
|
||||
"<br/>" +
|
||||
"Size " +
|
||||
PrettyBytes(a.totalSize)
|
||||
Bytes.pretty(a.totalSize)
|
||||
);
|
||||
},
|
||||
},
|
||||
@ -87,7 +97,7 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
params.marker +
|
||||
"Slot " +
|
||||
slot.id +
|
||||
PrettyBytes(parseFloat(slot.size))
|
||||
Bytes.pretty(parseFloat(slot.size))
|
||||
);
|
||||
},
|
||||
},
|
||||
@ -121,7 +131,7 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
return (
|
||||
params.marker +
|
||||
" Space remaining " +
|
||||
PrettyBytes(
|
||||
Bytes.pretty(
|
||||
space.quotaMaxBytes -
|
||||
space.quotaReservedBytes -
|
||||
space.quotaUsedBytes
|
||||
@ -170,6 +180,8 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
};
|
||||
|
||||
chart.current.setOption(option);
|
||||
chart.current?.resize();
|
||||
|
||||
// chart.current.off("click");
|
||||
// chart.current.on("click", function (params) {
|
||||
// // console.info(params.componentIndex);
|
||||
@ -196,7 +208,6 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
ref={div}
|
||||
className="sunburst"
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
}}></div>
|
||||
);
|
||||
|
@ -173,16 +173,18 @@ describe("files", () => {
|
||||
quotaUsedBytes: GB,
|
||||
totalBlocks: 0
|
||||
}
|
||||
assert.deepEqual(AvailabilityUtils.maxValue(space), 5 * GB);
|
||||
assert.deepEqual(AvailabilityUtils.maxValue(space), 5 * GB - 1);
|
||||
})
|
||||
|
||||
it("checks the availability max value", async () => {
|
||||
const availability = {
|
||||
totalSize: GB
|
||||
totalSizeUnit: "gb",
|
||||
totalSize: 1
|
||||
} as AvailabilityState
|
||||
|
||||
assert.deepEqual(AvailabilityUtils.isValid(availability, GB * 2), true);
|
||||
assert.deepEqual(AvailabilityUtils.isValid({ ...availability, totalSize: -1 }, GB), false);
|
||||
assert.deepEqual(AvailabilityUtils.isValid({ ...availability, totalSize: 2 * GB }, GB), false);
|
||||
assert.deepEqual(AvailabilityUtils.isValid({ ...availability, totalSize: GB }, 2 * GB), false);
|
||||
})
|
||||
|
||||
it("toggles item in array", async () => {
|
||||
|
@ -39,7 +39,8 @@ export const AvailabilityUtils = {
|
||||
return bytes / this.unitValue(unit || "gb")
|
||||
},
|
||||
maxValue(space: CodexNodeSpace) {
|
||||
return space.quotaMaxBytes - space.quotaReservedBytes - space.quotaUsedBytes
|
||||
// Remove 1 byte to allow to create an availability with the max space possible
|
||||
return space.quotaMaxBytes - space.quotaReservedBytes - space.quotaUsedBytes - 1
|
||||
},
|
||||
unitValue(unit: "gb" | "tb") {
|
||||
return unit === "tb" ? TB : GB
|
||||
@ -47,7 +48,7 @@ export const AvailabilityUtils = {
|
||||
isValid: (
|
||||
availability: AvailabilityState,
|
||||
max: number
|
||||
) => availability.totalSize > 0 && availability.totalSize <= max
|
||||
) => availability.totalSize > 0 && availability.totalSize * AvailabilityUtils.unitValue(availability.totalSizeUnit) <= max
|
||||
,
|
||||
toggle: <T>(arr: Array<T>, value: T) =>
|
||||
arr.includes(value) ? arr.filter(i => i !== value) : [...arr, value],
|
||||
@ -88,5 +89,5 @@ export const AvailabilityUtils = {
|
||||
"#D2493C22",
|
||||
"#D2493C11",
|
||||
"#D2493C00",
|
||||
]
|
||||
],
|
||||
}
|
@ -9,6 +9,8 @@ import {
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { CodexAvailabilityCreateResponse } from "@codex-storage/sdk-js";
|
||||
import { Times } from "../../utils/times";
|
||||
import { AvailabilityUtils } from "./availability.utils";
|
||||
|
||||
|
||||
export function useAvailabilityMutation(
|
||||
@ -42,15 +44,15 @@ export function useAvailabilityMutation(
|
||||
|
||||
return fn({
|
||||
...input,
|
||||
duration,
|
||||
totalSize: Math.trunc(totalSize),
|
||||
duration: Times.value(durationUnit) * duration,
|
||||
totalSize: Math.trunc(totalSize * AvailabilityUtils.unitValue(totalSizeUnit)),
|
||||
});
|
||||
},
|
||||
onSuccess: (res, body) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["availabilities"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["space"] });
|
||||
|
||||
WebStorage.delete("availability");
|
||||
WebStorage.delete("availability-1");
|
||||
WebStorage.delete("availability-step");
|
||||
|
||||
if (typeof res === "object" && body.name) {
|
||||
|
@ -4,6 +4,13 @@
|
||||
padding: 16px;
|
||||
background-color: rgb(35, 35, 35);
|
||||
|
||||
@media (max-width: 800px) {
|
||||
&,
|
||||
td:nth-child(2) {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -147,18 +147,38 @@
|
||||
box-shadow: 0 0 0 3px rgba(150, 150, 150, 0.2);
|
||||
}
|
||||
|
||||
&:has(option[value="USD"]:checked) {
|
||||
background-image: url(/icons/us-flag.svg);
|
||||
&:has(option:checked) {
|
||||
background-position: 10px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px;
|
||||
}
|
||||
|
||||
&:has(option[value="USD"]:checked) {
|
||||
background-image: url(/icons/us-flag.svg);
|
||||
}
|
||||
|
||||
&:has(option[value="EUR"]:checked) {
|
||||
background-image: url(/icons/euro-flag.svg);
|
||||
background-position: 10px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 16px;
|
||||
}
|
||||
|
||||
&:has(option[value="AUD"]:checked) {
|
||||
background-image: url(/icons/aud-flag.svg);
|
||||
}
|
||||
|
||||
&:has(option[value="CAD"]:checked) {
|
||||
background-image: url(/icons/cad-flag.svg);
|
||||
}
|
||||
|
||||
&:has(option[value="CNY"]:checked) {
|
||||
background-image: url(/icons/cny-flag.svg);
|
||||
}
|
||||
|
||||
&:has(option[value="BTC"]:checked) {
|
||||
background-image: url(/icons/btc-flag.svg);
|
||||
}
|
||||
|
||||
&:has(option[value="ETH"]:checked) {
|
||||
background-image: url(/icons/eth-flag.svg);
|
||||
}
|
||||
|
||||
option {
|
||||
@ -205,6 +225,5 @@
|
||||
|
||||
.lines {
|
||||
height: 200;
|
||||
transform: scale(1.25);
|
||||
}
|
||||
}
|
||||
|
@ -33,24 +33,52 @@ export function WalletCard({ tab }: Props) {
|
||||
}
|
||||
}, [chart, div]);
|
||||
|
||||
useEffect(() => {
|
||||
const refresh = () => chart.current?.resize();
|
||||
|
||||
window.addEventListener("resize", refresh);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", refresh);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onCurrencyChange = async (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = e.currentTarget.value;
|
||||
setCurrency(value);
|
||||
|
||||
if (value === "USD") {
|
||||
setTokenValue(1540);
|
||||
} else {
|
||||
} else if (["BTC", "ETH"].includes(value) === false) {
|
||||
const json = await fetch(
|
||||
"https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/usd.json"
|
||||
).then((res) => res.json());
|
||||
setTokenValue(defaultTokenValue * json.usd.eur);
|
||||
setTokenValue(defaultTokenValue * json.usd[value.toLocaleLowerCase()]);
|
||||
} else {
|
||||
const json = await fetch(
|
||||
"https://api.coinbase.com/v2/prices/" +
|
||||
value.toLocaleLowerCase() +
|
||||
"-USD/spot.json"
|
||||
).then((res) => res.json());
|
||||
setTokenValue(defaultTokenValue / json.data.amount);
|
||||
}
|
||||
};
|
||||
|
||||
if (chart.current) {
|
||||
const today = new Date();
|
||||
const startOfWeek = today.getDate() - today.getDay() + 1;
|
||||
const startDates = [];
|
||||
|
||||
today.setDate(startOfWeek);
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
startDates.push(today.toISOString().split("T")[0]);
|
||||
today.setDate(today.getDate() + 7);
|
||||
}
|
||||
|
||||
const data = {
|
||||
daily: ["MON", "TUE", "WED", "THU", "WED", "SAT", "SUN"],
|
||||
weekly: ["1", "2", "3", "4", "5", "6"],
|
||||
weekly: startDates,
|
||||
monthly: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN"],
|
||||
}[tab];
|
||||
|
||||
@ -72,6 +100,14 @@ export function WalletCard({ tab }: Props) {
|
||||
type: "value",
|
||||
show: false,
|
||||
},
|
||||
grid: [
|
||||
{
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 50,
|
||||
bottom: 50,
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
data: [220, 932, 401, 934, 1290, 1330, 1450].slice(0, data.length),
|
||||
@ -89,7 +125,7 @@ export function WalletCard({ tab }: Props) {
|
||||
},
|
||||
};
|
||||
|
||||
chart.current.setOption(option);
|
||||
chart.current.setOption(option, true);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -115,7 +151,10 @@ export function WalletCard({ tab }: Props) {
|
||||
<section>
|
||||
{/* <WalletChart></WalletChart> */}
|
||||
{/* <WalletLines></WalletLines> */}
|
||||
<div className="lines" ref={div} style={{ height: 200 }}></div>
|
||||
<div
|
||||
className="lines"
|
||||
ref={div}
|
||||
style={{ height: 200, width: "100%" }}></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@ -124,14 +163,20 @@ export function WalletCard({ tab }: Props) {
|
||||
<span>TOKEN</span>
|
||||
<div className="row">
|
||||
<var>
|
||||
{tokenValue.toFixed(2)} {currency}
|
||||
{tokenValue.toFixed(["BTC", "ETH"].includes(currency) ? 8 : 2)}{" "}
|
||||
{currency}
|
||||
</var>
|
||||
<small>+ 5%</small>
|
||||
</div>
|
||||
</div>
|
||||
<select defaultValue={currency} onChange={onCurrencyChange}>
|
||||
<option value={"USD"}>USD</option>
|
||||
<option value={"BTC"}>BTC</option>
|
||||
<option value={"ETH"}>ETH</option>
|
||||
<option value={"EUR"}>EUR</option>
|
||||
<option value={"AUD"}>AUD</option>
|
||||
<option value={"CAD"}>CAD</option>
|
||||
<option value={"CNY"}>CNY</option>
|
||||
</select>
|
||||
</footer>
|
||||
</div>
|
||||
|
@ -10,6 +10,37 @@
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
transition: bottom 0.35s;
|
||||
position: fixed;
|
||||
bottom: -500px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(47, 47, 47, 1);
|
||||
border-top: 1px solid rgba(150, 150, 150, 0.2);
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
|
||||
padding: 16px;
|
||||
|
||||
&[aria-expanded] {
|
||||
bottom: 0;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
li + li {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(150, 150, 150, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.folder-button {
|
||||
display: none;
|
||||
|
@ -1,10 +1,19 @@
|
||||
import { ButtonIcon, Cell } from "@codex-storage/marketplace-ui-components";
|
||||
import {
|
||||
Backdrop,
|
||||
ButtonIcon,
|
||||
Cell,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { FolderButton } from "./FolderButton";
|
||||
import { CodexDataContent } from "@codex-storage/sdk-js";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import "./FileActions.css";
|
||||
import DownloadIcon from "../../assets/icons/download-file.svg?react";
|
||||
import InfoFileIcon from "../../assets/icons/info-file.svg?react";
|
||||
import DotsIcon from "../../assets/icons/dots.svg?react";
|
||||
import { useIsMobile } from "../../hooks/useMobile";
|
||||
import { useState } from "react";
|
||||
import { attributes } from "../../utils/attributes";
|
||||
import CopyIcon from "../../assets/icons/copy.svg?react";
|
||||
|
||||
type Props = {
|
||||
content: CodexDataContent;
|
||||
@ -19,7 +28,53 @@ export function FileActions({
|
||||
onFolderToggle,
|
||||
onDetails,
|
||||
}: Props) {
|
||||
const isMobile = useIsMobile();
|
||||
const url = CodexSdk.url() + "/api/codex/v1/data/";
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const onClose = () => setIsExpanded(false);
|
||||
|
||||
const onOpen = () => setIsExpanded(true);
|
||||
|
||||
const onCopy = (cid: string) => {
|
||||
setIsExpanded(false);
|
||||
navigator.clipboard.writeText(cid);
|
||||
};
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<Cell className="file-actions">
|
||||
<>
|
||||
<ButtonIcon
|
||||
variant="small"
|
||||
onClick={onOpen}
|
||||
Icon={() => (
|
||||
<DotsIcon width={24} height={24}></DotsIcon>
|
||||
)}></ButtonIcon>
|
||||
<ul {...attributes({ "aria-expanded": isExpanded })}>
|
||||
<li
|
||||
onClick={() => {
|
||||
window.open(url + content.cid, "_blank");
|
||||
setIsExpanded(false);
|
||||
}}>
|
||||
<DownloadIcon width={20}></DownloadIcon> Download
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
onDetails(content.cid);
|
||||
setIsExpanded(false);
|
||||
}}>
|
||||
<InfoFileIcon width={20}></InfoFileIcon> Details
|
||||
</li>
|
||||
<li onClick={() => onCopy(content.cid)}>
|
||||
<CopyIcon width={20}></CopyIcon> Copy
|
||||
</li>
|
||||
</ul>
|
||||
<Backdrop open={isExpanded} onClose={onClose}></Backdrop>
|
||||
</>
|
||||
</Cell>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Cell className="file-actions">
|
||||
|
@ -17,6 +17,12 @@
|
||||
height: 40px;
|
||||
background-color: rgba(20, 20, 20, 0.6);
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,11 +14,27 @@ type Props = {
|
||||
};
|
||||
|
||||
export function FileCell({ content }: Props) {
|
||||
const [toast, setToast] = useState({ time: 0, message: "" });
|
||||
const [toast, setToast] = useState({
|
||||
time: 0,
|
||||
message: "",
|
||||
variant: "success" as "success" | "error",
|
||||
});
|
||||
|
||||
const onCopy = (cid: string) => {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(cid);
|
||||
setToast({ message: "CID copied to the clipboard.", time: Date.now() });
|
||||
setToast({
|
||||
message: "CID copied to the clipboard.",
|
||||
time: Date.now(),
|
||||
variant: "success" as "success",
|
||||
});
|
||||
} else {
|
||||
setToast({
|
||||
message: "Sorry the CID cannot be copied to the clipboard.",
|
||||
time: Date.now(),
|
||||
variant: "error" as "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -44,7 +60,11 @@ export function FileCell({ content }: Props) {
|
||||
)}></ButtonIcon>
|
||||
</div>
|
||||
|
||||
<Toast message={toast.message} time={toast.time} variant={"success"} />
|
||||
<Toast
|
||||
message={toast.message}
|
||||
time={toast.time}
|
||||
variant={toast.variant}
|
||||
/>
|
||||
</Cell>
|
||||
</>
|
||||
);
|
||||
|
@ -21,17 +21,6 @@
|
||||
span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
background-color: rgb(47, 47, 47);
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
left: -3px;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview {
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
WebFileIcon,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { CodexDataContent, CodexPurchase } from "@codex-storage/sdk-js";
|
||||
import { PrettyBytes } from "../../utils/bytes";
|
||||
import { Bytes } from "../../utils/bytes";
|
||||
import { CidCopyButton } from "./CidCopyButton";
|
||||
import "./FileDetails.css";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
@ -136,7 +136,7 @@ export function FileDetails({ onClose, details }: Props) {
|
||||
|
||||
<li>
|
||||
<p>Size:</p>
|
||||
<p>{PrettyBytes(details.manifest.datasetSize)}</p>
|
||||
<p>{Bytes.pretty(details.manifest.datasetSize)}</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
import { PrettyBytes } from "../../utils/bytes";
|
||||
import { Bytes } from "../../utils/bytes";
|
||||
import "./Files.css";
|
||||
import {
|
||||
Tabs,
|
||||
@ -164,7 +164,7 @@ export function Files({ limit }: Props) {
|
||||
<Row
|
||||
cells={[
|
||||
<FileCell content={c}></FileCell>,
|
||||
<Cell>{PrettyBytes(c.manifest.datasetSize)}</Cell>,
|
||||
<Cell>{Bytes.pretty(c.manifest.datasetSize)}</Cell>,
|
||||
<Cell>
|
||||
{FilesUtils.formatDate(c.manifest.uploadedAt).toString()}
|
||||
</Cell>,
|
||||
|
@ -294,8 +294,8 @@ describe("files", () => {
|
||||
});
|
||||
|
||||
it("formats date", async () => {
|
||||
const utcDate = new Date(Date.UTC(2024, 10, 20, 11, 36));
|
||||
|
||||
assert.equal(FilesUtils.formatDate(1732102577), "20 Nov 2024, 11:36");
|
||||
|
||||
assert.equal(FilesUtils.formatDate(1732102577), "20 Nov 2024, " + utcDate.getHours() + ":" + utcDate.getMinutes());
|
||||
})
|
||||
})
|
@ -7,6 +7,12 @@
|
||||
> div:first-child {
|
||||
position: relative;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
@ -22,5 +28,11 @@
|
||||
select {
|
||||
border-color: rgba(150, 150, 150, 1);
|
||||
padding-left: 40px;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import PurchaseIcon from "../../assets/icons/purchase.svg?react";
|
||||
import HostIcon from "../../assets/icons/host.svg?react";
|
||||
import LogsIcon from "../../assets/icons/logs.svg?react";
|
||||
import SettingsIcon from "../../assets/icons/settings.svg?react";
|
||||
import CloseIcon from "../../assets/icons/close.svg?react";
|
||||
import HelpIcon from "../../assets/icons/help.svg?react";
|
||||
import DisclaimerIcon from "../../assets/icons/disclaimer.svg?react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
@ -65,6 +66,8 @@ export function Menu({ isExpanded, onExpanded }: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
const Icon = isMobile ? CloseIcon : ExpandIcon;
|
||||
|
||||
return (
|
||||
<>
|
||||
<aside
|
||||
@ -77,7 +80,7 @@ export function Menu({ isExpanded, onExpanded }: Props) {
|
||||
<Logo onClick={onLogoClick} width={30} />
|
||||
<Logotype height={34} />
|
||||
<div>
|
||||
<ExpandIcon onClick={onExpandMenu}></ExpandIcon>
|
||||
<Icon onClick={onExpandMenu}></Icon>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -142,20 +145,20 @@ export function Menu({ isExpanded, onExpanded }: Props) {
|
||||
</NavLink>
|
||||
<NavLink onClick={onClose} to="/dashboard/availabilities">
|
||||
<span>
|
||||
<HostIcon />
|
||||
<HostIcon width={20} height={20} />
|
||||
</span>
|
||||
<span>Host</span>
|
||||
</NavLink>
|
||||
<hr />
|
||||
<NavLink onClick={onClose} to="/dashboard/peers">
|
||||
<span>
|
||||
<PeersIcon width={20} />
|
||||
<PeersIcon width={20} height={20} />
|
||||
</span>
|
||||
<span>Peers</span>
|
||||
</NavLink>
|
||||
<NavLink onClick={onClose} to="/dashboard/logs">
|
||||
<span>
|
||||
<LogsIcon width={24} />
|
||||
<LogsIcon width={20} height={20} />
|
||||
</span>
|
||||
<span>Log</span>
|
||||
</NavLink>
|
||||
|
@ -19,6 +19,7 @@
|
||||
left: -300px;
|
||||
position: fixed;
|
||||
z-index: 12;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&[aria-expanded] {
|
||||
|
@ -36,6 +36,12 @@
|
||||
|
||||
section > *:first-child {
|
||||
flex: 0.5;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
flex: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -203,6 +209,12 @@
|
||||
right: 6rem;
|
||||
bottom: 16px;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
animation-name: example;
|
||||
animation-duration: 2.5s;
|
||||
|
@ -18,6 +18,12 @@
|
||||
top: -35px;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
|
7
src/components/StorageRequestSetup/Commitment.css
Normal file
7
src/components/StorageRequestSetup/Commitment.css
Normal file
@ -0,0 +1,7 @@
|
||||
.commitment {
|
||||
--codex-input-group-background-color: transparent;
|
||||
|
||||
span {
|
||||
right: 155px;
|
||||
}
|
||||
}
|
70
src/components/StorageRequestSetup/Commitment.tsx
Normal file
70
src/components/StorageRequestSetup/Commitment.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import { InputGroup, Tooltip } from "@codex-storage/marketplace-ui-components";
|
||||
import "../CardNumbers/CardNumbers.css";
|
||||
import "./Commitment.css";
|
||||
|
||||
import { ChangeEvent, useState } from "react";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import InfoIcon from "../../assets/icons/info.svg?react";
|
||||
import { attributes } from "../../utils/attributes";
|
||||
|
||||
type Props = {
|
||||
unit: "months" | "days";
|
||||
value: string;
|
||||
onChange: (value: string, unit: "months" | "days") => void;
|
||||
onValidation?: (value: string) => string;
|
||||
};
|
||||
|
||||
const TESTNET_MAX_VALUE = 7;
|
||||
|
||||
export function Commitment({ unit, value, onValidation, onChange }: Props) {
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const onValueChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
onValueOrUnitChange(e.currentTarget.value, unit);
|
||||
};
|
||||
const onUnitChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
onValueOrUnitChange(value, e.currentTarget.value as "months" | "days");
|
||||
};
|
||||
|
||||
const onValueOrUnitChange = (val: string, unit: "months" | "days") => {
|
||||
onChange(val, unit);
|
||||
|
||||
const msg = onValidation?.(val);
|
||||
|
||||
if (msg) {
|
||||
setError(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
setError("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(["card-number cardNumber-container commitment"])}
|
||||
{...attributes({ "aria-invalid": !!error })}>
|
||||
<InputGroup
|
||||
id="duration"
|
||||
name="duration"
|
||||
type="number"
|
||||
label="Full period of the contract"
|
||||
isInvalid={!!error}
|
||||
onChange={onValueChange}
|
||||
onGroupChange={onUnitChange}
|
||||
value={value}
|
||||
min={0}
|
||||
max={TESTNET_MAX_VALUE}
|
||||
group={[
|
||||
["days", "days"],
|
||||
// ["months", "months"],
|
||||
]}
|
||||
groupValue={unit}
|
||||
/>
|
||||
|
||||
<Tooltip message={error || "The duration of the request in months"}>
|
||||
<InfoIcon></InfoIcon>
|
||||
</Tooltip>
|
||||
<span>{"Contract duration"}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -22,8 +22,8 @@ const CONFIRM_STATE = 2;
|
||||
|
||||
const defaultStorageRequest: StorageRequest = {
|
||||
cid: "",
|
||||
availabilityUnit: "months",
|
||||
availability: 1,
|
||||
availabilityUnit: "days",
|
||||
tolerance: 1,
|
||||
proofProbability: 1,
|
||||
nodes: 3,
|
||||
@ -43,7 +43,7 @@ export function StorageRequestCreate() {
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
WebStorage.get<number>("storage-request-step"),
|
||||
WebStorage.get<StorageRequest>("storage-request"),
|
||||
WebStorage.get<StorageRequest>("storage-request-3"),
|
||||
]).then(([s, data]) => {
|
||||
if (s) {
|
||||
dispatch({
|
||||
@ -83,11 +83,11 @@ export function StorageRequestCreate() {
|
||||
WebStorage.set("storage-request-step", step);
|
||||
|
||||
if (step == CONFIRM_STATE) {
|
||||
const { availabilityUnit, availability, expiration, ...rest } =
|
||||
const { availability, availabilityUnit, expiration, ...rest } =
|
||||
storageRequest;
|
||||
mutateAsync({
|
||||
...rest,
|
||||
duration: Times.toSeconds(availability, availabilityUnit),
|
||||
duration: availability * Times.value(availabilityUnit),
|
||||
expiry: expiration * 60,
|
||||
});
|
||||
} else {
|
||||
@ -101,7 +101,7 @@ export function StorageRequestCreate() {
|
||||
const onStorageRequestChange = (data: Partial<StorageRequest>) => {
|
||||
const val = { ...storageRequest, ...data };
|
||||
|
||||
WebStorage.set("storage-request", val);
|
||||
WebStorage.set("storage-request-3", val);
|
||||
|
||||
setStorageRequest(val);
|
||||
};
|
||||
|
@ -14,6 +14,7 @@ import CommitmentIcon from "../../assets/icons/commitment.svg?react";
|
||||
import RequestDurationIcon from "../../assets/icons/request-duration.svg?react";
|
||||
import { attributes } from "../../utils/attributes";
|
||||
import { Strings } from "../../utils/strings";
|
||||
import { Commitment } from "./Commitment";
|
||||
|
||||
type Durability = {
|
||||
nodes: number;
|
||||
@ -27,6 +28,8 @@ const durabilities = [
|
||||
{ nodes: 5, tolerance: 2, proofProbability: 4 },
|
||||
];
|
||||
|
||||
const TESTNET_MAX_VALUE = 7;
|
||||
|
||||
const findDurabilityIndex = (d: Durability) => {
|
||||
const s = JSON.stringify({
|
||||
nodes: d.nodes,
|
||||
@ -57,10 +60,10 @@ export function StorageRequestReview({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const invalid = isInvalidConstrainst(
|
||||
storageRequest.nodes,
|
||||
storageRequest.tolerance
|
||||
);
|
||||
const invalid =
|
||||
isInvalidConstrainst(storageRequest.nodes, storageRequest.tolerance) ||
|
||||
storageRequest.availability > TESTNET_MAX_VALUE ||
|
||||
storageRequest.availability == 0;
|
||||
|
||||
dispatch({
|
||||
type: "toggle-buttons",
|
||||
@ -133,26 +136,29 @@ export function StorageRequestReview({
|
||||
const isInvalidAvailability = (data: string) => {
|
||||
const [value] = data.split(" ");
|
||||
|
||||
const error = isInvalidNumber(value);
|
||||
const error =
|
||||
isInvalidNumber(value) ||
|
||||
isInvalidAvailabilityNumber(value) ||
|
||||
isRequiredNumber(value);
|
||||
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
// if (!unit.endsWith("s")) {
|
||||
// unit += "s";
|
||||
// }
|
||||
|
||||
// if (!units.includes(unit)) {
|
||||
// return "Invalid unit must one of: minutes, hours, days, months, years";
|
||||
// }
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
const isInvalidNumber = (value: string) =>
|
||||
isNaN(Number(value)) ? "The value is not a number" : "";
|
||||
|
||||
const isRequiredNumber = (value: string) =>
|
||||
value == "0" ? "The value has to be more than 0." : "";
|
||||
|
||||
const isInvalidAvailabilityNumber = (value: string) =>
|
||||
parseInt(value, 10) > TESTNET_MAX_VALUE
|
||||
? "The maximum value is on the current Testnet is 7 days"
|
||||
: "";
|
||||
|
||||
const onNodesChange = (value: string) =>
|
||||
onUpdateDurability({ nodes: Number(value) });
|
||||
|
||||
@ -162,16 +168,12 @@ export function StorageRequestReview({
|
||||
const onProofProbabilityChange = (value: string) =>
|
||||
onUpdateDurability({ proofProbability: Number(value) });
|
||||
|
||||
const onAvailabilityChange = (value: string) => {
|
||||
const onAvailabilityChange = (value: string, unit: "days" | "months") => {
|
||||
const [availability] = value.split(" ");
|
||||
|
||||
// if (!availabilityUnit.endsWith("s")) {
|
||||
// availabilityUnit += "s";
|
||||
// }
|
||||
|
||||
onStorageRequestChange({
|
||||
availability: Number(availability),
|
||||
availabilityUnit: "months",
|
||||
availabilityUnit: unit,
|
||||
});
|
||||
};
|
||||
|
||||
@ -184,18 +186,6 @@ export function StorageRequestReview({
|
||||
const onCollateralChange = (value: string) =>
|
||||
onStorageRequestChange({ collateral: Number(value) });
|
||||
|
||||
// const pluralizeUnit = () => {
|
||||
// if (data.availability > 1 && !data.availabilityUnit.endsWith("s")) {
|
||||
// return data.availability + " " +data.availabilityUnit + "s";
|
||||
// }
|
||||
|
||||
// if (data.availability <= 1 && data.availabilityUnit.endsWith("s")) {
|
||||
// return data.availabilityUnit.slice(0, -1);
|
||||
// }
|
||||
|
||||
// return data.availabilityUnit;
|
||||
// };
|
||||
|
||||
const availability = storageRequest.availability;
|
||||
|
||||
return (
|
||||
@ -286,22 +276,19 @@ export function StorageRequestReview({
|
||||
</div>
|
||||
|
||||
<div className="grid">
|
||||
<CardNumbers
|
||||
helper="The duration of the request in months"
|
||||
id="duration"
|
||||
title={"Full period of the contract"}
|
||||
<Commitment
|
||||
unit={storageRequest.availabilityUnit}
|
||||
value={availability.toString()}
|
||||
onChange={onAvailabilityChange}
|
||||
onValidation={isInvalidAvailability}
|
||||
unit="Contract duration"></CardNumbers>
|
||||
onValidation={isInvalidAvailability}></Commitment>
|
||||
<CardNumbers
|
||||
helper="Represents how much collateral is asked from hosts that wants to fill a slots"
|
||||
helper="Represents how much collateral is asked from hosts if they don't fulfill the contract."
|
||||
id="collateral"
|
||||
unit={"Collateral"}
|
||||
value={storageRequest.collateral.toString()}
|
||||
onChange={onCollateralChange}
|
||||
onValidation={isInvalidNumber}
|
||||
title="Reward tokens for hosts"></CardNumbers>
|
||||
title="Penality tokens"></CardNumbers>
|
||||
<CardNumbers
|
||||
helper="The maximum amount of tokens paid per second per slot to hosts the client is willing to pay."
|
||||
id="reward"
|
||||
@ -309,7 +296,7 @@ export function StorageRequestReview({
|
||||
value={storageRequest.reward.toString()}
|
||||
onChange={onRewardChange}
|
||||
onValidation={isInvalidNumber}
|
||||
title="Penality tokens"></CardNumbers>
|
||||
title="Reward tokens for hosts"></CardNumbers>
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
|
@ -16,29 +16,14 @@ export type StoragePriceStepValue = {
|
||||
expiration: number;
|
||||
};
|
||||
|
||||
export type StorageAvailabilityUnit =
|
||||
| "days"
|
||||
| "months"
|
||||
| "years"
|
||||
| "minutes"
|
||||
| "hours";
|
||||
|
||||
export type StorageAvailabilityValue = {
|
||||
value: number;
|
||||
unit: StorageAvailabilityUnit;
|
||||
};
|
||||
|
||||
export type AvailabilityUnit =
|
||||
| "days"
|
||||
| "months"
|
||||
| "years"
|
||||
| "minutes"
|
||||
| "hours";
|
||||
|
||||
export type StorageRequest = {
|
||||
cid: string;
|
||||
availability: number;
|
||||
availabilityUnit: AvailabilityUnit;
|
||||
availabilityUnit: "months" | "days";
|
||||
tolerance: number;
|
||||
proofProbability: number;
|
||||
nodes: number;
|
||||
|
@ -29,7 +29,7 @@ export function useStorageRequestMutation(
|
||||
// }
|
||||
|
||||
WebStorage.delete("storage-request-step");
|
||||
WebStorage.delete("storage-request");
|
||||
WebStorage.delete("storage-request-3");
|
||||
|
||||
setError(null);
|
||||
|
||||
|
@ -9,7 +9,7 @@ export function usePersistence(isCodexOnline: boolean) {
|
||||
queryKey: [],
|
||||
queryFn: async () => {
|
||||
return CodexSdk.marketplace()
|
||||
.purchases()
|
||||
.activeSlots()
|
||||
.then((data) => Promises.rejectOnError(data, report));
|
||||
},
|
||||
|
||||
|
@ -6,6 +6,12 @@
|
||||
|
||||
> .card {
|
||||
flex: 1 1 50%;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
|
@ -10,7 +10,7 @@ import "./availabilities.css";
|
||||
import { AvailabilitiesTable } from "../../components/Availability/AvailabilitiesTable";
|
||||
import { AvailabilityEdit } from "../../components/Availability/AvailabilityEdit";
|
||||
import { Strings } from "../../utils/strings";
|
||||
import { PrettyBytes } from "../../utils/bytes";
|
||||
import { Bytes } from "../../utils/bytes";
|
||||
import { Sunburst } from "../../components/Availability/Sunburst";
|
||||
import { Errors } from "../../utils/errors";
|
||||
import { AvailabilityWithSlots } from "../../components/Availability/types";
|
||||
@ -111,7 +111,7 @@ export function AvailabilitiesRoute() {
|
||||
(a, index) => ({
|
||||
title: Strings.shortId(a.id),
|
||||
size: a.totalSize,
|
||||
tooltip: a.id + "\u000D\u000A" + PrettyBytes(a.totalSize),
|
||||
tooltip: a.id + "\u000D\u000A" + Bytes.pretty(a.totalSize),
|
||||
color: AvailabilityUtils.availabilityColors[index],
|
||||
})
|
||||
);
|
||||
@ -170,7 +170,7 @@ export function AvailabilitiesRoute() {
|
||||
<footer>
|
||||
<b>Node</b>
|
||||
<small>
|
||||
{PrettyBytes(space.quotaMaxBytes)} allocated for the node
|
||||
{Bytes.pretty(space.quotaMaxBytes)} allocated for the node
|
||||
</small>
|
||||
</footer>
|
||||
</div>
|
||||
|
@ -35,8 +35,19 @@
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 187px;
|
||||
gap: 8px;
|
||||
|
||||
@media (min-width: 801px) {
|
||||
& {
|
||||
width: 187px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,4 +175,12 @@
|
||||
.gauge {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.peers-chart {
|
||||
transform: scale(0.8);
|
||||
margin: auto;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,9 @@
|
||||
background-color: rgb(20, 20, 20);
|
||||
|
||||
> div {
|
||||
padding: 16px;
|
||||
padding: 8px;
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
@media (min-width: 800px) {
|
||||
padding: 24px 48px;
|
||||
}
|
||||
}
|
||||
|
12
src/utils/bytes.test.ts
Normal file
12
src/utils/bytes.test.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { assert, describe, it } from "vitest";
|
||||
import { Bytes } from "./bytes";
|
||||
import { GB } from "./constants";
|
||||
|
||||
describe("bytes", () => {
|
||||
it("display the bytes", async () => {
|
||||
assert.equal(Bytes.pretty(0), "0 B");
|
||||
assert.equal(Bytes.pretty(512), "512.0 B");
|
||||
assert.equal(Bytes.pretty(1025), "1.0 KB");
|
||||
assert.equal(Bytes.pretty(GB), "1.0 GB");
|
||||
});
|
||||
})
|
@ -1,14 +1,17 @@
|
||||
export const PrettyBytes = (bytes: number) => {
|
||||
const sizes = ["bytes", "KB", "MB", "GB", "TB"];
|
||||
export const Bytes = {
|
||||
pretty(bytes: number) {
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
||||
if (bytes == 0) {
|
||||
return "0 b";
|
||||
return "0 B";
|
||||
}
|
||||
|
||||
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)).toString());
|
||||
|
||||
if (i == 0) {
|
||||
return bytes + " " + sizes[i];
|
||||
return bytes.toFixed(1) + " " + sizes[i];
|
||||
}
|
||||
|
||||
return (bytes / Math.pow(1024, i)).toFixed(1) + " " + sizes[i];
|
||||
};
|
||||
}
|
||||
|
||||
}
|
28
src/utils/times.test.ts
Normal file
28
src/utils/times.test.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { assert, describe, it } from "vitest";
|
||||
import { Times } from "./times";
|
||||
|
||||
describe("times", () => {
|
||||
it("display the times", async () => {
|
||||
assert.equal(Times.pretty(0), "0 second");
|
||||
assert.equal(Times.pretty(2), "2 seconds");
|
||||
assert.equal(Times.pretty(60), "1 minute");
|
||||
assert.equal(Times.pretty(90), "1.5 minutes");
|
||||
assert.equal(Times.pretty(3600), "1 hour");
|
||||
assert.equal(Times.pretty(3600 * 2), "2 hours");
|
||||
assert.equal(Times.pretty(3600 * 24), "1 day");
|
||||
assert.equal(Times.pretty(3600 * 36), "1.5 days");
|
||||
assert.equal(Times.pretty(3600 * 24 * 30), "1 month");
|
||||
});
|
||||
|
||||
it("guess the time unit", async () => {
|
||||
assert.equal(Times.unit(0), "hours");
|
||||
assert.equal(Times.unit(3600 * 24), "days");
|
||||
assert.equal(Times.unit(3600 * 24 * 30), "months");
|
||||
})
|
||||
|
||||
it("get the seconds for a time unit given", async () => {
|
||||
assert.equal(Times.value("hours"), 3600);
|
||||
assert.equal(Times.value("days"), 3600 * 24);
|
||||
assert.equal(Times.value("months"), 3600 * 24 * 30);
|
||||
})
|
||||
})
|
@ -6,31 +6,33 @@ export type TimesUnit =
|
||||
| "hours"
|
||||
| "seconds";
|
||||
|
||||
const plural = (value: number, unit: TimesUnit) =>
|
||||
value > 1 ? value + ` ${unit}` : value + ` ${unit.slice(0, -1)}`;
|
||||
const plural = (value: number, unit: TimesUnit) => {
|
||||
const val = Number.isInteger(value) ? value : value.toFixed(1)
|
||||
return value > 1 ? val + ` ${unit}` : val + ` ${unit.slice(0, -1)}`;
|
||||
}
|
||||
|
||||
export const Times = {
|
||||
toSeconds(value: number, unit: TimesUnit) {
|
||||
let seconds = value;
|
||||
let val = value;
|
||||
/* eslint-disable no-fallthrough */
|
||||
switch (unit) {
|
||||
// @ts-expect-error - We don't want to break
|
||||
case "years":
|
||||
seconds *= 365;
|
||||
val *= 365;
|
||||
// @ts-expect-error - We don't want to break
|
||||
case "months":
|
||||
seconds *= 30;
|
||||
val *= 30;
|
||||
// @ts-expect-error - We don't want to break
|
||||
case "days":
|
||||
seconds *= 24;
|
||||
val *= 24;
|
||||
// @ts-expect-error - We don't want to break
|
||||
case "hours":
|
||||
seconds *= 60;
|
||||
val *= 60;
|
||||
case "minutes":
|
||||
seconds *= 60;
|
||||
val *= 60;
|
||||
}
|
||||
|
||||
return seconds;
|
||||
return val;
|
||||
},
|
||||
|
||||
pretty(value: number) {
|
||||
@ -62,7 +64,7 @@ export const Times = {
|
||||
return plural(value, "seconds");
|
||||
},
|
||||
|
||||
unit(value: number) {
|
||||
unit(value: number): "months" | "days" | "hours" {
|
||||
let seconds = 30 * 24 * 60 * 60;
|
||||
|
||||
if (value >= seconds) {
|
||||
@ -77,7 +79,7 @@ export const Times = {
|
||||
return "hours"
|
||||
},
|
||||
|
||||
unitValue(unit: "hours" | "days" | "months") {
|
||||
value(unit: "hours" | "days" | "months") {
|
||||
switch (unit) {
|
||||
case "months": {
|
||||
return 30 * 24 * 60 * 60
|
||||
@ -90,4 +92,5 @@ export const Times = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user