feat: initial Proof of Concept UI (#1)

* initial dashboard layout

* add node status card

* add accounting section, pull peer data

* add file functionality with bee-js,  first iteration of accounts page

* Add balances and chequebook table

* add blockie / identicon for addresses

* add basic settlements table

* implement theme overrides

* cleanup logging

* Add troubleshooting block

* add initial dark theme support, add copy to clipboard, QR code support

* show active element on sidebar

* remove duplicate status page and make status page index

* Update package.json

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>

* Update public/index.html

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>

* Update src/pages/accounting/AccountCard.tsx

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>

* change bee api client to use beeJS library

* add initial setup workflow

* breakout ethereum address component, define initial setup workflow

* add types to responses, add additional node troubleshooting info to workflow

* make setup steps nonlinear and interactive

* make host endpoint dynamic on setup

* split out api calls into custom hooks, add component loading indicators

* add depost / withdrawl functionality, show transactions in BZZ

* add multiOS code support troubleshooting, check for balance in chequebook on setup

* add ability to change apis in settings page

* show file loading status

* Style active sidebar item

* reload on theme change

* modify troubleshooting verbage, add cashout functionality and details,

* facilitate file upload with beeJS

* update readme to show UI samples

* remove nnPeersWatermark from peers page

* split node steps into separate components, make status page visible at anytime

* minor UI/UX enhancements

* format accounting page

* remove WIP wallet connection code

* Update src/components/CashoutModal.tsx

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>

* use bigint for deposits/withdrawls

* revise status card

* clean up unused imports and variables

* add api status to sidebar

* obfuscate pages with troubleshooting component when apis not connected

* add localhost OS detection for troubleshooting code

* cleanup extra logos

* monospace BZZ in tables

* hide troubleshooting page while loading API status

* Remove ability to remove peers

* add null types to API responses

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>
This commit is contained in:
matmertz25
2021-03-12 12:01:56 -05:00
committed by GitHub
parent f4ce271479
commit 34d2dfda5a
60 changed files with 6155 additions and 364 deletions
+89
View File
@@ -0,0 +1,89 @@
import React from 'react';
import Button from '@material-ui/core/Button';
import Input from '@material-ui/core/Input';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import { Snackbar } from '@material-ui/core';
import { beeDebugApi } from '../services/bee';
export default function DepositModal() {
const [open, setOpen] = React.useState(false);
const [peerId, setPeerId] = React.useState('');
const [showToast, setToastVisibility] = React.useState(false);
const [toastContent, setToastContent] = React.useState('');
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleCashout = () => {
if (peerId) {
beeDebugApi.chequebook.peerCashout(peerId)
.then(res => {
setOpen(false);
handleToast(`Successfully cashed out cheque. Transaction ${res.data.transactionHash}`)
})
.catch(error => {
handleToast('Error with cashout')
})
} else {
handleToast('Peer Id invalid')
}
};
const handleToast = (text: string) => {
setToastContent(text)
setToastVisibility(true);
setTimeout(
() => setToastVisibility(false),
7000
);
};
return (
<div>
<Button variant="contained" color="primary" onClick={handleClickOpen} style={{marginLeft:'7px'}}>
Cashout
</Button>
<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={showToast}
message={toastContent}
/>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Cashout Cheque</DialogTitle>
<DialogContent>
<DialogContentText style={{marginTop: '20px'}}>
Specify the peer Id of the peer you would like to cashout.
</DialogContentText>
<Input
autoFocus
margin="dense"
id="peerId"
type="text"
placeholder='Peer Id'
fullWidth
onChange={(e) => setPeerId(e.target.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleCashout} color="primary">
Cashout
</Button>
</DialogActions>
</Dialog>
</div>
);
}
+37
View File
@@ -0,0 +1,37 @@
import React from 'react';
import { IconButton, Snackbar } from '@material-ui/core';
import {CopyToClipboard} from 'react-copy-to-clipboard';
import { Clipboard } from 'react-feather';
interface IProps {
value: string,
}
export default function ClipboardCopy(props: IProps) {
const [copied, setCopied] = React.useState(false);
const handleCopy = () => {
setCopied(true);
setTimeout(
() => setCopied(false),
3000
);
};
return (
<div style={{marginRight:'3px', marginLeft:'3px'}}>
<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={copied}
message="Copied"
/>
<IconButton color="primary" size='small' onClick={handleCopy}>
<CopyToClipboard text={props.value}
>
<Clipboard style={{height:'20px'}}/>
</CopyToClipboard>
</IconButton>
</div>
);
}
+24
View File
@@ -0,0 +1,24 @@
import SyntaxHighlighter from 'react-syntax-highlighter';
import { hybrid } from 'react-syntax-highlighter/dist/esm/styles/hljs';
interface IProps {
code: string,
language: string,
showLineNumbers?: boolean,
}
const CodeBlock = (props: IProps) => {
return (
<div style={{textAlign:'left'}}>
<SyntaxHighlighter
language={props.language}
style={hybrid}
showLineNumbers={props.showLineNumbers}
>
{props.code}
</SyntaxHighlighter>
</div>
);
};
export default CodeBlock;
+158
View File
@@ -0,0 +1,158 @@
import React, { useEffect } from 'react';
import { withStyles, Theme, createStyles } from '@material-ui/core/styles';
import { Tabs, Tab, Box, Typography } from '@material-ui/core';
import CodeBlock from './CodeBlock';
interface TabPanelProps {
children?: React.ReactNode;
index: any;
value: any;
}
interface IProps {
linux: string;
mac: string;
showLineNumbers?: boolean
}
function a11yProps(index: any) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
function getOS() {
var userAgent = window.navigator.userAgent,
platform = window.navigator.platform,
macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'],
windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE'],
iosPlatforms = ['iPhone', 'iPad', 'iPod'],
os = null;
if (macosPlatforms.indexOf(platform) !== -1) {
os = 'macOS';
} else if (iosPlatforms.indexOf(platform) !== -1) {
os = 'iOS';
} else if (windowsPlatforms.indexOf(platform) !== -1) {
os = 'windows';
} else if (/Android/.test(userAgent)) {
os = 'android';
} else if (!os && /Linux/.test(platform)) {
os = 'linux';
}
return os;
}
export default function CodeBlockTabs(props: IProps) {
const [value, setValue] = React.useState(0);
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setValue(newValue);
};
useEffect(() => {
let os = getOS()
if(os === 'windows') {
setValue(0)
} else if (os === 'linux') {
setValue(0)
} else if (os === 'macOS') {
setValue(1)
}
}, [])
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box style={{ marginTop: '-12px' }}>
<Typography>{children}</Typography>
</Box>
)}
</div>
);
}
const AntTabs = withStyles({
root: {
borderBottom: '1px solid #e8e8e8',
},
indicator: {
backgroundColor: '#3f51b5',
},
})(Tabs);
interface StyledTabProps {
label: string;
}
const AntTab = withStyles((theme: Theme) =>
createStyles({
root: {
textTransform: 'none',
minWidth: 72,
backgroundColor: 'transparent',
fontWeight: theme.typography.fontWeightRegular,
marginRight: theme.spacing(4),
fontFamily: [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
'&:hover': {
color: '#3f51b5',
opacity: 1,
},
'&$selected': {
color: '#3f51b5',
fontWeight: theme.typography.fontWeightMedium,
},
'&:focus': {
color: '#3f51b5',
},
},
selected: {},
}),
)((props: StyledTabProps) => <Tab disableRipple {...props} />);
return (
<div>
<AntTabs style={{ marginTop: '12px' }} value={value} onChange={handleChange} aria-label="ant example">
<AntTab label="Linux" {...a11yProps(0)} />
<AntTab label="MacOS" {...a11yProps(1)} />
</AntTabs>
<TabPanel value={value} index={0}>
<CodeBlock
showLineNumbers={props.showLineNumbers}
language='bash'
code={props.linux}
/>
</TabPanel>
<TabPanel value={value} index={1}>
<CodeBlock
showLineNumbers={props.showLineNumbers}
language='bash'
code={props.mac}
/>
</TabPanel>
</div>
)
}
+48
View File
@@ -0,0 +1,48 @@
import React, { useState } from 'react';
import { TextField, Button, CircularProgress, Container } from '@material-ui/core';
interface IProps {
defaultHost?: string,
hostName: string,
}
export default function ConnectToHost(props: IProps) {
const [hostInputVisible, toggleHostInputVisibility] = useState(false)
const [connectingToHost, setConnectingToHost] = useState(false)
const [host, setHost] = useState('')
const handleNewHostConnection = () => {
if (host) {
setConnectingToHost(true)
sessionStorage.setItem(props.hostName, host)
toggleHostInputVisibility(!hostInputVisible)
window.location.reload();
}
}
return (
<div>
{hostInputVisible ?
<div style={{display:'flex'}}>
<TextField
defaultValue={props.defaultHost}
label="Enter host"
variant="outlined"
size='small'
onChange={(e) => setHost(e.target.value)}
style={{marginRight:'15px', minWidth:'300px'}}
/>
<Button onClick={() => handleNewHostConnection()} size='small' variant="outlined">Connect</Button>
<Button style={{marginLeft:'7px'}} onClick={() => toggleHostInputVisibility(!hostInputVisible)} size='small'>Cancel</Button>
</div>
:
connectingToHost ?
<Container style={{textAlign:'center', padding:'0px'}}>
<CircularProgress size={20} />
</Container>
:
<Button onClick={() => toggleHostInputVisibility(!hostInputVisible)} size='small' variant="outlined">Change host</Button>
}
</div>
)
}
+88
View File
@@ -0,0 +1,88 @@
import React from 'react';
import Button from '@material-ui/core/Button';
import Input from '@material-ui/core/Input';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import { Snackbar } from '@material-ui/core';
import { beeDebugApi } from '../services/bee';
export default function DepositModal() {
const [open, setOpen] = React.useState(false);
const [amount, setAmount] = React.useState(BigInt(0));
const [showToast, setToastVisibility] = React.useState(false);
const [toastContent, setToastContent] = React.useState('');
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleWithdraw = () => {
if (amount > 0) {
beeDebugApi.chequebook.deposit(amount)
.then(res => {
setOpen(false);
handleToast(`Successful Deposit. Transaction ${res.data.transactionHash}`)
})
.catch(error => {
handleToast('Error with Deposit')
})
} else {
handleToast('Must be amount of greater than 0')
}
};
const handleToast = (text: string) => {
setToastContent(text)
setToastVisibility(true);
setTimeout(
() => setToastVisibility(false),
7000
);
};
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen} style={{marginLeft:'7px'}}>
Deposit
</Button>
<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={showToast}
message={toastContent}
/>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Deposit Funds</DialogTitle>
<DialogContent>
<DialogContentText>
Specify the amount you would like to deposit to your node.
</DialogContentText>
<Input
autoFocus
margin="dense"
id="name"
type="number"
placeholder='Amount'
fullWidth
onChange={(e) => setAmount(BigInt(e.target.value))}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleWithdraw} color="primary">
Deposit
</Button>
</DialogActions>
</Dialog>
</div>
);
}
+54
View File
@@ -0,0 +1,54 @@
import React from 'react';
import { Typography } from '@material-ui/core/';
import QRCodeModal from './QRCodeModal';
import ClipboardCopy from './ClipboardCopy';
// @ts-ignore
import Identicon from 'react-identicons';
interface IProps {
address: string | undefined,
network?: string,
hideBlockie?: boolean,
transaction?: boolean,
truncate?: boolean,
}
export default function EthereumAddress(props: IProps) {
return (
<Typography component="p" variant="subtitle1">
{props.address ?
<div style={{display:'flex'}}>
{props.hideBlockie ?
null
:
<div style={{paddingTop:'5px', marginRight: '10px', }}>
<Identicon size='20' string={props.address} />
</div>}
<div>
<a
style={props.truncate ?
{ marginRight:'7px', maxWidth:'200px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display:'block'}
:
{ marginRight:'7px'}
}
href={`https://${props.network}.${process.env.REACT_APP_ETHERSCAN_HOST}/${props.transaction ? 'tx' : 'address' }/${props.address}`}
target='_blank'
rel="noreferrer"
>
{ props.address }
</a>
</div>
<QRCodeModal
value={ props.address }
label={'Ethereum Address'}
/>
<ClipboardCopy
value={ props.address }
/>
</div>
: '-' }
</Typography>
)
}
+93
View File
@@ -0,0 +1,93 @@
import React from 'react'
import { Theme, createStyles, makeStyles, useTheme } from '@material-ui/core/styles';
import { Card, CardContent, Typography } from '@material-ui/core/';
import EthereumAddress from '../components/EthereumAddress';
import { Skeleton } from '@material-ui/lab';
import type { ChequebookAddressResponse } from '@ethersphere/bee-js';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
display: 'flex',
marginTop: '20px',
},
details: {
display: 'flex',
flexDirection: 'column',
},
content: {
flex: '1 0 auto',
},
status: {
color: '#fff',
backgroundColor: '#76a9fa',
}
}),
);
interface NodeAddresses {
overlay: string,
underlay: string[],
ethereum: string,
public_key: string,
pss_public_key: string
}
interface IProps{
nodeAddresses: NodeAddresses | null,
isLoadingNodeAddresses: boolean,
chequebookAddress: ChequebookAddressResponse | null,
isLoadingChequebookAddress: boolean,
}
function EthereumAddressCard(props: IProps) {
const classes = useStyles();
const theme = useTheme();
return (
<div>
<Card className={classes.root}>
{props.isLoadingNodeAddresses ?
<div style={{padding: '16px'}}>
<Skeleton width={300} height={30} animation="wave" />
<Skeleton width={300} height={50} animation="wave" />
</div>
:
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>
<span>Ethereum Address</span>
</Typography>
<EthereumAddress
address={props.nodeAddresses?.ethereum}
network={'goerli'}
/>
</CardContent>
</div>}
{props.isLoadingChequebookAddress ?
<div style={{padding: '16px'}}>
<Skeleton width={300} height={30} animation="wave" />
<Skeleton width={300} height={50} animation="wave" />
</div>
:
<div className={classes.details}>
<CardContent className={classes.content}>
<Typography variant="subtitle1" gutterBottom>
<span>Chequebook Contract Address</span>
</Typography>
<EthereumAddress
address={props.chequebookAddress?.chequebookaddress}
network={'goerli'}
/>
</CardContent>
</div>}
</Card>
</div>
)
}
export default EthereumAddressCard
+70
View File
@@ -0,0 +1,70 @@
import React, { useState } from 'react';
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles';
import { AppBar, Toolbar, Chip, IconButton } from '@material-ui/core/';
import { Sun, Moon } from 'react-feather';
const drawerWidth = 240;
const useStyles = makeStyles((theme: Theme) =>
createStyles({
appBar: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
background:'linear-gradient(35deg,#fb6340,#fbb140)!important'
},
network: {
}
}),
);
export default function SideBar(props: any) {
const [darkMode, toggleDarkMode] = useState(false);
const switchTheme = () => {
let theme = localStorage.getItem('theme')
if (theme) {
localStorage.setItem('theme', theme === 'light' ? 'dark' : 'light')
} else {
localStorage.setItem('theme', darkMode ? 'dark' : 'light')
}
toggleDarkMode(!darkMode)
window.location.reload()
}
const classes = useStyles();
return (
<div>
<AppBar position="fixed" className={classes.appBar}>
<Toolbar style={{display: 'flex'}}>
<Chip
style={{ marginLeft: '7px'}}
size="small"
label='Goerli'
className={classes.network}
/>
<div style={{width:'100%'}}>
<div style={{float:'right'}} >
<IconButton style={{marginRight:'10px'}} aria-label="dark-mode" onClick={() => switchTheme()}>
{props.themeMode === 'dark' ?
<Moon />
:
<Sun />
}
</IconButton>
{/* <Chip
label="Connect Wallet"
color="primary"
/> */}
</div>
</div>
</Toolbar>
</AppBar>
</div>
);
}
+43
View File
@@ -0,0 +1,43 @@
import React,{ useState } from 'react';
import QRCode from 'qrcode.react';
import { IconButton, Dialog, DialogTitle } from '@material-ui/core';
import { FilterCenterFocusSharp } from '@material-ui/icons';
interface IProps {
value: string,
label: string,
}
export default function QRCodeModal(props: IProps) {
const [open, setOpen] = useState(false);
const handleOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div>
<IconButton color="primary" size='small' onClick={handleOpen}>
<FilterCenterFocusSharp/>
</IconButton>
<Dialog onClose={handleClose} aria-labelledby="simple-dialog-title" open={open}>
<div style={{padding: '30px', textAlign: 'center'}}>
<DialogTitle id="simple-dialog-title">{ props.label }</DialogTitle>
<QRCode
value={props.value}
size={150}
bgColor={"#ffffff"}
fgColor={"#000000"}
level={"L"}
includeMargin={false}
renderAs={"svg"}
/>
</div>
</Dialog>
</div>
);
}
+50
View File
@@ -0,0 +1,50 @@
import React from 'react';
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
import { Paper, InputBase, IconButton } from '@material-ui/core';
import { Search } from '@material-ui/icons';
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
padding: '2px 4px',
display: 'flex',
alignItems: 'center',
width: 400,
},
input: {
marginLeft: theme.spacing(1),
flex: 1,
},
iconButton: {
padding: 10,
},
divider: {
height: 28,
margin: 4,
},
}),
);
interface IProps {
}
export default function SearchBar(props: IProps) {
const classes = useStyles();
return (
<div>
<Paper component="form" className={classes.root}>
<InputBase
className={classes.input}
placeholder="Enter hash e.g. 0773a91efd6547c754fc1d95fb1c62c7d1b47f959c2caa685dfec8736da95c1c"
inputProps={{ 'aria-label': 'search google maps' }}
/>
<IconButton type="submit" className={classes.iconButton} aria-label="search">
<Search />
</IconButton>
</Paper>
</div>
)
}
+144
View File
@@ -0,0 +1,144 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { createStyles, Theme, makeStyles } from '@material-ui/core/styles';
import { ListItemText, ListItemIcon, ListItem, Divider, List, Drawer, Link as MUILink} from '@material-ui/core';
import { OpenInNewSharp } from '@material-ui/icons';
import { Activity, FileText, DollarSign, Share2, Settings } from 'react-feather';
import SwarmLogo from '../assets/swarm-logo-2.svg';
import SwarmLogoWhite from '../assets/swarm-logo-2-white.png';
const drawerWidth = 240;
const navBarItems = [
{
'label': 'Status',
'id': 'status',
'path': '/',
'icon': 'activity'
},
{
'label': 'Files',
'id': 'files',
'path': '/files/',
'icon': 'file-text'
},
{
'label': 'Accounting',
'id': 'accounting',
'path': '/accounting/',
'icon': 'dollar-sign'
},
{
'label': 'Peers',
'id': 'peers',
'path': '/peers/',
'icon': 'share-2'
},
{
'label': 'Settings',
'id': 'settings',
'path': '/settings/',
'icon': 'settings'
}
]
const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
display: 'flex',
},
appBar: {
width: `calc(100% - ${drawerWidth}px)`,
marginLeft: drawerWidth,
backgroundColor:'#dd7200'
},
drawer: {
width: drawerWidth,
flexShrink: 0,
},
drawerPaper: {
width: drawerWidth,
},
activeSideBar: {
color: '#fb6340',
},
activeSideBarItem: {
borderLeft: '4px solid #fb6340',
},
toolbar: theme.mixins.toolbar,
}),
);
const getIcon = (iconPath: string) => {
switch (iconPath) {
case 'activity':
return <Activity style={{height: '20px'}} />
case 'file-text':
return <FileText style={{height: '20px'}} />
case 'dollar-sign':
return <DollarSign style={{height: '20px'}} />
case 'share-2':
return <Share2 style={{height: '20px'}} />
case 'settings':
return <Settings style={{height: '20px'}} />
}
}
export default function SideBar(props: any) {
const classes = useStyles();
return (
<div className={classes.root}>
<Drawer
className={classes.drawer}
variant="permanent"
classes={{
paper: classes.drawerPaper,
}}
anchor="left"
>
<div className={classes.toolbar} style={{ textAlign:'center' }}>
<Link to='/'>
<img alt='swarm' src={props.themeMode === 'light' ? SwarmLogo : SwarmLogoWhite} style={{maxHeight: '60px', alignItems:'center'}} />
</Link>
</div>
<Divider />
<List>
{navBarItems.map(item => (
<Link to={item.path} key={item.id} style={{ color:'inherit', textDecoration:'none'}}>
<ListItem button selected={props.location.pathname === item.path} className={props.location.pathname === item.path ? classes.activeSideBarItem : ''}>
<ListItemIcon className={props.location.pathname === item.path ? classes.activeSideBar : ''}>
{ getIcon(item.icon) }
</ListItemIcon>
<ListItemText primary={item.label} className={props.location.pathname === item.path ? classes.activeSideBar : ''} />
</ListItem>
</Link>
))}
</List>
<Divider />
<List>
<MUILink href={process.env.REACT_APP_BEE_DOCS_HOST} target="_blank" style={{textDecoration:'none'}}>
<ListItem button>
<ListItemText primary={'Docs'} />
<OpenInNewSharp fontSize="small" />
</ListItem>
</MUILink>
</List>
<div style={{position:'fixed', bottom: 0, width: 'inherit', padding: '10px'}}>
<ListItem>
<div style={{marginRight:'30px'}}>
<div style={{backgroundColor: props.health ? '#32c48d' : '#c9201f', marginRight: '7px', height: '10px',width: '10px', borderRadius: '50%', display: 'inline-block'}} />
<span>API</span>
</div>
<div>
<div style={{backgroundColor: props.nodeHealth?.status === 'ok' ? '#32c48d' : '#c9201f', marginRight: '7px', height: '10px',width: '10px', borderRadius: '50%', display: 'inline-block'}} />
<span>Debug API</span>
</div>
</ListItem>
</div>
</Drawer>
</div>
);
}
+49
View File
@@ -0,0 +1,49 @@
import React from 'react'
import { makeStyles, } from '@material-ui/core/styles';
import { Card, CardContent, Typography } from '@material-ui/core/';
import { Skeleton } from '@material-ui/lab';
const useStyles = makeStyles({
root: {
minWidth: 275,
},
title: {
fontSize: 16,
},
pos: {
marginBottom: 12,
},
});
interface IProps {
label: string,
statistic: string,
loading?: boolean,
}
export default function StatCard(props: IProps) {
const classes = useStyles();
return (
<Card className={classes.root}>
<CardContent>
{props.loading ?
<div>
<Skeleton width={180} height={25} animation="wave" />
<Skeleton width={180} height={35} animation="wave" />
</div>
:
<div>
<Typography className={classes.title} color="textSecondary" gutterBottom>
{props.label}
</Typography>
<Typography variant="h5" component="h2">
{props.statistic}
</Typography>
</div>
}
</CardContent>
</Card>
)
}
@@ -0,0 +1,39 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { makeStyles, } from '@material-ui/core/styles';
import { Card, CardContent, Typography } from '@material-ui/core/';
const useStyles = makeStyles({
root: {
flexGrow: 1,
marginTop: '20px'
},
title: {
textAlign:'center',
fontSize: 26,
},
});
export default function TroubleshootConnectionCard() {
const classes = useStyles();
return (
<Card className={classes.root}>
<CardContent>
<Typography className={classes.title} gutterBottom>
Looks like your node is not connected
</Typography>
<div style={{marginBottom:'20px', textAlign:'center'}}>
<strong><Link to='/' >Click to run status checks</Link> on your nodes connection or check out the <a href={process.env.REACT_APP_BEE_DOCS_HOST} target='_blank' >Swarm Bee Docs</a></strong>
</div>
<div style={{marginBottom:'20px', textAlign:'center'}}>
<p style={{marginTop:'50px'}}>Still not working? Drop us a message on the Ethereum Swarm <a href={process.env.REACT_APP_BEE_DISCORD_HOST} target='_blank' >Discord</a></p>
</div>
</CardContent>
</Card>
)
}
+88
View File
@@ -0,0 +1,88 @@
import React from 'react';
import Button from '@material-ui/core/Button';
import Input from '@material-ui/core/Input';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import DialogTitle from '@material-ui/core/DialogTitle';
import { Snackbar } from '@material-ui/core';
import { beeDebugApi } from '../services/bee';
export default function WithdrawlModal() {
const [open, setOpen] = React.useState(false);
const [amount, setAmount] = React.useState(BigInt(0));
const [showToast, setToastVisibility] = React.useState(false);
const [toastContent, setToastContent] = React.useState('');
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
const handleWithdraw = () => {
if (amount > 0) {
beeDebugApi.chequebook.withdraw(amount)
.then(res => {
setOpen(false);
handleToast(`Successful withdrawl. Transaction ${res.data.transactionHash}`)
})
.catch(error => {
handleToast('Error with withdrawl')
})
} else {
handleToast('Must be amount of greater than 0')
}
};
const handleToast = (text: string) => {
setToastContent(text)
setToastVisibility(true);
setTimeout(
() => setToastVisibility(false),
7000
);
};
return (
<div>
<Button variant="outlined" color="primary" onClick={handleClickOpen}>
Withdraw
</Button>
<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open={showToast}
message={toastContent}
/>
<Dialog open={open} onClose={handleClose} aria-labelledby="form-dialog-title">
<DialogTitle id="form-dialog-title">Withdraw Funds</DialogTitle>
<DialogContent>
<DialogContentText>
Specify the amount you would like to withdraw from your node.
</DialogContentText>
<Input
autoFocus
margin="dense"
id="name"
type="number"
placeholder='Amount'
fullWidth
onChange={(e) => setAmount(BigInt(e.target.value))}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Cancel
</Button>
<Button onClick={handleWithdraw} color="primary">
Withdraw
</Button>
</DialogActions>
</Dialog>
</div>
);
}