2018-06-06 19:37:03 +00:00
|
|
|
import React, { Fragment, PureComponent } from 'react';
|
2018-06-20 15:13:33 +00:00
|
|
|
import web3 from 'web3';
|
2018-06-13 19:04:06 +00:00
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { actions as accountActions } from '../../reducers/accounts';
|
2018-07-09 21:53:56 +00:00
|
|
|
import Hidden from '@material-ui/core/Hidden';
|
2018-07-10 18:41:46 +00:00
|
|
|
import Typography from '@material-ui/core/Typography';
|
2018-06-11 17:18:59 +00:00
|
|
|
import ENSSubdomainRegistry from 'Embark/contracts/ENSSubdomainRegistry';
|
2018-07-09 21:53:56 +00:00
|
|
|
import { Button, Field, TextInput, MobileSearch, Card, Info, Text } from '../../ui/components'
|
2018-06-08 13:11:51 +00:00
|
|
|
import { IconCheck } from '../../ui/icons'
|
2018-06-06 19:37:03 +00:00
|
|
|
import theme from '../../ui/theme'
|
2018-06-05 20:48:30 +00:00
|
|
|
import { withFormik } from 'formik';
|
|
|
|
import PublicResolver from 'Embark/contracts/PublicResolver';
|
|
|
|
import { hash } from 'eth-ens-namehash';
|
2018-06-06 19:37:03 +00:00
|
|
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
2018-06-11 17:18:59 +00:00
|
|
|
import RegisterSubDomain from '../ens/registerSubDomain';
|
2018-06-13 21:44:14 +00:00
|
|
|
import StatusLogo from '../../ui/icons/components/StatusLogo'
|
|
|
|
import EnsLogo from '../../ui/icons/logos/ens.png';
|
2018-06-20 15:13:33 +00:00
|
|
|
import { formatPrice } from '../ens/utils'
|
2018-06-19 14:12:49 +00:00
|
|
|
const { getPrice, getExpirationTime } = ENSSubdomainRegistry.methods;
|
2018-06-05 14:43:05 +00:00
|
|
|
|
2018-06-11 19:55:42 +00:00
|
|
|
const invalidSuffix = '0000000000000000000000000000000000000000'
|
2018-06-06 19:37:03 +00:00
|
|
|
const nullAddress = '0x0000000000000000000000000000000000000000'
|
2018-06-11 17:18:59 +00:00
|
|
|
const validAddress = address => address != nullAddress;
|
2018-06-11 19:55:42 +00:00
|
|
|
const validStatusAddress = address => !address.includes(invalidSuffix);
|
2018-06-05 20:48:30 +00:00
|
|
|
const formatName = domainName => domainName.includes('.') ? domainName : `${domainName}.stateofus.eth`;
|
2018-06-11 17:18:59 +00:00
|
|
|
const getDomain = fullDomain => formatName(fullDomain).split('.').slice(1).join('.');
|
|
|
|
const hashedDomain = domainName => hash(getDomain(domainName));
|
2018-06-20 15:13:33 +00:00
|
|
|
const { fromWei } = web3.utils;
|
2018-06-06 19:37:03 +00:00
|
|
|
|
2018-06-05 14:43:05 +00:00
|
|
|
const cardStyle = {
|
2018-06-12 22:17:04 +00:00
|
|
|
width: '100%',
|
2018-06-12 18:08:25 +00:00
|
|
|
padding: '30px',
|
|
|
|
height: '425px'
|
2018-06-05 14:43:05 +00:00
|
|
|
}
|
2018-06-05 00:34:56 +00:00
|
|
|
|
2018-06-06 19:37:03 +00:00
|
|
|
const addressStyle = {
|
|
|
|
fontSize: '18px',
|
|
|
|
fontWeight: 400,
|
2018-06-07 21:55:50 +00:00
|
|
|
cursor: 'copy',
|
2018-06-08 13:11:51 +00:00
|
|
|
wordWrap: 'break-word',
|
2018-06-06 19:37:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const backButton = {
|
|
|
|
fontSize: '40px',
|
2018-06-07 21:55:50 +00:00
|
|
|
color: theme.accent,
|
|
|
|
cursor: 'pointer'
|
2018-06-06 19:37:03 +00:00
|
|
|
}
|
|
|
|
|
2018-06-19 14:12:49 +00:00
|
|
|
const generatePrettyDate = (timestamp) => new Date(timestamp * 1000).toDateString();
|
|
|
|
|
2018-06-12 15:11:28 +00:00
|
|
|
class RenderAddresses extends PureComponent {
|
|
|
|
state = { copied: false }
|
|
|
|
|
|
|
|
render() {
|
2018-06-19 14:12:49 +00:00
|
|
|
const { domainName, address, statusAccount, expirationTime } = this.props
|
2018-06-12 15:11:28 +00:00
|
|
|
const { copied } = this.state
|
|
|
|
const markCopied = (v) => { this.setState({ copied: v }) }
|
|
|
|
const isCopied = address => address == copied;
|
|
|
|
const renderCopied = address => isCopied(address) && <span style={{ color: theme.positive }}><IconCheck/>Copied!</span>;
|
|
|
|
return (
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
2018-06-19 14:12:49 +00:00
|
|
|
<Info.Action title="Click to copy"><b>{formatName(domainName).toUpperCase()}</b>{expirationTime && <i> (Expires {generatePrettyDate(expirationTime)})</i>} Resolves To:</Info.Action>
|
2018-06-12 15:11:28 +00:00
|
|
|
{address && <Text style={{ marginTop: '1em' }}>Ethereum Address {renderCopied(address)}</Text>}
|
|
|
|
<CopyToClipboard text={address} onCopy={markCopied}>
|
|
|
|
<div style={addressStyle}>{address}</div>
|
|
|
|
</CopyToClipboard>
|
|
|
|
{validStatusAddress(statusAccount) && <Text style={{ marginTop: '1em' }}>Status Address {renderCopied(statusAccount)}</Text>}
|
|
|
|
{validStatusAddress(statusAccount) && <CopyToClipboard text={statusAccount} onCopy={markCopied}>
|
|
|
|
<div style={{ ...addressStyle, color: isCopied ? theme.primary : null }}>{statusAccount}</div>
|
|
|
|
</CopyToClipboard>}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-10 18:41:46 +00:00
|
|
|
const RegisterInfoCard = ({ formattedDomain, domainPrice }) => (
|
|
|
|
<Fragment>
|
|
|
|
<Hidden mdDown>
|
|
|
|
<Info.Action title="No address is associated with this domain">
|
2018-07-10 19:23:21 +00:00
|
|
|
<span style={{ color: theme.accent }}>{formattedDomain.toLowerCase()}</span> can be registered for {!!domainPrice && formatPrice(fromWei(domainPrice))} SNT
|
2018-07-10 18:41:46 +00:00
|
|
|
</Info.Action>
|
|
|
|
</Hidden>
|
|
|
|
<Hidden mdUp>
|
2018-07-10 19:23:21 +00:00
|
|
|
<Info background="#415be3" style={{ margin: '0.4em' }}>
|
2018-07-10 18:41:46 +00:00
|
|
|
<Typography variant="title" style={
|
|
|
|
{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'space-evenly', height: '4em', color: '#ffffff', textAlign: 'center', margin: '10%' }
|
|
|
|
}>
|
2018-07-10 19:23:21 +00:00
|
|
|
<b>{formattedDomain.toLowerCase()}</b>
|
2018-07-11 17:31:33 +00:00
|
|
|
<div style={{ border: '1px solid', borderRadius: '25px', width: '5em', height: '2em', paddingTop: '0.1em', fontWeight: 300, marginTop: '0.5em' }}>
|
2018-07-10 18:41:46 +00:00
|
|
|
{!!domainPrice && formatPrice(fromWei(domainPrice))} SNT
|
|
|
|
</div>
|
2018-07-11 17:31:33 +00:00
|
|
|
<div style={{ fontWeight: 100, marginTop: '0.3em' }}>1 year</div>
|
2018-07-10 18:41:46 +00:00
|
|
|
</Typography>
|
|
|
|
</Info>
|
|
|
|
</Hidden>
|
2018-07-10 19:23:21 +00:00
|
|
|
<Hidden mdUp>
|
2018-07-11 18:44:32 +00:00
|
|
|
<Typography style={{ textAlign: 'center', padding: '1.5em' }}>
|
|
|
|
This name will be pointed to the wallet address and contact code below
|
2018-07-10 19:23:21 +00:00
|
|
|
</Typography>
|
|
|
|
</Hidden>
|
2018-07-10 18:41:46 +00:00
|
|
|
</Fragment>
|
|
|
|
)
|
|
|
|
|
2018-06-11 17:18:59 +00:00
|
|
|
class Register extends PureComponent {
|
|
|
|
state = { domainPrice: null };
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
const { domainName } = this.props;
|
|
|
|
getPrice(hashedDomain(domainName))
|
|
|
|
.call()
|
|
|
|
.then((res) => { this.setState({ domainPrice: res })});
|
|
|
|
}
|
|
|
|
|
2018-06-13 19:04:06 +00:00
|
|
|
onRegistered = (address, statusAccount) => {
|
|
|
|
const { domainPrice } = this.state;
|
|
|
|
const { subtractFromBalance } = this.props;
|
|
|
|
subtractFromBalance(domainPrice);
|
|
|
|
this.setState({ registered: { address, statusAccount } });
|
|
|
|
}
|
|
|
|
|
2018-06-11 17:18:59 +00:00
|
|
|
render() {
|
|
|
|
const { domainName, setStatus } = this.props;
|
2018-06-12 15:11:28 +00:00
|
|
|
const { domainPrice, registered } = this.state;
|
2018-06-11 19:55:42 +00:00
|
|
|
const formattedDomain = formatName(domainName);
|
|
|
|
const formattedDomainArray = formattedDomain.split('.')
|
2018-06-11 17:18:59 +00:00
|
|
|
return (
|
|
|
|
<Fragment>
|
2018-06-12 15:11:28 +00:00
|
|
|
{!registered ?
|
|
|
|
<Fragment>
|
2018-07-10 18:41:46 +00:00
|
|
|
<RegisterInfoCard {...{ formattedDomain, domainPrice }}/>
|
2018-06-12 15:11:28 +00:00
|
|
|
<RegisterSubDomain
|
|
|
|
subDomain={formattedDomainArray[0]}
|
|
|
|
domainName={formattedDomainArray.slice(1).join('.')}
|
|
|
|
domainPrice={domainPrice}
|
2018-06-13 19:04:06 +00:00
|
|
|
registeredCallbackFn={this.onRegistered} />
|
2018-06-12 15:11:28 +00:00
|
|
|
</Fragment> :
|
|
|
|
<RenderAddresses {...this.props} address={registered.address} statusAccount={registered.statusAccount} />}
|
2018-06-11 17:18:59 +00:00
|
|
|
<div style={backButton} onClick={() => setStatus(null)}>←</div>
|
|
|
|
</Fragment>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-13 19:04:06 +00:00
|
|
|
const mapDispatchToProps = dispatch => ({
|
|
|
|
subtractFromBalance(amount) {
|
|
|
|
dispatch(accountActions.subtractfromSntTokenBalance(amount));
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const ConnectedRegister = connect(null, mapDispatchToProps)(Register);
|
|
|
|
|
2018-06-12 18:08:25 +00:00
|
|
|
const DisplayAddress = (props) => (
|
2018-06-12 15:11:28 +00:00
|
|
|
<Fragment>
|
2018-06-12 18:08:25 +00:00
|
|
|
{validAddress(props.address) ?
|
|
|
|
<RenderAddresses {...props} />
|
2018-06-12 15:11:28 +00:00
|
|
|
:
|
2018-07-10 18:41:46 +00:00
|
|
|
<Hidden mdUp>
|
|
|
|
<Info.Action title="No address is associated with this domain">
|
|
|
|
{props.domainName.toUpperCase()}
|
|
|
|
</Info.Action>
|
|
|
|
</Hidden>
|
|
|
|
}
|
2018-06-12 18:08:25 +00:00
|
|
|
<div style={backButton} onClick={() => props.setStatus(null)}>←</div>
|
2018-06-12 15:11:28 +00:00
|
|
|
</Fragment>
|
|
|
|
)
|
2018-06-06 19:37:03 +00:00
|
|
|
|
2018-07-09 21:53:56 +00:00
|
|
|
const LookupForm = ({ handleSubmit, values, handleChange }) => (
|
|
|
|
<Fragment>
|
2018-07-13 17:59:18 +00:00
|
|
|
<form onSubmit={handleSubmit}>
|
2018-07-10 18:41:46 +00:00
|
|
|
<Hidden mdDown>
|
2018-07-10 14:15:26 +00:00
|
|
|
<Field label="Enter Domain or Status Name" wide>
|
|
|
|
<TextInput
|
|
|
|
value={values.domainName}
|
|
|
|
name="domainName"
|
|
|
|
onChange={handleChange}
|
|
|
|
wide
|
|
|
|
required />
|
|
|
|
</Field>
|
|
|
|
</Hidden>
|
2018-07-10 18:41:46 +00:00
|
|
|
<Hidden mdUp>
|
2018-07-13 17:59:18 +00:00
|
|
|
<MobileSearch
|
|
|
|
search
|
|
|
|
name="domainName"
|
|
|
|
placeholder='Search for vacant name'
|
|
|
|
value={values.domainName}
|
|
|
|
onChange={handleChange}
|
|
|
|
required
|
|
|
|
wide />
|
|
|
|
<Typography variant="subheading" style={{ color: '#939ba1', textAlign: 'center', marginTop: '25vh' }}>
|
|
|
|
Symbols * / <br/>
|
|
|
|
are not supported
|
|
|
|
</Typography>
|
2018-07-10 14:15:26 +00:00
|
|
|
</Hidden>
|
2018-07-10 18:41:46 +00:00
|
|
|
<Hidden mdDown>
|
2018-07-10 14:15:26 +00:00
|
|
|
<Button mode="strong" type="submit" wide>
|
|
|
|
Lookup Address
|
|
|
|
</Button>
|
|
|
|
</Hidden>
|
2018-07-09 21:53:56 +00:00
|
|
|
</form>
|
|
|
|
</Fragment>
|
|
|
|
)
|
|
|
|
|
2018-06-05 20:48:30 +00:00
|
|
|
const InnerForm = ({
|
|
|
|
values,
|
|
|
|
errors,
|
|
|
|
touched,
|
|
|
|
handleChange,
|
|
|
|
handleBlur,
|
|
|
|
handleSubmit,
|
|
|
|
isSubmitting,
|
2018-06-06 19:37:03 +00:00
|
|
|
status,
|
|
|
|
setStatus
|
2018-06-05 20:48:30 +00:00
|
|
|
}) => (
|
2018-07-09 21:53:56 +00:00
|
|
|
<div style={{ margin: '10px' }}>
|
2018-07-10 18:41:46 +00:00
|
|
|
<Hidden mdDown>
|
2018-07-10 14:15:26 +00:00
|
|
|
<span style={{ display: 'flex', justifyContent: 'space-evenly', marginBottom: '10px' }}>
|
|
|
|
<StatusLogo />
|
|
|
|
<img style={{ maxWidth: '150px', alignSelf: 'center' }} src={EnsLogo} alt="Ens Logo"/>
|
|
|
|
</span>
|
2018-07-09 21:53:56 +00:00
|
|
|
</Hidden>
|
2018-06-11 17:18:59 +00:00
|
|
|
{!status
|
2018-07-09 21:53:56 +00:00
|
|
|
? <LookupForm {...{ handleSubmit, values, handleChange }} />
|
2018-06-11 17:18:59 +00:00
|
|
|
: validAddress(status.address) ?
|
|
|
|
<DisplayAddress
|
|
|
|
domainName={values.domainName}
|
|
|
|
address={status.address}
|
|
|
|
statusAccount={status.statusAccount}
|
2018-06-19 14:12:49 +00:00
|
|
|
expirationTime={status.expirationTime}
|
2018-06-11 17:18:59 +00:00
|
|
|
setStatus={setStatus} /> :
|
2018-06-13 19:04:06 +00:00
|
|
|
<ConnectedRegister
|
2018-06-11 17:18:59 +00:00
|
|
|
setStatus={setStatus}
|
|
|
|
domainName={values.domainName} />
|
|
|
|
}
|
2018-07-09 21:53:56 +00:00
|
|
|
</div>
|
2018-06-05 00:34:56 +00:00
|
|
|
)
|
|
|
|
|
2018-06-05 20:48:30 +00:00
|
|
|
const NameLookup = withFormik({
|
|
|
|
mapPropsToValues: props => ({ domainName: '' }),
|
2018-06-07 21:55:50 +00:00
|
|
|
async handleSubmit(values, { status, setSubmitting, setStatus }) {
|
2018-06-05 20:48:30 +00:00
|
|
|
const { domainName } = values;
|
2018-06-07 21:55:50 +00:00
|
|
|
const { addr, text } = PublicResolver.methods;
|
|
|
|
const lookupHash = hash(formatName(domainName));
|
|
|
|
const address = await addr(lookupHash).call();
|
|
|
|
const statusAccount = await text(lookupHash, 'statusAccount').call();
|
2018-06-19 14:12:49 +00:00
|
|
|
const expirationTime = await getExpirationTime(lookupHash).call();
|
|
|
|
setStatus({ address, statusAccount, expirationTime });
|
2018-06-05 20:48:30 +00:00
|
|
|
}
|
|
|
|
})(InnerForm)
|
|
|
|
|
2018-06-05 00:34:56 +00:00
|
|
|
export default NameLookup;
|