From 4f9abc614eedd5ce3a279a4686cc832c4d1e62c7 Mon Sep 17 00:00:00 2001 From: rolandlor <33499567+rolandlor@users.noreply.github.com> Date: Mon, 4 Dec 2023 22:01:51 +0100 Subject: [PATCH] feat: update postage stamp creation screen (#641) * style: UI changes for postage stamp * feat: New postage stamp standard page --------- Co-authored-by: Seres Roland --- package-lock.json | 46 ++-- package.json | 2 +- src/pages/account/stamps/AccountStamps.tsx | 2 +- src/pages/files/Upload.tsx | 4 +- ...tsx => CreatePostageStampAdvancedPage.tsx} | 6 +- .../stamps/CreatePostageStampStandardPage.tsx | 20 ++ ...n.tsx => PostageStampAdvancedCreation.tsx} | 116 +++++---- .../stamps/PostageStampStandardCreation.tsx | 230 ++++++++++++++++++ src/pages/stamps/index.tsx | 2 +- src/routes.tsx | 9 +- src/theme.tsx | 17 ++ src/utils/stamps.ts | 25 ++ 12 files changed, 392 insertions(+), 87 deletions(-) rename src/pages/stamps/{CreatePostageStampPage.tsx => CreatePostageStampAdvancedPage.tsx} (64%) create mode 100644 src/pages/stamps/CreatePostageStampStandardPage.tsx rename src/pages/stamps/{PostageStampCreation.tsx => PostageStampAdvancedCreation.tsx} (79%) create mode 100644 src/pages/stamps/PostageStampStandardCreation.tsx create mode 100644 src/utils/stamps.ts diff --git a/package-lock.json b/package-lock.json index e87c86e..44f1a35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.24.1", "license": "BSD-3-Clause", "dependencies": { - "@ethersphere/bee-js": "^6.2.0", + "@ethersphere/bee-js": "^6.6.0", "@ethersphere/swarm-cid": "^0.1.0", "@material-ui/core": "4.12.3", "@material-ui/icons": "4.11.2", @@ -2440,13 +2440,13 @@ } }, "node_modules/@ethersphere/bee-js": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-6.2.0.tgz", - "integrity": "sha512-QWuVvW+c9z+AFWuRL+YBZECDyV3G5qeD9L0J7Tx7HYMugoDjMAjeRyNHjkf0a5np57q3i9MAE2P678rjSJ1Yaw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-6.6.0.tgz", + "integrity": "sha512-f39yEbkCX7mnKSn0x9cV2TAlnMiemiJCiTVLhS6+g7nMbud1r269gzEHopElDaP5VJIsJy1uwD0VN4+HxIp3bg==", "dependencies": { "@ethersphere/swarm-cid": "^0.1.0", "@types/readable-stream": "^2.3.13", - "axios": "^1.3.4", + "axios": "^0.27.2", "cafe-utility": "^10.8.1", "elliptic": "^6.5.4", "fetch-blob": "2.1.2", @@ -2466,13 +2466,12 @@ } }, "node_modules/@ethersphere/bee-js/node_modules/axios": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", - "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" } }, "node_modules/@ethersphere/bee-js/node_modules/form-data": { @@ -16491,7 +16490,8 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "node_modules/psl": { "version": "1.8.0", @@ -22305,13 +22305,13 @@ } }, "@ethersphere/bee-js": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-6.2.0.tgz", - "integrity": "sha512-QWuVvW+c9z+AFWuRL+YBZECDyV3G5qeD9L0J7Tx7HYMugoDjMAjeRyNHjkf0a5np57q3i9MAE2P678rjSJ1Yaw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@ethersphere/bee-js/-/bee-js-6.6.0.tgz", + "integrity": "sha512-f39yEbkCX7mnKSn0x9cV2TAlnMiemiJCiTVLhS6+g7nMbud1r269gzEHopElDaP5VJIsJy1uwD0VN4+HxIp3bg==", "requires": { "@ethersphere/swarm-cid": "^0.1.0", "@types/readable-stream": "^2.3.13", - "axios": "^1.3.4", + "axios": "^0.27.2", "cafe-utility": "^10.8.1", "elliptic": "^6.5.4", "fetch-blob": "2.1.2", @@ -22324,13 +22324,12 @@ }, "dependencies": { "axios": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", - "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" } }, "form-data": { @@ -32620,7 +32619,8 @@ "proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "psl": { "version": "1.8.0", diff --git a/package.json b/package.json index 6b310a3..2e1cdd2 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "url": "https://github.com/ethersphere/bee-dashboard.git" }, "dependencies": { - "@ethersphere/bee-js": "^6.2.0", + "@ethersphere/bee-js": "^6.6.0", "@ethersphere/swarm-cid": "^0.1.0", "@material-ui/core": "4.12.3", "@material-ui/icons": "4.11.2", diff --git a/src/pages/account/stamps/AccountStamps.tsx b/src/pages/account/stamps/AccountStamps.tsx index 658d8fa..b29167f 100644 --- a/src/pages/account/stamps/AccountStamps.tsx +++ b/src/pages/account/stamps/AccountStamps.tsx @@ -46,7 +46,7 @@ export function AccountStamps(): ReactElement { if (status.all === CheckState.ERROR) return function navigateToNewStamp() { - navigate(ROUTES.ACCOUNT_STAMPS_NEW) + navigate(ROUTES.ACCOUNT_STAMPS_NEW_STANDARD) } return ( diff --git a/src/pages/files/Upload.tsx b/src/pages/files/Upload.tsx index 94a84a2..16a1594 100644 --- a/src/pages/files/Upload.tsx +++ b/src/pages/files/Upload.tsx @@ -18,7 +18,7 @@ import { detectIndexHtml, getAssetNameFromFiles, packageFile } from '../../utils import { persistIdentity, updateFeed } from '../../utils/identity' import { HISTORY_KEYS, putHistory } from '../../utils/local-storage' import { FeedPasswordDialog } from '../feeds/FeedPasswordDialog' -import { PostageStampCreation } from '../stamps/PostageStampCreation' +import { PostageStampAdvancedCreation } from '../stamps/PostageStampAdvancedCreation' import { PostageStampSelector } from '../stamps/PostageStampSelector' import { AssetPreview } from './AssetPreview' import { StampPreview } from './StampPreview' @@ -186,7 +186,7 @@ export function Upload(): ReactElement { {hasAnyStamps && stampMode === 'SELECT' ? ( setStamp(stamp)} defaultValue={stamp?.batchID} /> ) : ( - setStampMode('SELECT')} /> + setStampMode('SELECT')} /> )} diff --git a/src/pages/stamps/CreatePostageStampPage.tsx b/src/pages/stamps/CreatePostageStampAdvancedPage.tsx similarity index 64% rename from src/pages/stamps/CreatePostageStampPage.tsx rename to src/pages/stamps/CreatePostageStampAdvancedPage.tsx index 2888824..31f860d 100644 --- a/src/pages/stamps/CreatePostageStampPage.tsx +++ b/src/pages/stamps/CreatePostageStampAdvancedPage.tsx @@ -2,7 +2,7 @@ import { ReactElement } from 'react' import { useNavigate } from 'react-router' import { HistoryHeader } from '../../components/HistoryHeader' import { ROUTES } from '../../routes' -import { PostageStampCreation } from './PostageStampCreation' +import { PostageStampAdvancedCreation } from './PostageStampAdvancedCreation' export function CreatePostageStampPage(): ReactElement { const navigate = useNavigate() @@ -13,8 +13,8 @@ export function CreatePostageStampPage(): ReactElement { return (
- Buy new postage stamp - + Buy new postage stamp batch +
) } diff --git a/src/pages/stamps/CreatePostageStampStandardPage.tsx b/src/pages/stamps/CreatePostageStampStandardPage.tsx new file mode 100644 index 0000000..92a4083 --- /dev/null +++ b/src/pages/stamps/CreatePostageStampStandardPage.tsx @@ -0,0 +1,20 @@ +import { ReactElement } from 'react' +import { useNavigate } from 'react-router' +import { HistoryHeader } from '../../components/HistoryHeader' +import { ROUTES } from '../../routes' +import { PostageStampStandardCreation } from './PostageStampStandardCreation' + +export function CreatePostageStampBasicPage(): ReactElement { + const navigate = useNavigate() + + function onFinished() { + navigate(ROUTES.ACCOUNT_STAMPS) + } + + return ( +
+ Buy new postage stamp batch + +
+ ) +} diff --git a/src/pages/stamps/PostageStampCreation.tsx b/src/pages/stamps/PostageStampAdvancedCreation.tsx similarity index 79% rename from src/pages/stamps/PostageStampCreation.tsx rename to src/pages/stamps/PostageStampAdvancedCreation.tsx index 0089f4b..971ed15 100644 --- a/src/pages/stamps/PostageStampCreation.tsx +++ b/src/pages/stamps/PostageStampAdvancedCreation.tsx @@ -1,5 +1,6 @@ import { PostageBatchOptions } from '@ethersphere/bee-js' import { Box, Grid, Typography } from '@material-ui/core' +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import BigNumber from 'bignumber.js' import { useSnackbar } from 'notistack' import { ReactElement, useContext, useState } from 'react' @@ -18,12 +19,32 @@ import { waitUntilStampExists, } from '../../utils' import { getHumanReadableFileSize } from '../../utils/file' +import { Link } from 'react-router-dom' +import { ROUTES } from '../../routes' interface Props { onFinished: () => void } -export function PostageStampCreation({ onFinished }: Props): ReactElement { +const useStyles = makeStyles((theme: Theme) => + createStyles({ + link: { + color: '#dd7700', + textDecoration: 'underline', + '&:hover': { + textDecoration: 'none', + + // https://github.com/mui-org/material-ui/issues/22543 + '@media (hover: none)': { + textDecoration: 'none', + }, + }, + }, + }), +) + +export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElement { + const classes = useStyles() const { chainState } = useContext(BeeContext) const { refresh } = useContext(StampsContext) const { beeDebugApi } = useContext(SettingsContext) @@ -153,22 +174,25 @@ export function PostageStampCreation({ onFinished }: Props): ReactElement { return ( <> - - - To upload data to Swarm network, you will need to purchase a postage stamp. If you're not familiar with - this, please read{' '} - - this guide - - . - + + Batch name - validateDepthInput(event.target.value)} /> + setLabelInput(event.target.value)} /> + + + setImmutable(event.target.value === 'Yes')} + options={[ + { value: 'Yes', label: 'Yes' }, + { value: 'No', label: 'No' }, + ]} + /> + + + validateDepthInput(event.target.value)} /> Corresponding file size @@ -185,38 +209,14 @@ export function PostageStampCreation({ onFinished }: Props): ReactElement { {!amountError && amountInput ? getTtl(Number.parseInt(amountInput, 10)) : '-'} + + + Current price of 24000 per block + + {amountError && {amountError}} - - setLabelInput(event.target.value)} /> - - - setImmutable(event.target.value === 'Yes')} - options={[ - { value: 'Yes', label: 'Yes' }, - { value: 'No', label: 'No' }, - ]} - /> - - - {immutable && ( - - Once an immutable stamp is maxed out, it disallows further content uploads, thereby safeguarding your - previously uploaded content from unintentional overwriting. - - )} - {!immutable && ( - - When a mutable stamp reaches full capacity, it still permits new content uploads. However, this comes - with the caveat of overwriting previously uploaded content associated with the same stamp. - - )} - - - + Indicative Price @@ -227,14 +227,24 @@ export function PostageStampCreation({ onFinished }: Props): ReactElement { - - Buy New Stamp - + + + + + Buy New Stamp + + + + + Standard mode + + + ) } diff --git a/src/pages/stamps/PostageStampStandardCreation.tsx b/src/pages/stamps/PostageStampStandardCreation.tsx new file mode 100644 index 0000000..ed68333 --- /dev/null +++ b/src/pages/stamps/PostageStampStandardCreation.tsx @@ -0,0 +1,230 @@ +import { PostageBatchOptions } from '@ethersphere/bee-js' +import { Utils } from '@ethersphere/bee-js' +import { Box, Button, Grid, Slider, Typography } from '@material-ui/core' +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' +import { useSnackbar } from 'notistack' +import { ReactElement, useContext, useState } from 'react' +import Check from 'remixicon-react/CheckLineIcon' +import { SwarmButton } from '../../components/SwarmButton' +import { SwarmTextInput } from '../../components/SwarmTextInput' +import { Context as SettingsContext } from '../../providers/Settings' +import { Context as StampsContext } from '../../providers/Stamps' +import { Link } from 'react-router-dom' +import { ROUTES } from '../../routes' +import { calculateStampPrice, convertAmountToSeconds, secondsToTimeString, waitUntilStampExists } from '../../utils' + +interface Props { + onFinished: () => void +} +const useStyles = makeStyles((theme: Theme) => + createStyles({ + link: { + color: '#dd7700', + textDecoration: 'underline', + '&:hover': { + textDecoration: 'none', + + // https://github.com/mui-org/material-ui/issues/22543 + '@media (hover: none)': { + textDecoration: 'none', + }, + }, + }, + buttonSelected: { + color: 'white', + backgroundColor: theme.palette.primary.main, + }, + }), +) + +const marks = [ + { value: 1, label: '1 day' }, + { value: 365, label: '365 days' }, +] + +export function PostageStampStandardCreation({ onFinished }: Props): ReactElement { + const getDepthForCapacity = Utils.getDepthForCapacity + const getAmountForTtl = Utils.getAmountForTtl + const classes = useStyles() + const { refresh } = useContext(StampsContext) + const { beeDebugApi } = useContext(SettingsContext) + + const [depthInput, setDepthInput] = useState(getDepthForCapacity(4)) + const [amountInput, setAmountInput] = useState(Number.parseInt(getAmountForTtl(30))) + const [labelInput, setLabelInput] = useState('') + const [submitting, setSubmitting] = useState(false) + const [buttonValue, setButtonValue] = useState(4) + + function sliderValueChange(event: any, newValue: any) { + const amountValue = Number.parseInt(getAmountForTtl(newValue)) + setAmountInput(amountValue) + } + + const { enqueueSnackbar } = useSnackbar() + + function getTtl(amount: number): string { + const pricePerBlock = 24000 + + return `${secondsToTimeString( + convertAmountToSeconds(amount, pricePerBlock), + )} (with price of ${pricePerBlock.toFixed(0)} per block)` + } + + function getPrice(depth: number, amount: bigint): string { + const price = calculateStampPrice(depth, amount) + + return `${price.toSignificantDigits()} xBZZ` + } + + async function submit() { + try { + // This is really just a typeguard, the validation pretty much guarantees these will have the right values + if (!depthInput || !amountInput) { + return + } + + if (!beeDebugApi) { + return + } + + setSubmitting(true) + const amount = BigInt(amountInput) + const depth = depthInput + const options: PostageBatchOptions = { + waitForUsable: false, + label: labelInput || undefined, + immutableFlag: false, + } + + const batchId = await beeDebugApi.createPostageBatch(amount.toString(), depth, options) + await waitUntilStampExists(batchId, beeDebugApi) + await refresh() + onFinished() + } catch (e) { + console.error(e) // eslint-disable-line + enqueueSnackbar(`Error: ${(e as Error).message}`, { variant: 'error' }) + } + setSubmitting(false) + } + + function handleBatchSize(event: any) { + let value = event.target.innerText + value = Number(value.substring(0, value.length - 3)) + setButtonValue(value) + const capacity = getDepthForCapacity(value) + setDepthInput(capacity) + } + + return ( + <> + + + A postage stamp batch containes postage stamps that will give you the right to upload data to the Swarm + network. If you're not familiar with this, please read  + + this guide + + . + + + + Batch name + + + setLabelInput(e.target.value)} /> + + + Batch size + + + + + + + + + + + + + + + + Data persistence + + + + + + + + Corresponding TTL (Time to live) + {amountInput ? getTtl(amountInput) : '-'} + + + + + Current price of 24000 per block + + + + + + Indicative Price + {getPrice(depthInput, BigInt(amountInput))} + + + + + + Buy New Stamp + + + + + Advanced mode + + + + + ) +} diff --git a/src/pages/stamps/index.tsx b/src/pages/stamps/index.tsx index 2a1bae6..79e7796 100644 --- a/src/pages/stamps/index.tsx +++ b/src/pages/stamps/index.tsx @@ -44,7 +44,7 @@ export default function Stamp(): ReactElement { if (status.all === CheckState.ERROR) return function navigateToNewStamp() { - navigate(ROUTES.ACCOUNT_STAMPS_NEW) + navigate(ROUTES.ACCOUNT_STAMPS_NEW_STANDARD) } return ( diff --git a/src/routes.tsx b/src/routes.tsx index 0093ee4..f72b0c8 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -16,7 +16,7 @@ import GiftCards from './pages/gift-code' import Info from './pages/info' import LightModeRestart from './pages/restart/LightModeRestart' import Settings from './pages/settings' -import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampPage' +import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampAdvancedPage' import Status from './pages/status' import TopUp from './pages/top-up' import { BankCardTopUpIndex } from './pages/top-up/BankCardTopUpIndex' @@ -25,6 +25,7 @@ import { GiftCardFund } from './pages/top-up/GiftCardFund' import { GiftCardTopUpIndex } from './pages/top-up/GiftCardTopUpIndex' import { Swap } from './pages/top-up/Swap' import { Context as SettingsContext } from './providers/Settings' +import { CreatePostageStampBasicPage } from './pages/stamps/CreatePostageStampStandardPage' export enum ROUTES { INFO = '/', @@ -46,7 +47,8 @@ export enum ROUTES { ACCOUNT_WALLET = '/account/wallet', ACCOUNT_CHEQUEBOOK = '/account/chequebook', ACCOUNT_STAMPS = '/account/stamps', - ACCOUNT_STAMPS_NEW = '/account/stamps/new', + ACCOUNT_STAMPS_NEW_STANDARD = '/account/stamps/new', + ACCOUNT_STAMPS_NEW_ADVANCED = '/account/stamps/new/advanced', ACCOUNT_FEEDS = '/account/feeds', ACCOUNT_FEEDS_NEW = '/account/feeds/new', ACCOUNT_FEEDS_UPDATE = '/account/feeds/update/:hash', @@ -86,7 +88,8 @@ const BaseRouter = (): ReactElement => { } /> } /> } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/src/theme.tsx b/src/theme.tsx index 98a88c5..d78ae12 100644 --- a/src/theme.tsx +++ b/src/theme.tsx @@ -159,6 +159,23 @@ const componentsOverrides = (theme: Theme) => ({ backgroundColor: 'transparent', }, }, + MuiSlider: { + root: { + '& .MuiSlider-valueLabel': { + top: '-27px', + '& span': { + height: '20px', + borderRadius: '0px', + transform: 'none', + '& span': { + display: 'flex', + alignItems: 'center', + transform: 'none', + }, + }, + }, + }, + }, }) const propsOverrides = { diff --git a/src/utils/stamps.ts b/src/utils/stamps.ts new file mode 100644 index 0000000..47d20bf --- /dev/null +++ b/src/utils/stamps.ts @@ -0,0 +1,25 @@ +export function getStampUsage(utilization: number, depth: number, bucketDepth: number): number { + return utilization / Math.pow(2, depth - bucketDepth) +} + +export function getStampMaximumCapacityBytes(depth: number): number { + return 2 ** depth * 4096 +} + +export function getStampCostInPlur(depth: number, amount: number): number { + return 2 ** depth * amount +} + +export function getStampCostInBzz(depth: number, amount: number): number { + const BZZ_UNIT = 10 ** 16 + + return getStampCostInPlur(depth, amount) / BZZ_UNIT +} + +// export function getStampTtlSeconds(amount: number, pricePerBlock = 24_000, blockTime = 5): number { +// return (amount * blockTime) / pricePerBlock +// } + +export function getStampTtlSeconds(amount: number, pricePerBlock = 24_000, blockTime = 5): number { + return (amount * blockTime) / pricePerBlock +}