react-router, better navigation

This commit is contained in:
Aleksandr Pantiukhov 2018-10-09 17:03:20 +02:00 committed by Barry G
parent 2273f58576
commit a09eee1287
8 changed files with 303 additions and 218 deletions

View File

@ -3,15 +3,35 @@ import Typography from '@material-ui/core/Typography';
import { connect } from 'react-redux';
import { YOUR_CONTACT_CODE } from './constants';
import { checkAndDispatchStatusContactCode } from '../../actions/accounts';
import styled from "styled-components";
const WrappedDisplayBox = ({ displayType, pubKey, getStatusContactCode }) => (
const DisplayLabel = styled.div`
font-size: 14px;
color: #939BA1;
margin: 0 1em;
`;
const DisplayBox = styled.div`
border: 1px solid #EEF2F5;
border-radius: 8px;
margin: 7px 12px 14px 12px;
display: flex;
flex-direction: column;
justifyContent: space-around;
min-height: 4em;
word-wrap: break-word;
`;
const WrappedDisplayBox = ({displayType, pubKey, getStatusContactCode}) => (
<div onClick={() => getStatusContactCode(displayType, pubKey)}>
<div style={{ fontSize: '14px', color: '#939BA1', margin: '0 1em' }}>{displayType}</div>
<div style={{ border: '1px solid #EEF2F5', borderRadius: '8px', margin: '7px 12px 14px 12px', display: 'flex', flexDirection: 'column', justifyContent: 'space-around', minHeight: '4em' }}>
<div style={{ margin: '16px', wordBreak: 'break-word' }}>
<DisplayLabel>
{displayType}
</DisplayLabel>
<DisplayBox>
<div style={{margin: '16px'}}>
<Typography type='body1'>{pubKey}</Typography>
</div>
</div>
</DisplayBox>
</div>
);

View File

@ -1 +1,2 @@
export const YOUR_CONTACT_CODE = 'Your contact code';
export const YOUR_WALLET_ADDRESS = 'Your wallet address';

View File

@ -34,6 +34,7 @@ import { nullAddress, getResolver } from './utils/domain';
import { YOUR_CONTACT_CODE } from './constants';
import DisplayBox from './DisplayBox';
import styled from "styled-components";
import { Route } from "react-router-dom";
const normalizer = new IDNANormalizer();
const invalidSuffix = '0000000000000000000000000000000000000000'
@ -240,6 +241,7 @@ class Register extends PureComponent {
const formattedDomain = formatName(domainName);
const formattedDomainArray = formattedDomain.split('.');
const isOwner = defaultAccount === ownerAddress;
return (
<div style={style}>
{!registered && !submitted ?
@ -267,7 +269,7 @@ const mapDispatchToProps = dispatch => ({
const mapStateToProps = state => ({
defaultAccount: getDefaultAccount(state)
})
});
const ConnectedRegister = connect(mapStateToProps, mapDispatchToProps)(Register);
@ -283,94 +285,51 @@ const DisplayAddress = connect(mapStateToProps)((props) => (
</Hidden>
}
</Fragment>
))
));
const LookupForm = ({ handleSubmit, values, handleChange, isWarningDisplayed }) => (
<Fragment>
<form onSubmit={handleSubmit} onBlur={handleSubmit} >
<Hidden mdDown>
<Field label="Enter Domain or Status Name" style={{ margin: 50 }}>
<TextInput
value={values.domainName}
name="domainName"
onChange={handleChange}
wide
required />
</Field>
</Hidden>
<Hidden mdUp>
<MobileSearch
search
name="domainName"
placeholder='Search for available name'
value={values.domainName}
onChange={handleChange}
required
wide />
{isWarningDisplayed && <Warning>Names are made with<br/>letters and numbers only</Warning>}
</Hidden>
<Hidden mdDown>
<Button mode="strong" type="submit" style={{ marginLeft: '3%', maxWidth: '95%' }} wide>
Lookup Address
</Button>
</Hidden>
</form>
</Fragment>
)
class LookupForm extends React.Component {
render() {
const { handleSubmit, values, handleChange, isWarningDisplayed } = this.props;
const InnerForm = ({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
status,
setStatus,
defaultAccount
}) => (
<div>
<Hidden mdDown>
<span style={{ display: 'flex', justifyContent: 'space-evenly', margin: '50 0 10 0' }}>
<StatusLogo />
<img style={{maxWidth: '150px', alignSelf: 'center'}} src={EnsLogo} alt="Ens Logo"/>
</span>
</Hidden>
{!status || !status.address ?
<LookupForm {...{handleSubmit, values, handleChange}} isWarningDisplayed={status && status.isInvalidDomain}/>
:
validAddress(status.address) || defaultAccount === status.ownerAddress ?
<DisplayAddress
{...{handleSubmit, values, handleChange}}
domainName={status.domainName}
address={status.address}
statusAccount={status.statusAccount}
expirationTime={status.expirationTime}
creationTime={status.creationTime} ownerAddress={status.ownerAddress}
registryOwnsDomain={status.registryOwnsDomain}
setStatus={setStatus}/> :
<div>
<LookupForm {...{handleSubmit, values, handleChange}} isWarningDisplayed={false}/>
<ConnectedRegister
style={{position: 'relative'}}
setStatus={setStatus}
registryOwnsDomain={status.registryOwnsDomain}
ownerAddress={status.ownerAddress}
domainName={status.domainName}/>
</div>
}
</div>
);
return (
<Fragment>
<form onSubmit={handleSubmit} onBlur={handleSubmit}>
<Hidden mdDown>
<Field label="Enter Domain or Status Name" style={{margin: 50}}>
<TextInput
value={values.domainName}
name="domainName"
onChange={handleChange}
wide
required/>
</Field>
</Hidden>
<Hidden mdUp>
<MobileSearch
search
name="domainName"
placeholder='Search for available name'
value={values.domainName}
onChange={handleChange}
required
wide/>
{isWarningDisplayed && <Warning>Names are made with<br/>letters and numbers only</Warning>}
</Hidden>
<Hidden mdDown>
<Button mode="strong" type="submit" style={{marginLeft: '3%', maxWidth: '95%'}} wide>
Lookup Address
</Button>
</Hidden>
</form>
</Fragment>
);
}
}
const isValidDomainName = val => /^([a-z0-9]+)$/.test(val.toLowerCase());
const NameLookup = withFormik({
mapPropsToValues: props => ({ domainName: '' }),
async handleSubmit(values, { status, setSubmitting, setStatus }) {
const { domainName } = values;
class SearchResultsPage extends React.Component {
async loadDomainInformation({setStatus, domainName}) {
if (isValidDomainName(domainName)) {
const { methods: { owner, resolver } } = ENSRegistry;
const lookupHash = hash(formatName(domainName));
@ -403,6 +362,109 @@ const NameLookup = withFormik({
setStatus({isInvalidDomain: true });
}
}
})(InnerForm);
export default connect(mapStateToProps)(NameLookup);
componentDidMount() {
const { setValues } = this.props;
const domainName = this.props.match.params.domainName;
setValues({domainName: domainName});
this.loadDomainInformation({
setStatus: this.props.setStatus,
domainName: domainName
});
}
componentWillReceiveProps(nextProps) {
const { setValues, match } = this.props;
const oldDomainName = match.params.domainName;
const newDomainName = nextProps.match.params.domainName;
if (oldDomainName !== newDomainName) {
setValues({domainName: newDomainName});
this.loadDomainInformation({
setStatus: this.props.setStatus,
domainName: newDomainName
});
}
}
render() {
const {
values,
handleChange,
handleSubmit,
status,
setStatus,
defaultAccount
} = this.props;
return (
<div>
{!status || !status.address ?
<LookupForm {...this.props} isWarningDisplayed={status && status.isInvalidDomain}/>
:
validAddress(status.address) || defaultAccount === status.ownerAddress ?
<DisplayAddress
{...{handleSubmit, values, handleChange}}
domainName={status.domainName}
address={status.address}
statusAccount={status.statusAccount}
expirationTime={status.expirationTime}
creationTime={status.creationTime} ownerAddress={status.ownerAddress}
registryOwnsDomain={status.registryOwnsDomain}
setStatus={setStatus}/> :
<div>
<LookupForm {...this.props} isWarningDisplayed={false}/>
<ConnectedRegister
style={{position: 'relative'}}
setStatus={setStatus}
registryOwnsDomain={status.registryOwnsDomain}
ownerAddress={status.ownerAddress}
domainName={status.domainName}/>
</div>
}
</div>
);
}
}
class NameLookupContainer extends React.Component {
render() {
const {match, status} = this.props;
return (
<div>
<Hidden mdDown>
<span style={{ display: 'flex', justifyContent: 'space-evenly', margin: '50 0 10 0' }}>
<StatusLogo />
<img style={{maxWidth: '150px', alignSelf: 'center'}} src={EnsLogo} alt="Ens Logo"/>
</span>
</Hidden>
{match &&
<div>
<Route
exact
path={match.url}
render={() => <LookupForm {...this.props} isWarningDisplayed={status && status.isInvalidDomain}/>}/>
<Route
path={`${match.url}/:domainName`}
render={({match}) => <SearchResultsPage {...this.props} match={match}/>}/>
</div>
}
</div>
)
}
}
const NameLookupContainerWrapped = withFormik({
mapPropsToValues: () => ({ domainName: '' }),
async handleSubmit(values, { props }) {
const { domainName } = values;
props.history.push(`${props.match.url}/${domainName}`);
}
})(NameLookupContainer);
export default connect(mapStateToProps)(NameLookupContainerWrapped);

View File

@ -2,27 +2,44 @@ import web3 from "Embark/web3"
import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar';
import TestToken from 'Embark/contracts/TestToken';
import React from 'react';
import { connect } from 'react-redux';
import {connect} from 'react-redux';
import Hidden from '@material-ui/core/Hidden';
import {withStyles} from '@material-ui/core/styles';
import CircularProgress from '@material-ui/core/CircularProgress';
import { Button, MobileButton } from '../../ui/components';
import { withFormik } from 'formik';
import { hash } from 'eth-ens-namehash';
import { zeroAddress, zeroBytes32, formatPrice } from './utils';
import { getStatusContactCode, getSNTAllowance, getCurrentAccount } from '../../reducers/accounts';
import {Button, MobileButton} from '../../ui/components';
import {withFormik} from 'formik';
import {hash} from 'eth-ens-namehash';
import {zeroAddress, zeroBytes32, formatPrice} from './utils';
import {getStatusContactCode, getSNTAllowance, getCurrentAccount} from '../../reducers/accounts';
import FieldGroup from '../standard/FieldGroup';
import LinearProgress from '@material-ui/core/LinearProgress';
import Terms from './terms';
import { generateXY } from '../../utils/ecdsa';
import { getResolver } from './utils/domain';
import {generateXY} from '../../utils/ecdsa';
import {getResolver} from './utils/domain';
import DisplayBox from './DisplayBox';
import { YOUR_CONTACT_CODE } from './constants';
import { YOUR_CONTACT_CODE, YOUR_WALLET_ADDRESS } from './constants';
import classNames from "classnames";
const { soliditySha3, fromWei } = web3.utils;
const styles = theme => ({
button: {
margin: theme.spacing.unit,
backgroundColor: 'rgba(67, 96, 223, 0.1)',
},
buttonText: {
color: '#4360df',
margin: '0 20px',
fontWeight: 400,
textTransform: 'uppercase'
}
});
const formRef = React.createRef();
const displayTerms = status => status === 'terms';
const InnerForm = ({
classes,
values,
errors,
handleChange,
@ -64,13 +81,14 @@ const InnerForm = ({
button={
<Button
mode="strong"
style={{ marginTop: '5px' }}
onClick={() => {
style={{marginTop: '5px'}}
onClick={() => {
UsernameRegistrar.methods.getPrice()
.call()
.then((res) => { setFieldValue('price', fromWei(res)); });
}}
>
.call()
.then((res) => {
setFieldValue('price', fromWei(res));
});
}}>
Get Price
</Button>
}
@ -110,41 +128,18 @@ const InnerForm = ({
<Hidden mdUp>
<DisplayBox displayType="Your wallet address" pubKey={values.address} />
<DisplayBox displayType={YOUR_WALLET_ADDRESS} pubKey={values.address} />
<DisplayBox displayType={YOUR_CONTACT_CODE} pubKey={values.statusAddress} />
{/*<div style={{ fontSize: '14px', color: '#939BA1', margin: '0 1em' }}>Your contact code</div>*/}
{/*<div style={{ border: '1px solid #EEF2F5', borderRadius: '8px', margin: '0.5 1em 1em', display: 'flex', flexDirection: 'column', justifyContent: 'space-around', minHeight: '4em' }}>*/}
{/*<div style={{ margin: '3%', wordBreak: 'break-word' }}>*/}
{/*<Typography type='body1' onClick={() => setFieldValue('statusAddress', '')} style={{ textAlign: 'center', padding: '30px 0', color: 'blue', cursor: 'pointer'}}>*/}
{/*Grant Access*/}
{/*</Typography>*/}
{/*</div>*/}
{/*</div>*/}
{/*<Field label="Your Wallet Address">*/}
{/*<MobileSearch*/}
{/*name="address"*/}
{/*style={{ marginTop: '10px' }}*/}
{/*placeholder="Your wallet address"*/}
{/*value={values.address}*/}
{/*onChange={handleChange}*/}
{/*onClick={() => setFieldValue('address', '')}*/}
{/*required*/}
{/*wide />*/}
{/*</Field>*/}
{/*<Field label="Your contact code">*/}
{/*<MobileSearch*/}
{/*name="statusAddress"*/}
{/*style={{ marginTop: '10px' }}*/}
{/*placeholder="Status Messenger Address"*/}
{/*value={values.statusAddress}*/}
{/*onChange={handleChange}*/}
{/*onClick={() => setFieldValue('statusAddress', '')}*/}
{/*wide />*/}
{/*</Field>*/}
<div style={{ position: 'relative', left: 0, right: 0, bottom: 0 }}>
{!isSubmitting ? <MobileButton onClick={() => { setStatus('terms') }} text={`${editAccount ? 'Save' : 'Register'} with transaction`} style={{ width: '100%' }} /> : <CircularProgress style={{ marginLeft: '45%' }} />}
<div style={{ position: 'relative', left: 0, right: 0, bottom: 0, textAlign: 'center' }}>
{!isSubmitting ?
<Button onClick={() => {setStatus('terms')}} className={classNames(classes.button)}>
<div className={classNames(classes.buttonText)}>
{`${editAccount ? 'Save' : 'Register'} with transaction`}
</div>
</Button>
:
<CircularProgress />}
<Terms open={displayTerms(status)} onSubmit={() => { setStatus(null); formRef.current.dispatchEvent(new Event('submit')) }} form={formRef} />
</div>
</Hidden>
@ -152,6 +147,8 @@ const InnerForm = ({
</form>
);
const StyledInnerForm = withStyles(styles)(InnerForm);
const RegisterSubDomain = withFormik({
mapPropsToValues: props => ({ subDomain: '', domainName: '', price: '', statusAddress: props.statusContactCode || '', address: web3.eth.defaultAccount || '' }),
validate(values, props) {
@ -219,7 +216,7 @@ const RegisterSubDomain = withFormik({
});
}
}
})(InnerForm);
})(StyledInnerForm);
const mapStateToProps = state => ({
statusContactCode: getStatusContactCode(state),

View File

@ -1,54 +1,65 @@
import React from 'react';
import classNames from 'classnames';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import {withStyles} from '@material-ui/core/styles';
import {Button} from '../../ui/components';
import StatusCards from '../../ui/icons/svg/intro_name.svg';
import {BrowserRouter as Router, Route, Link} from "react-router-dom";
import './welcome.css';
const styles = theme => ({
button: {
margin: theme.spacing.unit,
borderRadius: '4px',
backgroundColor: 'rgba(67, 96, 223, 0.1)',
},
buttonText: {
color: '#4360df',
margin: '0 20px',
fontWeight: 400,
textTransform: 'uppercase'
}
});
const buttonText = { color: '#4360df', margin: '0 20px', fontWeight: 300 };
const WelcomeContent = () => (
<div>
<div className="ens-welcome__guide"><span>How it works</span></div>
<div>
<div className="ens-welcome__guide"><span>How it works</span></div>
<ol className="ens-welcome__list">
<li className="item">
<div className="title">Simplify your ETH address</div>
<div className="text">Your complex wallet address (0x...) becomes an easy to read, remember & share URL: <span className="ens-welcome__highlight">myname.stateofus.eth</span></div>
</li>
<li className="item">
<div className="title">100 SNT to register</div>
<div className="text">Register once to keep the name forever. After 1 year, you can release the name and get your SNT back.</div>
</li>
<li className="item">
<div className="title">Connect & get paid</div>
<div className="text">Share your name to chat on Status or receive ETH and tokens.</div>
</li>
</ol>
<div className="ens-welcome__info">Already have a Status subdomain? <a href="">Manage it</a></div>
<div className="text-light"><small>Powered by Ethereum Name Services</small></div>
<ol className="ens-welcome__list">
<li className="item">
<div className="title">Simplify your ETH address</div>
<div className="text">Your complex wallet address (0x...) becomes an easy to read, remember & share URL: <span
className="ens-welcome__highlight">myname.stateofus.eth</span></div>
</li>
<li className="item">
<div className="title">100 SNT to register</div>
<div className="text">Register once to keep the name forever. After 1 year, you can release the name and get
your SNT back.
</div>
</li>
<li className="item">
<div className="title">Connect & get paid</div>
<div className="text">Share your name to chat on Status or receive ETH and tokens.</div>
</li>
</ol>
<div className="ens-welcome__info">Already have a Status subdomain? <a href="">Manage it</a></div>
<div className="text-light">
<small>Powered by Ethereum Name Services</small>
</div>
</div>
);
const Welcome = ({ classes, toggleSearch }) => (
const Welcome = ({ classes }) => (
<div className="ens-welcome">
<img className="ens-welcome__img" src={StatusCards} />
<h2 className="ens-welcome__title">
ENS names transform those crazy-long addresses into unique usernames
</h2>
<Button size="large" className={classNames(classes.button)} onClick={toggleSearch}>
<div style={buttonText}>Let's Go</div>
</Button>
<WelcomeContent />
<Link to="/search">
<Button className={classNames(classes.button)}>
<div className={classNames(classes.buttonText)}>Let's Go</div>
</Button>
</Link>
<WelcomeContent/>
</div>
);

View File

@ -1,43 +1,54 @@
import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
import React from 'react';
import 'typeface-roboto'
import CssBaseline from '@material-ui/core/CssBaseline';
import { Tabs, Tab } from 'react-bootstrap';
import Toggle from 'react-toggle';
import EmbarkJS from 'Embark/EmbarkJS';
import TopNavbar from './components/topnavbar';
import TestTokenUI from './components/testtoken';
import ERC20TokenUI from './components/erc20token';
import TestToken from 'Embark/contracts/TestToken';
import ENSSubManagement from './components/ensSubManagement';
import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar';
import NameLookup from './components/ens/nameLookup';
import AdminMode from './components/AdminMode';
import TokenPermissions from './components/standard/TokenPermissionConnect';
import web3 from "Embark/web3";
import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography';
import Welcome from './components/ens/welcome';
import Fade from '@material-ui/core/Fade';
import Hidden from '@material-ui/core/Hidden';
import Web3Render from './components/standard/Web3Render';
import StatusOptimized from './components/standard/StatusOptimized';
import { HashRouter, Route } from "react-router-dom";
import './dapp.css';
const { getNetworkType } = web3.eth.net;
const symbols = {
'ropsten': 'STT',
'private': 'SNT',
'main': 'SNT'
}
const Web3RenderContent = ({ network, history, match }) => (
<Web3Render ready={network === 'ropsten'} network={'Ropsten'}>
<div>
<NameLookup {...{history, match}}/>
<Hidden mdDown>
<div style={{textAlign: 'center', margin: '0px 40px'}}>
<TokenPermissions
symbol={symbols[network] || 'SNT'}
spender={UsernameRegistrar._address}
methods={TestToken.methods}/>
<hr/>
<Toggle onChange={() => {
this.setState({admin: !admin})
}}/>
<br/>
<span>Admin Mode</span>
</div>
</Hidden>
</div>
</Web3Render>
);
class App extends React.Component {
constructor(props) {
super(props)
}
state = { admin: false, searching: false };
state = { admin: false };
componentDidMount(){
EmbarkJS.onReady((err) => {
@ -46,43 +57,24 @@ class App extends React.Component {
}
render() {
const { admin, network, searching } = this.state;
const toggleSearch = () => { this.setState({ searching: !searching }) };
const isRopsten = network === 'ropsten';
const isMainnet = network === 'main';
const { admin, network } = this.state;
return (
<div>
<Hidden mdDown>
<StatusOptimized />
</Hidden>
<div style={{ display: admin ? 'block' : 'none' }} >
<AdminMode style={{ display: admin ? 'block' : 'none' }}/>
</div>
{!searching && <Fade in={!searching}>
<div>
<Welcome toggleSearch={toggleSearch} />
<HashRouter>
<div>
<Hidden mdDown>
<StatusOptimized/>
</Hidden>
<div style={{display: admin ? 'block' : 'none'}}>
<AdminMode style={{display: admin ? 'block' : 'none'}}/>
</div>
</Fade>}
{searching && <Fade in={searching}>
<Web3Render ready={isRopsten} network={'Ropsten'}>
<div>
<NameLookup />
<Hidden mdDown>
<div style={{ textAlign: 'center', margin: '0px 40px' }}>
<TokenPermissions
symbol={symbols[network] || 'SNT'}
spender={UsernameRegistrar._address}
methods={TestToken.methods} />
<hr/>
<Toggle onChange={() => { this.setState({ admin: !admin })}} />
<br/>
<span>Admin Mode</span>
</div>
</Hidden>
</div>
</Web3Render>
</Fade>}
</div>
<Route exact path="/" component={Welcome}/>
<Route path="/search" render={({history, match}) => (
<Web3RenderContent {...{history, match, network}} />
)}/>
</div>
</HashRouter>
);
}
}

View File

@ -177,7 +177,7 @@ const StyledButton = styled.button.attrs({ type: 'button' })`
color: ${textSecondary};
background: ${contentBackground};
border: 0;
border-radius: 3px;
border-radius: 4px;
outline: 0;
cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')};
&,

View File

@ -35,6 +35,8 @@
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.4.2",
"react-redux": "^5.0.7",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"react-toggle": "^4.0.2",
"redux": "^4.0.0",
"redux-action-creator": "^2.3.0",