Merge pull request #89 from codex-storage/releases/v0.0.13
Releases/v0.0.13
8
.github/workflows/playwright.yml
vendored
@ -8,9 +8,9 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
codex_version: v0.1.7
|
||||
circuit_version: v0.1.7
|
||||
marketplace_address: "0xfE822Df439d987849a90B64a4C0e26a297DBD47F"
|
||||
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 }}
|
||||
@ -119,7 +119,7 @@ jobs:
|
||||
run: npm run test
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
# if: ${{ !cancelled() }}
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
|
||||
@ -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('.availabilities-create').first().click();
|
||||
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();
|
||||
@ -17,17 +22,16 @@ test('create an availability', async ({ page }) => {
|
||||
await page.getByLabel('Nickname').fill('test');
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await expect(page.getByText('Confirm your new sale')).toBeVisible();
|
||||
await expect(page.getByText('512.0 MB').first()).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('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 }) => {
|
||||
await page.goto('/dashboard/availabilities');
|
||||
await page.waitForTimeout(500);
|
||||
await page.locator('.availabilities-create').first().click();
|
||||
await page.locator('.availability-edit button').first().click();
|
||||
await expect(page.locator('.stepper-number-done')).not.toBeVisible()
|
||||
await expect(page.locator('.step--active')).toBeVisible()
|
||||
await expect(page.locator('footer .button--primary')).not.toHaveAttribute("disabled");
|
||||
@ -55,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();
|
||||
})
|
||||
@ -1,31 +1,29 @@
|
||||
// import { test, expect } from '@playwright/test';
|
||||
// import path, { dirname } from 'path';
|
||||
// import { fileURLToPath } from 'url';
|
||||
import { test, expect } from '@playwright/test';
|
||||
import path, { dirname } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// const __filename = fileURLToPath(import.meta.url);
|
||||
// const __dirname = dirname(__filename);
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// test('download a file', async ({ page, browserName }) => {
|
||||
// // https://github.com/microsoft/playwright/issues/13037
|
||||
// test.skip(browserName.toLowerCase() !== 'chromium',
|
||||
// `Test only for chromium!`);
|
||||
test('download a file', async ({ page, browserName }) => {
|
||||
// https://github.com/microsoft/playwright/issues/13037
|
||||
test.skip(browserName.toLowerCase() !== 'chromium',
|
||||
`Test only for chromium!`);
|
||||
|
||||
// await page.goto('/dashboard');
|
||||
// await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
// path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
// ]);
|
||||
// await page.context().grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
// await page.locator('.files-fileActions > button:nth-child(3)').first().click();
|
||||
// await page.getByRole('button', { name: 'Copy CID' }).click();
|
||||
// const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
|
||||
// const cid = await handle.jsonValue()
|
||||
// await page.locator('.sheets > .backdrop').click();
|
||||
// await page.locator('.download-input input').click();
|
||||
// await page.locator('.download-input input').fill(cid);
|
||||
// // const page1Promise = page.waitForEvent('popup');
|
||||
// const downloadPromise = page.waitForEvent('download');
|
||||
// await page.locator('div').filter({ hasText: /^Download a fileDownload$/ }).getByRole('button').click();
|
||||
// // const page1 = await page1Promise;
|
||||
// const download = await downloadPromise;
|
||||
// expect(await download.failure()).toBeNull()
|
||||
// });
|
||||
await page.goto('/dashboard');
|
||||
await page.locator('div').getByTestId("upload").setInputFiles([
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await page.context().grantPermissions(["clipboard-read", "clipboard-write"]);
|
||||
await page.locator('.file-cell button').first().click();
|
||||
const handle = await page.evaluateHandle(() => navigator.clipboard.readText());
|
||||
const cid = await handle.jsonValue()
|
||||
|
||||
await page.locator('.download-input input').fill(cid);
|
||||
// const page1Promise = page.waitForEvent('popup');
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
await page.locator('.download-input + button').click();
|
||||
// const page1 = await page1Promise;
|
||||
const download = await downloadPromise;
|
||||
expect(await download.failure()).toBeNull()
|
||||
});
|
||||
@ -4,17 +4,17 @@ test('create a folder', async ({ page }) => {
|
||||
await page.goto('/dashboard');
|
||||
await page.locator('#folder').click();
|
||||
await page.locator('#folder').fill('abc');
|
||||
await expect(page.getByText('Enter the folder name')).toBeVisible();
|
||||
await expect(page.getByPlaceholder('Folder name')).toBeVisible();
|
||||
await page.locator('#folder').fill('abc ');
|
||||
await expect(page.getByText('9 alpha characters maximum')).toBeVisible();
|
||||
await expect(page.getByPlaceholder('Folder name')).toHaveAttribute("aria-invalid", "true");
|
||||
await page.locator('#folder').fill('abc !');
|
||||
await expect(page.getByText('9 alpha characters maximum')).toBeVisible();
|
||||
await expect(page.getByPlaceholder('Folder name')).toHaveAttribute("aria-invalid", "true");
|
||||
await page.locator('#folder').fill('abc )');
|
||||
await expect(page.getByText('9 alpha characters maximum')).toBeVisible();
|
||||
await expect(page.getByPlaceholder('Folder name')).toHaveAttribute("aria-invalid", "true");
|
||||
await page.locator('#folder').fill('Favorites )');
|
||||
await expect(page.getByText('This folder already exists')).toBeVisible();
|
||||
await expect(page.getByPlaceholder('Folder name')).toHaveAttribute("aria-invalid", "true");
|
||||
await page.locator('#folder').fill('abc-_');
|
||||
await expect(page.getByText('Enter the folder name')).toBeVisible();
|
||||
await expect(page.getByPlaceholder('Folder name')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Folder' }).click();
|
||||
await expect(page.locator('span').filter({ hasText: 'abc-_' })).toBeVisible();
|
||||
await expect(page.locator('span').filter({ hasText: 'abc-_' }).first()).toBeVisible();
|
||||
})
|
||||
@ -1,41 +1,58 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { test, expect, } from '@playwright/test';
|
||||
|
||||
test('onboarding steps', async ({ page }) => {
|
||||
await page.context().setOffline(false)
|
||||
await page.goto('/');
|
||||
await expect(page.getByText("Codex is a durable, decentralised data storage protocol, created so the world community can preserve its most important knowledge without risk of censorship.")).toBeVisible()
|
||||
await page.locator('.navigation').click();
|
||||
await expect(page.locator('.navigation')).toHaveAttribute("aria-disabled");
|
||||
await page.getByLabel('Preferred name').fill('Arnaud');
|
||||
await expect(page.locator('.navigation')).not.toHaveAttribute("aria-disabled");
|
||||
await page.locator('.navigation').click();
|
||||
test.describe('onboarding', () => {
|
||||
test('onboarding steps', async ({ page, browserName }) => {
|
||||
await page.context().setOffline(false)
|
||||
await page.goto('/');
|
||||
await expect(page.getByText("Codex is a durable, decentralised data storage protocol, created so the world community can preserve its most important knowledge without risk of censorship.")).toBeVisible()
|
||||
await page.locator('.navigation').click();
|
||||
await expect(page.locator('.navigation')).toHaveAttribute("aria-disabled");
|
||||
await page.getByLabel('Preferred name').fill('Arnaud');
|
||||
await expect(page.locator('.navigation')).not.toHaveAttribute("aria-disabled");
|
||||
await page.locator('.navigation').click();
|
||||
|
||||
// Network
|
||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")).not.toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")).toBeVisible()
|
||||
// Network
|
||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")).not.toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")).toBeVisible()
|
||||
|
||||
// Port forwarding
|
||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).not.toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).toBeVisible()
|
||||
// Port forwarding
|
||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).not.toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).toBeVisible()
|
||||
|
||||
// Codex node
|
||||
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-error")).not.toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-success")).toBeVisible()
|
||||
// Codex node
|
||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).not.toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).toBeVisible()
|
||||
|
||||
// Marketplace
|
||||
await expect(page.locator(".health-checks ul li").nth(4).getByTestId("icon-error")).not.toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(4).getByTestId("icon-success")).toBeVisible()
|
||||
// Marketplace
|
||||
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-warning")).toBeVisible({ timeout: 10_000 })
|
||||
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-success")).not.toBeVisible()
|
||||
|
||||
await page.context().setOffline(true)
|
||||
// Can be simulated with File -> Work offline
|
||||
if (browserName.toLowerCase() !== 'firefox') {
|
||||
await page.context().setOffline(true)
|
||||
|
||||
// Network
|
||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")).toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")).not.toBeVisible()
|
||||
// Network
|
||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-error")).toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(1).getByTestId("icon-success")).not.toBeVisible()
|
||||
|
||||
await page.context().setOffline(false)
|
||||
});
|
||||
await page.context().setOffline(false)
|
||||
}
|
||||
});
|
||||
|
||||
// await expect(page.locator('#root')).toContainText('Network connected');
|
||||
// await page.locator('a').nth(2).click();
|
||||
// await page.context().setOffline(true)
|
||||
// await expect(page.locator('#root')).toContainText('Network disconnected');
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.context().setOffline(false)
|
||||
});
|
||||
})
|
||||
|
||||
test('does not display undefined when delete the url value', async ({ page }) => {
|
||||
await page.goto('/onboarding-checks');
|
||||
await page.locator('#url').focus()
|
||||
|
||||
for (let i = 0; i < "http://localhost:8080".length; i++) {
|
||||
await page.keyboard.press('Backspace');
|
||||
}
|
||||
|
||||
await expect(page.locator('#url')).toHaveValue("");
|
||||
await expect(page.locator('#url')).toHaveAttribute("aria-invalid")
|
||||
await expect(page.locator('.refresh svg')).toHaveAttribute("color", "#494949")
|
||||
});
|
||||
@ -14,17 +14,17 @@ test('update the URL with wrong URL applies', async ({ page }) => {
|
||||
await page.getByLabel('Address').click();
|
||||
await page.getByLabel('Address').fill('hello');
|
||||
await expect(page.getByLabel('Address')).toHaveAttribute("aria-invalid")
|
||||
await expect(page.locator(".refresh svg")).toHaveAttribute("aria-disabled")
|
||||
await expect(page.locator(".refresh svg")).toHaveAttribute("color", "#494949")
|
||||
await page.getByLabel('Address').fill('http://127.0.0.1:8079');
|
||||
await expect(page.getByLabel('Address')).not.toHaveAttribute("aria-invalid")
|
||||
await expect(page.locator(".refresh svg")).not.toHaveAttribute("aria-disabled")
|
||||
await expect(page.getByLabel('Address')).toHaveValue("http://127.0.0.1")
|
||||
await expect(page.getByLabel('Port')).toHaveValue("8079")
|
||||
await page.locator(".refresh").click()
|
||||
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-error")).toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-success")).not.toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).not.toBeVisible()
|
||||
await page.getByLabel('Address').fill('http://127.0.0.1:8080');
|
||||
await page.locator(".refresh").click()
|
||||
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-error")).not.toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(3).getByTestId("icon-success")).toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-error")).not.toBeVisible()
|
||||
await expect(page.locator(".health-checks ul li").nth(2).getByTestId("icon-success")).toBeVisible()
|
||||
})
|
||||
@ -20,7 +20,7 @@ test('create a storage request', async ({ page }) => {
|
||||
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 page.getByRole('cell', { name: 'pending' }).getByRole('paragraph').click();
|
||||
await expect(page.getByTestId('cell-pending').first()).toBeVisible();
|
||||
})
|
||||
|
||||
test('select a uploaded cid when creating a storage request', async ({ page }) => {
|
||||
@ -30,7 +30,7 @@ test('select a uploaded cid when creating a storage request', async ({ page }) =
|
||||
]);
|
||||
await page.locator('a').filter({ hasText: 'Purchases' }).click();
|
||||
await page.getByRole('button', { name: 'Storage Request' }).click();
|
||||
await page.getByPlaceholder('Select or type your CID').click();
|
||||
await page.getByPlaceholder('CID').click();
|
||||
await page.locator('.dropdown ul li').nth(1).click();
|
||||
await expect(page.getByText('button[disabled]')).not.toBeVisible();
|
||||
})
|
||||
@ -73,6 +73,61 @@ test('remove the CID when the file is deleted', async ({ page }) => {
|
||||
path.join(__dirname, "assets", 'chatgpt.jpg'),
|
||||
]);
|
||||
await expect(page.locator('#cid')).not.toBeEmpty()
|
||||
await page.locator('.button-icon--small').click();
|
||||
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();
|
||||
// })
|
||||
4229
package-lock.json
generated
23
package.json
@ -5,7 +5,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/codex-storage/codex-marketplace-ui"
|
||||
},
|
||||
"version": "0.0.7",
|
||||
"version": "0.0.13",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 127.0.0.1 --port 5173",
|
||||
@ -15,7 +15,8 @@
|
||||
"preview": "vite preview --host 127.0.0.1 --port 5173",
|
||||
"format": "prettier --write ./src",
|
||||
"test": "npx playwright test",
|
||||
"test:unit": "vitest run"
|
||||
"test:unit": "vitest run",
|
||||
"knip": "knip"
|
||||
},
|
||||
"keywords": [
|
||||
"Codex",
|
||||
@ -25,25 +26,25 @@
|
||||
"React"
|
||||
],
|
||||
"dependencies": {
|
||||
"@codex-storage/marketplace-ui-components": "^0.0.43",
|
||||
"@codex-storage/sdk-js": "^0.0.15",
|
||||
"@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",
|
||||
"@tanstack/react-query": "^5.51.15",
|
||||
"@tanstack/react-router": "^1.58.7",
|
||||
"dotted-map": "^2.2.3",
|
||||
"echarts": "^5.5.1",
|
||||
"emoji-picker-react": "^4.12.0",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"lucide-react": "^0.445.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"preact": "^10.24.3",
|
||||
"react-router": "^6.28.0",
|
||||
"react-router-dom": "^6.28.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.48.0",
|
||||
"@preact/preset-vite": "^2.9.1",
|
||||
"@svgr/plugin-svgo": "^8.1.0",
|
||||
"@tanstack/router-plugin": "^1.58.4",
|
||||
"@types/node": "^22.7.5",
|
||||
"@types/node": "^22.9.1",
|
||||
"@types/react": "^18.3.8",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
||||
@ -56,7 +57,9 @@
|
||||
"postcss": "^8.4.47",
|
||||
"postcss-nesting": "^13.0.1",
|
||||
"prettier": "^3.3.3",
|
||||
"typescript": "5.5.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.4.7",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
"vitest": "^2.1.4"
|
||||
|
||||
@ -34,6 +34,8 @@ export default defineConfig({
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
|
||||
screenshot: "only-on-failure",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
|
||||
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
@ -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
@ -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
@ -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
@ -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 |
43
public/icons/euro-flag.svg
Normal file
@ -0,0 +1,43 @@
|
||||
<svg
|
||||
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: #0052b4" cx="256" cy="256" r="256" />
|
||||
<g>
|
||||
<polygon
|
||||
style="fill: #ffda44"
|
||||
points="256.001,100.174 264.29,125.683 291.11,125.683 269.411,141.448 277.7,166.957
|
||||
256.001,151.191 234.301,166.957 242.59,141.448 220.891,125.683 247.712,125.683 " />
|
||||
<polygon
|
||||
style="fill: #ffda44"
|
||||
points="145.814,145.814 169.714,157.99 188.679,139.026 184.482,165.516 208.381,177.693
|
||||
181.89,181.889 177.694,208.381 165.517,184.482 139.027,188.679 157.992,169.714 " />
|
||||
<polygon
|
||||
style="fill: #ffda44"
|
||||
points="100.175,256 125.684,247.711 125.684,220.89 141.448,242.59 166.958,234.301 151.191,256
|
||||
166.958,277.699 141.448,269.411 125.684,291.11 125.684,264.289 " />
|
||||
<polygon
|
||||
style="fill: #ffda44"
|
||||
points="145.814,366.186 157.991,342.286 139.027,323.321 165.518,327.519 177.693,303.62
|
||||
181.89,330.111 208.38,334.307 184.484,346.484 188.679,372.974 169.714,354.009 " />
|
||||
<polygon
|
||||
style="fill: #ffda44"
|
||||
points="256.001,411.826 247.711,386.317 220.891,386.317 242.591,370.552 234.301,345.045
|
||||
256.001,360.809 277.7,345.045 269.411,370.552 291.11,386.317 264.289,386.317 " />
|
||||
<polygon
|
||||
style="fill: #ffda44"
|
||||
points="366.187,366.186 342.288,354.01 323.322,372.975 327.519,346.483 303.622,334.307
|
||||
330.112,330.111 334.308,303.62 346.484,327.519 372.974,323.321 354.009,342.288 " />
|
||||
<polygon
|
||||
style="fill: #ffda44"
|
||||
points="411.826,256 386.317,264.289 386.317,291.11 370.552,269.41 345.045,277.699 360.81,256
|
||||
345.045,234.301 370.553,242.59 386.317,220.89 386.317,247.712 " />
|
||||
<polygon
|
||||
style="fill: #ffda44"
|
||||
points="366.187,145.814 354.01,169.714 372.975,188.679 346.483,184.481 334.308,208.38
|
||||
330.112,181.889 303.622,177.692 327.519,165.516 323.322,139.027 342.289,157.991 " />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@ -1,5 +1,4 @@
|
||||
import { ReactNode } from "react";
|
||||
import "./App.css";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
.container {
|
||||
padding: 1.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
padding-inline: 1.5rem;
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
.indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.indicator-point {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
animation-duration: 3s;
|
||||
animation-name: flash;
|
||||
animation-iteration-count: infinite;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.indicator-point-online {
|
||||
background-color: var(--codex-color-primary);
|
||||
}
|
||||
|
||||
.indicator-point-offline {
|
||||
background-color: rgb(217, 53, 38);
|
||||
}
|
||||
|
||||
@keyframes flash {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
40% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
.text-contrast {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
mix-blend-mode: difference;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text--primary {
|
||||
color: var(--codex-color-primary);
|
||||
}
|
||||
|
||||
.text--error {
|
||||
color: var(--codex-color-error);
|
||||
}
|
||||
|
||||
.text--spacing {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.text--warning {
|
||||
color: var(--codex-color-warning);
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
<svg width="250" height="200" viewBox="0 0 250 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="250" height="200" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M63 134H154C154.515 134 155.017 133.944 155.5 133.839C155.983 133.944 156.485 134 157 134H209C212.866 134 216 130.866 216 127C216 123.134 212.866 120 209 120H203C199.134 120 196 116.866 196 113C196 109.134 199.134 106 203 106H222C225.866 106 229 102.866 229 99C229 95.134 225.866 92 222 92H200C203.866 92 207 88.866 207 85C207 81.134 203.866 78 200 78H136C139.866 78 143 74.866 143 71C143 67.134 139.866 64 136 64H79C75.134 64 72 67.134 72 71C72 74.866 75.134 78 79 78H39C35.134 78 32 81.134 32 85C32 88.866 35.134 92 39 92H64C67.866 92 71 95.134 71 99C71 102.866 67.866 106 64 106H24C20.134 106 17 109.134 17 113C17 116.866 20.134 120 24 120H63C59.134 120 56 123.134 56 127C56 130.866 59.134 134 63 134ZM226 134C229.866 134 233 130.866 233 127C233 123.134 229.866 120 226 120C222.134 120 219 123.134 219 127C219 130.866 222.134 134 226 134Z" fill="#C1F0A4" fill-opacity="0.5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M92 140C79.8497 140 70 130.374 70 118.5C70 106.626 79.8497 97 92 97C92.5167 97 93.0292 97.0174 93.537 97.0517C93.1842 95.0878 93 93.0654 93 91C93 72.2223 108.222 57 127 57C141.991 57 154.716 66.702 159.239 80.1695C160.31 80.0575 161.398 80 162.5 80C179.345 80 193 93.4315 193 110C193 125.741 180.675 138.727 165 139.978V140H108.508H92ZM103.996 140H97.0314Z" fill="white"/>
|
||||
<path d="M103.996 140H97.0314M92 140C79.8497 140 70 130.374 70 118.5C70 106.626 79.8497 97 92 97C92.5167 97 93.0292 97.0174 93.537 97.0517C93.1842 95.0878 93 93.0654 93 91C93 72.2223 108.222 57 127 57C141.991 57 154.716 66.702 159.239 80.1695C160.31 80.0575 161.398 80 162.5 80C179.345 80 193 93.4315 193 110C193 125.741 180.675 138.727 165 139.978V140H108.508H92Z" stroke="#56CE0C" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M116.612 64.3426C116.612 96.5657 145.633 123.118 183 126.703C178.317 132.461 171.086 136.367 162.847 136.981V137H95.7431C87.6013 137 74 133.57 74 118.548C74 103.527 84.5742 100.097 95.7431 100.097C96.218 100.097 96.6891 100.112 97.1559 100.141C96.8316 98.4556 96.7746 96.7184 96.6623 94.9474C95.9038 82.9842 101.123 67.907 116.63 63C116.618 63.4473 116.612 63.8944 116.612 64.3426ZM127.116 114.758C124.078 114.758 121.614 117.192 121.614 120.195C121.614 123.198 124.078 125.632 127.116 125.632C130.155 125.632 132.618 123.198 132.618 120.195C132.618 117.192 130.155 114.758 127.116 114.758Z" fill="#C1F0A4" fill-opacity="0.5"/>
|
||||
<path d="M127.5 126C130.538 126 133 123.538 133 120.5C133 117.462 130.538 115 127.5 115C124.462 115 122 117.462 122 120.5C122 123.538 124.462 126 127.5 126Z" stroke="#56CE0C" stroke-width="2.5"/>
|
||||
<path d="M112 109L119 103.507L112 98.2776" stroke="#56CE0C" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M143 109L136 103.507L143 98.2776" stroke="#56CE0C" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M137 67C143.509 68.7226 148.648 73.8129 150.44 80.2932" stroke="#56CE0C" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path d="M158 50C159.657 50 161 48.6569 161 47C161 45.3431 159.657 44 158 44C156.343 44 155 45.3431 155 47C155 48.6569 156.343 50 158 50Z" stroke="#56CE0C" stroke-width="2"/>
|
||||
<path d="M189 66C190.657 66 192 64.6569 192 63C192 61.3431 190.657 60 189 60C187.343 60 186 61.3431 186 63C186 64.6569 187.343 66 189 66Z" fill="#56CE0C"/>
|
||||
<path d="M165.757 57.7573L174.116 66.1156M174.243 57.7573L165.884 66.1156L174.243 57.7573Z" stroke="#56CE0C" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M71.4038 75.5962L77.4038 81.5962M77.4038 75.5962L71.4038 81.5962L77.4038 75.5962Z" stroke="#56CE0C" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M85 69C86.6569 69 88 67.6569 88 66C88 64.3431 86.6569 63 85 63C83.3431 63 82 64.3431 82 66C82 67.6569 83.3431 69 85 69Z" fill="#56CE0C"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.9 KiB |
5
src/assets/icons/arrow-onboarding.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M9.27273 17.5341L7.51705 15.7955L12.7756 10.5369H0V7.99716H12.7756L7.51705 2.74716L9.27273 1L17.5398 9.26705L9.27273 17.5341Z"
|
||||
fill="#7BFBAF" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 244 B |
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 |
11
src/assets/icons/durability-custom.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg
|
||||
width="74"
|
||||
height="89"
|
||||
viewBox="0 0 74 89"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M37 0L0 22.2V66.46L37 88.66L74 66.46V22.2L37 0ZM68.12 23.33L47.83 35.5L39 30.2V5.86L68.12 23.33ZM46 47.4L40.88 44.33L46 41.26V47.4ZM28 41.26L33.12 44.33L28 47.4V41.26ZM39 40.8V34.86L43.95 37.83L39 40.8ZM35 40.8L30.05 37.83L35 34.86V40.8ZM35 47.86V53.8L30.05 50.83L35 47.86ZM39 47.86L43.95 50.83L39 53.8V47.86ZM35 5.86V30.2L26.17 35.5L5.88 23.33L35 5.86ZM4 26.86L24 38.86V49.8L4 61.8V26.86ZM5.88 65.33L26.17 53.16L35 58.46V82.8L5.88 65.33ZM39 82.8V58.46L47.83 53.16L68.12 65.33L39 82.8ZM70 61.8L50 49.8V38.86L70 26.86V61.8Z"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 702 B |
11
src/assets/icons/durability-high.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg
|
||||
width="74"
|
||||
height="89"
|
||||
viewBox="0 0 74 89"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M37 0L0 22.2V66.46L37 88.66L74 66.46V22.2L37 0ZM57.29 16.83L50 21.2V12.46L57.29 16.83ZM39 5.86L46 10.06V23.6L39 27.8V5.86ZM28 10.06L35 5.86V30.2L28 34.4V10.06ZM24 12.46V21.2L16.72 16.83L24 12.46ZM12.84 19.16L24 25.86V34.2L5.88 23.33L12.84 19.16ZM4 26.86L26.17 40.16L35 34.86V43.2L26.17 48.5L4 35.2V26.86ZM4 39.86L11.45 44.33L4 48.8V39.86ZM4 53.46L15.33 46.66L22.29 50.83L4 61.8V53.46ZM24 76.2L16.72 71.83L24 67.46V76.2ZM12.84 69.5L5.88 65.33L26.17 53.16L33.12 57.33L12.84 69.5ZM35 82.8L28 78.6V65.06L35 60.86V82.8ZM46 78.6L39 82.8V56.2L30.05 50.83L37 46.66L46 52.06V78.6ZM39 43.2V32.46L61.17 19.16L68.12 23.33L46 36.6V47.4L39 43.2ZM50 76.2V67.46L57.29 71.83L50 76.2ZM61.17 69.5L50 62.8V54.46L68.12 65.33L61.17 69.5ZM70 61.8L50 49.8V41.46L70 53.46V61.8ZM70 48.8L62.55 44.33L70 39.86V48.8ZM70 35.2L58.67 42L51.71 37.83L70 26.86V35.2Z"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1011 B |
11
src/assets/icons/durability-low.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg
|
||||
width="74"
|
||||
height="89"
|
||||
viewBox="0 0 74 89"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M37 0L0 22.2V66.465L37 88.665L74 66.465V22.201L37 0ZM70 61.801L59 55.201V31.201L39 19.201V5.866L70 24.466V61.801ZM37 22.666L53.112 32.333L37 42L20.888 32.333L37 22.666ZM19 35.865L35 45.465V64.8L19 55.2V35.865ZM39 45.465L55 35.865V55.2L39 64.8V45.465ZM35 5.866V19.201L15 31.201V55.201L4 61.801V24.465L35 5.866ZM5.888 65.333L17 58.666L37 70.666L57 58.666L68.112 65.333L37 84L5.888 65.333Z"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 566 B |
11
src/assets/icons/durability-medium.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg
|
||||
width="74"
|
||||
height="89"
|
||||
viewBox="0 0 74 89"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M37 0L0 22.2V66.46L37 88.66L74 66.46V22.2L37 0ZM57.29 16.83L50 21.2V12.46L57.29 16.83ZM24 12.46V21.2L16.72 16.83L24 12.46ZM4 24.46L12.84 19.16L24 25.86V36.8L15.33 42L4 35.2V24.46ZM4 39.86L11.45 44.33L4 48.8V39.86ZM28 39.06V10.06L35 5.86V43.2L4 61.8V53.46L28 39.06ZM16.72 71.83L24 67.46V76.2L16.72 71.83ZM37 84L28 78.6V65.06L37 59.66L46 65.06V78.6L37 84ZM50 76.2V67.46L57.29 71.83L50 76.2ZM61.17 69.5L37 55L12.84 69.5L5.88 65.33L37 46.66L68.12 65.33L61.17 69.5ZM70 61.8L39 43.2V5.86L46 10.06V39.06L70 53.46V61.8ZM70 48.8L62.55 44.33L70 39.86V48.8ZM58.67 42L50 36.8V25.86L61.17 19.16L70 24.46V35.2L58.67 42Z"
|
||||
fill="currentColor"
|
||||
fill-opacity="0.1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 785 B |
@ -1,6 +1,5 @@
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
data-testid="icon-success"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
|
Before Width: | Height: | Size: 389 B After Width: | Height: | Size: 391 B |
@ -1,6 +1,5 @@
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
data-testid="icon-warning"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
|
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 326 B |
@ -1,11 +0,0 @@
|
||||
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_274_4287)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.7001 32.7386C19.7705 32.7792 19.8502 32.8001 19.9306 32.8001C20.0111 32.8001 20.188 32.7225 20.188 32.7225L30.85 26.581C30.8555 26.5783 30.8632 26.5744 30.8706 26.5701C30.942 26.5295 31.0002 26.4707 31.0406 26.4008C31.0819 26.3302 31.1036 26.25 31.1036 26.1685C31.1036 26.1588 31.1032 26.1499 31.1028 26.1432V13.8597C31.1036 13.8486 31.1036 13.8387 31.1036 13.8337L31.1036 13.8324C31.1036 13.7507 31.0818 13.671 31.0413 13.6009C31.0008 13.5302 30.9421 13.4718 30.872 13.4313C30.8644 13.4268 30.8564 13.4225 30.8484 13.4186L20.1868 7.27756C20.179 7.27228 20.1714 7.26767 20.165 7.26389L20.1644 7.26354C20.0937 7.22255 20.0136 7.20151 19.9325 7.20151H19.9306C19.8495 7.20151 19.77 7.22295 19.701 7.26233C19.692 7.26733 19.6837 7.27257 19.6762 7.27763L9.01204 13.4202C9.00655 13.4229 8.99886 13.4269 8.99144 13.4311C8.92049 13.4717 8.86173 13.531 8.82123 13.6011C8.78067 13.6712 8.75879 13.7509 8.75879 13.8327C8.75879 13.8425 8.75919 13.8513 8.75956 13.858V26.1419C8.75876 26.153 8.75878 26.1629 8.75879 26.1679L8.75879 26.1692C8.75879 26.2508 8.78058 26.3312 8.82193 26.4018C8.86238 26.4712 8.92065 26.5296 8.98909 26.5693L8.99047 26.5701L8.99187 26.5708C9.00023 26.5754 9.00781 26.5793 9.01357 26.5822L19.6768 32.7241C19.6818 32.7275 19.6906 32.7332 19.7001 32.7386ZM30.8023 26.4503C30.7969 26.4533 30.7908 26.4564 30.7847 26.4594C30.788 26.4578 30.7915 26.4561 30.7946 26.4544C30.7973 26.4531 30.7999 26.4517 30.8023 26.4503ZM30.9649 26.1472C30.9651 26.1495 30.9652 26.152 30.9653 26.1545C30.9655 26.1589 30.9657 26.1636 30.9657 26.1685C30.9657 26.167 30.9657 26.1655 30.9656 26.164C30.9655 26.158 30.9652 26.1523 30.9649 26.1472ZM20.3983 26.9771L24.3592 29.2549L20.3951 31.538L20.3983 26.9771ZM15.5034 29.2548L19.4645 26.9769L19.4676 31.538L15.5034 29.2548ZM25.7528 23.8924L29.7137 26.1701L25.7497 28.4532L25.7528 23.8924ZM20.3984 25.3628V20.8084L24.3514 23.0856L20.3984 25.3628ZM15.0439 22.2784V17.724L18.9969 20.0012L15.0439 22.2784ZM20.3984 19.1932V14.6388L24.3514 16.916L20.3984 19.1932ZM19.4649 13.0247L15.5038 10.7468L19.468 8.4636L19.4649 13.0247ZM10.1491 26.1701L14.1131 28.4532L14.11 23.8921L10.1491 26.1701ZM24.8194 23.8927L24.8225 28.4527L20.8658 26.1705L24.8194 23.8927ZM15.0438 23.8927L15.0406 28.4527L18.9974 26.1705L15.0438 23.8927ZM26.2198 23.0856L30.178 20.8025V25.3687L26.2198 23.0856ZM9.68483 20.8025V25.3687L13.643 23.0856L9.68483 20.8025ZM15.5111 23.085L19.4644 25.3624V20.8076L15.5111 23.085ZM29.7125 20.0004L25.7529 17.7227V22.2781L29.7125 20.0004ZM10.1503 20.0008L14.1099 22.2785V17.7231L10.1503 20.0008ZM24.8189 17.7232V22.278L20.8656 20.0006L24.8189 17.7232ZM30.178 14.6329V19.1991L26.2198 16.916L30.178 14.6329ZM13.643 16.916L9.68483 19.1991V14.6329L13.643 16.916ZM19.4644 14.6384V19.1928L15.5114 16.9156L19.4644 14.6384ZM29.7141 13.8315L25.7497 11.5482L25.7528 16.1095L29.7141 13.8315ZM14.1135 11.5484L14.1104 16.1095L10.1491 13.8315L14.1135 11.5484ZM24.8225 11.5489L24.8194 16.1089L20.8658 13.8311L24.8225 11.5489ZM18.9974 13.8311L15.0438 16.1089L15.0406 11.5489L18.9974 13.8311ZM24.3596 10.7467L20.3951 8.46359L20.3983 13.0247L24.3596 10.7467Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_274_4287">
|
||||
<rect width="40" height="40" fill="black"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
@ -1,89 +0,0 @@
|
||||
<svg
|
||||
width="119"
|
||||
height="110"
|
||||
viewBox="0 0 119 110"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M103.25 26.99V31.3"
|
||||
stroke="#4A5059"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M101.1 29.15H105.4"
|
||||
stroke="#4A5059"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M15.09 10.36V14.67"
|
||||
stroke="#4A5059"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M12.9301 12.51H17.2401"
|
||||
stroke="#4A5059"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M48.5499 2.28996C49.0912 2.28996 49.5299 1.85119 49.5299 1.30996C49.5299 0.768717 49.0912 0.329956 48.5499 0.329956C48.0087 0.329956 47.5699 0.768717 47.5699 1.30996C47.5699 1.85119 48.0087 2.28996 48.5499 2.28996Z"
|
||||
fill="#4A5059" />
|
||||
<path
|
||||
d="M79.97 41.25C71.59 41.25 63.08 40.92 55.11 38.7C47.29 36.5 40.15 32.32 33.71 27.5C29.51 24.32 25.71 21.8 20.26 22.18C14.9469 22.4538 9.8614 24.4235 5.75001 27.8C-1.17999 33.86 -0.129995 45.07 2.64001 52.95C6.8 64.82 19.5 73.07 30.22 78.44C42.65 84.65 56.3 88.26 70 90.33C82 92.16 97.41 93.48 107.81 85.64C117.37 78.44 119.99 62 117.65 50.9C117.081 47.6187 115.331 44.6591 112.73 42.58C106.02 37.67 96.02 40.95 88.48 41.11C85.68 41.17 82.83 41.24 79.97 41.25Z"
|
||||
fill="#272933" />
|
||||
<path
|
||||
d="M59.48 109.79C79.7544 109.79 96.19 108.765 96.19 107.5C96.19 106.235 79.7544 105.21 59.48 105.21C39.2056 105.21 22.77 106.235 22.77 107.5C22.77 108.765 39.2056 109.79 59.48 109.79Z"
|
||||
fill="#272933" />
|
||||
<path
|
||||
d="M75.0746 3.65264L26.1993 10.2608C24.634 10.4725 23.5367 11.913 23.7483 13.4783L33.2265 83.5804C33.4381 85.1457 34.8786 86.2431 36.4439 86.0314L85.3192 79.4232C86.8845 79.2116 87.9818 77.7711 87.7702 76.2058L78.292 6.10365C78.0804 4.53836 76.6399 3.44101 75.0746 3.65264Z"
|
||||
fill="#4A5059" />
|
||||
<path
|
||||
d="M91.32 13.95H42C40.4205 13.95 39.14 15.2304 39.14 16.81V87.55C39.14 89.1295 40.4205 90.41 42 90.41H91.32C92.8995 90.41 94.18 89.1295 94.18 87.55V16.81C94.18 15.2304 92.8995 13.95 91.32 13.95Z"
|
||||
fill="#2D333E"
|
||||
stroke="#4A5059"
|
||||
stroke-miterlimit="10" />
|
||||
<path
|
||||
d="M78.5099 68.86H54.7999C53.9108 68.86 53.1899 69.5808 53.1899 70.47V70.48C53.1899 71.3692 53.9108 72.09 54.7999 72.09H78.5099C79.3991 72.09 80.1199 71.3692 80.1199 70.48V70.47C80.1199 69.5808 79.3991 68.86 78.5099 68.86Z"
|
||||
fill="#4A5059" />
|
||||
<path
|
||||
d="M77.66 82.0601H54.91C54.6394 82.0601 54.42 82.2794 54.42 82.5501V82.7001C54.42 82.9707 54.6394 83.1901 54.91 83.1901H77.66C77.9307 83.1901 78.15 82.9707 78.15 82.7001V82.5501C78.15 82.2794 77.9307 82.0601 77.66 82.0601Z"
|
||||
fill="#4A5059" />
|
||||
<path
|
||||
d="M83.46 76.77H49.95C49.5911 76.77 49.3 77.061 49.3 77.42C49.3 77.779 49.5911 78.07 49.95 78.07H83.46C83.819 78.07 84.11 77.779 84.11 77.42C84.11 77.061 83.819 76.77 83.46 76.77Z"
|
||||
fill="#4A5059" />
|
||||
<path
|
||||
d="M89.59 16.27H43.73C42.5923 16.27 41.67 17.1923 41.67 18.33V58.1C41.67 59.2377 42.5923 60.16 43.73 60.16H89.59C90.7277 60.16 91.65 59.2377 91.65 58.1V18.33C91.65 17.1923 90.7277 16.27 89.59 16.27Z"
|
||||
fill="#2D333E"
|
||||
stroke="#4A5059"
|
||||
stroke-miterlimit="10" />
|
||||
<path
|
||||
d="M72.78 47.84C73.1093 47.8378 73.4299 47.7339 73.698 47.5427C73.9661 47.3514 74.1687 47.0821 74.2781 46.7715C74.3875 46.4608 74.3983 46.124 74.3092 45.807C74.22 45.49 74.0352 45.2082 73.78 45C71.6916 43.278 69.0631 42.347 66.3563 42.3708C63.6496 42.3946 61.0378 43.3715 58.98 45.13C58.7303 45.345 58.5526 45.6314 58.4709 45.9506C58.3891 46.2697 58.4073 46.6063 58.5228 46.9148C58.6384 47.2233 58.8458 47.489 59.1171 47.6759C59.3884 47.8628 59.7105 47.962 60.04 47.96L72.78 47.84Z"
|
||||
stroke="#4A5059"
|
||||
stroke-width="0.71"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M80.95 37.82L75.12 34.46L80.95 31.1"
|
||||
stroke="#4A5059"
|
||||
stroke-width="0.71"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M51.89 37.82L57.71 34.46L51.89 31.1"
|
||||
stroke="#4A5059"
|
||||
stroke-width="0.71"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M19.92 59.16V63.46"
|
||||
stroke="#4A5059"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M17.77 61.3101H22.07"
|
||||
stroke="#4A5059"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round" />
|
||||
<path
|
||||
d="M101.42 62.2C101.961 62.2 102.4 61.7612 102.4 61.22C102.4 60.6788 101.961 60.24 101.42 60.24C100.879 60.24 100.44 60.6788 100.44 61.22C100.44 61.7612 100.879 62.2 101.42 62.2Z"
|
||||
fill="#4A5059" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@ -5,7 +5,6 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { ReactElement, useEffect } from "react";
|
||||
import { useCodexConnection } from "../../hooks/useCodexConnection";
|
||||
import { usePersistence } from "../../hooks/usePersistence";
|
||||
import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||
import DashboardIcon from "../../assets/icons/dashboard.svg?react";
|
||||
import PeersIcon from "../../assets/icons/peers.svg?react";
|
||||
import NodesIcon from "../../assets/icons/nodes.svg?react";
|
||||
@ -19,13 +18,17 @@ import PurchasesIcon from "../../assets/icons/purchase.svg?react";
|
||||
import HelpIcon from "../../assets/icons/help.svg?react";
|
||||
import DisclaimerIcon from "../../assets/icons/disclaimer.svg?react";
|
||||
import { WalletConnect } from "../WalletLogin/WalletLogin";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Logo from "../../assets/icons/logo.svg?react";
|
||||
import { useIsMobile } from "../../hooks/useMobile";
|
||||
|
||||
type Props = {
|
||||
onIconClick: () => void;
|
||||
onExpanded: (val: boolean) => void;
|
||||
};
|
||||
|
||||
const icons: Record<string, ReactElement> = {
|
||||
dashboard: <DashboardIcon />,
|
||||
dashboard: <DashboardIcon width={24} />,
|
||||
peers: <PeersIcon width={24} />,
|
||||
settings: <SettingsIcon width={24} />,
|
||||
files: <FilesIcon width={24} />,
|
||||
@ -50,13 +53,13 @@ const descriptions: Record<string, string> = {
|
||||
disclaimer: "Important information.",
|
||||
};
|
||||
|
||||
export function AppBar({ onIconClick }: Props) {
|
||||
export function AppBar({ onIconClick, onExpanded }: Props) {
|
||||
const online = useNetwork();
|
||||
const queryClient = useQueryClient();
|
||||
const codex = useCodexConnection();
|
||||
const persistence = usePersistence(codex.enabled);
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate({ from: location.pathname });
|
||||
const navigate = useNavigate();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
useEffect(() => {
|
||||
queryClient.invalidateQueries({
|
||||
@ -67,20 +70,24 @@ export function AppBar({ onIconClick }: Props) {
|
||||
|
||||
const offline = !online || !codex.enabled;
|
||||
|
||||
const onNodeClick = () => navigate({ to: "/dashboard/settings" });
|
||||
const onNodeClick = () => navigate("/dashboard/settings");
|
||||
|
||||
const title =
|
||||
location.pathname.split("/")[2] || location.pathname.split("/")[1];
|
||||
const networkIconColor = online
|
||||
? "#3EE089"
|
||||
: "var(--codex-input-color-error)";
|
||||
const networkIconColor = online ? "#3EE089" : "var(--codex-color-error)";
|
||||
const nodesIconColor =
|
||||
codex.enabled === false
|
||||
? "var(--codex-input-color-error)"
|
||||
? "var(--codex-color-error)"
|
||||
: persistence.enabled
|
||||
? "#3EE089"
|
||||
: "var(--codex-input-color-warning)";
|
||||
|
||||
const icon = isMobile ? (
|
||||
<Logo onClick={() => onExpanded(true)} width={30}></Logo>
|
||||
) : (
|
||||
icons[title]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@ -90,7 +97,7 @@ export function AppBar({ onIconClick }: Props) {
|
||||
["app-bar--no-persistence", !persistence.enabled]
|
||||
)}>
|
||||
<div className="row gap">
|
||||
<span onClick={onIconClick}>{icons[title]}</span>
|
||||
<span onClick={onIconClick}>{icon}</span>
|
||||
|
||||
<div>
|
||||
<h1>{title}</h1>
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
.app-bar {
|
||||
height: 80px;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--codex-border-color);
|
||||
border-bottom: 1px solid rgba(150, 150, 150, 0.2);
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #96969633;
|
||||
border-bottom: 1px solid rgba(150, 150, 150, 0.2);
|
||||
box-sizing: border-box;
|
||||
background-color: #1c1c1c;
|
||||
background-color: rgb(28, 28, 28);
|
||||
border-right: 12px solid transparent;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@ -19,15 +19,15 @@
|
||||
}
|
||||
|
||||
&:not(.app-bar--offline):not(.app-bar--no-persistence) {
|
||||
border-right-color: #6ccc93;
|
||||
border-right-color: rgb(108, 204, 147);
|
||||
}
|
||||
|
||||
&.app-bar--offline {
|
||||
border-right-color: var(--codex-input-color-error);
|
||||
border-right-color: rgb(251, 55, 72);
|
||||
}
|
||||
|
||||
&.app-bar--no-persistence:not(.app-bar--offline) {
|
||||
border-right-color: rgb(var(--codex-color-warning));
|
||||
border-right-color: rgb(251, 198, 75);
|
||||
}
|
||||
|
||||
h1 {
|
||||
@ -44,28 +44,34 @@
|
||||
400 14px/20px "Inter",
|
||||
sans-serif;
|
||||
letter-spacing: -0.006em;
|
||||
color: #969696cc;
|
||||
color: rgba(150, 150, 150, 0.8);
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
span {
|
||||
background: #141414;
|
||||
background: rgb(20, 20, 20);
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #353639;
|
||||
border: 1px solid rgb(53, 54, 57);
|
||||
border-radius: 50%;
|
||||
color: #969696;
|
||||
color: rgba(150, 150, 150, 1);
|
||||
}
|
||||
}
|
||||
|
||||
aside {
|
||||
svg {
|
||||
padding: 10px;
|
||||
background-color: #141414;
|
||||
border-radius: var(--codex-border-radius);
|
||||
background-color: rgb(20, 20, 20);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
span {
|
||||
@ -73,7 +79,7 @@
|
||||
500 14px/20px "Inter",
|
||||
sans-serif;
|
||||
letter-spacing: -0.006em;
|
||||
color: #8d8d8d;
|
||||
color: rgb(141, 141, 141);
|
||||
|
||||
@media (max-width: 999px) {
|
||||
& {
|
||||
@ -86,10 +92,10 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* @media (min-width: 1000px) {
|
||||
.appBar-burger {
|
||||
display: none;
|
||||
@media (max-width: 800px) {
|
||||
.wallet-login {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
@ -4,14 +4,13 @@ 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";
|
||||
import { Fragment, useState } from "react";
|
||||
import { AvailabilityReservations } from "./AvailabilityReservations";
|
||||
import { AvailabilityIdCell } from "./AvailabilityIdCell";
|
||||
import { Arrays } from "../../utils/arrays";
|
||||
import { SlotRow } from "./SlotRow";
|
||||
import { AvailabilityWithSlots } from "./types";
|
||||
import { AvailabilityDiskRow } from "./AvailabilityDiskRow";
|
||||
@ -68,7 +67,8 @@ export function AvailabilitiesTable({ availabilities, space }: Props) {
|
||||
const rows = sorted.map((a) => {
|
||||
const showDetails = details.includes(a.id);
|
||||
|
||||
const onShowDetails = () => setDetails(Arrays.toggle(details, a.id));
|
||||
const onShowDetails = () =>
|
||||
setDetails(AvailabilityUtils.toggle(details, a.id));
|
||||
const hasSlots = a.slots.length > 0;
|
||||
|
||||
return (
|
||||
@ -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,17 +1,10 @@
|
||||
.availability-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 1px solid var(--codex-border-color);
|
||||
border-radius: var(--codex-border-radius);
|
||||
padding: 0.5rem;
|
||||
background-color: #14141499;
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
background-color: rgba(20, 20, 20, 0.6);
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
|
||||
.button-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #2f2f2f;
|
||||
border: 1px solid #96969633;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,29 +3,29 @@
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.availabilitConfirm-bottom {
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
> div {
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-start;
|
||||
|
||||
.availabilitConfirm-subtitle {
|
||||
margin-bottom: 0.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
> div:first-child {
|
||||
background-color: rgb(193, 240, 164, 0.2);
|
||||
border-radius: 50%;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.availabilitConfirm-iconContainer {
|
||||
background-color: rgb(var(--codex-color-primary-rgb), 0.2);
|
||||
border-radius: 50%;
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
svg {
|
||||
color: rgb(193, 240, 164);
|
||||
}
|
||||
}
|
||||
|
||||
.availabilitConfirm-icon {
|
||||
color: rgb(var(--codex-color-primary-rgb));
|
||||
b {
|
||||
margin-bottom: 0.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import "./AvailabilityConfirm.css";
|
||||
import { AvailabilityComponentProps } from "./types";
|
||||
import "./AvailabilityConfirm.css";
|
||||
import { Info } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { SpaceAllocation } from "@codex-storage/marketplace-ui-components";
|
||||
import NodesIcon from "../../assets/icons/nodes.svg?react";
|
||||
import InfoIcon from "../../assets/icons/info.svg?react";
|
||||
|
||||
export function AvailabilityConfirm({
|
||||
dispatch,
|
||||
@ -54,15 +54,15 @@ export function AvailabilityConfirm({
|
||||
},
|
||||
]}></SpaceAllocation>
|
||||
|
||||
<div className="availabilitConfirm-bottom">
|
||||
<div className="availabilitConfirm-iconContainer">
|
||||
<Info className="availabilitConfirm-icon" />
|
||||
<div>
|
||||
<div>
|
||||
<InfoIcon />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<b className="availabilitConfirm-subtitle">Confirm your new sale</b>
|
||||
<b>Confirm your new sale</b>
|
||||
|
||||
<p className="availabilitConfirm-message">
|
||||
<p>
|
||||
By clicking 'Next', you will establish a new sale based on the space
|
||||
allocation specified above. Do you want to confirm ?
|
||||
</p>
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
import { createContext } from "react";
|
||||
import { AvailabilityWithSlots } from "./types";
|
||||
|
||||
export const AvailabilityContext = createContext<AvailabilityWithSlots | null>(null);
|
||||
@ -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>,
|
||||
|
||||
@ -30,7 +30,7 @@
|
||||
}
|
||||
|
||||
svg {
|
||||
color: #969696;
|
||||
color: rgba(150, 150, 150, 1);
|
||||
}
|
||||
|
||||
header {
|
||||
@ -38,10 +38,6 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.space-allocation {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@ -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",
|
||||
@ -42,9 +42,9 @@ export function AvailabilityEdit({
|
||||
hasLabel = true,
|
||||
}: Props) {
|
||||
const steps = useRef(["Sale", "Confirmation", "Success"]);
|
||||
const [availability, setAvailability] = useState<AvailabilityState>(
|
||||
defaultAvailabilityData
|
||||
);
|
||||
const [availability, setAvailability] = useState<
|
||||
AvailabilityState & { slots?: unknown }
|
||||
>(defaultAvailabilityData);
|
||||
const { state, dispatch } = useStepperReducer();
|
||||
const { mutateAsync, error } = useAvailabilityMutation(dispatch, state);
|
||||
const editAvailabilityValue = useRef(0);
|
||||
@ -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,
|
||||
@ -112,7 +92,8 @@ export function AvailabilityEdit({
|
||||
WebStorage.set("availability-step", step);
|
||||
|
||||
if (step == CONFIRM_STATE) {
|
||||
const { slots, name, ...rest } = availability as any;
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
const { slots, name, ...rest } = availability;
|
||||
mutateAsync(rest);
|
||||
} else {
|
||||
dispatch({
|
||||
@ -156,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);
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
option {
|
||||
background-color: #232323;
|
||||
background-color: rgb(35, 35, 35);
|
||||
}
|
||||
|
||||
header {
|
||||
@ -20,6 +20,12 @@
|
||||
|
||||
.row {
|
||||
margin-bottom: 16px;
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
& {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group {
|
||||
@ -29,13 +35,13 @@
|
||||
|
||||
&[aria-invalid] {
|
||||
.input-group > div > div > div:nth-child(2) {
|
||||
border-color: var(--codex-input-color-error);
|
||||
border-color: var(-codex-color-error);
|
||||
}
|
||||
|
||||
label,
|
||||
svg,
|
||||
select {
|
||||
color: var(--codex-input-color-error);
|
||||
color: var(-codex-color-error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"],
|
||||
@ -185,40 +181,38 @@ export function AvailabilityForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="row gap">
|
||||
<div className="group">
|
||||
<Input
|
||||
id="minPrice"
|
||||
name="minPrice"
|
||||
type="number"
|
||||
label="Min price"
|
||||
min={0}
|
||||
onChange={onInputChange}
|
||||
value={availability.minPrice.toString()}
|
||||
/>
|
||||
<Tooltip message={"Minimum price to be paid (in amount of tokens)"}>
|
||||
<InfoIcon></InfoIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="row gap">
|
||||
<div className="group">
|
||||
<Input
|
||||
id="minPrice"
|
||||
name="minPrice"
|
||||
type="number"
|
||||
label="Min price"
|
||||
min={0}
|
||||
onChange={onInputChange}
|
||||
value={availability.minPrice.toString()}
|
||||
/>
|
||||
<Tooltip message={"Minimum price to be paid (in amount of tokens)"}>
|
||||
<InfoIcon></InfoIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="group">
|
||||
<Input
|
||||
id="maxCollateral"
|
||||
name="maxCollateral"
|
||||
type="number"
|
||||
label="Max collateral"
|
||||
min={0}
|
||||
onChange={onInputChange}
|
||||
value={availability.maxCollateral.toString()}
|
||||
/>
|
||||
<Tooltip
|
||||
message={
|
||||
"Maximum collateral user is willing to pay per filled Slot (in amount of tokens)"
|
||||
}>
|
||||
<InfoIcon></InfoIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="group">
|
||||
<Input
|
||||
id="maxCollateral"
|
||||
name="maxCollateral"
|
||||
type="number"
|
||||
label="Max collateral"
|
||||
min={0}
|
||||
onChange={onInputChange}
|
||||
value={availability.maxCollateral.toString()}
|
||||
/>
|
||||
<Tooltip
|
||||
message={
|
||||
"Maximum collateral user is willing to pay per filled Slot (in amount of tokens)"
|
||||
}>
|
||||
<InfoIcon></InfoIcon>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -11,14 +11,14 @@ import { Promises } from "../../utils/promises";
|
||||
import { CodexAvailability } from "@codex-storage/sdk-js";
|
||||
import { useEffect } from "react";
|
||||
import { ErrorPlaceholder } from "../ErrorPlaceholder/ErrorPlaceholder";
|
||||
import { availabilityColors } from "./availability.colors";
|
||||
import { AvailabilityUtils } from "./availability.utils";
|
||||
|
||||
type Props = {
|
||||
availability: CodexAvailability | null;
|
||||
open: boolean;
|
||||
onClose: () => unknown;
|
||||
};
|
||||
// TODO remove this
|
||||
|
||||
export function AvailabilityReservations({
|
||||
availability,
|
||||
onClose,
|
||||
@ -95,12 +95,15 @@ export function AvailabilityReservations({
|
||||
...data.map((val, index) => ({
|
||||
title: val.id,
|
||||
size: parseInt(val.size, 10),
|
||||
color: availabilityColors[index],
|
||||
color: AvailabilityUtils.availabilityColors[index],
|
||||
})),
|
||||
{
|
||||
title: "Availability remaining",
|
||||
size: totalSize - totalUsed,
|
||||
color: availabilityColors[availabilityColors.length - 1],
|
||||
color:
|
||||
AvailabilityUtils.availabilityColors[
|
||||
AvailabilityUtils.availabilityColors.length - 1
|
||||
],
|
||||
},
|
||||
];
|
||||
const isEmpty = !data.length;
|
||||
|
||||
@ -1,164 +0,0 @@
|
||||
import {
|
||||
Stepper,
|
||||
useStepperReducer,
|
||||
Button,
|
||||
Modal,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { AvailabilityForm } from "./AvailabilityForm";
|
||||
import { Plus } from "lucide-react";
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { AvailabilityConfirm } from "./AvailabilityConfirmation";
|
||||
import { WebStorage } from "../../utils/web-storage";
|
||||
import { AvailabilityState } from "./types";
|
||||
import { STEPPER_DURATION } from "../../utils/constants";
|
||||
import { useAvailabilityMutation } from "./useAvailabilityMutation";
|
||||
import { AvailabilitySuccess } from "./AvailabilitySuccess";
|
||||
import { AvailabilityError } from "./AvailabilityError";
|
||||
import "./AvailabilityCreate.css";
|
||||
|
||||
type Props = {
|
||||
space: CodexNodeSpace;
|
||||
hasLabel?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const CONFIRM_STATE = 2;
|
||||
|
||||
const defaultAvailabilityData: AvailabilityState = {
|
||||
totalSize: 1,
|
||||
duration: 1,
|
||||
minPrice: 0,
|
||||
maxCollateral: 0,
|
||||
totalSizeUnit: "gb",
|
||||
durationUnit: "days",
|
||||
};
|
||||
|
||||
export function AvailabilitySheetCreate({
|
||||
space,
|
||||
className = "",
|
||||
hasLabel = true,
|
||||
}: Props) {
|
||||
const steps = useRef(["Sale", "Confirmation", "Success"]);
|
||||
const [availability, setAvailability] = useState<AvailabilityState>(
|
||||
defaultAvailabilityData
|
||||
);
|
||||
const { state, dispatch } = useStepperReducer();
|
||||
const { mutateAsync, error } = useAvailabilityMutation(dispatch, state);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
WebStorage.get<number>("availability-step"),
|
||||
WebStorage.get<AvailabilityState>("availability"),
|
||||
]).then(([s, a]) => {
|
||||
if (s) {
|
||||
dispatch({
|
||||
type: "next",
|
||||
step: s,
|
||||
});
|
||||
}
|
||||
|
||||
if (a) {
|
||||
setAvailability(a);
|
||||
}
|
||||
});
|
||||
}, [dispatch]);
|
||||
|
||||
const components = [
|
||||
AvailabilityForm,
|
||||
AvailabilityConfirm,
|
||||
error ? AvailabilityError : AvailabilitySuccess,
|
||||
];
|
||||
|
||||
const onNextStep = async (step: number) => {
|
||||
if (step === components.length) {
|
||||
setAvailability(defaultAvailabilityData);
|
||||
|
||||
dispatch({
|
||||
step: 0,
|
||||
type: "next",
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "close",
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
WebStorage.set("availability-step", step);
|
||||
|
||||
if (step == CONFIRM_STATE) {
|
||||
mutateAsync(availability);
|
||||
} else {
|
||||
dispatch({
|
||||
step,
|
||||
type: "next",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onAvailabilityChange = (data: Partial<AvailabilityState>) => {
|
||||
const val = { ...availability, ...data };
|
||||
|
||||
WebStorage.set("availability", val);
|
||||
|
||||
setAvailability(val);
|
||||
};
|
||||
|
||||
const onOpen = () => {
|
||||
if (availability.id) {
|
||||
WebStorage.set("availability-step", 0);
|
||||
WebStorage.set("availability", defaultAvailabilityData);
|
||||
|
||||
setAvailability(defaultAvailabilityData);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: "open",
|
||||
});
|
||||
|
||||
dispatch({
|
||||
step: 0,
|
||||
type: "next",
|
||||
});
|
||||
};
|
||||
|
||||
const onClose = () => dispatch({ type: "close" });
|
||||
|
||||
const Body = components[state.step] || (() => <span />);
|
||||
const backLabel = state.step ? "Back" : "Close";
|
||||
const nextLabel = state.step === steps.current.length - 1 ? "Finish" : "Next";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
label={hasLabel ? "Sale" : ""}
|
||||
Icon={Plus}
|
||||
onClick={onOpen}
|
||||
variant="primary"
|
||||
className={className}
|
||||
/>
|
||||
|
||||
<Modal open={state.open} onClose={onClose}>
|
||||
<Stepper
|
||||
titles={steps.current}
|
||||
state={state}
|
||||
dispatch={dispatch}
|
||||
duration={STEPPER_DURATION}
|
||||
onNextStep={onNextStep}
|
||||
backLabel={backLabel}
|
||||
nextLabel={nextLabel}>
|
||||
<Body
|
||||
dispatch={dispatch}
|
||||
state={state}
|
||||
onAvailabilityChange={onAvailabilityChange}
|
||||
availability={availability}
|
||||
space={space}
|
||||
error={error}
|
||||
/>
|
||||
</Stepper>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
.availabilitySpaceAllocation-title {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
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 "../NodeSpace/nodeSpace.domain";
|
||||
|
||||
type Props = {
|
||||
space: CodexNodeSpace;
|
||||
availability: AvailabilityState;
|
||||
};
|
||||
|
||||
export function AvailabilitySpaceAllocation({ availability, space }: Props) {
|
||||
const { quotaMaxBytes, quotaReservedBytes, quotaUsedBytes } = space;
|
||||
const isUpdating = !!availability.id;
|
||||
const allocated = isUpdating
|
||||
? quotaReservedBytes - availability.totalSize + quotaUsedBytes
|
||||
: quotaReservedBytes + quotaUsedBytes;
|
||||
const remaining =
|
||||
availability.totalSize > quotaMaxBytes - allocated
|
||||
? quotaMaxBytes - allocated
|
||||
: quotaMaxBytes - allocated - availability.totalSize;
|
||||
|
||||
const spaceData = [
|
||||
{
|
||||
title: "Space allocated",
|
||||
size: Math.trunc(allocated),
|
||||
color: nodeSpaceAllocationColors[0],
|
||||
},
|
||||
{
|
||||
title: "New space allocation",
|
||||
size: Math.trunc(availability.totalSize),
|
||||
color: nodeSpaceAllocationColors[1],
|
||||
},
|
||||
{
|
||||
title: "Remaining space",
|
||||
size: Math.trunc(remaining),
|
||||
color: nodeSpaceAllocationColors[nodeSpaceAllocationColors.length - 1],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<b className="availabilitySpaceAllocation-title">Node space allocation</b>
|
||||
|
||||
<SpaceAllocation data={spaceData} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import { Placeholder } from "@codex-storage/marketplace-ui-components";
|
||||
import { SuccessIcon } from "../SuccessIcon/SuccessIcon";
|
||||
import { AvailabilityComponentProps } from "./types";
|
||||
import { useEffect } from "react";
|
||||
import SuccessCircleIcon from "../../assets/icons/success-circle.svg?react";
|
||||
|
||||
export function AvailabilitySuccess({ dispatch }: AvailabilityComponentProps) {
|
||||
useEffect(() => {
|
||||
@ -14,7 +14,7 @@ export function AvailabilitySuccess({ dispatch }: AvailabilityComponentProps) {
|
||||
|
||||
return (
|
||||
<Placeholder
|
||||
Icon={<SuccessIcon />}
|
||||
Icon={<SuccessCircleIcon width={40} height={40} />}
|
||||
title="Success"
|
||||
message="The new sale will appear in your sale list. You can safely close this dialog."></Placeholder>
|
||||
);
|
||||
|
||||
@ -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,5 +1,3 @@
|
||||
.sunburst {
|
||||
height: 350px;
|
||||
width: 350px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@ -1,19 +1,27 @@
|
||||
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";
|
||||
import { availabilityColors, slotColors } from "./availability.colors";
|
||||
import * as echarts from "echarts/core";
|
||||
|
||||
// Import bar charts, all suffixed with Chart
|
||||
import { SunburstChart } from "echarts/charts";
|
||||
import { AvailabilityWithSlots } from "./types";
|
||||
import "./Sunburst.css";
|
||||
import { AvailabilityUtils } from "./availability.utils";
|
||||
|
||||
type Props = {
|
||||
availabilities: AvailabilityWithSlots[];
|
||||
space: CodexNodeSpace;
|
||||
};
|
||||
|
||||
import { TooltipComponent } from "echarts/components";
|
||||
import { SVGRenderer } from "echarts/renderers";
|
||||
|
||||
echarts.use([SunburstChart, TooltipComponent, SVGRenderer]);
|
||||
|
||||
export function Sunburst({ availabilities, space }: Props) {
|
||||
const div = useRef<HTMLDivElement>(null);
|
||||
const chart = useRef<echarts.EChartsType | null>(null);
|
||||
@ -28,12 +36,22 @@ 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),
|
||||
value: a.totalSize,
|
||||
itemStyle: {
|
||||
color: availabilityColors[index],
|
||||
color: AvailabilityUtils.availabilityColors[index],
|
||||
borderColor: "transparent",
|
||||
},
|
||||
tooltip: {
|
||||
@ -57,7 +75,7 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
a.minPrice +
|
||||
"<br/>" +
|
||||
"Size " +
|
||||
PrettyBytes(a.totalSize)
|
||||
Bytes.pretty(a.totalSize)
|
||||
);
|
||||
},
|
||||
},
|
||||
@ -66,7 +84,7 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
value: parseFloat(slot.size),
|
||||
children: [],
|
||||
itemStyle: {
|
||||
color: slotColors[index],
|
||||
color: AvailabilityUtils.slotColors[index],
|
||||
borderColor: "transparent",
|
||||
},
|
||||
tooltip: {
|
||||
@ -79,7 +97,7 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
params.marker +
|
||||
"Slot " +
|
||||
slot.id +
|
||||
PrettyBytes(parseFloat(slot.size))
|
||||
Bytes.pretty(parseFloat(slot.size))
|
||||
);
|
||||
},
|
||||
},
|
||||
@ -87,81 +105,83 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
};
|
||||
});
|
||||
|
||||
const option: ECBasicOption = {
|
||||
series: {
|
||||
type: "sunburst",
|
||||
data: [
|
||||
...data,
|
||||
{
|
||||
name: "Space remaining",
|
||||
value:
|
||||
space.quotaMaxBytes -
|
||||
space.quotaReservedBytes -
|
||||
space.quotaUsedBytes,
|
||||
children: [],
|
||||
itemStyle: {
|
||||
color: "#2F2F2F",
|
||||
borderColor: "transparent",
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: "#333",
|
||||
textStyle: {
|
||||
color: "#fff",
|
||||
},
|
||||
formatter: (params: CallbackDataParams) => {
|
||||
return (
|
||||
params.marker +
|
||||
" Space remaining " +
|
||||
PrettyBytes(
|
||||
space.quotaMaxBytes -
|
||||
space.quotaReservedBytes -
|
||||
space.quotaUsedBytes
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
radius: [60, "90%"],
|
||||
itemStyle: {
|
||||
borderWidth: 1,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
levels: [
|
||||
{},
|
||||
{
|
||||
r0: "35%",
|
||||
r: "70%",
|
||||
label: {
|
||||
align: "right",
|
||||
},
|
||||
},
|
||||
{
|
||||
r0: "75%",
|
||||
r: "85%",
|
||||
itemStyle: {},
|
||||
label: {
|
||||
position: "outside",
|
||||
textShadowBlur: 5,
|
||||
textShadowColor: "#333",
|
||||
},
|
||||
downplay: {
|
||||
label: {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
tooltip: {
|
||||
// type: "item",
|
||||
},
|
||||
};
|
||||
|
||||
if (chart.current) {
|
||||
const option: ECBasicOption = {
|
||||
series: {
|
||||
type: "sunburst",
|
||||
data: [
|
||||
...data,
|
||||
{
|
||||
name: "Space remaining",
|
||||
value:
|
||||
space.quotaMaxBytes -
|
||||
space.quotaReservedBytes -
|
||||
space.quotaUsedBytes,
|
||||
children: [],
|
||||
itemStyle: {
|
||||
color: "#2F2F2F",
|
||||
borderColor: "transparent",
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: "#333",
|
||||
textStyle: {
|
||||
color: "#fff",
|
||||
},
|
||||
formatter: (params: CallbackDataParams) => {
|
||||
return (
|
||||
params.marker +
|
||||
" Space remaining " +
|
||||
Bytes.pretty(
|
||||
space.quotaMaxBytes -
|
||||
space.quotaReservedBytes -
|
||||
space.quotaUsedBytes
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
radius: [60, "90%"],
|
||||
itemStyle: {
|
||||
borderWidth: 1,
|
||||
},
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
levels: [
|
||||
{},
|
||||
{
|
||||
r0: "35%",
|
||||
r: "70%",
|
||||
label: {
|
||||
align: "right",
|
||||
},
|
||||
},
|
||||
{
|
||||
r0: "75%",
|
||||
r: "85%",
|
||||
itemStyle: {},
|
||||
label: {
|
||||
position: "outside",
|
||||
textShadowBlur: 5,
|
||||
textShadowColor: "#333",
|
||||
},
|
||||
downplay: {
|
||||
label: {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
tooltip: {
|
||||
// type: "item",
|
||||
},
|
||||
};
|
||||
|
||||
chart.current.setOption(option);
|
||||
chart.current?.resize();
|
||||
|
||||
// chart.current.off("click");
|
||||
// chart.current.on("click", function (params) {
|
||||
// // console.info(params.componentIndex);
|
||||
@ -180,5 +200,15 @@ export function Sunburst({ availabilities, space }: Props) {
|
||||
// });
|
||||
}
|
||||
|
||||
return <div id="chart" ref={div} className="sunburst"></div>;
|
||||
const size = window.innerWidth > 500 ? 350 : 300;
|
||||
|
||||
return (
|
||||
<div
|
||||
id="chart"
|
||||
ref={div}
|
||||
className="sunburst"
|
||||
style={{
|
||||
height: size,
|
||||
}}></div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
export const availabilityColors = [
|
||||
"#34A0FFFF",
|
||||
"#34A0FFEE",
|
||||
"#34A0FFDD",
|
||||
"#34A0FFCC",
|
||||
"#34A0FFBB",
|
||||
"#34A0FFAA",
|
||||
"#34A0FF99",
|
||||
];
|
||||
|
||||
export const slotColors = [
|
||||
"#D2493CFF",
|
||||
"#D2493CEE",
|
||||
"#D2493CDD",
|
||||
"#D2493CCC",
|
||||
"#D2493CBB",
|
||||
"#D2493CAA",
|
||||
"#D2493C99",
|
||||
];
|
||||
@ -1,46 +0,0 @@
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { GB, TB } from "../../utils/constants";
|
||||
import { AvailabilityState } from "./types";
|
||||
|
||||
export class AvailabilityDomain {
|
||||
space: CodexNodeSpace
|
||||
state: AvailabilityState
|
||||
|
||||
constructor(space: CodexNodeSpace, availability: AvailabilityState) {
|
||||
this.space = space
|
||||
this.state = availability
|
||||
}
|
||||
|
||||
get unitInBytes() {
|
||||
return this.state.totalSizeUnit === "gb" ? GB : TB;
|
||||
}
|
||||
|
||||
get unit() {
|
||||
return this.state.totalSizeUnit;
|
||||
}
|
||||
|
||||
get sizeInUnit() {
|
||||
return this.state.totalSize
|
||||
}
|
||||
|
||||
get maxInBytes() {
|
||||
return this.space.quotaMaxBytes - this.space.quotaReservedBytes - this.space.quotaUsedBytes
|
||||
}
|
||||
|
||||
get maxInUnit() {
|
||||
return this.maxInBytes / this.unitInBytes / this.unitInBytes
|
||||
}
|
||||
|
||||
isValid() {
|
||||
const size = this.state.totalSize * this.unitInBytes;
|
||||
|
||||
return size > 0 && size <= this.maxInBytes;
|
||||
}
|
||||
}
|
||||
|
||||
export const isAvailabilityValid = (
|
||||
availability: AvailabilityState,
|
||||
max: number
|
||||
) => availability.totalSize > 0 && availability.totalSize <= max;
|
||||
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { assert, describe, it } from "vitest";
|
||||
import { AvailabilityUtils } from "./availability.utils";
|
||||
import { GB, TB } from "../../utils/constants";
|
||||
import { CodexNodeSpace } from "@codex-storage/sdk-js";
|
||||
import { AvailabilityState } from "./types";
|
||||
|
||||
describe("files", () => {
|
||||
it("sorts by id", async () => {
|
||||
@ -156,4 +159,37 @@ describe("files", () => {
|
||||
|
||||
assert.deepEqual(ascSorted, [a, b]);
|
||||
});
|
||||
|
||||
it("returns the number of bytes per unit", async () => {
|
||||
assert.deepEqual(AvailabilityUtils.toUnit(GB, "gb"), 1);
|
||||
assert.deepEqual(AvailabilityUtils.toUnit(TB, "tb"), 1);
|
||||
})
|
||||
|
||||
|
||||
it("returns the max value possible for an availability", async () => {
|
||||
const space: CodexNodeSpace = {
|
||||
quotaMaxBytes: 8 * GB,
|
||||
quotaReservedBytes: 2 * GB,
|
||||
quotaUsedBytes: GB,
|
||||
totalBlocks: 0
|
||||
}
|
||||
assert.deepEqual(AvailabilityUtils.maxValue(space), 5 * GB - 1);
|
||||
})
|
||||
|
||||
it("checks the availability max value", async () => {
|
||||
const availability = {
|
||||
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: GB }, 2 * GB), false);
|
||||
})
|
||||
|
||||
it("toggles item in array", async () => {
|
||||
const array: string[] = []
|
||||
assert.deepEqual(AvailabilityUtils.toggle(array, "1"), ["1"]);
|
||||
assert.deepEqual(AvailabilityUtils.toggle(AvailabilityUtils.toggle(array, "1"), "1"), []);
|
||||
})
|
||||
})
|
||||
@ -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,6 +48,46 @@ 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],
|
||||
|
||||
availabilityColors: [
|
||||
"#34A0FFFF",
|
||||
"#34A0FFEE",
|
||||
"#34A0FFDD",
|
||||
"#34A0FFCC",
|
||||
"#34A0FFBB",
|
||||
"#34A0FFAA",
|
||||
"#34A0FF99",
|
||||
"#34A0FF88",
|
||||
"#34A0FF77",
|
||||
"#34A0FF66",
|
||||
"#34A0FF55",
|
||||
"#34A0FF44",
|
||||
"#34A0FF33",
|
||||
"#34A0FF22",
|
||||
"#34A0FF11",
|
||||
"#34A0FF00",
|
||||
],
|
||||
|
||||
slotColors: [
|
||||
"#D2493CFF",
|
||||
"#D2493CEE",
|
||||
"#D2493CDD",
|
||||
"#D2493CCC",
|
||||
"#D2493CBB",
|
||||
"#D2493CAA",
|
||||
"#D2493C99",
|
||||
"#D2493C88",
|
||||
"#D2493C77",
|
||||
"#D2493C66",
|
||||
"#D2493C55",
|
||||
"#D2493C44",
|
||||
"#D2493C33",
|
||||
"#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) {
|
||||
|
||||
@ -1,8 +1,15 @@
|
||||
.card {
|
||||
border: 1px solid #96969633;
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
background-color: #232323;
|
||||
background-color: rgb(35, 35, 35);
|
||||
|
||||
@media (max-width: 800px) {
|
||||
&,
|
||||
td:nth-child(2) {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
> header {
|
||||
display: flex;
|
||||
@ -12,7 +19,7 @@
|
||||
margin-bottom: 16px;
|
||||
|
||||
svg {
|
||||
color: #969696;
|
||||
color: rgba(150, 150, 150, 1);
|
||||
}
|
||||
|
||||
> div {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
.card-number {
|
||||
--codex-card-number-label-color: #7b7b7b;
|
||||
--codex-card-number-unit-color: #969696;
|
||||
--codex-card-number-label-color: rgb(123, 123, 123);
|
||||
--codex-card-number-unit-color: rgba(150, 150, 150, 1);
|
||||
|
||||
&[aria-invalid] {
|
||||
--codex-card-number-label-color: var(--codex-input-color-error);
|
||||
--codex-card-number-unit-color: var(--codex-input-color-error);
|
||||
--codex-card-number-label-color: var(-codex-color-error);
|
||||
--codex-card-number-unit-color: var(-codex-color-error);
|
||||
}
|
||||
|
||||
position: relative;
|
||||
@ -33,21 +33,6 @@
|
||||
color: var(--codex-card-number-unit-color);
|
||||
}
|
||||
|
||||
/* svg::after {
|
||||
content: attr(data-title);
|
||||
background-color: #2f2f2f;
|
||||
color: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
overflow: visible;
|
||||
} */
|
||||
|
||||
span {
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
|
||||
@ -58,34 +58,6 @@ export function CardNumbers({
|
||||
<InfoIcon></InfoIcon>
|
||||
</Tooltip>
|
||||
<span>{unit}</span>
|
||||
|
||||
{/* <div
|
||||
className={classnames(["cardNumber"], ["cardNumber--error", !!error])}>
|
||||
<div className="cardNumber-dataContainer">
|
||||
<>
|
||||
<p
|
||||
ref={ref}
|
||||
className="cardNumber-data"
|
||||
onBlur={onBlur}
|
||||
onInput={onInput}
|
||||
contentEditable={true}
|
||||
/>
|
||||
|
||||
<ButtonIcon
|
||||
onClick={onEditingClick}
|
||||
variant="small"
|
||||
Icon={Icon}></ButtonIcon>
|
||||
</>
|
||||
</div>
|
||||
<div className="cardNumber-info">
|
||||
<b className="cardNumber-title">{title}</b>
|
||||
</div>
|
||||
</div>
|
||||
{error ? (
|
||||
<small className="cardNumber-errorText">{error}</small>
|
||||
) : (
|
||||
<small className="cardNumber-helperText">{helper}</small>
|
||||
)} */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 550px;
|
||||
/* min-width: 550px; */
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
@ -13,8 +13,8 @@
|
||||
display: flex;
|
||||
list-style-type: none;
|
||||
margin-top: 16px;
|
||||
border: 1px solid #96969633;
|
||||
background-color: #14141499;
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
background-color: rgba(20, 20, 20, 0.6);
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
|
||||
@ -28,18 +28,22 @@
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
text-align: left;
|
||||
color: #969696;
|
||||
color: rgba(150, 150, 150, 1);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&[aria-selected] {
|
||||
background: #2f2f2f;
|
||||
background: rgb(47, 47, 47);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #161616;
|
||||
border: 1px solid #96969633;
|
||||
background-color: rgb(22, 22, 22);
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
border-radius: 6px;
|
||||
@ -67,7 +71,7 @@
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.006em;
|
||||
text-align: left;
|
||||
color: #969696;
|
||||
color: rgba(150, 150, 150, 1);
|
||||
}
|
||||
|
||||
var {
|
||||
@ -87,7 +91,7 @@
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
text-align: left;
|
||||
color: #7d7d7d;
|
||||
color: rgb(125, 125, 125);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,18 +2,36 @@ import "./ConnectedAccount.css";
|
||||
import { WalletCard } from "./WalletCard";
|
||||
import { ProgressCircle } from "./ProgressCircle";
|
||||
import ArrowRightIcon from "../../assets/icons/arrow-right.svg?react";
|
||||
import { useState } from "react";
|
||||
import { attributes } from "../../utils/attributes";
|
||||
|
||||
type TabType = "weekly" | "daily" | "monthly";
|
||||
|
||||
export function ConnectedAccount() {
|
||||
const [tab, setTab] = useState<TabType>("monthly");
|
||||
|
||||
return (
|
||||
<div className="connected-account">
|
||||
<main>
|
||||
<WalletCard></WalletCard>
|
||||
<WalletCard tab={tab}></WalletCard>
|
||||
</main>
|
||||
<footer>
|
||||
<ul>
|
||||
<li>Daily</li>
|
||||
<li aria-selected>Weekly</li>
|
||||
<li>Monthly</li>
|
||||
<li
|
||||
onClick={() => setTab("daily")}
|
||||
{...attributes({ "aria-selected": tab === "daily" })}>
|
||||
Daily
|
||||
</li>
|
||||
<li
|
||||
onClick={() => setTab("weekly")}
|
||||
{...attributes({ "aria-selected": tab === "weekly" })}>
|
||||
Weekly
|
||||
</li>
|
||||
<li
|
||||
onClick={() => setTab("monthly")}
|
||||
{...attributes({ "aria-selected": tab === "monthly" })}>
|
||||
Monthly
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<div className="row gap">
|
||||
|
||||
@ -3,20 +3,21 @@
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
background: #2f2f2f;
|
||||
background: rgb(47, 47, 47);
|
||||
|
||||
background-image:
|
||||
-webkit-linear-gradient(90deg, #2f2f2f 50%, transparent 50%),
|
||||
-webkit-linear-gradient(270deg, var(--codex-color-primary) 50%, #2f2f2f 50%);
|
||||
background-image: linear-gradient(90deg, #2f2f2f 50%, transparent 50%),
|
||||
linear-gradient(270deg, var(--codex-color-primary) 50%, #2f2f2f 50%);
|
||||
-webkit-linear-gradient(90deg, rgb(47, 47, 47) 50%, transparent 50%),
|
||||
-webkit-linear-gradient(270deg, var(--codex-color-primary) 50%, rgb
|
||||
(47, 47, 47) 50%);
|
||||
background-image: linear-gradient(90deg, rgb(47, 47, 47) 50%, transparent 50%),
|
||||
linear-gradient(270deg, var(--codex-color-primary) 50%, rgb(47, 47, 47) 50%);
|
||||
|
||||
div {
|
||||
border-radius: 50%;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin: auto;
|
||||
background: #232323;
|
||||
background: rgb(35, 35, 35);
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ type Props = {
|
||||
value: number;
|
||||
};
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
export function ProgressCircle(_: Props) {
|
||||
return (
|
||||
<div className="progress-circle">
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
border: 1px solid #96969633;
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
border-radius: 16px;
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
@ -36,8 +36,8 @@
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
background-color: #161616;
|
||||
border: 1px solid #96969633;
|
||||
background-color: rgb(22, 22, 22);
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
cursor: pointer;
|
||||
@ -47,7 +47,7 @@
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 3px var(--codex-border-color);
|
||||
box-shadow: 0 0 0 3px rgba(150, 150, 150, 0.2);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
@ -84,7 +84,7 @@
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.006em;
|
||||
text-align: left;
|
||||
color: #ffffffb2;
|
||||
color: rgb(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
var {
|
||||
@ -92,7 +92,7 @@
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
letter-spacing: -0.005em;
|
||||
color: var(--text-strong-950, #ffffff);
|
||||
color: rgb(255, 255, 255);
|
||||
display: block;
|
||||
font-style: normal;
|
||||
}
|
||||
@ -130,30 +130,60 @@
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: #161616;
|
||||
background-color: rgb(22, 22, 22);
|
||||
border-radius: 8px;
|
||||
border: 1px solid #96969633;
|
||||
padding: 6px 6px 6px 44px;
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
padding: 6px 24px 6px 34px;
|
||||
outline: none;
|
||||
-moz-appearance: none; /* Firefox */
|
||||
-webkit-appearance: none; /* Safari and Chrome */
|
||||
appearance: none;
|
||||
position: relative;
|
||||
transition: box-shadow 0.35s;
|
||||
height: 31px;
|
||||
font-size: 0;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 3px var(--codex-border-color);
|
||||
box-shadow: 0 0 0 3px rgba(150, 150, 150, 0.2);
|
||||
}
|
||||
|
||||
&:has(option[value="US"]: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);
|
||||
}
|
||||
|
||||
&: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 {
|
||||
border-radius: 32px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,11 +196,11 @@
|
||||
}
|
||||
|
||||
small {
|
||||
color: #3ee089;
|
||||
color: rgb(62, 224, 137);
|
||||
height: 20px;
|
||||
width: 42px;
|
||||
border-radius: 16px;
|
||||
background-color: #1fc16b29;
|
||||
background-color: rgb(31, 193, 107, 0.16);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -192,4 +222,8 @@
|
||||
top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.lines {
|
||||
height: 200;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,133 @@
|
||||
import "./WalletCard.css";
|
||||
import ArrowRightIcon from "../../assets/icons/arrow-right.svg?react";
|
||||
import ArrowLeftIcon from "../../assets/icons/arrow-left.svg?react";
|
||||
import WalletChart from "../../assets/icons/chart.svg?react";
|
||||
import WalletLines from "../../assets/icons/lines.svg?react";
|
||||
import { useState, ChangeEvent, useRef, useEffect } from "react";
|
||||
|
||||
import * as echarts from "echarts/core";
|
||||
import { LineChart } from "echarts/charts";
|
||||
import { GridComponent } from "echarts/components";
|
||||
import { UniversalTransition } from "echarts/features";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
|
||||
const defaultTokenValue = 1540;
|
||||
|
||||
echarts.use([GridComponent, LineChart, CanvasRenderer, UniversalTransition]);
|
||||
|
||||
type Props = {
|
||||
tab: "weekly" | "daily" | "monthly";
|
||||
};
|
||||
|
||||
export function WalletCard({ tab }: Props) {
|
||||
const [tokenValue, setTokenValue] = useState(defaultTokenValue);
|
||||
const [currency, setCurrency] = useState("USD");
|
||||
const div = useRef<HTMLDivElement>(null);
|
||||
const chart = useRef<echarts.EChartsType | null>(null);
|
||||
const [, setRefresher] = useState(Date.now());
|
||||
|
||||
useEffect(() => {
|
||||
if (div.current && !chart.current) {
|
||||
chart.current = echarts.init(div.current, null, {
|
||||
renderer: "svg",
|
||||
});
|
||||
setRefresher(Date.now());
|
||||
}
|
||||
}, [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 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[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: startDates,
|
||||
monthly: ["JAN", "FEB", "MAR", "APR", "MAY", "JUN"],
|
||||
}[tab];
|
||||
|
||||
const option = {
|
||||
color: ["#3EE089"],
|
||||
xAxis: {
|
||||
type: "category",
|
||||
data: data,
|
||||
show: true,
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
width: 0.5,
|
||||
type: "dashed",
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
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),
|
||||
type: "line",
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
color: "#3EE089",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
type: "item",
|
||||
},
|
||||
};
|
||||
|
||||
chart.current.setOption(option, true);
|
||||
}
|
||||
|
||||
export function WalletCard() {
|
||||
return (
|
||||
<div className="wallet-card">
|
||||
<header>
|
||||
@ -26,8 +149,12 @@ export function WalletCard() {
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<WalletChart></WalletChart>
|
||||
<WalletLines></WalletLines>
|
||||
{/* <WalletChart></WalletChart> */}
|
||||
{/* <WalletLines></WalletLines> */}
|
||||
<div
|
||||
className="lines"
|
||||
ref={div}
|
||||
style={{ height: 200, width: "100%" }}></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@ -35,12 +162,21 @@ export function WalletCard() {
|
||||
<div>
|
||||
<span>TOKEN</span>
|
||||
<div className="row">
|
||||
<var>$1,540 USD</var>
|
||||
<var>
|
||||
{tokenValue.toFixed(["BTC", "ETH"].includes(currency) ? 8 : 2)}{" "}
|
||||
{currency}
|
||||
</var>
|
||||
<small>+ 5%</small>
|
||||
</div>
|
||||
</div>
|
||||
<select defaultValue={"US"}>
|
||||
<option value={"US"}></option>
|
||||
<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>
|
||||
|
||||
@ -13,31 +13,22 @@ export const CustomStateCellRender = ({ state, message }: Props) => {
|
||||
pending: PurchaseStateIcon,
|
||||
submitted: PurchaseStateIcon,
|
||||
started: PurchaseStateIcon,
|
||||
finished: SuccessCircleIcon,
|
||||
finished: () => <SuccessCircleIcon width={20} />,
|
||||
cancelled: ErrorCircleIcon,
|
||||
errored: ErrorCircleIcon,
|
||||
};
|
||||
|
||||
const states = {
|
||||
cancelled: "error",
|
||||
errored: "error",
|
||||
pending: "warning",
|
||||
started: "loading",
|
||||
submitted: "loading",
|
||||
finished: "success",
|
||||
};
|
||||
|
||||
const Icon = icons[state as keyof typeof icons] || PurchaseStateIcon;
|
||||
|
||||
return (
|
||||
<Cell>
|
||||
<p className={"cell-state" + states[state as keyof typeof states]}>
|
||||
<p className={"cell-state"}>
|
||||
{message ? (
|
||||
<Tooltip message={message}>
|
||||
<Icon width={20} className="cell-stateIcon" />
|
||||
<Icon width={20} data-testid={"cell-" + state} />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Icon width={20} className="cell-stateIcon" />
|
||||
<Icon width={20} data-testid={"cell-" + state} />
|
||||
)}
|
||||
</p>
|
||||
</Cell>
|
||||
|
||||
@ -8,7 +8,7 @@ export function Debug() {
|
||||
|
||||
if (isPending) {
|
||||
return (
|
||||
<div className="settings-debug-loader">
|
||||
<div>
|
||||
<Spinner width="3rem" />
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
.dialog::backdrop {
|
||||
background: rgba(70, 70, 70, 0.75);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background-color: var(--codex-background-secondary);
|
||||
border: none;
|
||||
color: var(--codex-color);
|
||||
min-width: 200px;
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
import { ReactNode, useEffect, useRef } from "react";
|
||||
import "./Dialog.css";
|
||||
import { Button } from "@codex-storage/marketplace-ui-components";
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
children: ReactNode;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function Dialog({ open, children, onClose }: Props) {
|
||||
const ref = useRef<HTMLDialogElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
ref.current?.showModal();
|
||||
} else {
|
||||
ref.current?.close();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<dialog ref={ref} onCancel={onClose} className="dialog">
|
||||
<div>{children}</div>
|
||||
<Button onClick={onClose} label="Close"></Button>
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
@ -19,8 +19,9 @@ export function Download() {
|
||||
id="cid"
|
||||
placeholder="CID"
|
||||
inputClassName="download-input"
|
||||
size={"medium" as any}
|
||||
variant={"medium"}
|
||||
autoComplete="off"
|
||||
value={cid}
|
||||
onChange={onCidChange}></Input>
|
||||
<Button label="Download" onClick={onDownload} variant="outline"></Button>
|
||||
</main>
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import { CircleX } from "lucide-react";
|
||||
|
||||
export function ErrorIcon() {
|
||||
return (
|
||||
<span className="text--error">
|
||||
<CircleX size="4rem" />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Placeholder } from "@codex-storage/marketplace-ui-components";
|
||||
import { ErrorIcon } from "../ErrorIcon/ErrorIcon";
|
||||
import ErrorCircleIcon from "../../assets/icons/error-circle.svg?react";
|
||||
|
||||
type Props = {
|
||||
subtitle?: string;
|
||||
@ -13,7 +13,7 @@ export function ErrorPlaceholder({ subtitle, error }: Props) {
|
||||
|
||||
return (
|
||||
<Placeholder
|
||||
Icon={<ErrorIcon />}
|
||||
Icon={<ErrorCircleIcon width={30} />}
|
||||
title="Error"
|
||||
subtitle={subtitle}
|
||||
message={message}></Placeholder>
|
||||
|
||||
@ -1,24 +1,18 @@
|
||||
.fileCell {
|
||||
.file-render {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.fileCell-cid {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tooltip:hover::after {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.fileCell-subtitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.fileCell-tooltip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fileCell .tooltip:hover:after {
|
||||
left: -33%;
|
||||
small {
|
||||
white-space: nowrap;
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
text-align: left;
|
||||
color: rgb(125, 125, 125);
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,17 +69,17 @@ export function FileCell({ requestId, purchaseCid, data, onMetadata }: Props) {
|
||||
|
||||
return (
|
||||
<Cell>
|
||||
<div className="fileCell">
|
||||
<div className="file-render">
|
||||
<WebFileIcon type={metadata.mimetype || "-"} />
|
||||
<div>
|
||||
<span className="fileCell-title">
|
||||
<div>
|
||||
<Tooltip message={filename}>{filename}</Tooltip>
|
||||
</span>
|
||||
<span className="fileCell-subtitle">
|
||||
</div>
|
||||
<div>
|
||||
<Tooltip message={cid}>
|
||||
<span className="fileCell-cid">{cidTruncated}</span>
|
||||
<small>{cidTruncated}</small>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Cell>
|
||||
|
||||
@ -2,18 +2,48 @@
|
||||
> div {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border: 1px solid var(--codex-border-color);
|
||||
border-radius: var(--codex-border-radius);
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
border-radius: 10px;
|
||||
padding: 0.5rem;
|
||||
background-color: #14141499;
|
||||
background-color: rgba(20, 20, 20, 0.6);
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #2f2f2f;
|
||||
border: 1px solid #96969633;
|
||||
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,15 +28,62 @@ 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">
|
||||
<div>
|
||||
<ButtonIcon
|
||||
variant="small"
|
||||
animation="bounce"
|
||||
onClick={() => window.open(url + content.cid, "_blank")}
|
||||
Icon={DownloadIcon}></ButtonIcon>
|
||||
Icon={() => <DownloadIcon width={20}></DownloadIcon>}></ButtonIcon>
|
||||
|
||||
<FolderButton
|
||||
folders={folders.map(([folder, files]) => [
|
||||
|
||||
@ -15,8 +15,14 @@
|
||||
.button-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #14141499;
|
||||
border: 1px solid #96969633;
|
||||
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) => {
|
||||
navigator.clipboard.writeText(cid);
|
||||
setToast({ message: "CID copied to the clipboard.", time: Date.now() });
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(cid);
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
.file-details {
|
||||
background-color: #232323;
|
||||
border-left: 1px solid #96969633;
|
||||
background-color: rgb(35, 35, 35);
|
||||
border-left: 1px solid rgba(150, 150, 150, 0.2);
|
||||
border-top-left-radius: 16px;
|
||||
border-bottom-left-radius: 16px;
|
||||
padding: 16px;
|
||||
@ -21,22 +21,11 @@
|
||||
span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
background-color: #2f2f2f;
|
||||
border: 1px solid #96969633;
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
left: -3px;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview {
|
||||
background-color: #14141499;
|
||||
border: 1px solid #69696933;
|
||||
background-color: rgba(20, 20, 20, 0.6);
|
||||
border: 1px solid rgba(105, 105, 105, 0.2);
|
||||
height: 150px;
|
||||
margin: auto;
|
||||
border-radius: 10px;
|
||||
@ -63,7 +52,7 @@
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.011em;
|
||||
text-align: left;
|
||||
color: #ffffff33;
|
||||
color: rgb(255, 255, 255, 0.2);
|
||||
|
||||
p {
|
||||
margin-top: 8px;
|
||||
@ -95,7 +84,7 @@
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.006em;
|
||||
text-align: left;
|
||||
color: #ffffffcc;
|
||||
color: rgba (255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
&:last-child p:nth-child(2) {
|
||||
@ -106,14 +95,14 @@
|
||||
letter-spacing: -0.006em;
|
||||
text-align: left;
|
||||
|
||||
color: #6beca1;
|
||||
color: rgb(107, 236, 161);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #96969633;
|
||||
border-bottom: 1px solid rgba(150, 150, 150, 0.2);
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
@ -128,7 +117,7 @@
|
||||
header {
|
||||
margin-top: 16px;
|
||||
display: block;
|
||||
border-bottom: 1px solid #96969633;
|
||||
border-bottom: 1px solid rgba(150, 150, 150, 0.2);
|
||||
padding-bottom: 16px;
|
||||
|
||||
> span {
|
||||
@ -138,15 +127,15 @@
|
||||
}
|
||||
|
||||
thead tr th {
|
||||
border-top: 1px solid #96969633;
|
||||
border-bottom: 1px solid #96969633;
|
||||
border-top: 1px solid rgba(150, 150, 150, 0.2);
|
||||
border-bottom: 1px solid rgba(150, 150, 150, 0.2);
|
||||
|
||||
&:first-child {
|
||||
border-left: 1px solid #96969633;
|
||||
border-left: 1px solid rgba(150, 150, 150, 0.2);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right: 1px solid #96969633;
|
||||
border-right: 1px solid rgba(150, 150, 150, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,8 +5,7 @@ import {
|
||||
WebFileIcon,
|
||||
} from "@codex-storage/marketplace-ui-components";
|
||||
import { CodexDataContent, CodexPurchase } from "@codex-storage/sdk-js";
|
||||
import { PrettyBytes } from "../../utils/bytes";
|
||||
import { Dates } from "../../utils/dates";
|
||||
import { Bytes } from "../../utils/bytes";
|
||||
import { CidCopyButton } from "./CidCopyButton";
|
||||
import "./FileDetails.css";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
@ -43,7 +42,8 @@ export function FileDetails({ onClose, details }: Props) {
|
||||
}
|
||||
|
||||
return {
|
||||
error: false as any,
|
||||
/* eslint-disable @typescript-eslint/prefer-as-const */
|
||||
error: false as false,
|
||||
data: all,
|
||||
};
|
||||
})
|
||||
@ -115,7 +115,11 @@ export function FileDetails({ onClose, details }: Props) {
|
||||
|
||||
<li>
|
||||
<p>Date:</p>
|
||||
<p>{Dates.format(details.manifest.uploadedAt).toString()}</p>
|
||||
<p>
|
||||
{FilesUtils.formatDate(
|
||||
details.manifest.uploadedAt
|
||||
).toString()}
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
@ -125,7 +129,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>
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
padding: 4px 8px;
|
||||
gap: 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #96969633;
|
||||
background-color: #2f2f2f;
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
background-color: rgb(47, 47, 47);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
text-align: left;
|
||||
color: #969696;
|
||||
color: rgba(150, 150, 150, 1);
|
||||
text-transform: capitalize;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -17,11 +17,11 @@
|
||||
transition: box-shadow 0.35s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 3px var(--codex-border-color);
|
||||
box-shadow: 0 0 0 3px rgba(150, 150, 150, 0.2);
|
||||
}
|
||||
|
||||
svg {
|
||||
color: #969696;
|
||||
color: rgba(150, 150, 150, 1);
|
||||
}
|
||||
|
||||
&.filter--active {
|
||||
|
||||
@ -24,6 +24,17 @@
|
||||
}
|
||||
|
||||
table thead tr th {
|
||||
background-color: #14141499;
|
||||
background-color: rgba(20, 20, 20, 0.6);
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
table th:nth-child(2),
|
||||
table td:nth-child(2),
|
||||
table th:nth-child(3),
|
||||
table td:nth-child(3),
|
||||
section,
|
||||
.filters {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
import { PrettyBytes } from "../../utils/bytes";
|
||||
import { Dates } from "../../utils/dates";
|
||||
import { Bytes } from "../../utils/bytes";
|
||||
import "./Files.css";
|
||||
import {
|
||||
Tabs,
|
||||
@ -165,8 +164,10 @@ export function Files({ limit }: Props) {
|
||||
<Row
|
||||
cells={[
|
||||
<FileCell content={c}></FileCell>,
|
||||
<Cell>{PrettyBytes(c.manifest.datasetSize)}</Cell>,
|
||||
<Cell>{Dates.format(c.manifest.uploadedAt).toString()}</Cell>,
|
||||
<Cell>{Bytes.pretty(c.manifest.datasetSize)}</Cell>,
|
||||
<Cell>
|
||||
{FilesUtils.formatDate(c.manifest.uploadedAt).toString()}
|
||||
</Cell>,
|
||||
<FileActions
|
||||
content={c}
|
||||
folders={folders}
|
||||
@ -198,7 +199,7 @@ export function Files({ limit }: Props) {
|
||||
pattern="[A-Za-z0-9_\-]*"
|
||||
autoComplete="off"
|
||||
maxLength={9}
|
||||
size={"medium" as any}
|
||||
variant={"medium"}
|
||||
placeholder="Folder name"
|
||||
onChange={onFolderChange}></Input>
|
||||
|
||||
|
||||
@ -4,12 +4,12 @@
|
||||
transform: translate(310px, -200px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
background-color: var(--codex-background);
|
||||
background-color: rgba(47, 47, 47, 1);
|
||||
padding: 0.5rem;
|
||||
border-radius: var(--codex-border-radius);
|
||||
border-radius: 8px;
|
||||
width: 150px;
|
||||
right: -40px;
|
||||
border: 1px solid var(--codex-border-color);
|
||||
border: 1px solid rgba(150, 150, 150, 0.2);
|
||||
z-index: -1;
|
||||
|
||||
&[aria-expanded] {
|
||||
@ -22,13 +22,13 @@
|
||||
padding: 0.75rem;
|
||||
transition: background-color 0.35s;
|
||||
cursor: pointer;
|
||||
border-radius: var(--codex-border-radius);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--codex-background-light);
|
||||
background-color: rgba(150, 150, 150, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { Backdrop, ButtonIcon } from "@codex-storage/marketplace-ui-components";
|
||||
import { CheckCircle } from "lucide-react";
|
||||
import "./FolderButton.css";
|
||||
import { useState } from "react";
|
||||
import { attributes } from "../../utils/attributes";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import FolderIcon from "../../assets/icons/folder.svg?react";
|
||||
import SuccessCircleIcon from "../../assets/icons/success-circle.svg?react";
|
||||
|
||||
type Props = {
|
||||
folders: [string, boolean][];
|
||||
@ -42,7 +42,7 @@ export function FolderButton({ folders, onFolderToggle }: Props) {
|
||||
{folders.map(([folder, isActive]) => (
|
||||
<div key={folder} onClick={() => onFolderToggle(folder)}>
|
||||
<span>{folder}</span>
|
||||
{isActive && <CheckCircle size={"1rem"}></CheckCircle>}
|
||||
{isActive && <SuccessCircleIcon width={20}></SuccessCircleIcon>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -292,4 +292,10 @@ describe("files", () => {
|
||||
|
||||
assert.deepEqual(FilesUtils.applyFilters(files, ["archive"]), [files[1]]);
|
||||
});
|
||||
|
||||
it("formats date", async () => {
|
||||
const utcDate = new Date(Date.UTC(2024, 10, 20, 11, 36));
|
||||
|
||||
assert.equal(FilesUtils.formatDate(1732102577), "20 Nov 2024, " + utcDate.getHours() + ":" + utcDate.getMinutes());
|
||||
})
|
||||
})
|
||||
@ -89,6 +89,16 @@ export const FilesUtils = {
|
||||
return files.filter(
|
||||
(file) => filters.length === 0 || filters.includes(this.type(file.manifest.mimetype))
|
||||
)
|
||||
},
|
||||
formatDate(date: number) {
|
||||
if (!date) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat("en-GB", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
}).format(new Date(date * 1000));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -87,7 +87,7 @@
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 14px;
|
||||
color: #828282;
|
||||
color: rgb(130, 130, 130);
|
||||
padding-left: 1.25rem;
|
||||
margin-top: 1.75rem;
|
||||
margin-bottom: 3rem;
|
||||
@ -101,8 +101,8 @@
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
gap: 16px;
|
||||
border-top: 1px solid #96969633;
|
||||
border-bottom: 1px solid #96969633;
|
||||
border-top: 1px solid rgba(150, 150, 150, 0.2);
|
||||
border-bottom: 1px solid rgba(150, 150, 150, 0.2);
|
||||
|
||||
&:first-child {
|
||||
font-family: Inter;
|
||||
|
||||
@ -7,7 +7,7 @@ import { Input, Spinner } from "@codex-storage/marketplace-ui-components";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import "./HealthChecks.css";
|
||||
import { CodexSdk } from "../../sdk/codex";
|
||||
import { HealthCheckUtil } from "./health-check.utils";
|
||||
import { HealthCheckUtils } from "./health-check.utils";
|
||||
import { PortForwardingUtil } from "../../hooks/port-forwarding.util";
|
||||
import SuccessCircleIcon from "../../assets/icons/success-circle.svg?react";
|
||||
import ErrorCircleIcon from "../../assets/icons/error-circle.svg?react";
|
||||
@ -30,9 +30,9 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
const [isAddressInvalid, setIsAddressInvalid] = useState(false);
|
||||
const [isPortInvalid, setIsPortInvalid] = useState(false);
|
||||
const [address, setAddress] = useState(
|
||||
HealthCheckUtil.removePort(CodexSdk.url())
|
||||
HealthCheckUtils.removePort(CodexSdk.url())
|
||||
);
|
||||
const [port, setPort] = useState(HealthCheckUtil.getPort(CodexSdk.url()));
|
||||
const [port, setPort] = useState(HealthCheckUtils.getPort(CodexSdk.url()));
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
useEffect(
|
||||
@ -54,12 +54,16 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
|
||||
setIsAddressInvalid(!element.checkValidity());
|
||||
|
||||
const address = HealthCheckUtil.removePort(value);
|
||||
setAddress(address);
|
||||
setAddress(value);
|
||||
|
||||
if (HealthCheckUtil.containsPort(value)) {
|
||||
const p = HealthCheckUtil.getPort(value);
|
||||
if (HealthCheckUtils.containsPort(value)) {
|
||||
const address = HealthCheckUtils.removePort(value);
|
||||
setAddress(address);
|
||||
|
||||
const p = HealthCheckUtils.getPort(value);
|
||||
setPort(p);
|
||||
} else {
|
||||
setAddress(value);
|
||||
}
|
||||
};
|
||||
|
||||
@ -74,7 +78,7 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
const onSave = () => {
|
||||
const url = address + ":" + port;
|
||||
|
||||
if (HealthCheckUtil.isUrlInvalid(url)) {
|
||||
if (HealthCheckUtils.isUrlInvalid(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -112,7 +116,7 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
{isAddressInvalid ? (
|
||||
<ErrorCircleIcon width={16} />
|
||||
) : (
|
||||
<SuccessCircleIcon />
|
||||
<SuccessCircleIcon width={20} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -125,7 +129,7 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
value={port}
|
||||
isInvalid={isPortInvalid}
|
||||
placeholder="8080"></Input>
|
||||
<SuccessCircleIcon></SuccessCircleIcon>
|
||||
<SuccessCircleIcon width={20}></SuccessCircleIcon>
|
||||
</div>
|
||||
|
||||
<div className="refresh">
|
||||
@ -152,9 +156,9 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
<li>
|
||||
<span>
|
||||
{online ? (
|
||||
<SuccessCircleIcon></SuccessCircleIcon>
|
||||
<SuccessCircleIcon width={16} height={16}></SuccessCircleIcon>
|
||||
) : (
|
||||
<ErrorCircleIcon width={16} />
|
||||
<ErrorCircleIcon width={16} height={16} />
|
||||
)}
|
||||
</span>
|
||||
Internet connection
|
||||
@ -164,9 +168,9 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
{codex.isFetching ? (
|
||||
<Spinner></Spinner>
|
||||
) : codex.isSuccess ? (
|
||||
<SuccessCircleIcon></SuccessCircleIcon>
|
||||
<SuccessCircleIcon width={16} height={16}></SuccessCircleIcon>
|
||||
) : (
|
||||
<ErrorCircleIcon width={16} />
|
||||
<ErrorCircleIcon width={16} height={16} />
|
||||
)}
|
||||
</span>
|
||||
Codex connection
|
||||
@ -176,9 +180,9 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
{portForwarding.isFetching ? (
|
||||
<Spinner></Spinner>
|
||||
) : portForwarding.enabled ? (
|
||||
<SuccessCircleIcon></SuccessCircleIcon>
|
||||
<SuccessCircleIcon width={16} height={16}></SuccessCircleIcon>
|
||||
) : (
|
||||
<WarningIcon />
|
||||
<WarningIcon width={16} height={16} />
|
||||
)}
|
||||
</span>
|
||||
Port forwarding
|
||||
@ -188,9 +192,9 @@ export function HealthChecks({ online, onStepValid }: Props) {
|
||||
{persistence.isFetching ? (
|
||||
<Spinner></Spinner>
|
||||
) : persistence.enabled ? (
|
||||
<SuccessCircleIcon></SuccessCircleIcon>
|
||||
<SuccessCircleIcon width={16} height={16}></SuccessCircleIcon>
|
||||
) : (
|
||||
<WarningIcon />
|
||||
<WarningIcon width={16} height={16} />
|
||||
)}
|
||||
</span>
|
||||
Marketplace
|
||||
|
||||
@ -1,34 +1,34 @@
|
||||
import { assert, describe, it } from "vitest";
|
||||
import { HealthCheckUtil } from "./health-check.utils";
|
||||
import { HealthCheckUtils } from "./health-check.utils";
|
||||
|
||||
describe("health check", () => {
|
||||
it("remove the port from an url", async () => {
|
||||
assert.deepEqual(HealthCheckUtil.removePort("http://localhost:8080"), "http://localhost");
|
||||
assert.deepEqual(HealthCheckUtils.removePort("http://localhost:8080"), "http://localhost");
|
||||
});
|
||||
|
||||
it("get the port from an url", async () => {
|
||||
assert.deepEqual(HealthCheckUtil.getPort("http://localhost:8080"), 8080);
|
||||
assert.deepEqual(HealthCheckUtils.getPort("http://localhost:8080"), 8080);
|
||||
});
|
||||
|
||||
it("get the default port when the url does not contain the port", async () => {
|
||||
assert.deepEqual(HealthCheckUtil.getPort("http://localhost"), 80);
|
||||
assert.deepEqual(HealthCheckUtils.getPort("http://localhost"), 80);
|
||||
});
|
||||
|
||||
it("returns true when the url contains a port", async () => {
|
||||
assert.deepEqual(HealthCheckUtil.containsPort("http://localhost:8080"), true);
|
||||
assert.deepEqual(HealthCheckUtils.containsPort("http://localhost:8080"), true);
|
||||
});
|
||||
|
||||
it("returns false when the url does not contain a port", async () => {
|
||||
assert.deepEqual(HealthCheckUtil.containsPort("http://localhost"), false);
|
||||
assert.deepEqual(HealthCheckUtils.containsPort("http://localhost"), false);
|
||||
});
|
||||
|
||||
|
||||
it("returns true when the url is invalid", async () => {
|
||||
assert.deepEqual(HealthCheckUtil.isUrlInvalid("http://"), true);
|
||||
assert.deepEqual(HealthCheckUtils.isUrlInvalid("http://"), true);
|
||||
});
|
||||
|
||||
it("returns false when the url is valid", async () => {
|
||||
assert.deepEqual(HealthCheckUtil.isUrlInvalid("http://localhost:8080"), false);
|
||||
assert.deepEqual(HealthCheckUtils.isUrlInvalid("http://localhost:8080"), false);
|
||||
});
|
||||
|
||||
it("returns the tcp port", async () => {
|
||||
@ -57,7 +57,7 @@ describe("health check", () => {
|
||||
"revision": "2fb7031e"
|
||||
}
|
||||
}
|
||||
assert.deepEqual(HealthCheckUtil.getTcpPort(debug), { error: false, data: 8070 });
|
||||
assert.deepEqual(HealthCheckUtils.getTcpPort(debug), { error: false, data: 8070 });
|
||||
});
|
||||
|
||||
it("returns an error when the addr is empty", async () => {
|
||||
@ -85,7 +85,7 @@ describe("health check", () => {
|
||||
"revision": "2fb7031e"
|
||||
}
|
||||
}
|
||||
assert.deepEqual(HealthCheckUtil.getTcpPort(debug).error, true);
|
||||
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
|
||||
});
|
||||
|
||||
it("returns an error when the addr is misformated", async () => {
|
||||
@ -114,7 +114,7 @@ describe("health check", () => {
|
||||
"revision": "2fb7031e"
|
||||
}
|
||||
}
|
||||
assert.deepEqual(HealthCheckUtil.getTcpPort(debug).error, true);
|
||||
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
|
||||
});
|
||||
|
||||
it("returns an error when the port is misformated", async () => {
|
||||
@ -143,6 +143,14 @@ describe("health check", () => {
|
||||
"revision": "2fb7031e"
|
||||
}
|
||||
}
|
||||
assert.deepEqual(HealthCheckUtil.getTcpPort(debug).error, true);
|
||||
assert.deepEqual(HealthCheckUtils.getTcpPort(debug).error, true);
|
||||
});
|
||||
|
||||
it("extracts the announced ip", async () => {
|
||||
assert.deepEqual(HealthCheckUtils.extractAnnounceAddresses([
|
||||
"/ip4/127.0.0.1/tcp/8070"
|
||||
]).data, "127.0.0.1");
|
||||
assert.deepEqual(HealthCheckUtils.extractAnnounceAddresses([]).error, true);
|
||||
assert.deepEqual(HealthCheckUtils.extractAnnounceAddresses(["hello"]).error, true);
|
||||
});
|
||||
})
|
||||
@ -1,6 +1,6 @@
|
||||
import { CodexDebugInfo, SafeValue, CodexError } from "@codex-storage/sdk-js"
|
||||
|
||||
export const HealthCheckUtil = {
|
||||
export const HealthCheckUtils = {
|
||||
removePort(url: string) {
|
||||
const parts = url.split(":")
|
||||
return parts[0] + ":" + parts[1]
|
||||
@ -29,6 +29,21 @@ export const HealthCheckUtil = {
|
||||
}
|
||||
},
|
||||
|
||||
extractAnnounceAddresses(announceAddresses: string[]): SafeValue<string> {
|
||||
if (announceAddresses.length === 0) {
|
||||
return { error: true, data: new CodexError("Not existing announce address") }
|
||||
}
|
||||
|
||||
|
||||
const ip = announceAddresses[0].split("/")
|
||||
|
||||
if (ip.length !== 5) {
|
||||
return { error: true, data: new CodexError("Misformatted ip") }
|
||||
}
|
||||
|
||||
return { error: false, data: ip[2] }
|
||||
},
|
||||
|
||||
getTcpPort(info: CodexDebugInfo): SafeValue<number> {
|
||||
if (info.addrs.length === 0) {
|
||||
return { error: true, data: new CodexError("Not existing address") }
|
||||
|
||||
@ -7,11 +7,17 @@
|
||||
> div:first-child {
|
||||
position: relative;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
left: 16px;
|
||||
color: #969696;
|
||||
color: rgba(150, 150, 150, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +26,13 @@
|
||||
}
|
||||
|
||||
select {
|
||||
border-color: #969696;
|
||||
border-color: rgba(150, 150, 150, 1);
|
||||
padding-left: 40px;
|
||||
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ export function ManifestFetch() {
|
||||
id="cid"
|
||||
value={cid}
|
||||
placeholder="CID"
|
||||
size={"medium" as any}
|
||||
variant={"medium"}
|
||||
autoComplete="off"
|
||||
onChange={onCidChange}></Input>
|
||||
<Button label="Fetch" onClick={onDownload} variant="outline"></Button>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { attributes } from "../../utils/attributes";
|
||||
import "./menu.css";
|
||||
import { ComponentType, useState } from "react";
|
||||
import { ComponentType, useEffect } from "react";
|
||||
import { classnames } from "../../utils/classnames";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import HomeIcon from "../../assets/icons/home.svg?react";
|
||||
import ExpandIcon from "../../assets/icons/expand.svg?react";
|
||||
import WalletIcon from "../../assets/icons/wallet.svg?react";
|
||||
@ -17,13 +16,21 @@ 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";
|
||||
import { useIsMobile } from "../../hooks/useMobile";
|
||||
|
||||
export type MenuItemComponentProps = {
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export type Props = {
|
||||
isExpanded: boolean;
|
||||
onExpanded: (val: boolean) => void;
|
||||
};
|
||||
|
||||
export type MenuItem =
|
||||
| {
|
||||
type: "separator";
|
||||
@ -36,16 +43,30 @@ export type MenuItem =
|
||||
Component: ComponentType<MenuItemComponentProps>;
|
||||
};
|
||||
|
||||
export function Menu() {
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
export function Menu({ isExpanded, onExpanded }: Props) {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const onLogoClick = () => {
|
||||
if (isExpanded === false) {
|
||||
setIsExpanded(true);
|
||||
onExpanded(true);
|
||||
}
|
||||
};
|
||||
|
||||
const onExpandMenu = () => setIsExpanded(!isExpanded);
|
||||
useEffect(() => {
|
||||
if (isMobile) {
|
||||
onExpanded(false);
|
||||
}
|
||||
}, [isMobile, onExpanded]);
|
||||
|
||||
const onExpandMenu = () => onExpanded(!isExpanded);
|
||||
|
||||
const onClose = () => {
|
||||
if (isMobile) {
|
||||
onExpanded(false);
|
||||
}
|
||||
};
|
||||
|
||||
const Icon = isMobile ? CloseIcon : ExpandIcon;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -59,42 +80,43 @@ export function Menu() {
|
||||
<Logo onClick={onLogoClick} width={30} />
|
||||
<Logotype height={34} />
|
||||
<div>
|
||||
<ExpandIcon onClick={onExpandMenu}></ExpandIcon>
|
||||
<Icon onClick={onExpandMenu}></Icon>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="items">
|
||||
<Link to="/dashboard" activeOptions={{ exact: true }}>
|
||||
<NavLink onClick={onClose} to="/dashboard" end>
|
||||
<span>
|
||||
<HomeIcon />
|
||||
</span>
|
||||
<span>Dashboard</span>
|
||||
</Link>
|
||||
<Link to="/dashboard/wallet">
|
||||
</NavLink>
|
||||
<NavLink onClick={onClose} to="/dashboard/wallet">
|
||||
<span>
|
||||
<WalletIcon width={20} height={20} />
|
||||
</span>
|
||||
<span>Wallet</span>
|
||||
</Link>
|
||||
<Link to="/dashboard/files">
|
||||
</NavLink>
|
||||
<NavLink onClick={onClose} to="/dashboard/files">
|
||||
<span>
|
||||
<FilesIcon width={20} />
|
||||
</span>
|
||||
<span>Files</span>
|
||||
</Link>
|
||||
<Link
|
||||
to="/dashboard/nodes"
|
||||
disabled={true}
|
||||
</NavLink>
|
||||
<NavLink
|
||||
onClick={onClose}
|
||||
to="#"
|
||||
aria-disabled={true}
|
||||
data-title="Coming soon">
|
||||
data-title="Coming soon"
|
||||
end>
|
||||
<span>
|
||||
<NodesIcon width={20} />
|
||||
</span>
|
||||
<span>Nodes</span>
|
||||
</Link>
|
||||
<Link
|
||||
to="/dashboard/analytics"
|
||||
disabled={true}
|
||||
</NavLink>
|
||||
<NavLink
|
||||
onClick={onClose}
|
||||
to="#"
|
||||
aria-disabled={true}
|
||||
title="Coming soon"
|
||||
data-title="Coming soon">
|
||||
@ -102,10 +124,10 @@ export function Menu() {
|
||||
<AnalyticsIcon />
|
||||
</span>
|
||||
<span>Analytics</span>
|
||||
</Link>
|
||||
<Link
|
||||
to="/dashboard/device"
|
||||
disabled={true}
|
||||
</NavLink>
|
||||
<NavLink
|
||||
onClick={onClose}
|
||||
to="#"
|
||||
aria-disabled={true}
|
||||
title="Coming soon"
|
||||
data-title="Coming soon">
|
||||
@ -113,52 +135,52 @@ export function Menu() {
|
||||
<DeviceIcon />
|
||||
</span>
|
||||
<span>Devices</span>
|
||||
</Link>
|
||||
</NavLink>
|
||||
<hr />
|
||||
<Link to="/dashboard/purchases">
|
||||
<NavLink onClick={onClose} to="/dashboard/purchases">
|
||||
<span>
|
||||
<PurchaseIcon />
|
||||
</span>
|
||||
<span>Purchases</span>
|
||||
</Link>
|
||||
<Link to="/dashboard/availabilities">
|
||||
</NavLink>
|
||||
<NavLink onClick={onClose} to="/dashboard/availabilities">
|
||||
<span>
|
||||
<HostIcon />
|
||||
<HostIcon width={20} height={20} />
|
||||
</span>
|
||||
<span>Host</span>
|
||||
</Link>
|
||||
</NavLink>
|
||||
<hr />
|
||||
<Link to="/dashboard/peers">
|
||||
<NavLink onClick={onClose} to="/dashboard/peers">
|
||||
<span>
|
||||
<PeersIcon width={20} />
|
||||
<PeersIcon width={20} height={20} />
|
||||
</span>
|
||||
<span>Peers</span>
|
||||
</Link>
|
||||
<Link to="/dashboard/logs">
|
||||
</NavLink>
|
||||
<NavLink onClick={onClose} to="/dashboard/logs">
|
||||
<span>
|
||||
<LogsIcon width={24} />
|
||||
<LogsIcon width={20} height={20} />
|
||||
</span>
|
||||
<span>Log</span>
|
||||
</Link>
|
||||
</NavLink>
|
||||
<section></section>
|
||||
<Link to="/dashboard/settings">
|
||||
<NavLink onClick={onClose} to="/dashboard/settings">
|
||||
<span>
|
||||
<SettingsIcon width={24} />
|
||||
</span>
|
||||
<span>Settings</span>
|
||||
</Link>
|
||||
<Link to="/dashboard/help">
|
||||
</NavLink>
|
||||
<NavLink onClick={onClose} to="/dashboard/help">
|
||||
<span>
|
||||
<HelpIcon />
|
||||
</span>
|
||||
<span>Help</span>
|
||||
</Link>
|
||||
<Link to="/dashboard/disclaimer">
|
||||
</NavLink>
|
||||
<NavLink onClick={onClose} to="/dashboard/disclaimer">
|
||||
<span>
|
||||
<DisclaimerIcon />
|
||||
</span>
|
||||
<span>Disclaimer</span>
|
||||
</Link>
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@ -1,46 +1,38 @@
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #1c1c1c;
|
||||
border-radius: var(--codex-border-radius);
|
||||
background-color: rgb(28, 28, 28);
|
||||
transition: left 0.25s;
|
||||
position: sticky;
|
||||
z-index: 10;
|
||||
view-transition-name: main-menu;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
transition:
|
||||
width 0.5s,
|
||||
font-size 0.5s,
|
||||
left 0.05s;
|
||||
min-width: 0;
|
||||
left 0.5s;
|
||||
width: 272px;
|
||||
min-width: 80px;
|
||||
|
||||
@media (max-width: 1199px) {
|
||||
@media (max-width: 800px) {
|
||||
& {
|
||||
width: 80px;
|
||||
.items {
|
||||
a {
|
||||
width: 26px;
|
||||
gap: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
left: -300px;
|
||||
position: fixed;
|
||||
z-index: 12;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
span + span {
|
||||
font-size: 0;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&[aria-expanded] {
|
||||
left: 0px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
&[aria-expanded] a[data-title]:hover::after {
|
||||
content: attr(data-title);
|
||||
background-color: #2f2f2f;
|
||||
color: #fff;
|
||||
background-color: rgb(47, 47, 47);
|
||||
color: rgb(255, 255, 255);
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
@ -63,9 +55,11 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
span + span {
|
||||
font-size: 0;
|
||||
display: none;
|
||||
@media (min-width: 801px) {
|
||||
span + span {
|
||||
font-size: 0;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -86,7 +80,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
background-color: #060606;
|
||||
background-color: rgb(6, 6, 6);
|
||||
border-radius: 8px;
|
||||
|
||||
> svg:first-child {
|
||||
@ -127,7 +121,7 @@
|
||||
height: 100%;
|
||||
margin-bottom: 2.5rem;
|
||||
gap: 0.5rem;
|
||||
border-top: 1px solid #96969633;
|
||||
border-top: 1px solid rgba(150, 150, 150, 0.2);
|
||||
|
||||
&::before {
|
||||
height: 20px;
|
||||
@ -154,17 +148,17 @@
|
||||
top: 115px;
|
||||
}
|
||||
|
||||
&:has(.active:nth-child(4))::before {
|
||||
/* &:has(.active:nth-child(4))::before {
|
||||
top: 158px;
|
||||
}
|
||||
|
||||
} */
|
||||
/*
|
||||
&:has(.active:nth-child(5))::before {
|
||||
top: 201px;
|
||||
}
|
||||
|
||||
&:has(.active:nth-child(6))::before {
|
||||
top: 244px;
|
||||
}
|
||||
} */
|
||||
|
||||
&:has(.active:nth-child(8))::before {
|
||||
top: 339px;
|
||||
@ -201,7 +195,7 @@
|
||||
hr {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
border: 0.1px solid #96969633;
|
||||
border: 0.1px solid rgba(150, 150, 150, 0.2);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -220,16 +214,16 @@
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.006em;
|
||||
color: #969696;
|
||||
color: rgba(150, 150, 150, 1);
|
||||
border-radius: 8px;
|
||||
transition: background-color 0.35s;
|
||||
position: relative;
|
||||
margin-left: 6px;
|
||||
|
||||
&:hover:not([aria-disabled="true"]),
|
||||
&.active {
|
||||
background-color: var(--codex-highlight-color);
|
||||
color: #c7c7c7;
|
||||
&.active:not([aria-disabled="true"]) {
|
||||
background-color: rgb(47, 47, 47);
|
||||
color: rgb(199, 199, 199);
|
||||
}
|
||||
|
||||
span:first-child {
|
||||
@ -244,10 +238,15 @@
|
||||
span + span {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&.active span:first-child {
|
||||
@media (min-width: 801px) {
|
||||
span + span {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.active:not([aria-disabled="true"]) span:first-child {
|
||||
color: var(--codex-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,6 @@
|
||||
text-align: left;
|
||||
padding-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-top: 1px solid #96969633;
|
||||
border-top: 1px solid rgba(150, 150, 150, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
export const nodeSpaceAllocationColors = [
|
||||
"var(--codex-color-primary)",
|
||||
"#f9fa93",
|
||||
"#ccc",
|
||||
]
|
||||