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' ;
2018-08-01 20:00:15 +00:00
import { actions as accountActions , getDefaultAccount } from '../../reducers/accounts' ;
2018-08-17 20:28:17 +00:00
import { hash } from 'eth-ens-namehash' ;
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-08-16 19:14:43 +00:00
import { Button , Field , TextInput , MobileSearch , MobileButton , Card , Info , Text } from '../../ui/components'
2018-06-08 13:11:51 +00:00
import { IconCheck } from '../../ui/icons'
2018-08-10 18:29:15 +00:00
import { keyFromXY } from '../../utils/ecdsa' ;
2018-08-16 22:06:37 +00:00
import EditOptions from './EditOptions' ;
2018-08-17 19:46:59 +00:00
import ReleaseDomainAlert from './ReleaseDomain' ;
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' ;
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-07-13 19:08:21 +00:00
import { formatPrice } from '../ens/utils' ;
import CheckCircle from '../../ui/icons/components/baseline_check_circle_outline.png' ;
2018-08-17 20:28:17 +00:00
const { getPrice , getExpirationTime , release } = ENSSubdomainRegistry . methods ;
2018-07-31 18:24:51 +00:00
import NotInterested from '@material-ui/icons/NotInterested' ;
2018-08-07 18:12:34 +00:00
import Face from '@material-ui/icons/Face' ;
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-08-17 20:28:17 +00:00
const { soliditySha3 , 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-07-31 19:59:04 +00:00
const DisplayBox = ( { displayType , pubKey } ) => (
< div style = { { border : '1px solid #EEF2F5' , borderRadius : '8px' , margin : '1em' , display : 'flex' , flexDirection : 'column' , justifyContent : 'space-around' , minHeight : '4em' } } >
< div style = { { margin : '3%' , wordBreak : 'break-word' } } >
< div style = { { fontSize : '14px' , color : '#939BA1' } } > { displayType } < / d i v >
< Typography type = 'body1' > { pubKey } < / T y p o g r a p h y >
< / d i v >
< / d i v >
) ;
2018-08-07 18:12:34 +00:00
const MobileAddressDisplay = ( { domainName , address , statusAccount , expirationTime , defaultAccount , isOwner } ) => (
2018-08-01 20:00:15 +00:00
< Fragment >
2018-08-07 18:12:34 +00:00
< Info background = { isOwner ? '#44D058' : '#000000' } style = { { margin : '0.4em' , boxShadow : '0px 6px 10px rgba(0, 0, 0, 0.2)' } } >
2018-08-01 20:00:15 +00:00
< Typography variant = "title" style = {
{ display : 'flex' , flexDirection : 'column' , alignItems : 'center' , justifyContent : 'space-evenly' , height : '4em' , color : '#ffffff' , textAlign : 'center' , margin : '10%' }
} >
2018-08-07 18:12:34 +00:00
{ isOwner ? < Face style = { { marginBottom : '0.5em' , fontSize : '2em' } } / > : < NotInterested style = { { marginBottom : '0.5em' , fontSize : '2em' } } / > }
2018-08-10 19:15:39 +00:00
< b > { formatName ( domainName ) } < / b >
2018-08-01 20:00:15 +00:00
< div style = { { fontWeight : 300 } } >
2018-08-07 18:12:34 +00:00
{ expirationTime && < i > Locked until { generatePrettyDate ( expirationTime ) } < / i > }
2018-08-01 20:00:15 +00:00
< / d i v >
< / T y p o g r a p h y >
< / I n f o >
2018-08-07 18:12:34 +00:00
< Typography type = 'subheading' style = { { textAlign : 'center' , fontSize : '17px' , margin : '1em 0 0.3em 0' } } > { isOwner ? 'You\'re the owner of this name' : 'Name is unavailable' } < / T y p o g r a p h y >
< Typography type = 'body2' style = { { textAlign : 'center' } } > registered to the addresses below < / T y p o g r a p h y >
2018-08-01 20:00:15 +00:00
< DisplayBox displayType = 'Wallet Address' pubKey = { address } / >
{ validStatusAddress ( statusAccount ) && < DisplayBox displayType = 'Contact Code' pubKey = { statusAccount } / > }
< / F r a g m e n t >
)
2018-06-12 15:11:28 +00:00
class RenderAddresses extends PureComponent {
2018-08-16 22:06:37 +00:00
state = { copied : false , editMenu : false }
2018-06-12 15:11:28 +00:00
render ( ) {
2018-08-01 20:00:15 +00:00
const { domainName , address , statusAccount , expirationTime , defaultAccount } = this . props
2018-08-17 19:46:59 +00:00
const { copied , editMenu , editAction } = this . state
2018-06-12 15:11:28 +00:00
const markCopied = ( v ) => { this . setState ( { copied : v } ) }
const isCopied = address => address == copied ;
const renderCopied = address => isCopied ( address ) && < span style = { { color : theme . positive } } > < IconCheck / > Copied ! < / s p a n > ;
2018-08-07 18:12:34 +00:00
const isOwner = defaultAccount === address ;
2018-08-17 16:14:09 +00:00
const onClose = value => { this . setState ( { editAction : value , editMenu : false } ) }
2018-08-17 20:28:17 +00:00
const closeReleaseAlert = value => {
if ( value ) {
release (
soliditySha3 ( domainName ) ,
hash ( 'stateofus.eth' ) ,
)
. send ( )
}
this . setState ( { editAction : null } )
}
2018-06-12 15:11:28 +00:00
return (
2018-07-31 18:24:51 +00:00
< Fragment >
< Hidden mdDown >
< div style = { { display : 'flex' , flexDirection : 'column' } } >
2018-08-10 19:15:39 +00:00
< Info . Action title = "Click to copy" > < b > { formatName ( domainName ) } < /b>{expirationTime && <i> (Expires {generatePrettyDate(expirationTime)})</i > } Resolves To : < / I n f o . A c t i o n >
2018-07-31 18:24:51 +00:00
{ address && < Text style = { { marginTop : '1em' } } > Ethereum Address { renderCopied ( address ) } < / T e x t > }
< CopyToClipboard text = { address } onCopy = { markCopied } >
< div style = { addressStyle } > { address } < / d i v >
< / C o p y T o C l i p b o a r d >
{ validStatusAddress ( statusAccount ) && < Text style = { { marginTop : '1em' } } > Status Address { renderCopied ( statusAccount ) } < / T e x t > }
{ validStatusAddress ( statusAccount ) && < CopyToClipboard text = { statusAccount } onCopy = { markCopied } >
< div style = { { ... addressStyle , color : isCopied ? theme . primary : null } } > { statusAccount } < / d i v >
< / C o p y T o C l i p b o a r d > }
< / d i v >
< / H i d d e n >
< Hidden mdUp >
2018-08-07 18:12:34 +00:00
< MobileAddressDisplay { ... this . props } isOwner = { isOwner } / >
2018-08-16 22:06:37 +00:00
{ isOwner && < MobileButton text = "Edit" style = { { marginLeft : '35%' } } onClick = { ( ) => { this . setState ( { editMenu : true } ) } } / > }
2018-08-17 16:14:09 +00:00
{ editMenu && < EditOptions open = { editMenu } onClose = { onClose } / > }
2018-08-17 19:46:59 +00:00
< ReleaseDomainAlert open = { editAction === 'release' } handleClose = { closeReleaseAlert } / >
2018-07-31 18:24:51 +00:00
< / H i d d e n >
< / F r a g m e n t >
2018-06-12 15:11:28 +00:00
)
}
}
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 ( ) } < / s p a n > c a n b e r e g i s t e r e d f o r { ! ! d o m a i n P r i c e & & f o r m a t P r i c e ( f r o m W e i ( d o m a i n P r i c e ) ) } S N T
2018-07-10 18:41:46 +00:00
< / I n f o . A c t i o n >
< / H i d d e n >
< 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-13 19:08:21 +00:00
< img src = { CheckCircle } style = { { maxWidth : '2.5em' , marginBottom : '0.5em' } } / >
2018-07-10 19:23:21 +00:00
< b > { formattedDomain . toLowerCase ( ) } < / b >
2018-07-13 19:08:21 +00:00
< div style = { { fontWeight : 300 } } >
{ ! ! domainPrice && formatPrice ( fromWei ( domainPrice ) ) } SNT / 1 year
2018-07-10 18:41:46 +00:00
< / d i v >
< / T y p o g r a p h y >
< / I n f o >
< / H i d d e n >
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
< / T y p o g r a p h y >
< / H i d d e n >
2018-07-10 18:41:46 +00:00
< / F r a g m e n t >
)
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 ( ) {
2018-07-13 19:29:11 +00:00
const { domainName , setStatus , style } = 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 (
2018-07-13 19:29:11 +00:00
< div style = { style } >
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
< / F r a g m e n t > :
< RenderAddresses { ... this . props } address = { registered . address } statusAccount = { registered . statusAccount } / > }
2018-07-13 19:29:11 +00:00
< / d i v >
2018-06-11 17:18:59 +00:00
)
}
}
2018-06-13 19:04:06 +00:00
const mapDispatchToProps = dispatch => ( {
subtractFromBalance ( amount ) {
dispatch ( accountActions . subtractfromSntTokenBalance ( amount ) ) ;
} ,
} ) ;
2018-08-01 20:00:15 +00:00
const mapStateToProps = state => ( {
defaultAccount : getDefaultAccount ( state )
} )
const ConnectedRegister = connect ( mapStateToProps , mapDispatchToProps ) ( Register ) ;
2018-06-13 19:04:06 +00:00
2018-08-01 20:00:15 +00:00
const DisplayAddress = connect ( mapStateToProps ) ( ( 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" >
2018-08-10 19:15:39 +00:00
{ props . domainName }
2018-07-10 18:41:46 +00:00
< / I n f o . A c t i o n >
< / H i d d e n >
}
2018-06-12 15:11:28 +00:00
< / F r a g m e n t >
2018-08-01 20:00:15 +00:00
) )
2018-06-06 19:37:03 +00:00
2018-08-13 11:29:02 +00:00
const LookupForm = ( { handleSubmit , values , handleChange , justSearch } ) => (
2018-07-09 21:53:56 +00:00
< Fragment >
2018-08-10 16:21:59 +00:00
< form onSubmit = { handleSubmit } onBlur = { 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 / >
< / F i e l d >
< / H i d d e n >
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 }
2018-08-13 11:29:02 +00:00
onChange = { handleChange }
2018-07-13 17:59:18 +00:00
required
wide / >
2018-07-13 19:29:11 +00:00
{ ! justSearch && < Typography variant = "subheading" style = { { color : '#939ba1' , textAlign : 'center' , marginTop : '25vh' } } >
2018-07-13 17:59:18 +00:00
Symbols * / <br/ >
are not supported
2018-07-13 19:29:11 +00:00
< / T y p o g r a p h y > }
2018-07-10 14:15:26 +00:00
< / H i d d e n >
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
< / B u t t o n >
< / H i d d e n >
2018-07-09 21:53:56 +00:00
< / f o r m >
< / F r a g m e n t >
)
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 ,
2018-08-13 11:29:02 +00:00
setStatus
2018-06-05 20:48:30 +00:00
} ) => (
2018-07-13 19:29:11 +00:00
< div >
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" / >
< / s p a n >
2018-07-09 21:53:56 +00:00
< / H i d d e n >
2018-06-11 17:18:59 +00:00
{ ! status
2018-08-13 11:29:02 +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-07-13 19:29:11 +00:00
< div >
< LookupForm { ... { handleSubmit , values , handleChange } } justSearch / >
< ConnectedRegister
2018-07-31 01:11:20 +00:00
style = { { position : 'relative' } }
2018-07-13 19:29:11 +00:00
setStatus = { setStatus }
domainName = { values . domainName } / >
< / d i v >
2018-06-11 17:18:59 +00:00
}
2018-07-09 21:53:56 +00:00
< / d i v >
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-08-10 18:29:15 +00:00
const { addr , pubkey } = PublicResolver . methods ;
2018-06-07 21:55:50 +00:00
const lookupHash = hash ( formatName ( domainName ) ) ;
const address = await addr ( lookupHash ) . call ( ) ;
2018-08-10 18:29:15 +00:00
const keys = await pubkey ( lookupHash ) . call ( ) ;
const statusAccount = keyFromXY ( keys [ 0 ] , keys [ 1 ] ) ;
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 ;