feat: sync and update with all changes from fork (#720)

* feat: sync and update with all changes from fork
* refactor: extract clipboard copy logic into custom hook
* fix: correct spelling of DEFAULT_REFRESH_FREQUENCY_MS in Stamps and WalletBalance providers
* refactor(ui-tests): replace fixed sleeps with condition-based waits
* fix: handle null values for size and granteeCount in infoGroups
* fix(lint): add newline at end of file in useClipboardCopy hook
* fix(ui-tests): page.goto URL
* refactor: update import paths for useClipboardCopy

---------

Co-authored-by: Ferenc Sárai <sarai.ferenc@gmail.com>
This commit is contained in:
Bálint Ujvári
2026-03-02 11:34:39 +01:00
committed by GitHub
parent b0f00a624a
commit 519c411db0
303 changed files with 16609 additions and 29415 deletions
+19 -19
View File
@@ -1,7 +1,9 @@
import { createStyles, makeStyles, Tab, Tabs, Theme } from '@material-ui/core'
import { BeeModes } from '@ethersphere/bee-js'
import { ReactElement, useContext } from 'react'
import { Tab, Tabs } from '@mui/material'
import React, { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router-dom'
import { makeStyles } from 'tss-react/mui'
import { Context } from '../../providers/Bee'
import { ACCOUNT_TABS } from '../../routes'
@@ -17,28 +19,26 @@ interface Props {
active: 'WALLET' | 'CHEQUEBOOK' | 'STAMPS' | 'FEEDS' | 'STAKING'
}
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
flexGrow: 1,
marginBottom: theme.spacing(4),
textTransform: 'none',
marginLeft: theme.spacing(-0.25),
marginRight: theme.spacing(-0.25),
},
tab: {
marginLeft: theme.spacing(0.25),
marginRight: theme.spacing(0.25),
},
}),
)
const useStyles = makeStyles()(theme => ({
root: {
flexGrow: 1,
marginBottom: theme.spacing(4),
textTransform: 'none',
marginLeft: theme.spacing(-0.25),
marginRight: theme.spacing(-0.25),
},
tab: {
marginLeft: theme.spacing(0.25),
marginRight: theme.spacing(0.25),
},
}))
export function AccountNavigation({ active }: Props): ReactElement {
const classes = useStyles()
const { classes } = useStyles()
const navigate = useNavigate()
const { nodeInfo } = useContext(Context)
function onChange(event: React.ChangeEvent<Record<string, never>>, newValue: number) {
function onChange(_event: React.SyntheticEvent, newValue: number) {
navigate(ACCOUNT_TABS[newValue])
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { Box, Typography } from '@material-ui/core'
import { Box, Typography } from '@mui/material'
import { ReactElement } from 'react'
export function Header(): ReactElement {
@@ -1,5 +1,6 @@
import { Box } from '@material-ui/core'
import { Box } from '@mui/material'
import { ReactElement, useContext } from 'react'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemActions from '../../../components/ExpandableListItemActions'
@@ -8,7 +9,7 @@ import TroubleshootConnectionCard from '../../../components/TroubleshootConnecti
import DepositModal from '../../../containers/DepositModal'
import WithdrawModal from '../../../containers/WithdrawModal'
import { useAccounting } from '../../../hooks/accounting'
import { Context as BeeContext, CheckState } from '../../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
import { Context as SettingsContext } from '../../../providers/Settings'
import PeerBalances from '../../accounting/PeerBalances'
import { AccountNavigation } from '../AccountNavigation'
+4 -3
View File
@@ -1,19 +1,20 @@
import { Box } from '@material-ui/core'
import { NULL_TOPIC } from '@ethersphere/bee-js'
import { Box } from '@mui/material'
import { ReactElement, useContext, useState } from 'react'
import { useNavigate } from 'react-router'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import Trash from 'remixicon-react/DeleteBin7LineIcon'
import Download from 'remixicon-react/Download2LineIcon'
import Info from 'remixicon-react/InformationLineIcon'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemActions from '../../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
import { SwarmButton } from '../../../components/SwarmButton'
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
import { Context as BeeContext, CheckState } from '../../../providers/Bee'
import { Identity, Context as IdentityContext } from '../../../providers/Feeds'
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
import { Context as IdentityContext, Identity } from '../../../providers/Feeds'
import { ROUTES } from '../../../routes'
import { formatEnum } from '../../../utils'
import { persistIdentitiesWithoutUpdate } from '../../../utils/identity'
+2 -1
View File
@@ -1,11 +1,12 @@
import { ReactElement, useContext, useState } from 'react'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemActions from '../../../components/ExpandableListItemActions'
import { Loading } from '../../../components/Loading'
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
import StakeModal from '../../../containers/StakeModal'
import { Context as BeeContext, CheckState } from '../../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
import { AccountNavigation } from '../AccountNavigation'
import { Header } from '../Header'
+18 -18
View File
@@ -1,36 +1,36 @@
import { CircularProgress, Container, createStyles, makeStyles } from '@material-ui/core'
import { CircularProgress, Container } from '@mui/material'
import { ReactElement, useContext, useEffect } from 'react'
import { useNavigate } from 'react-router'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import { makeStyles } from 'tss-react/mui'
import { ChainSync } from '../../../components/ChainSync'
import { Loading } from '../../../components/Loading'
import { SwarmButton } from '../../../components/SwarmButton'
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
import { Context as BeeContext, CheckState } from '../../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
import { Context as StampsContext } from '../../../providers/Stamps'
import { ROUTES } from '../../../routes'
import StampsTable from '../../stamps/StampsTable'
import { AccountNavigation } from '../AccountNavigation'
import { Header } from '../Header'
const useStyles = makeStyles(() =>
createStyles({
root: {
width: '100%',
display: 'grid',
},
actions: {
display: 'flex',
width: '100%',
flex: '0 1 auto',
flexWrap: 'wrap',
alignItems: 'center',
},
}),
)
const useStyles = makeStyles()(() => ({
root: {
width: '100%',
display: 'grid',
},
actions: {
display: 'flex',
width: '100%',
flex: '0 1 auto',
flexWrap: 'wrap',
alignItems: 'center',
},
}))
export function AccountStamps(): ReactElement {
const classes = useStyles()
const { classes } = useStyles()
const navigate = useNavigate()
+4 -3
View File
@@ -1,21 +1,22 @@
import { Box, Grid, Typography } from '@material-ui/core'
import { BeeModes } from '@ethersphere/bee-js'
import { Box, Grid, Typography } from '@mui/material'
import { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router'
import Download from 'remixicon-react/DownloadLineIcon'
import Gift from 'remixicon-react/GiftLineIcon'
import Link from 'remixicon-react/LinkIcon'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemActions from '../../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
import { SwarmButton } from '../../../components/SwarmButton'
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
import { Context as BeeContext, CheckState } from '../../../providers/Bee'
import { WalletInfoCard } from '../../../pages/info/WalletInfoCard'
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
import { Context as SettingsContext } from '../../../providers/Settings'
import { ROUTES } from '../../../routes'
import { AccountNavigation } from '../AccountNavigation'
import { Header } from '../Header'
import { WalletInfoCard } from '../../../pages/info/WalletInfoCard'
export function AccountWallet(): ReactElement {
const { nodeAddresses, nodeInfo, status, walletBalance } = useContext(BeeContext)
+1
View File
@@ -1,5 +1,6 @@
import { BZZ } from '@ethersphere/bee-js'
import type { ReactElement } from 'react'
import CashoutModal from '../../components/CashoutModal'
import ExpandableList from '../../components/ExpandableList'
import ExpandableListItem from '../../components/ExpandableListItem'
-106
View File
@@ -1,106 +0,0 @@
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { Checkbox, InputBase, Typography } from '@material-ui/core'
import { useSnackbar } from 'notistack'
import { useEffect, useState } from 'react'
import RegisterIcon from 'remixicon-react/AddBoxLineIcon'
import LoginIcon from 'remixicon-react/LoginBoxLineIcon'
import { SwarmButton } from '../../components/SwarmButton'
import { Horizontal } from './Horizontal'
import { Vertical } from './Vertical'
interface Props {
fdp: FdpStorage
onSuccessfulLogin: () => void
}
export function FdpLogin({ fdp, onSuccessfulLogin }: Props) {
const [username, setUsername] = useState<string>('')
const [password, setPassword] = useState<string>('')
const [remember, setRemember] = useState<boolean>(false)
const [sepolia, setSepolia] = useState<string>('https://sepolia.drpc.org')
const { enqueueSnackbar } = useSnackbar()
const inputStyle = { background: 'white', padding: '2px 8px', width: '100%' }
useEffect(() => {
const storedSepolia = localStorage.getItem('sepolia')
if (storedSepolia) {
setSepolia(storedSepolia)
}
const fdpCredentials = localStorage.getItem('fdpCredentials')
if (fdpCredentials) {
const { username, password } = JSON.parse(fdpCredentials)
setUsername(username)
setPassword(password)
setRemember(true)
}
}, [])
async function onLogin() {
localStorage.setItem('sepolia', sepolia)
if (remember) {
localStorage.setItem('fdpCredentials', JSON.stringify({ username, password }))
} else {
localStorage.removeItem('fdpCredentials')
}
enqueueSnackbar('Logging in...', { variant: 'info' })
try {
await fdp.account.login(username, password)
enqueueSnackbar('Logged in successfully', { variant: 'success' })
onSuccessfulLogin()
} catch {
enqueueSnackbar('Login failed', { variant: 'error' })
} finally {
setUsername('')
setPassword('')
setRemember(false)
}
}
function onRegister() {
window.open('https://create.fairdatasociety.org/', '_blank')
}
return (
<div
style={{
maxWidth: '500px',
margin: 'auto',
}}
>
<Vertical gap={16} full>
<Vertical gap={8} left full>
<Typography variant="body2">Sepolia JSON RPC</Typography>
<InputBase value={sepolia} onChange={e => setSepolia(e.target.value)} style={inputStyle} />
</Vertical>
<Vertical gap={8} left full>
<Typography variant="body2">Username</Typography>
<InputBase value={username} onChange={e => setUsername(e.target.value)} style={inputStyle} />
</Vertical>
<Vertical gap={8} left full>
<Typography variant="body2">Password</Typography>
<InputBase value={password} onChange={e => setPassword(e.target.value)} style={inputStyle} type="password" />
</Vertical>
<Vertical gap={8} left full>
<Horizontal>
<Checkbox checked={remember} onChange={e => setRemember(e.target.checked)} />
<Typography variant="body2">Remember me</Typography>
</Horizontal>
</Vertical>
<Vertical left full>
<Horizontal gap={4}>
<SwarmButton iconType={LoginIcon} onClick={onLogin}>
Login
</SwarmButton>
<SwarmButton iconType={RegisterIcon} onClick={onRegister}>
Registration
</SwarmButton>
</Horizontal>
</Vertical>
</Vertical>
</div>
)
}
-98
View File
@@ -1,98 +0,0 @@
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { useState } from 'react'
import { CafeReactFs } from '../../react-fs/CafeReactFs'
import { FsItem, FsItemType } from '../../react-fs/CafeReactType'
import { joinUrl } from '../../react-fs/Utility'
interface Props {
fdp: FdpStorage
name: string
}
export function FdpPod({ fdp, name }: Props) {
const [reloader, setReloader] = useState(0)
function reload() {
setReloader(reloader + 1)
}
return (
<CafeReactFs
rootAlias={`/${name}`}
backgroundColor="#ffffff"
reloader={reloader}
onDeleteFile={async (path: string) => {
await fdp.file.delete(name, path)
reload()
}}
onDeleteDirectory={async (path: string) => {
await fdp.directory.delete(name, path)
reload()
}}
onUpload={(path: string) => {
const input = document.createElement('input')
input.type = 'file'
input.multiple = true
input.click()
return new Promise<void>(resolve => {
input.onchange = async () => {
if (!input.files || !input.files.length) {
resolve()
return
}
for (const file of Array.from(input.files)) {
const data = await file.arrayBuffer()
await fdp.file.uploadData(name, joinUrl(path, file.name), new Uint8Array(data))
}
reload()
resolve()
}
})
}}
onCreateDirectory={async (path: string) => {
// eslint-disable-next-line no-alert
const newDirectoryName = prompt('Directory name')
if (!newDirectoryName) {
return
}
await fdp.directory.create(name, joinUrl(path, newDirectoryName))
reload()
}}
// eslint-disable-next-line require-await
onSync={async () => {
setReloader(reloader + 1)
}}
download={async (path: string) => {
const data = await fdp.file.downloadData(name, path)
const url = URL.createObjectURL(new Blob([data]))
const a = document.createElement('a')
a.href = url
a.download = path.split('/').pop() || 'Untitled'
a.click()
}}
list={async (path: string) => {
const fdpResponse = await fdp.directory.read(name, path)
const items: FsItem[] = []
for (const directory of fdpResponse.directories) {
items.push({
name: directory.name,
$type: FsItemType.DIRECTORY,
id: directory.name,
})
}
for (const file of fdpResponse.files) {
items.push({
name: file.name,
$type: FsItemType.FILE,
id: file.name,
})
}
return items
}}
/>
)
}
-30
View File
@@ -1,30 +0,0 @@
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { Pod } from '@fairdatasociety/fdp-storage/dist/pod/types'
import { CircularProgress, Typography } from '@material-ui/core'
import { FdpPod } from './FdpPod'
import { Vertical } from './Vertical'
interface Props {
fdp: FdpStorage
pods: Pod[]
loadingPods: boolean
}
export function FdpPods({ fdp, pods, loadingPods }: Props) {
if (loadingPods) {
return (
<Vertical gap={32} full>
<CircularProgress />
<Typography>Loading your pods...</Typography>
</Vertical>
)
}
return (
<Vertical gap={16} full left>
{pods.map(pod => (
<FdpPod key={pod.index} fdp={fdp} name={pod.name} />
))}
</Vertical>
)
}
-22
View File
@@ -1,22 +0,0 @@
interface Props {
children: React.ReactNode
p?: string
gap?: number
between?: boolean
background?: string
}
export function Horizontal({ children, p = '0', gap = 8, between, background }: Props) {
const style = {
display: 'flex',
flexDirection: 'row' as 'row', //eslint-disable-line
alignItems: 'center',
justifyContent: between ? 'space-between' : 'flex-start',
gap: `${gap}px`,
padding: p,
background,
width: between ? '100%' : 'auto',
}
return <div style={style}>{children}</div>
}
-20
View File
@@ -1,20 +0,0 @@
interface Props {
children: React.ReactNode
p?: number
gap?: number
left?: boolean
full?: boolean
}
export function Vertical({ children, p = 0, gap = 0, left = false, full = false }: Props) {
const style = {
display: 'flex',
flexDirection: 'column' as 'column', //eslint-disable-line
alignItems: left ? 'flex-start' : 'center',
gap: `${gap}px`,
width: full ? '100%' : 'auto',
padding: `${p}px`,
}
return <div style={style}>{children}</div>
}
-169
View File
@@ -1,169 +0,0 @@
import { FdpStorage } from '@fairdatasociety/fdp-storage'
import { Pod } from '@fairdatasociety/fdp-storage/dist/pod/types'
import { CircularProgress, Typography } from '@material-ui/core'
import { Bee, MantarayNode } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, useEffect, useState } from 'react'
import ImportIcon from 'remixicon-react/AddBoxLineIcon'
import PlusCircle from 'remixicon-react/AddCircleLineIcon'
import { SwarmButton } from '../../components/SwarmButton'
import { joinUrl } from '../../react-fs/Utility'
import { FdpLogin } from './FdpLogin'
import { FdpPods } from './FdpPods'
import { Horizontal } from './Horizontal'
import { Vertical } from './Vertical'
async function makeFdp(): Promise<FdpStorage | null> {
const bee = new Bee('http://localhost:1633')
const sepolia = localStorage.getItem('sepolia') ?? 'https://sepolia.drpc.org'
const postageBatches = await bee.getAllPostageBatch()
const usableBatches = postageBatches.filter(batch => batch.usable)
const highestCapacityBatch = usableBatches.length ? usableBatches.reduce((a, b) => (a.depth > b.depth ? a : b)) : null
if (!highestCapacityBatch) {
return null
}
// TODO: FDS has bad types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new FdpStorage('http://localhost:1633', highestCapacityBatch.batchID.toHex() as any, {
ensOptions: {
rpcUrl: sepolia,
contractAddresses: {
ensRegistry: '0x42a96D45d787685ac4b36292d218B106Fb39be7F',
fdsRegistrar: '0xFBF00389140C00384d88d458239833E3231a7414',
nameResolver: '0xE20ECe6Ea93c4edE41e4d3B973f6679F1E89986A',
publicResolver: '0xC904989B579c2B216A75723688C784038AA99B56',
reverseResolver: '0xbDC8D98d3cbFd68EA9c165E1f15Df6e77A2ae0C5',
},
gasEstimation: 1,
performChecks: true,
},
providerOptions: {
url: sepolia,
},
ensDomain: 'fds',
})
}
export default function FDP(): ReactElement {
const [fdp, setFdp] = useState<FdpStorage | null>(null)
const [pods, setPods] = useState<Pod[]>([])
const [loggedIn, setLoggedIn] = useState<boolean>(false)
const [loadingPods, setLoadingPods] = useState<boolean>(false)
const [creatingPod, setCreatingPod] = useState<boolean>(false)
const { enqueueSnackbar } = useSnackbar()
useEffect(() => {
makeFdp().then(fdp => {
if (!fdp) {
enqueueSnackbar('FDP could not be initialized. Do you have a postage batch?', { variant: 'error' })
}
setFdp(fdp)
})
}, [enqueueSnackbar])
useEffect(() => {
if (fdp && loggedIn) {
setLoadingPods(true)
fdp.personalStorage.list().then(pods => {
setPods(pods.pods)
setLoadingPods(false)
})
}
}, [fdp, loggedIn])
function onSuccessfulLogin() {
setLoggedIn(true)
}
function onCreatePod() {
if (!fdp) {
return
}
if (loadingPods || creatingPod) {
enqueueSnackbar('Please wait until the pods are loaded', { variant: 'info' })
return
}
// eslint-disable-next-line no-alert
const name = prompt('Enter a name for the new pod')
if (name) {
setCreatingPod(true)
fdp.personalStorage.create(name).then(() => {
fdp.personalStorage.list().then(pods => {
setPods(pods.pods)
setCreatingPod(false)
})
})
}
}
async function onImportPod() {
if (!fdp) {
return
}
if (loadingPods || creatingPod) {
enqueueSnackbar('Please wait until the pods are loaded', { variant: 'info' })
return
}
// eslint-disable-next-line no-alert
const name = prompt('Enter a name for the new pod')
// eslint-disable-next-line no-alert
const importHash = prompt('Enter the Swarm reference')
if (!name || !importHash) {
return
}
setCreatingPod(true)
const bee = new Bee('http://localhost:1633')
const manifest = await MantarayNode.unmarshal(bee, importHash)
await manifest.loadRecursively(bee)
const nodes = manifest.collect()
await fdp.personalStorage.create(name)
for (const node of nodes) {
await fdp.file.uploadData(
name,
joinUrl('/', node.fullPathString),
(await bee.downloadData(node.targetAddress)).toUint8Array(),
)
}
const pods = await fdp.personalStorage.list()
setPods(pods.pods)
setCreatingPod(false)
}
if (!fdp) {
return <CircularProgress />
}
return (
<Vertical gap={32} full left>
<Horizontal between>
<Typography variant="h1">Files</Typography>
{loggedIn && (
<Horizontal gap={4}>
<SwarmButton onClick={onCreatePod} iconType={PlusCircle}>
Create
</SwarmButton>
<SwarmButton onClick={onImportPod} iconType={ImportIcon}>
Import
</SwarmButton>
</Horizontal>
)}
</Horizontal>
{!loggedIn && <FdpLogin fdp={fdp} onSuccessfulLogin={onSuccessfulLogin} />}
{loggedIn && <FdpPods fdp={fdp} pods={pods} loadingPods={loadingPods || creatingPod} />}
{loggedIn && !loadingPods && !creatingPod && pods.length === 0 && (
<Typography>
<strong>You do not have any pods yet.</strong> Get started by clicking the Create or Import button on the top
right.
</Typography>
)}
</Vertical>
)
}
+6 -5
View File
@@ -1,11 +1,12 @@
import { Box, Grid, Typography } from '@material-ui/core'
import { NULL_TOPIC } from '@ethersphere/bee-js'
import { Box, Grid, Typography } from '@mui/material'
import { Form, Formik } from 'formik'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { useNavigate } from 'react-router'
import Check from 'remixicon-react/CheckLineIcon'
import X from 'remixicon-react/CloseLineIcon'
import { DocumentationText } from '../../components/DocumentationText'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
@@ -26,7 +27,7 @@ interface FormValues {
const initialValues: FormValues = {
identityName: '',
type: 'PRIVATE_KEY',
type: IdentityType.PrivateKey,
password: '',
}
@@ -102,12 +103,12 @@ export default function CreateNewFeed(): ReactElement {
formik
name="type"
options={[
{ label: 'Keypair Only', value: 'PRIVATE_KEY' },
{ label: 'Password Protected', value: 'V3' },
{ label: 'Keypair Only', value: IdentityType.PrivateKey },
{ label: 'Password Protected', value: IdentityType.V3 },
]}
/>
</Box>
{values.type === 'V3' && <SwarmTextInput name="password" label="Password" password formik />}
{values.type === IdentityType.V3 && <SwarmTextInput name="password" label="Password" password formik />}
<Box mt={2}>
<ExpandableListItemKey label="Topic" value={NULL_TOPIC.toHex()} />
</Box>
+2 -1
View File
@@ -1,7 +1,8 @@
import { Box, Typography } from '@material-ui/core'
import { Box, Typography } from '@mui/material'
import { ReactElement } from 'react'
import X from 'remixicon-react/CloseLineIcon'
import Trash from 'remixicon-react/DeleteBin7LineIcon'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { SwarmButton } from '../../components/SwarmButton'
import { SwarmDialog } from '../../components/SwarmDialog'
+12 -12
View File
@@ -1,33 +1,33 @@
import { Box, createStyles, makeStyles, Typography } from '@material-ui/core'
import { Box, Typography } from '@mui/material'
import { saveAs } from 'file-saver'
import { useSnackbar } from 'notistack'
import { ReactElement } from 'react'
import Download from 'remixicon-react/DownloadLineIcon'
import Clipboard from 'remixicon-react/ClipboardLineIcon'
import Download from 'remixicon-react/DownloadLineIcon'
import { makeStyles } from 'tss-react/mui'
import { Code } from '../../components/Code'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { SwarmButton } from '../../components/SwarmButton'
import { SwarmDialog } from '../../components/SwarmDialog'
import { TitleWithClose } from '../../components/TitleWithClose'
import { Identity } from '../../providers/Feeds'
import { Identity, IdentityType } from '../../providers/Feeds'
interface Props {
identity: Identity
onClose: () => void
}
const useStyles = makeStyles(() =>
createStyles({
wrapper: {
maxWidth: '100%',
},
}),
)
const useStyles = makeStyles()(() => ({
wrapper: {
maxWidth: '100%',
},
}))
export function ExportFeedDialog({ identity, onClose }: Props): ReactElement {
const { enqueueSnackbar } = useSnackbar()
const classes = useStyles()
const { classes } = useStyles()
function onDownload() {
saveAs(
@@ -39,7 +39,7 @@ export function ExportFeedDialog({ identity, onClose }: Props): ReactElement {
}
function getExportText() {
return identity.type === 'V3' ? 'JSON file' : 'the private key string'
return identity.type === IdentityType.V3 ? 'JSON file' : 'the private key string'
}
function onCopy() {
+2 -1
View File
@@ -1,7 +1,8 @@
import { Box, Typography } from '@material-ui/core'
import { Box, Typography } from '@mui/material'
import { ReactElement, useState } from 'react'
import Check from 'remixicon-react/CheckLineIcon'
import X from 'remixicon-react/CloseLineIcon'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { SwarmButton } from '../../components/SwarmButton'
import { SwarmDialog } from '../../components/SwarmDialog'
+8 -7
View File
@@ -1,7 +1,8 @@
import { Box } from '@material-ui/core'
import { Box } from '@mui/material'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import X from 'remixicon-react/CloseLineIcon'
import { DocumentationText } from '../../components/DocumentationText'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
@@ -11,6 +12,7 @@ import { Context as BeeContext } from '../../providers/Bee'
import { Context as IdentityContext } from '../../providers/Feeds'
import { Context as SettingsContext } from '../../providers/Settings'
import { ROUTES } from '../../routes'
import { FileOrigin } from '../files/FileNavigation'
import { UploadArea } from '../files/UploadArea'
export function FeedSubpage(): ReactElement {
@@ -32,11 +34,10 @@ export function FeedSubpage(): ReactElement {
return
}
try {
beeApi?.downloadData(identity.feedHash).then(() => setAvailable(true))
} catch {
setAvailable(false)
}
beeApi
?.downloadData(identity.feedHash)
.then(() => setAvailable(true))
.catch(() => setAvailable(false))
}, [beeApi, uuid, identity, navigate])
if (!identity || !status.all) {
@@ -50,7 +51,7 @@ export function FeedSubpage(): ReactElement {
return (
<div>
<HistoryHeader>{`${identity.name} Website`}</HistoryHeader>
<UploadArea showHelp={false} uploadOrigin={{ origin: 'FEED', uuid }} />
<UploadArea showHelp={false} uploadOrigin={{ origin: FileOrigin.Feed, uuid }} />
{available && identity.feedHash ? (
<>
<Box mb={4}>
+14 -14
View File
@@ -1,8 +1,10 @@
import { Box, createStyles, makeStyles, TextareaAutosize, Theme } from '@material-ui/core'
import { Box, TextareaAutosize } from '@mui/material'
import { useSnackbar } from 'notistack'
import React, { ReactElement, useContext, useRef, useState } from 'react'
import Check from 'remixicon-react/CheckLineIcon'
import Upload from 'remixicon-react/UploadLineIcon'
import { makeStyles } from 'tss-react/mui'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { SwarmButton } from '../../components/SwarmButton'
import { SwarmDialog } from '../../components/SwarmDialog'
@@ -15,18 +17,16 @@ interface Props {
onClose: () => void
}
const useStyles = makeStyles((theme: Theme) =>
createStyles({
textarea: {
width: '100%',
border: 0,
padding: theme.spacing(1),
},
displayNone: {
display: 'none',
},
}),
)
const useStyles = makeStyles()(theme => ({
textarea: {
width: '100%',
border: 0,
padding: theme.spacing(1),
},
displayNone: {
display: 'none',
},
}))
export function ImportFeedDialog({ onClose }: Props): ReactElement {
const [textareaValue, setTextareaValue] = useState('')
@@ -37,7 +37,7 @@ export function ImportFeedDialog({ onClose }: Props): ReactElement {
const { enqueueSnackbar } = useSnackbar()
const classes = useStyles()
const { classes } = useStyles()
async function onImport() {
const feed = await importIdentity(name, textareaValue)
+13 -4
View File
@@ -1,20 +1,22 @@
import { Box, Grid, Typography } from '@material-ui/core'
import { Box, Grid, Typography } from '@mui/material'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router'
import Bookmark from 'remixicon-react/BookmarkLineIcon'
import X from 'remixicon-react/CloseLineIcon'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { HistoryHeader } from '../../components/HistoryHeader'
import { SwarmButton } from '../../components/SwarmButton'
import { SelectEvent, SwarmSelect } from '../../components/SwarmSelect'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext } from '../../providers/Bee'
import { Identity, Context as IdentityContext } from '../../providers/Feeds'
import { Context as IdentityContext, Identity, IdentityType } from '../../providers/Feeds'
import { Context as SettingsContext } from '../../providers/Settings'
import { Context as StampContext } from '../../providers/Stamps'
import { ROUTES } from '../../routes'
import { persistIdentity, updateFeed } from '../../utils/identity'
import { FeedPasswordDialog } from './FeedPasswordDialog'
export default function UpdateFeed(): ReactElement {
@@ -56,7 +58,7 @@ export default function UpdateFeed(): ReactElement {
return
}
if (selectedIdentity.type === 'V3') {
if (selectedIdentity.type === IdentityType.V3) {
setShowPasswordPrompt(true)
} else {
onFeedUpdate(selectedIdentity)
@@ -73,8 +75,15 @@ export default function UpdateFeed(): ReactElement {
return
}
if (!hash) {
enqueueSnackbar(<span>Hash is invalid</span>, { variant: 'error' })
setLoading(false)
return
}
try {
await updateFeed(beeApi, identity, hash!, selectedStamp, password as string) // eslint-disable-line
await updateFeed(beeApi, identity, hash, selectedStamp, password as string)
persistIdentity(identities, identity)
setIdentities([...identities])
navigate(ROUTES.ACCOUNT_FEEDS_VIEW.replace(':uuid', identity.uuid))
+5 -3
View File
@@ -1,22 +1,24 @@
import { Box, Typography } from '@material-ui/core'
import { NULL_TOPIC } from '@ethersphere/bee-js'
import { Box, Typography } from '@mui/material'
import { ReactElement, useContext, useState } from 'react'
import { useNavigate } from 'react-router'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import Trash from 'remixicon-react/DeleteBin7LineIcon'
import Download from 'remixicon-react/DownloadLineIcon'
import Info from 'remixicon-react/InformationLineIcon'
import ExpandableList from '../../components/ExpandableList'
import ExpandableListItem from '../../components/ExpandableListItem'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import { SwarmButton } from '../../components/SwarmButton'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext, CheckState } from '../../providers/Bee'
import { Identity, Context as IdentityContext } from '../../providers/Feeds'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { Context as IdentityContext, Identity } from '../../providers/Feeds'
import { ROUTES } from '../../routes'
import { formatEnum } from '../../utils'
import { persistIdentitiesWithoutUpdate } from '../../utils/identity'
import { DeleteFeedDialog } from './DeleteFeedDialog'
import { ExportFeedDialog } from './ExportFeedDialog'
import { ImportFeedDialog } from './ImportFeedDialog'
+1 -1
View File
@@ -1,4 +1,4 @@
import { createContext, useContext, useMemo, useRef, useState, ReactNode, useCallback } from 'react'
import { createContext, ReactNode, useCallback, useContext, useMemo, useRef, useState } from 'react'
type Scope = 'selected' | 'all'
+2 -1
View File
@@ -1,4 +1,5 @@
import { createContext, useContext, useState, ReactNode } from 'react'
import { createContext, ReactNode, useContext, useState } from 'react'
import { ViewType } from '../../modules/filemanager/constants/transfers'
interface ViewContextProps {
+182 -114
View File
@@ -1,22 +1,157 @@
import { ReactElement, useContext, useEffect, useState, useRef, useCallback, useMemo } from 'react'
import './FileManager.scss'
import { DriveInfo, FileManagerBase } from '@solarpunkltd/file-manager-lib'
import { ReactElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import { SearchProvider } from './SearchContext'
import { ViewProvider } from './ViewContext'
import { Header } from '../../modules/filemanager/components/Header/Header'
import { Sidebar } from '../../modules/filemanager/components/Sidebar/Sidebar'
import { AdminStatusBar } from '../../modules/filemanager/components/AdminStatusBar/AdminStatusBar'
import { FileBrowser } from '../../modules/filemanager/components/FileBrowser/FileBrowser'
import { InitialModal } from '../../modules/filemanager/components/InitialModal/InitialModal'
import { Context as FMContext } from '../../providers/FileManager'
import { BrowserPlatform, cacheClearUrls, detectBrowser } from '../../providers/Platform'
import { Context as SettingsContext } from '../../providers/Settings'
import { Context as BeeContext, CheckState } from '../../providers/Bee'
import { PrivateKeyModal } from '../../modules/filemanager/components/PrivateKeyModal/PrivateKeyModal'
import { getSignerPk, removeSignerPk } from '../../../src/modules/filemanager/utils/common'
import { ErrorModal } from '../../../src/modules/filemanager/components/ErrorModal/ErrorModal'
import { ConfirmModal } from '../../modules/filemanager/components/ConfirmModal/ConfirmModal'
import { Button } from '../../modules/filemanager/components/Button/Button'
import { FormbricksIntegration } from '../../modules/filemanager/components/FormbricksIntegration/FormbricksIntegration'
import './FileManager.scss'
import { AdminStatusBar } from '@/modules/filemanager/components/AdminStatusBar/AdminStatusBar'
import { Button } from '@/modules/filemanager/components/Button/Button'
import { ConfirmModal } from '@/modules/filemanager/components/ConfirmModal/ConfirmModal'
import { ErrorModal } from '@/modules/filemanager/components/ErrorModal/ErrorModal'
import { FileBrowser } from '@/modules/filemanager/components/FileBrowser/FileBrowser'
import { FormbricksIntegration } from '@/modules/filemanager/components/FormbricksIntegration/FormbricksIntegration'
import { Header } from '@/modules/filemanager/components/Header/Header'
import { InitialModal } from '@/modules/filemanager/components/InitialModal/InitialModal'
import { PrivateKeyModal } from '@/modules/filemanager/components/PrivateKeyModal/PrivateKeyModal'
import { Sidebar } from '@/modules/filemanager/components/Sidebar/Sidebar'
import { getSignerPk, removeSignerPk } from '@/modules/filemanager/utils/common'
import { CheckState, Context as BeeContext } from '@/providers/Bee'
import { Context as FMContext } from '@/providers/FileManager'
import { BrowserPlatform, cacheClearUrls, detectBrowser } from '@/providers/Platform'
import { Context as SettingsContext } from '@/providers/Settings'
function PrivateKeyModalBlock({ onSaved }: { onSaved: () => void }) {
return (
<div className="fm-main">
<PrivateKeyModal onSaved={onSaved} />
</div>
)
}
function InitializationErrorBlock({ onOk }: { onOk: () => void }) {
return (
<div className="fm-main">
<div className="fm-loading">
<div className="fm-loading-title">Failed to initialize File Manager, reload and try again </div>
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '16px' }}>
<div style={{ minWidth: '120px' }}>
<Button label={'OK'} variant="primary" disabled={false} onClick={onOk} />
</div>
</div>
</div>
</div>
)
}
function ResetModalBlock({ cacheHelpUrl, onConfirm }: { cacheHelpUrl: string; onConfirm: () => void }) {
return (
<div className="fm-main">
<ConfirmModal
title="Reset File Manager State"
message={
<span>
Your File Manager state appears invalid. Please{' '}
<a
href={cacheHelpUrl}
target="_blank"
rel="noopener noreferrer"
style={{ display: 'inline', textDecoration: 'underline' }}
>
clear the browser cache
</a>{' '}
and reload the page. Then you can reset the File Manager to continue.
</span>
}
confirmLabel="Continue"
onConfirm={onConfirm}
background={false}
/>
</div>
)
}
function InitialModalBlock(props: {
resetState: boolean
handleVisibility: (isVisible: boolean) => void
handleShowError: (flag: boolean, error?: string) => void
setIsCreationInProgress: (isCreating: boolean) => void
}) {
return (
<div className="fm-main">
<InitialModal {...props} />
</div>
)
}
function LoadingBlock() {
return (
<div className="fm-main">
<div className="fm-loading" aria-live="polite">
<div className="fm-spinner" aria-hidden="true" />
<div className="fm-loading-title">File manager loading</div>
<div className="fm-loading-subtitle">Please wait a few seconds</div>
</div>
</div>
)
}
function ErrorModalBlock({ onClick, label }: { onClick: () => void; label: string }) {
return <ErrorModal label={label} onClick={onClick} />
}
function FileManagerMainContent(props: {
fm: FileManagerBase | null
showConnectionError: boolean
setShowConnectionError: (v: boolean) => void
isFormbricksActive: boolean
errorMessage: string
setErrorMessage: (msg: string) => void
loading: boolean
adminDrive: DriveInfo | null
isCreationInProgress: boolean
}) {
const {
fm,
showConnectionError,
setShowConnectionError,
isFormbricksActive,
errorMessage,
setErrorMessage,
loading,
adminDrive,
isCreationInProgress,
} = props
return (
<SearchProvider>
<ViewProvider>
<div className="fm-main">
{showConnectionError && fm && (
<ErrorModal
label="Bee node connection error. Please check your node status. File Manager will continue when connection is restored."
onClick={() => setShowConnectionError(false)}
/>
)}
<FormbricksIntegration isActive={isFormbricksActive} />
<Header />
<div className="fm-main-content">
<Sidebar errorMessage={errorMessage} setErrorMessage={setErrorMessage} loading={loading} />
<FileBrowser errorMessage={errorMessage} setErrorMessage={setErrorMessage} />
</div>
<AdminStatusBar
adminStamp={fm?.adminStamp || null}
adminDrive={adminDrive}
loading={loading}
isCreationInProgress={isCreationInProgress}
setErrorMessage={setErrorMessage}
/>
</div>
</ViewProvider>
</SearchProvider>
)
}
export function FileManagerPage(): ReactElement {
const isMountedRef = useRef(true)
@@ -136,99 +271,46 @@ export function FileManagerPage(): ReactElement {
const isFormbricksActive = Boolean(fm && fm.adminStamp && adminDrive && !showInitialModal && !loading)
if (!hasPk) {
return (
<div className="fm-main">
<PrivateKeyModal onSaved={handlePrivateKeySaved} />
</div>
)
return <PrivateKeyModalBlock onSaved={handlePrivateKeySaved} />
}
if (initializationError && !isLoading && !shallReset) {
return (
<div className="fm-main">
<div className="fm-loading">
<div className="fm-loading-title">Failed to initialize File Manager, reload and try again </div>
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '16px' }}>
<div style={{ minWidth: '120px' }}>
<Button
label={'OK'}
variant="primary"
disabled={false}
onClick={() => {
removeSignerPk()
setHasPk(false)
}}
/>
</div>
</div>
</div>
</div>
<InitializationErrorBlock
onOk={() => {
removeSignerPk()
setHasPk(false)
}}
/>
)
}
if (showResetModal) {
return (
<div className="fm-main">
<ConfirmModal
title="Reset File Manager State"
message={
<span>
Your File Manager state appears invalid. Please{' '}
<a
href={cacheHelpUrl}
target="_blank"
rel="noopener noreferrer"
style={{ display: 'inline', textDecoration: 'underline' }}
>
clear the browser cache
</a>{' '}
and reload the page. Then you can reset the File Manager to continue.
</span>
}
confirmLabel="Continue"
onConfirm={() => {
setShowResetModal(false)
}}
background={false}
/>
</div>
)
return <ResetModalBlock cacheHelpUrl={cacheHelpUrl} onConfirm={() => setShowResetModal(false)} />
}
if (!showErrorModal && (isEmptyState || isInvalidState)) {
return (
<div className="fm-main">
<InitialModal
resetState={shallReset}
handleVisibility={(isVisible: boolean) => setShowInitialModal(isVisible)}
handleShowError={(flag: boolean, error?: string) => {
setShowErrorModal(flag)
<InitialModalBlock
resetState={shallReset}
handleVisibility={(isVisible: boolean) => setShowInitialModal(isVisible)}
handleShowError={(flag: boolean, error?: string) => {
setShowErrorModal(flag)
if (error) {
setErrorMessage(error)
}
}}
setIsCreationInProgress={(isCreating: boolean) => setIsCreationInProgress(isCreating)}
/>
</div>
if (error) setErrorMessage(error)
}}
setIsCreationInProgress={(isCreating: boolean) => setIsCreationInProgress(isCreating)}
/>
)
}
if (!fm) {
return (
<div className="fm-main">
<div className="fm-loading" aria-live="polite">
<div className="fm-spinner" aria-hidden="true" />
<div className="fm-loading-title">File manager loading</div>
<div className="fm-loading-subtitle">Please wait a few seconds</div>
</div>
</div>
)
return <LoadingBlock />
}
if (showErrorModal) {
return (
<ErrorModal
<ErrorModalBlock
label={
'Error creating Admin Drive. Please try again. Possible causes include insufficient xDAI balance or a lost connection to the RPC.'
}
@@ -242,30 +324,16 @@ export function FileManagerPage(): ReactElement {
}
return (
<SearchProvider>
<ViewProvider>
<div className="fm-main">
{showConnectionError && fm && (
<ErrorModal
label="Bee node connection error. Please check your node status. File Manager will continue when connection is restored."
onClick={() => setShowConnectionError(false)}
/>
)}
<FormbricksIntegration isActive={isFormbricksActive} />
<Header />
<div className="fm-main-content">
<Sidebar errorMessage={errorMessage} setErrorMessage={setErrorMessage} loading={loading} />
<FileBrowser errorMessage={errorMessage} setErrorMessage={setErrorMessage} />
</div>
<AdminStatusBar
adminStamp={fm?.adminStamp || null}
adminDrive={adminDrive}
loading={loading}
isCreationInProgress={isCreationInProgress}
setErrorMessage={setErrorMessage}
/>
</div>
</ViewProvider>
</SearchProvider>
<FileManagerMainContent
fm={fm}
showConnectionError={showConnectionError}
setShowConnectionError={() => setShowConnectionError(false)}
isFormbricksActive={isFormbricksActive}
errorMessage={errorMessage}
setErrorMessage={setErrorMessage}
loading={loading}
adminDrive={adminDrive}
isCreationInProgress={isCreationInProgress}
/>
)
}
+1
View File
@@ -1,4 +1,5 @@
import { ReactElement } from 'react'
import { StripedWrapper } from '../../components/StripedWrapper'
interface Props {
+15 -14
View File
@@ -1,44 +1,45 @@
import { Box, Grid, Typography } from '@material-ui/core'
import { Web } from '@material-ui/icons'
import { Web } from '@mui/icons-material'
import { Box, Grid, Typography } from '@mui/material'
import { ReactElement, useMemo } from 'react'
import File from 'remixicon-react/FileLineIcon'
import Folder from 'remixicon-react/FolderLineIcon'
import { FitAudio } from '../../components/FitAudio'
import { FitImage } from '../../components/FitImage'
import { FitVideo } from '../../components/FitVideo'
import { shortenText } from '../../utils'
import { getHumanReadableFileSize } from '../../utils/file'
import { shortenHash } from '../../utils/hash'
import { AssetIcon } from './AssetIcon'
import { FitVideo } from '../../components/FitVideo'
import { FitAudio } from '../../components/FitAudio'
interface Props {
previewUri?: string
metadata?: Metadata
}
/* eslint-disable react/display-name */
const getPreviewComponent = (previewUri?: string, metadata?: Metadata) => {
const getPreviewElement = (previewUri?: string, metadata?: Metadata) => {
if (metadata?.isVideo) {
return () => <FitVideo src={previewUri} maxWidth="250px" maxHeight="175px" />
return <FitVideo src={previewUri} maxWidth="250px" maxHeight="175px" />
}
if (metadata?.isAudio) {
return () => <FitAudio src={previewUri} maxWidth="250px" />
return <FitAudio src={previewUri} maxWidth="250px" />
}
if (metadata?.isImage) {
return () => <FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} />
return <FitImage maxWidth="250px" maxHeight="175px" alt="Upload Preview" src={previewUri} />
}
if (metadata?.isWebsite) {
return () => <AssetIcon icon={<Web />} />
return <AssetIcon icon={<Web />} />
}
if (metadata?.type === 'folder') {
return () => <AssetIcon icon={<Folder />} />
return <AssetIcon icon={<Folder />} />
}
return () => <AssetIcon icon={<File />} />
return <AssetIcon icon={<File />} />
}
const getType = (metadata?: Metadata) => {
@@ -51,14 +52,14 @@ const getType = (metadata?: Metadata) => {
// TODO: add optional prop for indexDocument when it is already known (e.g. downloading a manifest)
export function AssetPreview({ metadata, previewUri }: Props): ReactElement | null {
const PreviewAssetComponent = useMemo(() => getPreviewComponent(previewUri, metadata), [metadata, previewUri])
const previewElement = useMemo(() => getPreviewElement(previewUri, metadata), [metadata, previewUri])
const type = useMemo(() => getType(metadata), [metadata])
return (
<Box mb={4}>
<Box bgcolor="background.paper">
<Grid container direction="row">
<PreviewAssetComponent />
{previewElement}
<Box p={2}>
{metadata?.hash && <Typography>Swarm Hash: {shortenHash(metadata.hash)}</Typography>}
{metadata?.name && metadata?.name !== metadata?.hash && (
+7 -6
View File
@@ -1,23 +1,24 @@
import { Box } from '@material-ui/core'
import { Reference } from '@ethersphere/bee-js'
import { Box } from '@mui/material'
import { ReactElement } from 'react'
import { DocumentationText } from '../../components/DocumentationText'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import ExpandableListItemLink from '../../components/ExpandableListItemLink'
interface Props {
isWebsite?: boolean
reference: string
reference?: string
}
export function AssetSummary({ isWebsite, reference }: Props): ReactElement {
const isHash = Reference.isValid(reference)
export function AssetSummary({ reference }: Props): ReactElement {
const isHash = reference ? Reference.isValid(reference) : false
return (
<>
<Box mb={4}>
{isHash && <ExpandableListItemKey label="Swarm hash" value={reference} />}
{!isHash && <ExpandableListItemLink label="ENS" value={reference} />}
{isHash && <ExpandableListItemKey label="Swarm hash" value={reference || ''} />}
{!isHash && <ExpandableListItemLink label="ENS" value={reference || ''} />}
</Box>
<DocumentationText>
The Swarm Gateway is graciously provided by the Swarm Foundation. This service is under development and provided
+12 -10
View File
@@ -1,14 +1,17 @@
import { Box } from '@material-ui/core'
import { Tag } from '@ethersphere/bee-js'
import { ReactElement, useContext, useEffect, useRef, useState } from 'react'
import { Box } from '@mui/material'
import { ReactElement, useCallback, useContext, useEffect, useRef, useState } from 'react'
import { DocumentationText } from '../../components/DocumentationText'
import { LinearProgressWithLabel } from '../../components/ProgressBar'
import { Context as SettingsContext } from '../../providers/Settings'
interface Props {
reference: string
reference?: string
}
const SYNC_CHECK_INTERVAL_MS = 2000
export function AssetSyncing({ reference }: Props): ReactElement {
const { beeApi } = useContext(SettingsContext)
@@ -16,8 +19,8 @@ export function AssetSyncing({ reference }: Props): ReactElement {
const [isRetrieveChecking, setIsRetrieveChecking] = useState<boolean>(false)
const [syncProgress, setSyncProgress] = useState<number>(0)
const syncCheck = async () => {
if (!beeApi) return
const syncCheck = useCallback(async () => {
if (!beeApi || !reference) return
let allTags: Tag[] = []
let offset = 0
@@ -36,10 +39,10 @@ export function AssetSyncing({ reference }: Props): ReactElement {
const progress = ((tag.seen + tag.synced) / tag.split) * 100
setSyncProgress(progress)
}
}
}, [beeApi, reference])
useEffect(() => {
syncTimer.current = setInterval(syncCheck, 2000)
syncTimer.current = setInterval(syncCheck, SYNC_CHECK_INTERVAL_MS)
return () => {
if (syncTimer.current) {
@@ -47,8 +50,7 @@ export function AssetSyncing({ reference }: Props): ReactElement {
syncTimer.current = null
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reference])
}, [reference, syncCheck])
useEffect(() => {
if (syncProgress === 100 && syncTimer.current) {
@@ -63,7 +65,7 @@ export function AssetSyncing({ reference }: Props): ReactElement {
To ensure it's not due to invalid synchronization data,
verify availability from at least 70% using one of the stewardship endpoints.
*/
if (beeApi && !isRetrieveChecking && syncProgress > 10 && syncProgress < 100) {
if (beeApi && reference && !isRetrieveChecking && syncProgress > 10 && syncProgress < 100) {
// It's a long running task make sure only one run occurs at a time.
setIsRetrieveChecking(true)
+12 -19
View File
@@ -1,8 +1,9 @@
import { BeeModes, MantarayNode, NULL_ADDRESS, Reference } from '@ethersphere/bee-js'
import { BeeModes, Reference } from '@ethersphere/bee-js'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import Search from 'remixicon-react/SearchLineIcon'
import ExpandableListItemInput from '../../components/ExpandableListItemInput'
import { History } from '../../components/History'
import { Context as BeeContext } from '../../providers/Bee'
@@ -10,8 +11,10 @@ import { Context as FileContext, defaultUploadOrigin } from '../../providers/Fil
import { Context as SettingsContext } from '../../providers/Settings'
import { ROUTES } from '../../routes'
import { recognizeEnsOrSwarmHash, regexpEns } from '../../utils'
import { HISTORY_KEYS, determineHistoryName, putHistory } from '../../utils/local-storage'
import { FileNavigation } from './FileNavigation'
import { determineHistoryName, LocalStorageKeys, putHistory } from '../../utils/localStorage'
import { loadManifest } from '../../utils/manifest'
import { FileNavigation, FileOrigin } from './FileNavigation'
export function Download(): ReactElement {
const [loading, setLoading] = useState(false)
@@ -43,22 +46,11 @@ export function Download(): ReactElement {
setLoading(true)
try {
let manifest = await MantarayNode.unmarshal(beeApi, identifier)
await manifest.loadRecursively(beeApi)
// If the manifest is a feed, resolve it and overwrite the manifest
await manifest.resolveFeed(beeApi).then(
async feed =>
await feed.ifPresentAsync(async feedUpdate => {
manifest = MantarayNode.unmarshalFromData(feedUpdate.payload.toUint8Array(), NULL_ADDRESS)
await manifest.loadRecursively(beeApi)
}),
)
const manifest = await loadManifest(beeApi, identifier)
const rootMetadata = manifest.getDocsMetadata()
putHistory(
HISTORY_KEYS.DOWNLOAD_HISTORY,
LocalStorageKeys.downloadHistory,
identifier,
determineHistoryName(identifier, rootMetadata.indexDocument),
)
@@ -74,7 +66,8 @@ export function Download(): ReactElement {
if (message.includes('Not Found: Not Found')) {
message = 'The specified hash was not found.'
}
console.error(error) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(error)
enqueueSnackbar(<span>Error: {message || 'Unknown'}</span>, { variant: 'error' })
} finally {
setLoading(false)
@@ -83,7 +76,7 @@ export function Download(): ReactElement {
return (
<>
{nodeInfo?.beeMode !== BeeModes.ULTRA_LIGHT && <FileNavigation active="DOWNLOAD" />}
{nodeInfo?.beeMode !== BeeModes.ULTRA_LIGHT && <FileNavigation active={FileOrigin.Download} />}
<ExpandableListItemInput
label="Swarm Hash or ENS"
onConfirm={value => onSwarmIdentifier(value)}
@@ -97,7 +90,7 @@ export function Download(): ReactElement {
mapperFn={value => recognizeEnsOrSwarmHash(value)}
loading={loading}
/>
<History title="Download History" localStorageKey={HISTORY_KEYS.DOWNLOAD_HISTORY} />
<History title="Download History" localStorageKey={LocalStorageKeys.downloadHistory} />
</>
)
}
+3 -2
View File
@@ -1,9 +1,10 @@
import { Box, Grid } from '@material-ui/core'
import { Box, Grid } from '@mui/material'
import { ReactElement } from 'react'
import X from 'remixicon-react/CloseLineIcon'
import Bookmark from 'remixicon-react/BookmarkLineIcon'
import X from 'remixicon-react/CloseLineIcon'
import Download from 'remixicon-react/DownloadLineIcon'
import Link from 'remixicon-react/LinkIcon'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { SwarmButton } from '../../components/SwarmButton'
+29 -23
View File
@@ -1,40 +1,46 @@
import { createStyles, makeStyles, Tab, Tabs, Theme } from '@material-ui/core'
import { ReactElement } from 'react'
import { Tab, Tabs } from '@mui/material'
import React, { ReactElement } from 'react'
import { useNavigate } from 'react-router-dom'
import { makeStyles } from 'tss-react/mui'
import { ROUTES } from '../../routes'
interface Props {
active: 'UPLOAD' | 'DOWNLOAD'
export enum FileOrigin {
Upload = 'UPLOAD',
Download = 'DOWNLOAD',
Feed = 'FEED',
}
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
flexGrow: 1,
marginBottom: theme.spacing(4),
},
leftTab: {
marginRight: theme.spacing(0.5),
},
rightTab: {
marginLeft: theme.spacing(0.5),
},
}),
)
interface Props {
active: FileOrigin
}
const useStyles = makeStyles()(theme => ({
root: {
flexGrow: 1,
marginBottom: theme.spacing(4),
},
leftTab: {
marginRight: theme.spacing(0.5),
},
rightTab: {
marginLeft: theme.spacing(0.5),
},
}))
export function FileNavigation({ active }: Props): ReactElement {
const classes = useStyles()
const { classes } = useStyles()
const navigate = useNavigate()
function onChange(event: React.ChangeEvent<Record<string, never>>, newValue: number) {
function onChange(_event: React.SyntheticEvent, newValue: number) {
navigate(newValue === 1 ? ROUTES.DOWNLOAD : ROUTES.UPLOAD)
}
return (
<div className={classes.root}>
<Tabs value={active === 'UPLOAD' ? 0 : 1} onChange={onChange} variant="fullWidth">
<Tab className={classes.leftTab} key="UPLOAD" label="Upload" />
<Tab className={classes.rightTab} key="DOWNLOAD" label="Download" />
<Tabs value={active === FileOrigin.Upload ? 0 : 1} onChange={onChange} variant="fullWidth">
<Tab className={classes.leftTab} key={FileOrigin.Upload} label="Upload" />
<Tab className={classes.rightTab} key={FileOrigin.Download} label="Download" />
</Tabs>
</div>
)
+2 -1
View File
@@ -1,5 +1,6 @@
import { Button, ListItemIcon, Menu, MenuItem, Typography } from '@material-ui/core'
import { Button, ListItemIcon, Menu, MenuItem, Typography } from '@mui/material'
import React, { ReactElement } from 'react'
import { EnrichedPostageBatch } from '../../providers/Stamps'
interface Props {
+33 -30
View File
@@ -1,10 +1,11 @@
import { Box, Typography } from '@material-ui/core'
import { Bytes, MantarayNode, NULL_ADDRESS } from '@ethersphere/bee-js'
import { Bytes } from '@ethersphere/bee-js'
import { Box, Typography } from '@mui/material'
import { saveAs } from 'file-saver'
import JSZip from 'jszip'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { HistoryHeader } from '../../components/HistoryHeader'
import { Loading } from '../../components/Loading'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
@@ -12,7 +13,9 @@ import { META_FILE_NAME } from '../../constants'
import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { ROUTES } from '../../routes'
import { determineHistoryName, HISTORY_KEYS, putHistory } from '../../utils/local-storage'
import { determineHistoryName, LocalStorageKeys, putHistory } from '../../utils/localStorage'
import { loadManifest } from '../../utils/manifest'
import { AssetPreview } from './AssetPreview'
import { AssetSummary } from './AssetSummary'
import { AssetSyncing } from './AssetSyncing'
@@ -23,7 +26,6 @@ export function Share(): ReactElement {
const { status } = useContext(BeeContext)
const { hash } = useParams()
const reference = hash! // eslint-disable-line
const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar()
@@ -39,23 +41,12 @@ export function Share(): ReactElement {
const isMountedRef = useRef(true)
async function prepare() {
if (!beeApi || !status.all) {
if (!beeApi || !status.all || !hash) {
return
}
try {
let manifest = await MantarayNode.unmarshal(beeApi, reference)
await manifest.loadRecursively(beeApi)
// If the manifest is a feed, resolve it and overwrite the manifest
await manifest.resolveFeed(beeApi).then(
async feed =>
await feed.ifPresentAsync(async feedUpdate => {
manifest = MantarayNode.unmarshalFromData(feedUpdate.payload.toUint8Array(), NULL_ADDRESS)
await manifest.loadRecursively(beeApi)
}),
)
const manifest = await loadManifest(beeApi, hash)
const entries = manifest.collectAndMap()
delete entries[META_FILE_NAME]
@@ -73,18 +64,18 @@ export function Share(): ReactElement {
setIndexDocument(indexDocument)
try {
const remoteMetadata = await beeApi.downloadFile(reference, META_FILE_NAME)
const remoteMetadata = await beeApi.downloadFile(hash, META_FILE_NAME)
const formattedMetadata = remoteMetadata.data.toJSON() as Metadata
if (formattedMetadata.isVideo || formattedMetadata.isAudio || formattedMetadata.isImage) {
if (!isMountedRef.current) return
setPreview(`${apiUrl}/bzz/${reference}`)
setPreview(`${apiUrl}/bzz/${hash}`)
}
if (!isMountedRef.current) return
setMetadata({ ...formattedMetadata, hash })
} catch (e) {
} catch {
// if metadata is not available or invalid go with the default one
const count = Object.keys(entries).length
@@ -93,7 +84,7 @@ export function Share(): ReactElement {
setMetadata({
hash,
type: count > 1 ? 'folder' : 'unknown',
name: reference,
name: hash,
count,
isWebsite: Boolean(indexDocument && /.*\.html?$/i.test(indexDocument)),
isVideo: Boolean(indexDocument && /.*\.(mp4|webm|ogv)$/i.test(indexDocument)),
@@ -113,7 +104,7 @@ export function Share(): ReactElement {
}
function onOpen() {
window.open(`${apiUrl}/bzz/${reference}/`, '_blank')
window.open(`${apiUrl}/bzz/${hash}/`, '_blank')
}
function onClose() {
@@ -127,10 +118,19 @@ export function Share(): ReactElement {
}
function onUpdateFeed() {
navigate(ROUTES.ACCOUNT_FEEDS_UPDATE.replace(':hash', reference))
if (!hash) {
// eslint-disable-next-line no-console
console.error('hash is invalid')
return
}
navigate(ROUTES.ACCOUNT_FEEDS_UPDATE.replace(':hash', hash))
}
useEffect(() => {
isMountedRef.current = true
return () => {
isMountedRef.current = false
}
@@ -143,14 +143,17 @@ export function Share(): ReactElement {
setLoading(false)
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reference])
}, [hash])
async function onDownload() {
if (!beeApi) {
if (!beeApi || !hash) {
// eslint-disable-next-line no-console
console.error('hash is invalid')
return
}
putHistory(HISTORY_KEYS.DOWNLOAD_HISTORY, reference, determineHistoryName(reference, indexDocument))
putHistory(LocalStorageKeys.downloadHistory, hash, determineHistoryName(hash, indexDocument))
setDownloading(true)
if (Object.keys(swarmEntries).length === 1) {
@@ -172,7 +175,7 @@ export function Share(): ReactElement {
const view = new Uint8Array(arrayBuffer)
view.set(dataArray)
const blob = new Blob([arrayBuffer], { type: metadata?.type || 'application/octet-stream' })
saveAs(blob, metadata?.name || singleFileName || reference)
saveAs(blob, metadata?.name || singleFileName || hash)
} else {
const zip = new JSZip()
for (const [path, hash] of Object.entries(swarmEntries)) {
@@ -196,7 +199,7 @@ export function Share(): ReactElement {
return
}
saveAs(content, reference + '.zip')
saveAs(content, hash + '.zip')
}
if (!isMountedRef.current) return
@@ -225,10 +228,10 @@ export function Share(): ReactElement {
<AssetPreview metadata={metadata} previewUri={preview} />
</Box>
<Box mb={4}>
<AssetSummary isWebsite={metadata?.isWebsite} reference={reference} />
<AssetSummary isWebsite={metadata?.isWebsite} reference={hash} />
</Box>
<Box mb={4}>
<AssetSyncing reference={reference} />
<AssetSyncing reference={hash} />
</Box>
<DownloadActionBar
onOpen={onOpen}
+2 -1
View File
@@ -1,5 +1,6 @@
import { Box, Typography } from '@material-ui/core'
import { Box, Typography } from '@mui/material'
import { ReactElement } from 'react'
import { EnrichedPostageBatch } from '../../providers/Stamps'
import { PostageStamp } from '../stamps/PostageStamp'
+19 -15
View File
@@ -1,32 +1,35 @@
import { Box } from '@material-ui/core'
import { Box } from '@mui/material'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { DocumentationText } from '../../components/DocumentationText'
import { HistoryHeader } from '../../components/HistoryHeader'
import { ProgressIndicator } from '../../components/ProgressIndicator'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { META_FILE_NAME } from '../../constants'
import { Context as BeeContext, CheckState } from '../../providers/Bee'
import { Identity, Context as IdentityContext } from '../../providers/Feeds'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { Context as IdentityContext, Identity, IdentityType } from '../../providers/Feeds'
import { Context as FileContext } from '../../providers/File'
import { Context as SettingsContext } from '../../providers/Settings'
import { EnrichedPostageBatch, Context as StampsContext } from '../../providers/Stamps'
import { Context as StampsContext, EnrichedPostageBatch } from '../../providers/Stamps'
import { ROUTES } from '../../routes'
import { waitUntilStampUsable } from '../../utils'
import { detectIndexHtml, getAssetNameFromFiles, packageFile } from '../../utils/file'
import { persistIdentity, updateFeed } from '../../utils/identity'
import { HISTORY_KEYS, putHistory } from '../../utils/local-storage'
import { LocalStorageKeys, putHistory } from '../../utils/localStorage'
import { FeedPasswordDialog } from '../feeds/FeedPasswordDialog'
import { PostageStampAdvancedCreation } from '../stamps/PostageStampAdvancedCreation'
import { PostageStampSelector } from '../stamps/PostageStampSelector'
import { AssetPreview } from './AssetPreview'
import { FileOrigin } from './FileNavigation'
import { StampPreview } from './StampPreview'
import { UploadActionBar } from './UploadActionBar'
import { StampMode, UploadActionBar } from './UploadActionBar'
export function Upload(): ReactElement {
const [step, setStep] = useState(0)
const [stampMode, setStampMode] = useState<'SELECT' | 'BUY'>('SELECT')
const [stampMode, setStampMode] = useState<StampMode>(StampMode.Select)
const [stamp, setStamp] = useState<EnrichedPostageBatch | null>(null)
const [isUploading, setUploading] = useState(false)
const [showPasswordPrompt, setShowPasswordPrompt] = useState(false)
@@ -44,7 +47,7 @@ export function Upload(): ReactElement {
useEffect(() => {
refresh()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
}, [refresh])
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
@@ -58,10 +61,10 @@ export function Upload(): ReactElement {
const identity = uploadOrigin.uuid ? identities.find(x => x.uuid === uploadOrigin.uuid) : null
const onUpload = () => {
if (uploadOrigin.origin === 'UPLOAD') {
if (uploadOrigin.origin === FileOrigin.Upload) {
uploadFiles()
} else {
if ((identity as Identity).type === 'PRIVATE_KEY') {
if ((identity as Identity).type === IdentityType.PrivateKey) {
uploadFiles()
} else {
setShowPasswordPrompt(true)
@@ -114,9 +117,9 @@ export function Upload(): ReactElement {
beeApi
.uploadFiles(stamp.batchID, fls, { indexDocument, deferred: true })
.then(hash => {
putHistory(HISTORY_KEYS.UPLOAD_HISTORY, hash.reference.toHex(), getAssetNameFromFiles(files))
putHistory(LocalStorageKeys.uploadHistory, hash.reference.toHex(), getAssetNameFromFiles(files))
if (uploadOrigin.origin === 'UPLOAD') {
if (uploadOrigin.origin === FileOrigin.Upload) {
navigate(ROUTES.HASH.replace(':hash', hash.reference.toHex()), { replace: true })
} else {
updateFeed(beeApi, identity as Identity, hash.reference, stamp.batchID, password as string).then(() => {
@@ -127,7 +130,8 @@ export function Upload(): ReactElement {
}
})
.catch(e => {
console.error(e) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(e)
enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' })
setUploading(false)
})
@@ -163,10 +167,10 @@ export function Upload(): ReactElement {
{step === 1 && (
<>
<Box mb={2}>
{hasAnyStamps && stampMode === 'SELECT' ? (
{hasAnyStamps && stampMode === StampMode.Select ? (
<PostageStampSelector onSelect={stamp => setStamp(stamp)} defaultValue={stamp?.batchID.toHex()} />
) : (
<PostageStampAdvancedCreation onFinished={() => setStampMode('SELECT')} />
<PostageStampAdvancedCreation onFinished={() => setStampMode(StampMode.Select)} />
)}
</Box>
<Box mb={4}>
+16 -10
View File
@@ -1,14 +1,20 @@
import { Box, Grid } from '@material-ui/core'
import { Box, Grid } from '@mui/material'
import { ReactElement } from 'react'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import ArrowLeft from 'remixicon-react/ArrowLeftLineIcon'
import Check from 'remixicon-react/CheckLineIcon'
import X from 'remixicon-react/CloseLineIcon'
import ArrowLeft from 'remixicon-react/ArrowLeftLineIcon'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import Layers from 'remixicon-react/StackLineIcon'
import { DocumentationText } from '../../components/DocumentationText'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { SwarmButton } from '../../components/SwarmButton'
export enum StampMode {
Buy = 'BUY',
Select = 'SELECT',
}
interface Props {
step: number
onUpload: () => void
@@ -19,8 +25,8 @@ interface Props {
hasStamp: boolean
hasAnyStamps: boolean
uploadLabel: string
stampMode: 'BUY' | 'SELECT'
setStampMode: (mode: 'BUY' | 'SELECT') => void
stampMode: StampMode
setStampMode: (mode: StampMode) => void
}
export function UploadActionBar({
@@ -68,7 +74,7 @@ export function UploadActionBar({
return (
<Grid container direction="row" justifyContent="space-between">
<ExpandableListItemActions>
{stampMode === 'SELECT' && (
{stampMode === StampMode.Select && (
<SwarmButton onClick={onProceed} iconType={Check} disabled={!hasStamp}>
Proceed With Selected Stamp
</SwarmButton>
@@ -79,11 +85,11 @@ export function UploadActionBar({
</ExpandableListItemActions>
{hasAnyStamps && (
<SwarmButton
disabled={stampMode === 'BUY' && !hasAnyStamps}
onClick={() => setStampMode(stampMode === 'BUY' ? 'SELECT' : 'BUY')}
iconType={stampMode === 'BUY' ? Layers : PlusSquare}
disabled={stampMode === StampMode.Buy && !hasAnyStamps}
onClick={() => setStampMode(stampMode === StampMode.Buy ? StampMode.Select : StampMode.Buy)}
iconType={stampMode === StampMode.Buy ? Layers : PlusSquare}
>
{stampMode === 'BUY' ? 'Use Existing Stamp' : 'Buy New Stamp'}
{stampMode === StampMode.Buy ? 'Use Existing Stamp' : 'Buy New Stamp'}
</SwarmButton>
)}
</Grid>
+76 -81
View File
@@ -1,75 +1,83 @@
import { createStyles, makeStyles, Theme } from '@material-ui/core'
import { DropzoneArea } from 'material-ui-dropzone'
import { BeeModes } from '@ethersphere/bee-js'
import { Box } from '@mui/material'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { ReactElement, useContext, useRef, useState } from 'react'
import { useDropzone } from 'react-dropzone'
import { useNavigate } from 'react-router-dom'
import PlusCircle from 'remixicon-react/AddCircleLineIcon'
import FilePlus from 'remixicon-react/FileAddLineIcon'
import FolderPlus from 'remixicon-react/FolderAddLineIcon'
import { useNavigate } from 'react-router-dom'
import { makeStyles } from 'tss-react/mui'
import { DocumentationText } from '../../components/DocumentationText'
import { SwarmButton } from '../../components/SwarmButton'
import { Context, UploadOrigin } from '../../providers/File'
import { Context as BeeContext } from '../../providers/Bee'
import { Context, UploadOrigin } from '../../providers/File'
import { ROUTES } from '../../routes'
import { detectIndexHtml } from '../../utils/file'
import { BeeModes } from '@ethersphere/bee-js'
interface Props {
uploadOrigin: UploadOrigin
showHelp: boolean
}
const MAX_FILE_SIZE = 1_000_000_000 // 1 gigabyte
const MAX_FILE_SIZE = 1_000_000_000 // 1 GB
const useStyles = makeStyles((theme: Theme) =>
createStyles({
areaWrapper: { position: 'relative', marginBottom: theme.spacing(2) },
dropzone: {
background: theme.palette.background.default,
outline: 'none',
color: 'transparent',
zIndex: 1,
'& svg': {
opacity: 0,
},
const useStyles = makeStyles()(theme => ({
areaWrapper: { position: 'relative', marginBottom: theme.spacing(2) },
dropzone: {
background: theme.palette.background.default,
outline: 'none',
border: '2px dashed #ccc',
borderRadius: 4,
padding: theme.spacing(4),
textAlign: 'center',
cursor: 'pointer',
minHeight: 200,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'&:hover': {
borderColor: theme.palette.primary.main,
},
buttonWrapper: {
top: '0',
left: '0',
position: 'absolute',
display: 'flex',
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
},
button: {
marginLeft: theme.spacing(0.5),
marginRight: theme.spacing(0.5),
zIndex: 2,
},
}),
)
},
buttonWrapper: {
display: 'flex',
width: '100%',
justifyContent: 'center',
alignItems: 'center',
gap: theme.spacing(1),
},
button: {
zIndex: 2,
},
}))
export function UploadArea({ uploadOrigin, showHelp }: Props): ReactElement {
const { setFiles, setUploadOrigin } = useContext(Context)
const { nodeInfo } = useContext(BeeContext)
const classes = useStyles()
const { classes } = useStyles()
const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar()
const [strictWebsiteMode, setStrictWebsiteMode] = useState(false)
const [version, setVersion] = useState(0)
const inputRef = useRef<HTMLInputElement>(null)
const getDropzoneInputDomElement = () => document.querySelector('.MuiDropzoneArea-root input') as HTMLInputElement
const onDrop = (acceptedFiles: File[]) => {
handleChange(acceptedFiles)
}
const { getRootProps, getInputProps } = useDropzone({
onDrop,
maxSize: MAX_FILE_SIZE,
noClick: true,
})
const onUploadCollectionClick = () => {
const element = getDropzoneInputDomElement()
if (element) {
element.setAttribute('directory', '')
element.setAttribute('webkitdirectory', '')
element.setAttribute('mozdirectory', '')
element.click()
if (inputRef.current) {
inputRef.current.setAttribute('directory', '')
inputRef.current.setAttribute('webkitdirectory', '')
inputRef.current.setAttribute('mozdirectory', '')
inputRef.current.click()
}
}
@@ -84,23 +92,14 @@ export function UploadArea({ uploadOrigin, showHelp }: Props): ReactElement {
}
const onUploadFileClick = () => {
const element = getDropzoneInputDomElement()
if (element) {
element.removeAttribute('directory')
element.removeAttribute('webkitdirectory')
element.removeAttribute('mozdirectory')
element.click()
if (inputRef.current) {
inputRef.current.removeAttribute('directory')
inputRef.current.removeAttribute('webkitdirectory')
inputRef.current.removeAttribute('mozdirectory')
inputRef.current.click()
}
}
const resetComponentOnAddingInvalidContent = () => {
setTimeout(() => {
setVersion(x => x + 1)
setFiles([])
}, 0)
}
const handleChange = (files?: File[]) => {
if (files) {
const FilePaths = files as FilePath[]
@@ -110,7 +109,8 @@ export function UploadArea({ uploadOrigin, showHelp }: Props): ReactElement {
enqueueSnackbar('To upload a website, there must be an index.html or index.htm in the root of the folder.', {
variant: 'error',
})
resetComponentOnAddingInvalidContent()
setFiles([])
setStrictWebsiteMode(false)
return
}
@@ -129,27 +129,22 @@ export function UploadArea({ uploadOrigin, showHelp }: Props): ReactElement {
return (
<>
{isUploadEnabled && (
<div className={classes.areaWrapper}>
<DropzoneArea
key={version}
dropzoneClass={classes.dropzone}
onChange={handleChange}
filesLimit={1e9}
maxFileSize={MAX_FILE_SIZE}
showPreviews={false}
/>
<div className={classes.buttonWrapper}>
<SwarmButton className={classes.button} onClick={onUploadFileClick} iconType={FilePlus}>
Add File
</SwarmButton>
<SwarmButton className={classes.button} onClick={onUploadFolderClick} iconType={FolderPlus}>
Add Folder
</SwarmButton>
<SwarmButton className={classes.button} onClick={onUploadWebsiteClick} iconType={PlusCircle}>
Add Website
</SwarmButton>
</div>
</div>
<Box className={classes.areaWrapper}>
<Box {...getRootProps()} className={classes.dropzone}>
<input {...getInputProps()} ref={inputRef} />
<Box className={classes.buttonWrapper}>
<SwarmButton className={classes.button} onClick={onUploadFileClick} iconType={FilePlus}>
Add File
</SwarmButton>
<SwarmButton className={classes.button} onClick={onUploadFolderClick} iconType={FolderPlus}>
Add Folder
</SwarmButton>
<SwarmButton className={classes.button} onClick={onUploadWebsiteClick} iconType={PlusCircle}>
Add Website
</SwarmButton>
</Box>
</Box>
</Box>
)}
{isUploadEnabled && showHelp && (
<DocumentationText>
+6 -4
View File
@@ -1,10 +1,12 @@
import { ReactElement, useContext } from 'react'
import { History } from '../../components/History'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { defaultUploadOrigin } from '../../providers/File'
import { HISTORY_KEYS } from '../../utils/local-storage'
import { FileNavigation } from './FileNavigation'
import { LocalStorageKeys } from '../../utils/localStorage'
import { FileNavigation, FileOrigin } from './FileNavigation'
import { UploadArea } from './UploadArea'
export function UploadLander(): ReactElement {
@@ -14,9 +16,9 @@ export function UploadLander(): ReactElement {
return (
<>
<FileNavigation active="UPLOAD" />
<FileNavigation active={FileOrigin.Upload} />
<UploadArea showHelp={true} uploadOrigin={defaultUploadOrigin} />
<History title="Upload History" localStorageKey={HISTORY_KEYS.UPLOAD_HISTORY} />
<History title="Upload History" localStorageKey={LocalStorageKeys.uploadHistory} />
</>
)
}
@@ -1,11 +1,11 @@
import { BZZ, DAI } from '@ethersphere/bee-js'
import { Box, Tooltip, Typography } from '@material-ui/core'
import { Wallet } from 'ethers'
import { Box, Tooltip, Typography } from '@mui/material'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router'
import Check from 'remixicon-react/CheckLineIcon'
import X from 'remixicon-react/CloseLineIcon'
import ExpandableListItem from '../../components/ExpandableListItem'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
@@ -16,6 +16,7 @@ import { Context as SettingsContext } from '../../providers/Settings'
import { Context as TopUpContext } from '../../providers/TopUp'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { createGiftWallet } from '../../utils/desktop'
import { generateWallet } from '../../utils/identity'
import { ResolvedWallet } from '../../utils/wallet'
const GIFT_WALLET_FUND_DAI_AMOUNT = DAI.fromDecimalString('0.1')
@@ -51,12 +52,13 @@ export default function Index(): ReactElement {
enqueueSnackbar('Sending funds to gift wallet...')
setLoading(true)
try {
const wallet = Wallet.createRandom()
const wallet = generateWallet()
addGiftWallet(wallet)
await createGiftWallet(desktopUrl, wallet.address)
enqueueSnackbar('Succesfully funded gift wallet', { variant: 'success' })
} catch (error) {
console.error(error) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(error)
enqueueSnackbar(`Failed to fund gift wallet: ${error}`, { variant: 'error' })
} finally {
setLoading(false)
+4 -3
View File
@@ -2,8 +2,9 @@ import { BZZ } from '@ethersphere/bee-js'
import { useContext } from 'react'
import { useNavigate } from 'react-router'
import ExchangeFunds from 'remixicon-react/ExchangeFundsLineIcon'
import Card from '../../components/Card'
import { Context as BeeContext } from '../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { ROUTES } from '../../routes'
export function ChequebookInfoCard() {
@@ -21,7 +22,7 @@ export function ChequebookInfoCard() {
icon={<ExchangeFunds />}
title={`${chequebookBalance?.availableBalance.toSignificantDigits(4)} xBZZ`}
subtitle="Network transfer balance."
status="ok"
status={CheckState.OK}
/>
)
}
@@ -40,7 +41,7 @@ export function ChequebookInfoCard() {
: 'No available balance.'
}
subtitle="Chequebook not setup."
status="error"
status={CheckState.ERROR}
/>
)
}
+6 -6
View File
@@ -1,9 +1,9 @@
import { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router'
import Globe from 'remixicon-react/GlobalLineIcon'
import Search from 'remixicon-react/SearchLineIcon'
import Settings from 'remixicon-react/Settings2LineIcon'
import { useNavigate } from 'react-router'
import Card from '../../components/Card'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { ROUTES } from '../../routes'
@@ -19,7 +19,7 @@ export default function NodeInfoCard(): ReactElement {
icon={<Globe />}
title="Connecting..."
subtitle="Attempting to establish connection to your Bee node."
status="connecting"
status={CheckState.CONNECTING}
/>
)
}
@@ -31,7 +31,7 @@ export default function NodeInfoCard(): ReactElement {
icon={<Globe />}
title="Starting up..."
subtitle="Your Bee node is currently launching."
status="loading"
status={CheckState.STARTING}
/>
)
}
@@ -43,7 +43,7 @@ export default function NodeInfoCard(): ReactElement {
icon={<Globe />}
title="Your node is not connected…"
subtitle="You are not connected to Swarm."
status="error"
status={CheckState.ERROR}
/>
)
}
@@ -55,7 +55,7 @@ export default function NodeInfoCard(): ReactElement {
icon={<Globe />}
title="Your node is running…"
subtitle="Connection to Swarm might not be optimal."
status="error"
status={CheckState.WARNING}
/>
)
}
@@ -66,7 +66,7 @@ export default function NodeInfoCard(): ReactElement {
icon={<Globe />}
title="Your node is connected."
subtitle="You are connected to Swarm."
status="ok"
status={CheckState.OK}
/>
)
}
+4 -3
View File
@@ -2,8 +2,9 @@ import { useContext } from 'react'
import { useNavigate } from 'react-router'
import Upload from 'remixicon-react/UploadLineIcon'
import Wallet from 'remixicon-react/Wallet3LineIcon'
import Card from '../../components/Card'
import { Context as BeeContext } from '../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { ROUTES } from '../../routes'
export function WalletInfoCard() {
@@ -29,7 +30,7 @@ export function WalletInfoCard() {
icon={<Wallet />}
title={balanceText}
subtitle="Current wallet balance."
status="ok"
status={CheckState.OK}
/>
)
}
@@ -44,7 +45,7 @@ export function WalletInfoCard() {
icon={<Upload />}
title="Your wallet is not setup."
subtitle="To share content on Swarm, please setup your wallet."
status="error"
status={CheckState.ERROR}
/>
)
}
+3 -1
View File
@@ -1,5 +1,6 @@
import { Button } from '@material-ui/core'
import { Button } from '@mui/material'
import { ReactElement, useContext } from 'react'
import { ChainSync } from '../../components/ChainSync'
import ExpandableListItem from '../../components/ExpandableListItem'
import Map from '../../components/Map'
@@ -8,6 +9,7 @@ import { useBeeDesktop, useNewBeeDesktopVersion } from '../../hooks/apiHooks'
import { Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { chainIdToName } from '../../utils/chain'
import { ChequebookInfoCard } from './ChequebookInfoCard'
import NodeInfoCard from './NodeInfoCard'
import { WalletInfoCard } from './WalletInfoCard'
@@ -1,7 +1,8 @@
import { Typography } from '@mui/material'
import { ReactElement } from 'react'
import { HistoryHeader } from '../../components/HistoryHeader'
import { Typography } from '@material-ui/core'
import { Link } from 'react-router-dom'
import { HistoryHeader } from '../../components/HistoryHeader'
import { ROUTES } from '../../routes'
export default function PageNotFound(): ReactElement {
+7 -3
View File
@@ -1,13 +1,16 @@
import { Box, Grid, Typography } from '@material-ui/core'
import { BeeModes } from '@ethersphere/bee-js'
import { Box, Grid, Typography } from '@mui/material'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect } from 'react'
import { useNavigate } from 'react-router'
import { ChainSync } from '../../components/ChainSync'
import { Waiting } from '../../components/Waiting'
import { Context } from '../../providers/Settings'
import { ROUTES } from '../../routes'
const LIGHTMODE_START_INTERVAL_MS = 3_000
export default function LightModeRestart(): ReactElement {
const navigate = useNavigate()
const { enqueueSnackbar } = useSnackbar()
@@ -27,8 +30,9 @@ export default function LightModeRestart(): ReactElement {
navigate(ROUTES.INFO)
}
})
.catch(console.error) // eslint-disable-line
}, 3_000)
// eslint-disable-next-line no-console
.catch(console.error)
}, LIGHTMODE_START_INTERVAL_MS)
return () => clearInterval(interval)
}, [beeApi, enqueueSnackbar, navigate])
+4 -2
View File
@@ -1,6 +1,7 @@
import CircularProgress from '@material-ui/core/CircularProgress'
import CircularProgress from '@mui/material/CircularProgress'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext } from 'react'
import ExpandableList from '../../components/ExpandableList'
import ExpandableListItemInput from '../../components/ExpandableListItemInput'
import { Context as BeeContext } from '../../providers/Bee'
@@ -44,7 +45,8 @@ export default function SettingsPage(): ReactElement {
await refresh()
} catch (e) {
console.error(e) //eslint-disable-line
// eslint-disable-next-line no-console
console.error(e)
enqueueSnackbar(`Failed to change RPC endpoint. ${e}`, { variant: 'error' })
}
}
@@ -1,7 +1,9 @@
import { ReactElement } from 'react'
import { useNavigate } from 'react-router'
import { HistoryHeader } from '../../components/HistoryHeader'
import { ROUTES } from '../../routes'
import { PostageStampAdvancedCreation } from './PostageStampAdvancedCreation'
export function CreatePostageStampPage(): ReactElement {
@@ -1,7 +1,9 @@
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 {
+2 -1
View File
@@ -1,5 +1,6 @@
import { Box, Grid, Typography } from '@material-ui/core'
import { Box, Grid, Typography } from '@mui/material'
import { ReactElement } from 'react'
import { Capacity } from '../../components/Capacity'
import { EnrichedPostageBatch } from '../../providers/Stamps'
@@ -1,11 +1,13 @@
import { Box, Grid, IconButton, Typography, createStyles, makeStyles } from '@material-ui/core'
import { PostageBatchOptions, Utils } from '@ethersphere/bee-js'
import { Box, Grid, IconButton, Typography } from '@mui/material'
import BigNumber from 'bignumber.js'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { Link } from 'react-router-dom'
import Check from 'remixicon-react/CheckLineIcon'
import Info from 'remixicon-react/InformationLineIcon'
import { makeStyles } from 'tss-react/mui'
import { SwarmButton } from '../../components/SwarmButton'
import { SwarmSelect } from '../../components/SwarmSelect'
import { SwarmTextInput } from '../../components/SwarmTextInput'
@@ -20,33 +22,31 @@ interface Props {
onFinished: () => void
}
const useStyles = makeStyles(() =>
createStyles({
link: {
color: '#dd7700',
textDecoration: 'underline',
'&:hover': {
textDecoration: 'none',
const useStyles = makeStyles()(() => ({
link: {
color: '#dd7700',
textDecoration: 'underline',
'&:hover': {
textDecoration: 'none',
// https://github.com/mui-org/material-ui/issues/22543
'@media (hover: none)': {
textDecoration: 'none',
},
// https://github.com/mui-org/material-ui/issues/22543
'@media (hover: none)': {
textDecoration: 'none',
},
},
stampVolumeWrapper: {
width: 'fit-content',
'& button': {
marginLeft: 4,
width: 24,
padding: 2,
},
},
stampVolumeWrapper: {
width: 'fit-content',
'& button': {
marginLeft: 4,
width: 24,
padding: 2,
},
}),
)
},
}))
export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElement {
const classes = useStyles()
const { classes } = useStyles()
const { chainState } = useContext(BeeContext)
const { refresh } = useContext(StampsContext)
const { beeApi } = useContext(SettingsContext)
@@ -115,7 +115,8 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
await refresh()
onFinished()
} catch (e) {
console.error(e) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(e)
enqueueSnackbar(`Error: ${(e as Error).message}`, { variant: 'error' })
}
setSubmitting(false)
@@ -180,7 +181,7 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
const effectiveVolume = getHumanReadableFileSize(Utils.getStampEffectiveBytes(depth))
return (
<Grid item container alignItems="center" className={classes.stampVolumeWrapper}>
<Grid container alignItems="center" className={classes.stampVolumeWrapper}>
<Typography>
Theoretical: ~{theoreticalMaximumVolume} / Effective: ~{effectiveVolume}
</Typography>
@@ -241,7 +242,7 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
<Box mb={2}>
<SwarmSelect
label="Immutable"
defaultValue="No"
value="No"
onChange={event => setImmutable(event.target.value === 'Yes')}
options={[
{ value: 'Yes', label: 'Yes' },
@@ -277,7 +278,7 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
</Box>
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Grid>
<SwarmButton
disabled={submitting || Boolean(depthError) || Boolean(amountError) || !depthInput || !amountInput}
onClick={submit}
@@ -287,7 +288,7 @@ export function PostageStampAdvancedCreation({ onFinished }: Props): ReactElemen
Buy New Stamp
</SwarmButton>
</Grid>
<Grid item>
<Grid>
<Link to={ROUTES.ACCOUNT_STAMPS_NEW_STANDARD} className={classes.link}>
Standard mode
</Link>
+7 -2
View File
@@ -1,4 +1,5 @@
import { ReactElement, useContext } from 'react'
import { ReactElement, useContext, useState } from 'react'
import { SwarmSelect } from '../../components/SwarmSelect'
import { Context, EnrichedPostageBatch } from '../../providers/Stamps'
@@ -9,11 +10,15 @@ interface Props {
export function PostageStampSelector({ onSelect, defaultValue }: Props): ReactElement {
const { stamps } = useContext(Context)
const [selected, setSelected] = useState<string>(defaultValue ?? '')
function onChange(stampId: string) {
setSelected(stampId)
if (!stamps) {
return
}
const stamp = stamps.find(x => x.batchID.toHex() === stampId)
if (stamp) {
@@ -28,7 +33,7 @@ export function PostageStampSelector({ onSelect, defaultValue }: Props): ReactEl
value: x.batchID.toHex(),
}))}
onChange={event => onChange(event.target.value as string)}
defaultValue={defaultValue}
value={selected}
placeholder="Please select a postage stamp..."
/>
)
@@ -1,10 +1,11 @@
import { Box, Button, Grid, Slider, Typography } from '@material-ui/core'
import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'
import { Duration, PostageBatchOptions, Size, Utils } from '@ethersphere/bee-js'
import { Box, Button, Grid, Slider, Typography } from '@mui/material'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { Link } from 'react-router-dom'
import Check from 'remixicon-react/CheckLineIcon'
import { makeStyles } from 'tss-react/mui'
import { SwarmButton } from '../../components/SwarmButton'
import { SwarmTextInput } from '../../components/SwarmTextInput'
import { Context as SettingsContext } from '../../providers/Settings'
@@ -15,26 +16,24 @@ import { secondsToTimeString } from '../../utils'
interface Props {
onFinished: () => void
}
const useStyles = makeStyles((theme: Theme) =>
createStyles({
link: {
color: '#dd7700',
textDecoration: 'underline',
'&:hover': {
textDecoration: 'none',
const useStyles = makeStyles()(theme => ({
link: {
color: '#dd7700',
textDecoration: 'underline',
'&:hover': {
textDecoration: 'none',
// https://github.com/mui-org/material-ui/issues/22543
'@media (hover: none)': {
textDecoration: 'none',
},
// https://github.com/mui-org/material-ui/issues/22543
'@media (hover: none)': {
textDecoration: 'none',
},
},
buttonSelected: {
color: 'white',
backgroundColor: theme.palette.primary.main,
},
}),
)
},
buttonSelected: {
color: 'white',
backgroundColor: theme.palette.primary.main,
},
}))
const marks = [
{ value: 1, label: '1 day' },
@@ -42,7 +41,7 @@ const marks = [
]
export function PostageStampStandardCreation({ onFinished }: Props): ReactElement {
const classes = useStyles()
const { classes } = useStyles()
const { refresh } = useContext(StampsContext)
const { beeApi } = useContext(SettingsContext)
@@ -100,7 +99,8 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
await refresh()
onFinished()
} catch (e) {
console.error(e) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(e)
enqueueSnackbar(`Error: ${(e as Error).message}`, { variant: 'error' })
}
setSubmitting(false)
@@ -139,7 +139,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
</Box>
<Box mb={2}>
<Grid container justifyContent="space-between" spacing={2}>
<Grid item xs={4}>
<Grid sx={{ width: { xs: '100%', sm: '33.333%' } }}>
<Button
variant="contained"
fullWidth
@@ -149,7 +149,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
4 GB
</Button>
</Grid>
<Grid item xs={4}>
<Grid sx={{ width: { xs: '100%', sm: '33.333%' } }}>
<Button
variant="contained"
fullWidth
@@ -159,7 +159,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
32 GB
</Button>
</Grid>
<Grid item xs={4}>
<Grid sx={{ width: { xs: '100%', sm: '33.333%' } }}>
<Button
variant="contained"
fullWidth
@@ -206,7 +206,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
</Grid>
</Box>
<Grid container justifyContent="space-between" alignItems="center">
<Grid item>
<Grid>
<SwarmButton
disabled={submitting || !depthInput || !amountInput}
onClick={submit}
@@ -216,7 +216,7 @@ export function PostageStampStandardCreation({ onFinished }: Props): ReactElemen
Buy New Stamp
</SwarmButton>
</Grid>
<Grid item>
<Grid>
<Link to={ROUTES.ACCOUNT_STAMPS_NEW_ADVANCED} className={classes.link}>
Advanced mode
</Link>
+23 -24
View File
@@ -1,10 +1,11 @@
import { createStyles, makeStyles, Theme } from '@material-ui/core'
import Button from '@material-ui/core/Button'
import Dialog from '@material-ui/core/Dialog'
import DialogContent from '@material-ui/core/DialogContent'
import DialogTitle from '@material-ui/core/DialogTitle'
import { Check, Clear } from '@material-ui/icons'
import { Check, Clear } from '@mui/icons-material'
import Button from '@mui/material/Button'
import Dialog from '@mui/material/Dialog'
import DialogContent from '@mui/material/DialogContent'
import DialogTitle from '@mui/material/DialogTitle'
import { ReactElement, useState } from 'react'
import { makeStyles } from 'tss-react/mui'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { SwarmSelect } from '../../components/SwarmSelect'
import { EnrichedPostageBatch } from '../../providers/Stamps'
@@ -15,28 +16,26 @@ interface Props {
onClose: () => void
}
const useStyles = makeStyles((theme: Theme) =>
createStyles({
dialog: {
background: theme.palette.background.default,
borderRadius: 0,
width: '100%',
maxWidth: '890px',
},
title: {
color: '#606060',
textAlign: 'center',
},
hint: {
marginBottom: '16px',
},
}),
)
const useStyles = makeStyles()(theme => ({
dialog: {
background: theme.palette.background.default,
borderRadius: 0,
width: '100%',
maxWidth: '890px',
},
title: {
color: '#606060',
textAlign: 'center',
},
hint: {
marginBottom: '16px',
},
}))
export function SelectPostageStampModal({ stamps, onSelect, onClose }: Props): ReactElement {
const [selectedStamp, setSelectedStamp] = useState<EnrichedPostageBatch | null>(null)
const classes = useStyles()
const { classes } = useStyles()
function onChange(stampId: string) {
const stamp = stamps.find(x => x.batchID.toHex() === stampId)
+2
View File
@@ -1,6 +1,7 @@
import { ReactElement, useContext } from 'react'
import TimerFlashFill from 'remixicon-react/TimerFlashFillIcon'
import TimerFlashLine from 'remixicon-react/TimerFlashLineIcon'
import ExpandableElement from '../../components/ExpandableElement'
import ExpandableList from '../../components/ExpandableList'
import ExpandableListItem from '../../components/ExpandableListItem'
@@ -11,6 +12,7 @@ import { Context } from '../../providers/Settings'
import { EnrichedPostageBatch } from '../../providers/Stamps'
import { secondsToTimeString } from '../../utils'
import { getHumanReadableFileSize } from '../../utils/file'
import { PostageStamp } from './PostageStamp'
interface Props {
+19 -19
View File
@@ -1,33 +1,33 @@
import { CircularProgress, Container } from '@material-ui/core'
import { createStyles, makeStyles } from '@material-ui/core/styles'
import { CircularProgress, Container } from '@mui/material'
import { ReactElement, useContext, useEffect } from 'react'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import { useNavigate } from 'react-router'
import PlusSquare from 'remixicon-react/AddBoxLineIcon'
import { makeStyles } from 'tss-react/mui'
import { SwarmButton } from '../../components/SwarmButton'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { Context as StampsContext } from '../../providers/Stamps'
import { ROUTES } from '../../routes'
import StampsTable from './StampsTable'
const useStyles = makeStyles(() =>
createStyles({
root: {
width: '100%',
display: 'grid',
},
actions: {
display: 'flex',
width: '100%',
flex: '0 1 auto',
flexWrap: 'wrap',
alignItems: 'center',
},
}),
)
const useStyles = makeStyles()(() => ({
root: {
width: '100%',
display: 'grid',
},
actions: {
display: 'flex',
width: '100%',
flex: '0 1 auto',
flexWrap: 'wrap',
alignItems: 'center',
},
}))
export default function Stamp(): ReactElement {
const classes = useStyles()
const { classes } = useStyles()
const navigate = useNavigate()
@@ -1,5 +1,6 @@
import type { ReactElement, ReactNode } from 'react'
import { useContext } from 'react'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItemActions from '../../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
@@ -1,13 +1,13 @@
import { ReactElement, useContext } from 'react'
import CodeBlockTabs from '../../../components/CodeBlockTabs'
import { Context as SettingsContext } from '../../../providers/Settings'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItem from '../../../components/ExpandableListItem'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import ExpandableListItemInput from '../../../components/ExpandableListItemInput'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
import { CheckState, Context } from '../../../providers/Bee'
import { Context as SettingsContext } from '../../../providers/Settings'
export default function NodeConnectionCheck(): ReactElement | null {
const { setApiUrl, apiUrl, isDesktop } = useContext(SettingsContext)
@@ -1,4 +1,5 @@
import { ReactElement, ReactNode, useContext } from 'react'
import ExpandableList from '../../../components/ExpandableList'
import ExpandableListItemNote from '../../../components/ExpandableListItemNote'
import StatusIcon from '../../../components/StatusIcon'
+2
View File
@@ -1,5 +1,7 @@
import { ReactElement, useContext } from 'react'
import { Context } from '../../providers/Settings'
import ChequebookDeployFund from './SetupSteps/ChequebookDeployFund'
import DesktopConnection from './SetupSteps/DesktopConnectionCheck'
import NodeConnectionCheck from './SetupSteps/NodeConnectionCheck'
@@ -1,8 +1,9 @@
import { DAI } from '@ethersphere/bee-js'
import { Box, Grid, Typography } from '@material-ui/core'
import { Box, Grid, Typography } from '@mui/material'
import { ReactElement, useContext } from 'react'
import { useNavigate } from 'react-router'
import Check from 'remixicon-react/CheckLineIcon'
import ExpandableListItem from '../../components/ExpandableListItem'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import { HistoryHeader } from '../../components/HistoryHeader'
@@ -11,6 +12,7 @@ import { SwarmButton } from '../../components/SwarmButton'
import { SwarmDivider } from '../../components/SwarmDivider'
import { Context } from '../../providers/Bee'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { TopUpProgressIndicator } from './TopUpProgressIndicator'
const MINIMUM_XDAI = DAI.fromDecimalString('0.5')
@@ -1,8 +1,10 @@
import { Typography } from '@material-ui/core'
import { Typography } from '@mui/material'
import { ReactElement } from 'react'
import Balance from './Balance'
import { ROUTES } from '../../routes'
import Balance from './Balance'
export function BankCardTopUpIndex(): ReactElement {
return (
<Balance
@@ -1,8 +1,10 @@
import { Typography } from '@material-ui/core'
import { Typography } from '@mui/material'
import { ReactElement } from 'react'
import Balance from './Balance'
import { ROUTES } from '../../routes'
import Balance from './Balance'
export function CryptoTopUpIndex(): ReactElement {
return (
<Balance
@@ -1,10 +1,11 @@
import { BeeModes } from '@ethersphere/bee-js'
import { Box, Typography } from '@material-ui/core'
import { Box, Typography } from '@mui/material'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router'
import ArrowDown from 'remixicon-react/ArrowDownLineIcon'
import Check from 'remixicon-react/CheckLineIcon'
import ExpandableListItem from '../../components/ExpandableListItem'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
import { HistoryHeader } from '../../components/HistoryHeader'
@@ -54,7 +55,8 @@ export function GiftCardFund(): ReactElement {
await restartBeeNode(desktopUrl)
navigate(ROUTES.RESTART_LIGHT)
} catch (error) {
console.error(error) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(error)
enqueueSnackbar(`Failed to upgrade: ${error}`, { variant: 'error' })
}
}
@@ -72,7 +74,8 @@ export function GiftCardFund(): ReactElement {
if (canUpgradeToLightNode) await restart()
} catch (error) {
console.error(error) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(error)
enqueueSnackbar(`Failed to fund: ${error}`, { variant: 'error' })
} finally {
setLoading(false)
@@ -1,10 +1,11 @@
import { Box, Typography } from '@material-ui/core'
import { BZZ, DAI } from '@ethersphere/bee-js'
import { Box, Typography } from '@mui/material'
import { Wallet } from 'ethers'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { useNavigate } from 'react-router'
import ArrowRight from 'remixicon-react/ArrowRightLineIcon'
import { HistoryHeader } from '../../components/HistoryHeader'
import { ProgressIndicator } from '../../components/ProgressIndicator'
import { SwarmButton } from '../../components/SwarmButton'
@@ -12,7 +13,7 @@ import { SwarmDivider } from '../../components/SwarmDivider'
import { SwarmTextInput } from '../../components/SwarmTextInput'
import { Context as SettingsContext } from '../../providers/Settings'
import { ROUTES } from '../../routes'
import { Rpc } from '../../utils/rpc'
import { RPC } from '../../utils/rpc'
export function GiftCardTopUpIndex(): ReactElement {
const { rpcProvider } = useContext(SettingsContext)
@@ -28,8 +29,8 @@ export function GiftCardTopUpIndex(): ReactElement {
setLoading(true)
try {
const wallet = new Wallet(giftCode, rpcProvider)
const dai = await Rpc._eth_getBalance(wallet.address, rpcProvider)
const bzz = await Rpc._eth_getBalanceERC20(wallet.address, rpcProvider)
const dai = await RPC._eth_getBalance(wallet.address, rpcProvider)
const bzz = await RPC._eth_getBalanceERC20(wallet.address, rpcProvider)
if (dai.lt(DAI.fromDecimalString('0.001')) || bzz.lt(BZZ.fromDecimalString('0.001'))) {
throw Error('Gift wallet does not have enough funds')
@@ -37,7 +38,8 @@ export function GiftCardTopUpIndex(): ReactElement {
enqueueSnackbar('Successfully verified gift wallet', { variant: 'success' })
navigate(ROUTES.TOP_UP_GIFT_CODE_FUND.replace(':privateKeyString', giftCode))
} catch (error) {
console.error(error) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(error)
enqueueSnackbar(`Gift wallet could not be verified: ${error}`, { variant: 'error' })
} finally {
setLoading(false)
@@ -1,10 +1,11 @@
import { BeeModes, BZZ, DAI } from '@ethersphere/bee-js'
import { Box, Typography } from '@material-ui/core'
import { Box, Typography } from '@mui/material'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useEffect, useState } from 'react'
import { useNavigate } from 'react-router'
import ArrowDown from 'remixicon-react/ArrowDownCircleLineIcon'
import Check from 'remixicon-react/CheckLineIcon'
import ExpandableListItem from '../../components/ExpandableListItem'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
@@ -18,7 +19,6 @@ import { Context as SettingsContext } from '../../providers/Settings'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { ROUTES } from '../../routes'
import { sleepMs } from '../../utils'
import { isSwapError, SwapError, wrapWithSwapError } from '../../utils/SwapError'
import {
getBzzPriceAsDai,
getDesktopConfiguration,
@@ -26,7 +26,10 @@ import {
restartBeeNode,
upgradeToLightNode,
} from '../../utils/desktop'
import { Rpc } from '../../utils/rpc'
import { isSwapError, SwapError, wrapWithSwapError } from '../../utils/errors'
import { LocalStorageKeys } from '../../utils/localStorage'
import { RPC } from '../../utils/rpc'
import { TopUpProgressIndicator } from './TopUpProgressIndicator'
const MINIMUM_XDAI = DAI.fromDecimalString('0.1')
@@ -126,7 +129,8 @@ export function Swap({ header }: Props): ReactElement {
navigate(ROUTES.RESTART_LIGHT)
} catch (error) {
console.error(error) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(error)
enqueueSnackbar(`Failed to upgrade: ${error}`, { variant: 'error' })
}
}
@@ -142,7 +146,7 @@ export function Swap({ header }: Props): ReactElement {
}
async function performSwapWithChecks(daiToSwap: DAI) {
if (!localStorage.getItem('apiKey')) {
if (!localStorage.getItem(LocalStorageKeys.apiKey)) {
throw new SwapError('API key is not set, reopen dashboard through Swarm Desktop')
}
@@ -162,7 +166,7 @@ export function Swap({ header }: Props): ReactElement {
throw new SwapError('Blockchain RPC endpoint is not configured in Swarm Desktop')
}
await wrapWithSwapError(
Rpc.getNetworkChainId(desktopConfiguration['blockchain-rpc-endpoint']),
RPC.getNetworkChainId(desktopConfiguration['blockchain-rpc-endpoint']),
`Blockchain RPC endpoint not reachable at ${desktopConfiguration['blockchain-rpc-endpoint']}`,
)
await wrapWithSwapError(sendSwapRequest(daiToSwap), GENERIC_SWAP_FAILED_ERROR_MESSAGE)
@@ -191,12 +195,14 @@ export function Swap({ header }: Props): ReactElement {
enqueueSnackbar(error.snackbarMessage, { variant: 'error' })
if (error.originalError) {
console.error(error.originalError) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(error.originalError)
}
} else {
// we have an unexpected error
enqueueSnackbar(`${GENERIC_SWAP_FAILED_ERROR_MESSAGE} ${error}`, { variant: 'error' })
console.error(error) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(error)
}
} finally {
setLoading(false)
@@ -1,4 +1,5 @@
import { ReactElement } from 'react'
import { ProgressIndicator } from '../../components/ProgressIndicator'
interface Props {
@@ -1,5 +1,5 @@
import { BeeModes, BZZ, DAI } from '@ethersphere/bee-js'
import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core'
import { Box, Grid, Typography } from '@mui/material'
import { useSnackbar } from 'notistack'
import { ReactElement, useContext, useState } from 'react'
import { useNavigate } from 'react-router'
@@ -8,37 +8,37 @@ import Check from 'remixicon-react/CheckLineIcon'
import Download from 'remixicon-react/DownloadLineIcon'
import Gift from 'remixicon-react/GiftLineIcon'
import MoneyDollarCircle from 'remixicon-react/MoneyDollarCircleLineIcon'
import { makeStyles } from 'tss-react/mui'
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
import { HistoryHeader } from '../../components/HistoryHeader'
import { Loading } from '../../components/Loading'
import { SwarmButton } from '../../components/SwarmButton'
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
import { Context as BeeContext, CheckState } from '../../providers/Bee'
import { CheckState, Context as BeeContext } from '../../providers/Bee'
import { Context as SettingsContext } from '../../providers/Settings'
import { Context as BalanceProvider } from '../../providers/WalletBalance'
import { ROUTES } from '../../routes'
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
const useStyles = makeStyles(() =>
createStyles({
checkWrapper: {
background: 'rgba(0, 230, 118, 0.25)',
borderRadius: 99999,
width: '180px',
height: '180px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
}),
)
const useStyles = makeStyles()(() => ({
checkWrapper: {
background: 'rgba(0, 230, 118, 0.25)',
borderRadius: 99999,
width: '180px',
height: '180px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
}))
const MINIMUM_XDAI = DAI.fromDecimalString('0.05')
const MINIMUM_XBZZ = BZZ.fromDecimalString('0.1')
export default function TopUp(): ReactElement {
const navigate = useNavigate()
const styles = useStyles()
const { classes } = useStyles()
const { isDesktop, desktopUrl } = useContext(SettingsContext)
const { nodeInfo, status } = useContext(BeeContext)
const { balance } = useContext(BalanceProvider)
@@ -59,7 +59,8 @@ export default function TopUp(): ReactElement {
await restartBeeNode(desktopUrl)
navigate(ROUTES.RESTART_LIGHT)
} catch (error) {
console.error(error) // eslint-disable-line
// eslint-disable-next-line no-console
console.error(error)
enqueueSnackbar(`Failed to upgrade: ${error}`, { variant: 'error' })
}
setLoading(false)
@@ -76,7 +77,7 @@ export default function TopUp(): ReactElement {
<HistoryHeader>Account</HistoryHeader>
<Grid container direction="column" alignItems="center">
<Box mb={6}>
<div className={styles.checkWrapper}>
<div className={classes.checkWrapper}>
<Download size={100} color="#ededed" />
</div>
</Box>