sigma prime and deja vu audits
|
@ -0,0 +1,6 @@
|
|||
# Status Mobile App Beta audit by Deja Vu
|
||||
|
||||
## Resources
|
||||
- [Summary Blog Post by Deja Vu](https://www.dejavusecurity.com/blog/status-case-study)
|
||||
- [Deja Vu Complete Report](./Deja_Vu-Status_Beta_Report_Finalized.pdf)
|
||||
- [Deja Vu Short Report](./18_DVS_Status_Case_Study_Short_Version_R2_WEB.pdf)
|
|
@ -0,0 +1,6 @@
|
|||
## ENS Usernames Contract audit by Sigma Prime
|
||||
Audit was performed on the ENS Username Contract at commit hash [eaefa92](https://github.com/status-im/ens-usernames/commit/eaefa92a258c784f1df4066e057e8170bcb6ef95#diff-dbff1e6b987cbb9a6b87ea8180c41e72)
|
||||
|
||||
## Resources
|
||||
- [Sigma Prime Blog Post](https://blog.sigmaprime.io/status-ens-review.html)
|
||||
-
|
|
@ -0,0 +1,44 @@
|
|||
# Status ENS Usernames
|
||||
|
||||
DApp to register usernames for Status Network, using ENS subnodes as usernames and Public Resolver to configure public key and/or public address.
|
||||
|
||||
|
||||
### Stateofus.eth Terms of name registration
|
||||
- 100 SNT is deposited, not spent; the amount is locked up for 1 year; only user can control their deposited funds.
|
||||
- After 1 year, the user can release the name for being registered again, and receive their deposit back.
|
||||
- Usernames are created as a subdomain of `stateofus.eth` [ENS domain](https://ens.domains/).
|
||||
- Usernames not allowed are less then 4 characters, or contained in this list (link to list), or starts with `0x` and more then 12 characters.
|
||||
- Revealing a registered not allowed username will result in loss of deposit and removal of username.
|
||||
- Users are free to own the subdomains, but they are property of Status Network, and might be subject of new terms.
|
||||
- If terms of the contract change—e.g. Status Network makes contract upgrades—the user has the right to get their deposit back and release the username, or do nothing and keep using username as long new contract allows it.
|
||||
- User's handle (name) is always secret to network until user reveals it to someone.
|
||||
- User's address(es) when associated with a username will be publicly visible.
|
||||
|
||||
Usernames eliminates the need to copy/scan - and worse, type - long hexadecimal addresses / public keys, by providing an ENS subdomain registry and recognition of ENS names in Status for interacting with other people in Status.
|
||||
|
||||
Requires https://github.com/creationix/nvm
|
||||
Usage:
|
||||
```
|
||||
nvm install v8.9.4
|
||||
nvm use v8.9.4
|
||||
npm install -g embark
|
||||
git clone https://github.com/status-im/ens-usernames.git
|
||||
cd ens-usernames
|
||||
npm install
|
||||
embark test
|
||||
embark blockchain
|
||||
embark run
|
||||
```
|
||||
|
||||
|
||||
## Deployment Details
|
||||
| Contract | Ropsten Address | Mainnet Address |
|
||||
| ---------------------------|------------------------------------------- | ------------------------------------------ |
|
||||
| ens/ENSRegistry | 0x112234455c3a32fd11230c42e7bccd4a84e02010 | 0x314159265dd8dbb310642f98f50c066173c1259b |
|
||||
| ens/PublicResolver | 0x29754bADB2640b98F6deF0f52D41418b0d2e0C51 | 0x5FfC014343cd971B7eb70732021E26C35B744cc4 |
|
||||
| token/TestToken | 0xc55cF4B03948D7EBc8b9E8BAD92643703811d162 | 0x744d70fdbe2ba4cf95131626614a1763df805b9e |
|
||||
| registry/UsernameRegistrar | 0x028F3Df706c5295Ba283c326F4692c375D14cb68 | 0xDBf9038cf5Aaa030890790dB87E746E00Fc352b3 |
|
||||
| common/MerkleProof | 0x5df00E70AD165D50228DB6d8285fB6EAAc630FD7 | 0x713ED9846463235df08D92B886938651105D3940 |
|
||||
| test/MerkleProofWrapper | 0x58E01078d14142E0370526dFdAE44E4f508c844B | 0x76E55E13C5891a90f7fCA2e1238a6B3463F564e2 |
|
||||
| common/SafeMath | 0x0F9992f7737f9ba3aceD170D4D1259cb2CEcc050 | 0xA115a57952D3337e2a1aB3Cb82bA376EEcDDc469 |
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-syntax-import-meta",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-json-strings",
|
||||
[
|
||||
"@babel/plugin-proposal-decorators",
|
||||
{
|
||||
"legacy": true
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-function-sent",
|
||||
"@babel/plugin-proposal-export-namespace-from",
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-throw-expressions"
|
||||
],
|
||||
"presets": [],
|
||||
"ignore": [
|
||||
"config/",
|
||||
"node_modules"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "airbnb",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/prop-types": 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
gitdir: ../../.git/modules/code/ens-usernames
|
|
@ -0,0 +1 @@
|
|||
*.sol linguist-language=Solidity
|
|
@ -0,0 +1,44 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# embark
|
||||
.embark/
|
||||
chains.json
|
||||
config/production/password
|
||||
config/livenet/password
|
||||
|
||||
# egg-related
|
||||
viper.egg-info/
|
||||
build/
|
||||
dist/
|
||||
.eggs/
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Coverage tests
|
||||
.coverage
|
||||
.cache/
|
||||
coverage/
|
||||
coverageEnv/
|
||||
coverage.json
|
||||
|
||||
# node
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
package-lock.json
|
||||
|
||||
# other
|
||||
.vs/
|
||||
bin/
|
||||
.idea/
|
|
@ -0,0 +1,51 @@
|
|||
import ERC20Token from 'Embark/contracts/ERC20Token'
|
||||
import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar'
|
||||
import TestToken from 'Embark/contracts/TestToken'
|
||||
|
||||
import { getDefaultAccount } from '../utils/web3Helpers'
|
||||
import { actions as accountActions } from '../reducers/accounts'
|
||||
import { isNil } from 'lodash'
|
||||
|
||||
const { receiveAccounts, receiveStatusContactCode } = accountActions
|
||||
const CONTACT_CODE = 'CONTACT_CODE'
|
||||
const STATUS_API_REQUEST = 'STATUS_API_REQUEST'
|
||||
const hasContactCode = () => !isNil(STATUS_API) && !isNil(STATUS_API[CONTACT_CODE])
|
||||
const statusApiSuccess = event => event.detail.permissions[0] === CONTACT_CODE
|
||||
const getContactCode = event => event.detail.data[CONTACT_CODE]
|
||||
|
||||
export const fetchAndDispatchAccountsWithBalances = (web3, dispatch) => {
|
||||
web3.eth.getAccounts((err, addresses) => {
|
||||
if (addresses) {
|
||||
const defaultAccount = web3.eth.defaultAccount || addresses[0]
|
||||
const accounts = addresses.map(async address => {
|
||||
const balance = await web3.eth.getBalance(address, 'latest')
|
||||
const SNTBalance = await ERC20Token.methods.balanceOf(address).call()
|
||||
return { address, balance, SNTBalance }
|
||||
})
|
||||
Promise.all(accounts).then(accounts => {
|
||||
dispatch(receiveAccounts(defaultAccount, accounts))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
export const checkAndDispatchStatusContactCode = dispatch => {
|
||||
window.addEventListener('statusapi', function (event) {
|
||||
if (statusApiSuccess(event)) dispatch(receiveStatusContactCode(getContactCode(event)))
|
||||
});
|
||||
|
||||
setTimeout(
|
||||
() => { window.postMessage({ type: STATUS_API_REQUEST, permissions: ["CONTACT_CODE", "CONTACTS"] }, '*') },
|
||||
1000
|
||||
)
|
||||
}
|
||||
|
||||
export const fetchAndDispatchSNTAllowance = dispatch => {
|
||||
const { methods: { allowance } } = TestToken;
|
||||
const { receiveSntAllowance } = accountActions;
|
||||
const spender = UsernameRegistrar._address;
|
||||
allowance(getDefaultAccount(), spender)
|
||||
.call()
|
||||
.then(allowance => {
|
||||
dispatch(receiveSntAllowance(allowance))
|
||||
})
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import { Tabs, Tab } from 'react-bootstrap';
|
||||
import TopNavbar from './topnavbar';
|
||||
import TestTokenUI from './testtoken';
|
||||
import ERC20TokenUI from './erc20token';
|
||||
import ENSSubManagement from './ensSubManagement';
|
||||
import NameLookup from './ens/nameLookup';
|
||||
|
||||
|
||||
const AdminMode = () => (
|
||||
<Fragment>
|
||||
<TopNavbar />
|
||||
<Tabs defaultActiveKey={1} id="uncontrolled-tab-example">
|
||||
<Tab eventKey={1} title="TestToken">
|
||||
<TestTokenUI />
|
||||
</Tab>
|
||||
<Tab eventKey={2} title="ERC20Token">
|
||||
<ERC20TokenUI />
|
||||
</Tab>
|
||||
<Tab eventKey={3} title="ENS Management">
|
||||
<ENSSubManagement />
|
||||
</Tab>
|
||||
<Tab eventKey={4} title="Name Lookup">
|
||||
<NameLookup />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default AdminMode;
|
|
@ -0,0 +1,66 @@
|
|||
import web3 from 'Embark/web3'
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Nav, MenuItem, NavDropdown } from 'react-bootstrap';
|
||||
import Blockies from 'react-blockies';
|
||||
import { string, bool, func, arrayOf, shape } from 'prop-types';
|
||||
import { getAccounts, getDefaultAccount, accountsIsLoading, actions as accountActions } from '../reducers/accounts';
|
||||
import './accountlist.css';
|
||||
|
||||
const AccList = ({
|
||||
accounts, defaultAccount, changeAccount, isLoading, classNameNavDropdown,
|
||||
}) => (
|
||||
<React.Fragment>
|
||||
{!isLoading ?
|
||||
<div className="accounts">
|
||||
<div className="selectedIdenticon">
|
||||
<Blockies seed={defaultAccount} />
|
||||
</div>
|
||||
<div className="accountList">
|
||||
<Nav>
|
||||
<NavDropdown key={1} title={defaultAccount} id="basic-nav-dropdown" className={classNameNavDropdown}>
|
||||
{accounts.map(account => (
|
||||
<MenuItem key={account.address} onClick={() => changeAccount(account.address)}>
|
||||
<div className="account">
|
||||
<div className="accountIdenticon">
|
||||
<Blockies seed={account.address} />
|
||||
</div>
|
||||
<div className="accountHexString">
|
||||
{account.address}
|
||||
</div>
|
||||
<div className="accountBalance">
|
||||
Ξ {account.balance / (10 ** 18)}
|
||||
</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
))}
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
</div>
|
||||
</div>
|
||||
: <div>Loading...</div>}
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
AccList.propTypes = {
|
||||
accounts: arrayOf(shape({ address: string, balance: string })).isRequired,
|
||||
defaultAccount: string,
|
||||
changeAccount: func.isRequired,
|
||||
isLoading: bool.isRequired,
|
||||
classNameNavDropdown: string
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
accounts: getAccounts(state),
|
||||
defaultAccount: getDefaultAccount(state),
|
||||
isLoading: accountsIsLoading(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
changeAccount(address) {
|
||||
web3.eth.defaultAccount = address;
|
||||
dispatch(accountActions.updateDefaultAccount(address));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AccList);
|
|
@ -0,0 +1,37 @@
|
|||
.identicon {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.selectedIdenticon {
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
margin: 7px 0;
|
||||
}
|
||||
|
||||
.accountHexString {
|
||||
margin-left: 7px;
|
||||
width: 267px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display:inline-block;
|
||||
}
|
||||
|
||||
.accountBalance {
|
||||
margin-left: 10px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
width:77px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.accountList {
|
||||
float: left;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.account {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="62px" height="98px" viewBox="0 0 62 98" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 39.1 (31720) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<polygon id="path-1" points="0.453356748 55.2658632 30.5000004 72.7014496 60.5810903 55.1361244 30.5000009 97.240191"></polygon>
|
||||
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-2">
|
||||
<feGaussianBlur stdDeviation="1.5" in="SourceAlpha" result="shadowBlurInner1"></feGaussianBlur>
|
||||
<feOffset dx="0" dy="0" in="shadowBlurInner1" result="shadowOffsetInner1"></feOffset>
|
||||
<feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"></feComposite>
|
||||
<feColorMatrix values="0 0 0 0 0.337254902 0 0 0 0 0.337254902 0 0 0 0 0.337254902 0 0 0 0.5 0" type="matrix" in="shadowInnerInner1"></feColorMatrix>
|
||||
</filter>
|
||||
<polygon id="path-3" points="0 49.4579439 30.5000008 0 60.706916 49.4579439 30.5 67.7757009"></polygon>
|
||||
<filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="filter-4">
|
||||
<feGaussianBlur stdDeviation="1.5" in="SourceAlpha" result="shadowBlurInner1"></feGaussianBlur>
|
||||
<feOffset dx="0" dy="0" in="shadowBlurInner1" result="shadowOffsetInner1"></feOffset>
|
||||
<feComposite in="shadowOffsetInner1" in2="SourceAlpha" operator="arithmetic" k2="-1" k3="1" result="shadowInnerInner1"></feComposite>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.4 0" type="matrix" in="shadowInnerInner1"></feColorMatrix>
|
||||
</filter>
|
||||
</defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Maker_LANDING_PAGE" transform="translate(-689.000000, -491.000000)">
|
||||
<g id="Group" transform="translate(689.500000, 491.000000)">
|
||||
<g id="Rectangle-3">
|
||||
<use fill="#8C8C8E" fill-rule="evenodd" xlink:href="#path-1"></use>
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-2)" xlink:href="#path-1"></use>
|
||||
</g>
|
||||
<g id="Rectangle-4">
|
||||
<use fill="#4C4C51" fill-rule="evenodd" xlink:href="#path-3"></use>
|
||||
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-3"></use>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 123 KiB |
|
@ -0,0 +1,72 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import ListItemText from '@material-ui/core/ListItemText';
|
||||
import DialogTitle from '@material-ui/core/DialogTitle';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import PersonIcon from '@material-ui/icons/Person';
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import DeleteOutline from '@material-ui/icons/Delete';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import blue from '@material-ui/core/colors/blue';
|
||||
|
||||
const styles = {
|
||||
paper: {
|
||||
margin: 0,
|
||||
maxWidth: '100%',
|
||||
borderRadius: '6px 6px 0 0',
|
||||
},
|
||||
list: {
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
margin: 0
|
||||
}
|
||||
};
|
||||
|
||||
class SimpleDialog extends React.Component {
|
||||
handleClose = () => {
|
||||
this.props.onClose(this.props.selectedValue);
|
||||
};
|
||||
|
||||
handleListItemClick = value => {
|
||||
this.props.onClose(value);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes, onClose, selectedValue, ...other } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog classes={{paper: classes.paper,}} onClose={this.handleClose} fullWidth paperFullWidth style={{alignItems: 'flex-end'}} aria-labelledby="simple-dialog-title" {...other}>
|
||||
<List>
|
||||
<ListItem button onClick={() => this.handleListItemClick('edit')} key="edit">
|
||||
<ListItemIcon>
|
||||
<EditIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Edit Contact Code" />
|
||||
</ListItem>
|
||||
<ListItem button onClick={() => this.handleListItemClick('release')}>
|
||||
<ListItemIcon>
|
||||
<DeleteOutline />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary="Release Name" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SimpleDialog.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func,
|
||||
selectedValue: PropTypes.string,
|
||||
};
|
||||
|
||||
const SimpleDialogWrapped = withStyles(styles)(SimpleDialog);
|
||||
export default SimpleDialogWrapped;
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import React from 'react';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Button from '@material-ui/core/Button';
|
||||
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 Slide from '@material-ui/core/Slide';
|
||||
import classNames from "classnames";
|
||||
|
||||
function Transition(props) {
|
||||
return <Slide direction="up" {...props} />;
|
||||
}
|
||||
|
||||
const styles = theme => ({
|
||||
dialog: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
actions: {
|
||||
background: 'rgba(255, 255, 255, 0.8)',
|
||||
margin: 0,
|
||||
borderTop: 'solid 1px #ccc',
|
||||
},
|
||||
button: {
|
||||
margin: '0',
|
||||
fontSize: '17px',
|
||||
color: '#007AFF',
|
||||
width: '50%',
|
||||
borderRight: 'solid 1px #ccc',
|
||||
borderRadius: 0,
|
||||
padding: '15px',
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const ReleaseDomainAlert = ({ classes, open, handleClose }) => (
|
||||
<div>
|
||||
<Dialog
|
||||
className={classNames(classes.dialog)}
|
||||
open={open}
|
||||
TransitionComponent={Transition}
|
||||
keepMounted
|
||||
onClose={handleClose}
|
||||
aria-labelledby="alert-dialog-slide-title"
|
||||
aria-describedby="alert-dialog-slide-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-slide-title">
|
||||
{"Release domain?"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-slide-description">
|
||||
Your SNT deposit will be returned and name will be available to other users.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions className={classNames(classes.actions)}>
|
||||
<Button onClick={() => handleClose(null)} className={classNames(classes.button)} color="primary">
|
||||
<strong>Cancel</strong>
|
||||
</Button>
|
||||
<Button onClick={() => handleClose(true)} className={classNames(classes.button)} color="primary">
|
||||
Yes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
export default withStyles(styles)(ReleaseDomainAlert);
|
|
@ -0,0 +1,92 @@
|
|||
import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar';
|
||||
import web3 from 'web3';
|
||||
import ENSRegistry from 'Embark/contracts/ENSRegistry';
|
||||
import React from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import FieldGroup from '../standard/FieldGroup';
|
||||
import { withFormik } from 'formik';
|
||||
import { hash } from 'eth-ens-namehash';
|
||||
import { debounce } from 'lodash/fp';
|
||||
|
||||
const { methods: { owner } } = ENSRegistry;
|
||||
|
||||
const delay = debounce(500);
|
||||
const getRegistry = (hashedRegistry, registrys) => registrys(hashedRegistry).call();
|
||||
const registryIsOwner = address => address == UsernameRegistrar._address;
|
||||
const fetchOwner = registryName => owner(hash(registryName)).call();
|
||||
const debounceFetchOwner = delay(fetchOwner);
|
||||
const getAndIsOwner = async registryName => {
|
||||
const address = await debounceFetchOwner(registryName);
|
||||
return registryIsOwner(address);
|
||||
}
|
||||
const fetchRegistry = delay(getRegistry);
|
||||
const setPrice = (registryFn, price) => registryFn(price || 0).send();
|
||||
|
||||
const InnerForm = ({
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
isSubmitting,
|
||||
}) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FieldGroup
|
||||
id="registryName"
|
||||
name="registryName"
|
||||
type="text"
|
||||
label="Registry Name"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.registryName}
|
||||
error={errors.registryName}
|
||||
/>
|
||||
<FieldGroup
|
||||
id="registryPrice"
|
||||
name="registryPrice"
|
||||
type="number"
|
||||
label="Registry Price"
|
||||
placeholder="(Optional) Registry will be free if left blank"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.registryPrice}
|
||||
/>
|
||||
<Button bsStyle="primary" type="submit" disabled={isSubmitting || !!Object.keys(errors).length}>{!isSubmitting ? 'Submit' : 'Submitting to the Blockchain - (this may take awhile)'}</Button>
|
||||
</form>
|
||||
)
|
||||
|
||||
const AddRegistry = withFormik({
|
||||
mapPropsToValues: props => ({ registryName: '', registryPrice: '' }),
|
||||
async validate(values) {
|
||||
const { registryName } = values;
|
||||
const errors = {};
|
||||
if (!registryName) errors.registryName = 'Required';
|
||||
if (registryName && !await getAndIsOwner(registryName)) errors.registryName = 'This registry is not owned by registry';
|
||||
if (Object.keys(errors).length) throw errors;
|
||||
},
|
||||
async handleSubmit(values, { setSubmitting }) {
|
||||
const { registryName, registryPrice } = values;
|
||||
const { methods: { state, activate, updateRegistryPrice } } = UsernameRegistrar;
|
||||
const { registryState } = await state();
|
||||
console.log(
|
||||
'Inputs for setPrice',
|
||||
Number(registryState) ? 'updateRegistryPrice' : 'activate',
|
||||
web3.utils.toWei(registryPrice.toString(), 'ether'),
|
||||
);
|
||||
setPrice(
|
||||
Number(registryState) ? updateRegistryPrice : activate,
|
||||
web3.utils.toWei(registryPrice.toString(), 'ether'),
|
||||
)
|
||||
.then(res => {
|
||||
setSubmitting(false);
|
||||
console.log(res);
|
||||
})
|
||||
.catch(err => {
|
||||
setSubmitting(false);
|
||||
console.log(err);
|
||||
})
|
||||
}
|
||||
})(InnerForm);
|
||||
|
||||
export default AddRegistry;
|
|
@ -0,0 +1,23 @@
|
|||
export default {
|
||||
release: {
|
||||
title: {
|
||||
sub: 'Done!',
|
||||
body: 'The released username will be available to other users'
|
||||
},
|
||||
subheading: 'Follow the progress in the Transaction History section of your wallet.'
|
||||
},
|
||||
registered: {
|
||||
title: {
|
||||
sub: 'Nice!',
|
||||
body: 'The name is yours once the transaction is complete'
|
||||
},
|
||||
subheading: 'Follow the progress in the Transaction History section of your wallet.'
|
||||
},
|
||||
edit: {
|
||||
title: {
|
||||
sub: 'Done!',
|
||||
body: 'Your changes will be saved when the transaction is complete'
|
||||
},
|
||||
subheading: 'Follow the progress in the Transaction History section of your wallet.'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar';
|
||||
import web3 from 'web3';
|
||||
import React from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import FieldGroup from '../standard/FieldGroup';
|
||||
import { withFormik } from 'formik';
|
||||
|
||||
const InnerForm = ({
|
||||
values,
|
||||
errors,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
isSubmitting,
|
||||
}) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FieldGroup
|
||||
id="newAddress"
|
||||
name="newAddress"
|
||||
type="text"
|
||||
label="New Controller Address"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.newAddress}
|
||||
error={errors.newAddress}
|
||||
/>
|
||||
<Button bsStyle="primary" type="submit" disabled={isSubmitting || !!Object.keys(errors).length}>{!isSubmitting ? 'Submit' : 'Submitting to the Blockchain - (this may take awhile)'}</Button>
|
||||
</form>
|
||||
)
|
||||
|
||||
const MoveDomain = withFormik({
|
||||
mapPropsToValues: props => ({ newAddress: '' }),
|
||||
async validate(values) {
|
||||
const { utils: { isAddress } } = web3;
|
||||
const { newAddress } = values;
|
||||
const errors = {};
|
||||
if (!isAddress(newAddress)) errors.newAddress = 'Please enter a valid address'
|
||||
if (Object.keys(errors).length) throw errors;
|
||||
},
|
||||
async handleSubmit(values, { setSubmitting }) {
|
||||
const { newAddress } = values;
|
||||
const { methods: { moveDomain } } = UsernameRegistrar;
|
||||
console.log(
|
||||
`inputs for moveDomain:}`,
|
||||
newAddress
|
||||
);
|
||||
|
||||
moveDomain(newAddress)
|
||||
.send()
|
||||
.then((res) => {
|
||||
setSubmitting(false);
|
||||
console.log(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
setSubmitting(false);
|
||||
console.log(err);
|
||||
})
|
||||
}
|
||||
})(InnerForm);
|
||||
|
||||
export default MoveDomain;
|
|
@ -0,0 +1,383 @@
|
|||
import React, { Fragment, PureComponent } from 'react';
|
||||
import web3 from 'web3';
|
||||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import { connect } from 'react-redux';
|
||||
import { actions as accountActions, getDefaultAccount } from '../../reducers/accounts';
|
||||
import { hash } from 'eth-ens-namehash';
|
||||
import { isNil } from 'lodash';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar';
|
||||
import ENSRegistry from 'Embark/contracts/ENSRegistry';
|
||||
import { Button, Field, TextInput, MobileSearch, MobileButton, Card, Info, Text } from '../../ui/components'
|
||||
import { IconCheck } from '../../ui/icons'
|
||||
import { keyFromXY } from '../../utils/ecdsa';
|
||||
import EditOptions from './EditOptions';
|
||||
import ReleaseDomainAlert from './ReleaseDomain';
|
||||
import theme from '../../ui/theme'
|
||||
import { withFormik } from 'formik';
|
||||
import PublicResolver from 'Embark/contracts/PublicResolver';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import RegisterSubDomain from '../ens/registerSubDomain';
|
||||
import StatusLogo from '../../ui/icons/components/StatusLogo'
|
||||
import EnsLogo from '../../ui/icons/logos/ens.png';
|
||||
import { formatPrice } from '../ens/utils';
|
||||
import CheckCircle from '../../ui/icons/components/baseline_check_circle_outline.png';
|
||||
import WarningIcon from '../../ui/icons/svg/warning.svg';
|
||||
const { getPrice, getReleaseTime, release } = UsernameRegistrar.methods;
|
||||
import NotInterested from '@material-ui/icons/NotInterested';
|
||||
import Face from '@material-ui/icons/Face';
|
||||
import Copy from './copy';
|
||||
import IDNANormalizer from 'idna-normalize';
|
||||
import { nullAddress, getResolver } from './utils/domain';
|
||||
|
||||
const normalizer = new IDNANormalizer();
|
||||
const invalidSuffix = '0000000000000000000000000000000000000000'
|
||||
const validAddress = address => address != nullAddress;
|
||||
const validStatusAddress = address => !address.includes(invalidSuffix);
|
||||
const formatName = domainName => domainName.includes('.') ? normalizer.normalize(domainName) : normalizer.normalize(`${domainName}.stateofus.eth`);
|
||||
const getDomain = fullDomain => formatName(fullDomain).split('.').slice(1).join('.');
|
||||
const hashedDomain = domainName => hash(getDomain(domainName));
|
||||
const registryIsOwner = address => address == UsernameRegistrar._address;
|
||||
const { soliditySha3, fromWei } = web3.utils;
|
||||
|
||||
|
||||
const cardStyle = {
|
||||
width: '100%',
|
||||
padding: '30px',
|
||||
height: '425px'
|
||||
}
|
||||
|
||||
const addressStyle = {
|
||||
fontSize: '18px',
|
||||
fontWeight: 400,
|
||||
cursor: 'copy',
|
||||
wordWrap: 'break-word',
|
||||
}
|
||||
|
||||
const backButton = {
|
||||
fontSize: '40px',
|
||||
color: theme.accent,
|
||||
cursor: 'pointer'
|
||||
}
|
||||
|
||||
const validTimestamp = timestamp => Number(timestamp) > 99999999;
|
||||
const generatePrettyDate = timestamp => new Date(timestamp * 1000).toDateString();
|
||||
|
||||
const DisplayBox = ({ displayType, pubKey }) => (
|
||||
<div>
|
||||
<div style={{ fontSize: '14px', color: '#939BA1', margin: '0 1em' }}>{displayType}</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'>{pubKey}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const MobileAddressDisplay = ({ domainName, address, statusAccount, releaseTime, defaultAccount, isOwner, edit, onSubmit, handleChange, values, handleSubmit }) => (
|
||||
<Fragment>
|
||||
<LookupForm {...{ handleSubmit, values, handleChange }} justSearch />
|
||||
<Info background={isOwner ? '#44D058' : '#000000'} style={{ margin: '0.4em', boxShadow: '0px 6px 10px rgba(0, 0, 0, 0.2)' }}>
|
||||
<Typography variant="title" style={
|
||||
{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'space-evenly', height: '4em', color: '#ffffff', textAlign: 'center', margin: '10%' }
|
||||
}>
|
||||
{isOwner ? <Face style={{ marginBottom: '0.5em', fontSize: '2em' }} /> : <NotInterested style={{ marginBottom: '0.5em', fontSize: '2em' }}/>}
|
||||
<b>{formatName(domainName)}</b>
|
||||
<div style={{ fontWeight: 300, fontSize: '15px', marginTop: '10px' }}>
|
||||
{isOwner
|
||||
? edit ? 'Edit Contact Code' : 'You own this ENS name'
|
||||
: 'unavailable'}
|
||||
|
||||
</div>
|
||||
</Typography>
|
||||
</Info>
|
||||
<Typography type='subheading' style={{ textAlign: 'center', fontSize: '17px', fontWeight: '500', margin: '1.5em 0 0.3em 0' }}>
|
||||
Registered {validTimestamp(releaseTime)}
|
||||
</Typography>
|
||||
<Typography type='body2' style={{ textAlign: 'center', margin: 10 }}>
|
||||
{edit
|
||||
? 'The contact code connects the domain with a unique Status account'
|
||||
: validAddress(address) ? 'to the addresses below' : 'Click \'Edit\' to add a valid address and contact code'}
|
||||
</Typography>
|
||||
{edit && <RegisterSubDomain
|
||||
subDomain={domainName}
|
||||
domainName="stateofus.eth"
|
||||
domainPrice="DO NOT SHOW"
|
||||
editAccount={true}
|
||||
preRegisteredCallback={onSubmit}
|
||||
registeredCallbackFn={console.log} />}
|
||||
{!edit && <DisplayBox displayType='Your wallet address' pubKey={address} />}
|
||||
{!edit && validStatusAddress(statusAccount) && <DisplayBox displayType='Your contact code' pubKey={statusAccount} />}
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
class RenderAddresses extends PureComponent {
|
||||
state = { copied: false, editMenu: false, editAction: false }
|
||||
|
||||
render() {
|
||||
const { domainName, address, statusAccount, releaseTime, defaultAccount, ownerAddress, setStatus, registryOwnsDomain } = this.props
|
||||
const { copied, editMenu, editAction, submitted } = 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>;
|
||||
const onClose = value => { this.setState({ editAction: value, editMenu: false }) }
|
||||
const onClickEdit = () => { validAddress(address) ? this.setState({ editMenu: true }) : this.setState({ editAction: 'edit' }) }
|
||||
const isOwner = defaultAccount === ownerAddress;
|
||||
const closeReleaseAlert = value => {
|
||||
if (!isNil(value)) {
|
||||
this.setState({ submitted: true })
|
||||
release(
|
||||
soliditySha3(domainName)
|
||||
)
|
||||
.send()
|
||||
} else {
|
||||
this.setState({ editAction: null })
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Fragment>
|
||||
<Hidden mdDown>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', margin: 50 }}>
|
||||
<Info.Action title="Click to copy"><b>{formatName(domainName)}</b>{releaseTime && <i> (Expires {generatePrettyDate(releaseTime)})</i>} Resolves To:</Info.Action>
|
||||
{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>
|
||||
</Hidden>
|
||||
<Hidden mdUp>
|
||||
{submitted ? <TransactionComplete type={editAction} setStatus={setStatus} /> : <MobileAddressDisplay {...this.props} isOwner={isOwner} edit={editAction === 'edit'} onSubmit={() => { this.setState({ submitted: true}) }}/>}
|
||||
{isOwner && !editAction && <MobileButton text="Edit" style={{ margin: 'auto', display: 'block' }} onClick={onClickEdit}/>}
|
||||
<EditOptions open={editMenu} onClose={onClose} />
|
||||
<ReleaseDomainAlert open={editAction === 'release' && !submitted} handleClose={closeReleaseAlert} />
|
||||
</Hidden>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const RegisterInfoCard = ({ formattedDomain, domainPrice, registryOwnsDomain }) => (
|
||||
<Fragment>
|
||||
<Hidden mdDown>
|
||||
<Info.Action title="No address is associated with this domain">
|
||||
<span style={{ color: theme.accent }}>{formattedDomain.toLowerCase()}</span> can be registered for {!!domainPrice && formatPrice(fromWei(domainPrice))} SNT
|
||||
</Info.Action>
|
||||
</Hidden>
|
||||
<Hidden mdUp>
|
||||
<Info background="#415be3" style={{ margin: '0.4em' }}>
|
||||
<Typography variant="title" style={
|
||||
{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'space-evenly', height: '4em', color: '#ffffff', textAlign: 'center', margin: '10%' }
|
||||
}>
|
||||
<img src={CheckCircle} style={{ maxWidth: '2.5em', marginBottom: '0.5em' }} />
|
||||
<b>{formattedDomain.toLowerCase()}</b>
|
||||
<div style={{ fontWeight: 300, fontSize: '15px' }}>
|
||||
available
|
||||
</div>
|
||||
</Typography>
|
||||
</Info>
|
||||
</Hidden>
|
||||
<Hidden mdUp>
|
||||
<Typography style={{ textAlign: 'center', fontSize: '17px', fontWeight: '500', margin: '1.5em 0 0.3em 0' }}>
|
||||
{!!domainPrice && formatPrice(fromWei(domainPrice))} SNT to register
|
||||
</Typography>
|
||||
<Typography style={{ textAlign: 'center', padding: '1.5em' }}>
|
||||
{registryOwnsDomain ?
|
||||
'Add your contact code to use your name in Status chat.' :
|
||||
'This domain is not owned by the registry'}
|
||||
</Typography>
|
||||
</Hidden>
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
const TransactionComplete = ({ type, setStatus }) => (
|
||||
<div style={{ textAlign: 'center', margin: '40% 15 10' }}>
|
||||
<Typography variant="title" style={{ marginBottom: '1rem' }}>
|
||||
{Copy[type]['title']['sub']}<br/>
|
||||
{Copy[type]['title']['body']}
|
||||
</Typography>
|
||||
<Typography variant="subheading" style={{ color: '#939BA1' }}>
|
||||
{Copy[type]['subheading']}
|
||||
</Typography>
|
||||
<MobileButton text="Main Page" style={{ marginTop: '12rem' }} onClick={() => { setStatus(null) } } />
|
||||
</div>
|
||||
);
|
||||
|
||||
class Register extends PureComponent {
|
||||
state = { domainPrice: null };
|
||||
|
||||
componentDidMount() {
|
||||
const { domainName } = this.props;
|
||||
getPrice()
|
||||
.call()
|
||||
.then((res) => { this.setState({ domainPrice: res })});
|
||||
}
|
||||
|
||||
onRegistered = (address, statusAccount) => {
|
||||
const { domainPrice } = this.state;
|
||||
const { subtractFromBalance } = this.props;
|
||||
subtractFromBalance(domainPrice);
|
||||
this.setState({ registered: { address, statusAccount } });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { domainName, setStatus, style, registryOwnsDomain, ownerAddress, defaultAccount } = this.props;
|
||||
const { domainPrice, registered, submitted } = this.state;
|
||||
const formattedDomain = formatName(domainName);
|
||||
const formattedDomainArray = formattedDomain.split('.');
|
||||
const isOwner = defaultAccount === ownerAddress;
|
||||
return (
|
||||
<div style={style}>
|
||||
{!registered && !submitted ?
|
||||
<Fragment>
|
||||
<RegisterInfoCard {...{ formattedDomain, domainPrice, registryOwnsDomain }}/>
|
||||
{registryOwnsDomain &&
|
||||
<RegisterSubDomain
|
||||
subDomain={formattedDomainArray[0]}
|
||||
domainName={formattedDomainArray.slice(1).join('.')}
|
||||
domainPrice={domainPrice}
|
||||
preRegisteredCallback={() => { this.setState({ submitted: true }) }}
|
||||
registeredCallbackFn={this.onRegistered} />}
|
||||
</Fragment> :
|
||||
submitted && !registered ? <TransactionComplete type="registered" setStatus={setStatus} /> : <RenderAddresses {...this.props} address={registered.address} statusAccount={registered.statusAccount} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
subtractFromBalance(amount) {
|
||||
dispatch(accountActions.subtractfromSntTokenBalance(amount));
|
||||
},
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
defaultAccount: getDefaultAccount(state)
|
||||
})
|
||||
|
||||
const ConnectedRegister = connect(mapStateToProps, mapDispatchToProps)(Register);
|
||||
|
||||
const DisplayAddress = connect(mapStateToProps)((props) => (
|
||||
<Fragment>
|
||||
{validAddress(props.address) || props.defaultAccount === props.ownerAddress ?
|
||||
<RenderAddresses {...props} />
|
||||
:
|
||||
<Hidden mdUp>
|
||||
<Info.Action title="No address is associated with this domain">
|
||||
{props.domainName}
|
||||
</Info.Action>
|
||||
</Hidden>
|
||||
}
|
||||
</Fragment>
|
||||
))
|
||||
|
||||
const LookupForm = ({ handleSubmit, values, handleChange, justSearch }) => (
|
||||
<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 />
|
||||
{!justSearch &&
|
||||
<div style={{ textAlign: 'center', marginTop: '40vh' }}>
|
||||
<img style={{ display: 'block', margin: '0 auto 15px' }} src={WarningIcon} />
|
||||
Names are made with<br/>letters and numbers only
|
||||
</div>
|
||||
}
|
||||
</Hidden>
|
||||
<Hidden mdDown>
|
||||
<Button mode="strong" type="submit" style={{ marginLeft: '3%', maxWidth: '95%' }} wide>
|
||||
Lookup Address
|
||||
</Button>
|
||||
</Hidden>
|
||||
</form>
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
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
|
||||
? <LookupForm {...{ handleSubmit, values, handleChange }} />
|
||||
: validAddress(status.address) || defaultAccount === status.ownerAddress ?
|
||||
<DisplayAddress
|
||||
{...{ handleSubmit, values, handleChange }}
|
||||
domainName={status.resolvedDomainName}
|
||||
address={status.address}
|
||||
statusAccount={status.statusAccount}
|
||||
releaseTime={status.releaseTime}
|
||||
ownerAddress={status.ownerAddress}
|
||||
registryOwnsDomain={status.registryOwnsDomain}
|
||||
setStatus={setStatus} /> :
|
||||
<div>
|
||||
<LookupForm {...{ handleSubmit, values, handleChange }} justSearch />
|
||||
<ConnectedRegister
|
||||
style={{ position: 'relative' }}
|
||||
setStatus={setStatus}
|
||||
registryOwnsDomain={status.registryOwnsDomain}
|
||||
ownerAddress={status.ownerAddress}
|
||||
domainName={status.resolvedDomainName} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
|
||||
const NameLookup = withFormik({
|
||||
mapPropsToValues: props => ({ domainName: '' }),
|
||||
async handleSubmit(values, { status, setSubmitting, setStatus }) {
|
||||
const { domainName } = values;
|
||||
const { methods: { owner, resolver } } = ENSRegistry;
|
||||
const lookupHash = hash(formatName(domainName));
|
||||
const resolverContract = await getResolver(lookupHash);
|
||||
const { addr, pubkey } = resolverContract.methods;
|
||||
const address = addr(lookupHash).call();
|
||||
const keys = pubkey(lookupHash).call();
|
||||
const ownerAddress = owner(lookupHash).call();
|
||||
const suffixOwner = owner(hash(getDomain(domainName))).call();
|
||||
const releaseTime = getReleaseTime(lookupHash).call();
|
||||
Promise.all([address, keys, ownerAddress, releaseTime, suffixOwner])
|
||||
.then(([ address, keys, ownerAddress, releaseTime, suffixOwner ]) => {
|
||||
const statusAccount = keyFromXY(keys[0], keys[1]);
|
||||
const registryOwnsDomain = registryIsOwner(suffixOwner)
|
||||
const resolvedDomainName = domainName;
|
||||
setStatus({ address, statusAccount, releaseTime, ownerAddress, registryOwnsDomain, resolvedDomainName });
|
||||
})
|
||||
}
|
||||
})(InnerForm)
|
||||
|
||||
export default connect(mapStateToProps)(NameLookup);
|
|
@ -0,0 +1,243 @@
|
|||
import web3 from "Embark/web3"
|
||||
import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar';
|
||||
import PublicResolver from 'Embark/contracts/PublicResolver';
|
||||
import TestToken from 'Embark/contracts/TestToken';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import { Button, MobileSearch, MobileButton, Field } 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 Typography from '@material-ui/core/Typography';
|
||||
import TokenPermissions from '../standard/TokenPermissionConnect';
|
||||
import Terms from './terms';
|
||||
import { generateXY } from '../../utils/ecdsa';
|
||||
import { getResolver } from './utils/domain';
|
||||
|
||||
const { soliditySha3, fromWei } = web3.utils;
|
||||
|
||||
|
||||
const DisplayBox = ({ displayType, pubKey }) => (
|
||||
<div>
|
||||
<div style={{ fontSize: '14px', color: '#939BA1', margin: '0 1em' }}>{displayType}</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'>{pubKey}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const formRef = React.createRef();
|
||||
const displayTerms = status => status === 'terms';
|
||||
const InnerForm = ({
|
||||
values,
|
||||
errors,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
isSubmitting,
|
||||
setFieldValue,
|
||||
subDomain,
|
||||
domainName,
|
||||
domainPrice,
|
||||
editAccount,
|
||||
setStatus,
|
||||
status,
|
||||
SNTAllowance,
|
||||
SNTBalance,
|
||||
}) => (
|
||||
<form onSubmit={handleSubmit} ref={formRef}>
|
||||
<div style={{ margin: '10px' }}>
|
||||
{!subDomain &&
|
||||
<FieldGroup
|
||||
id="subDomain"
|
||||
name="subDomain"
|
||||
type="text"
|
||||
label="Sub Domain"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.subDomain}
|
||||
error={errors.subDomain}
|
||||
/>}
|
||||
{!domainName &&
|
||||
<FieldGroup
|
||||
id="domainName"
|
||||
name="domainName"
|
||||
type="text"
|
||||
label="Domain Name"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.domainName}
|
||||
button={
|
||||
<Button
|
||||
mode="strong"
|
||||
style={{ marginTop: '5px' }}
|
||||
onClick={() => {
|
||||
UsernameRegistrar.methods.getPrice()
|
||||
.call()
|
||||
.then((res) => { setFieldValue('price', fromWei(res)); });
|
||||
}}
|
||||
>
|
||||
Get Price
|
||||
</Button>
|
||||
}
|
||||
/>}
|
||||
{!domainPrice &&
|
||||
<FieldGroup
|
||||
id="price"
|
||||
name="price"
|
||||
label="Domain Price"
|
||||
disabled
|
||||
value={values.price ? `${formatPrice(values.price)} SNT` : ''} />}
|
||||
<Hidden mdDown>
|
||||
<FieldGroup
|
||||
id="statusAddress"
|
||||
name="statusAddress"
|
||||
type="text"
|
||||
label="Status messenger address domain resolves to"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.statusAddress}
|
||||
error={errors.statusAddress}
|
||||
wide="true"
|
||||
/>
|
||||
<FieldGroup
|
||||
id="address"
|
||||
name="address"
|
||||
type="text"
|
||||
label="Ethereum address domain resolves to"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.address}
|
||||
error={errors.address}
|
||||
button={<Button mode="strong" style={{ padding: '5px 15px 5px 15px', marginTop: '5px' }} onClick={() => setFieldValue('address', web3.eth.defaultAccount)}>Use My Primary Address</Button>}
|
||||
/>
|
||||
{!isSubmitting ? <Button wide mode="strong" type="submit" disabled={isSubmitting || !!Object.keys(errors).length}>{!isSubmitting ? 'Submit' : 'Submitting to the Blockchain - (this may take awhile)'}</Button> : <LinearProgress />}
|
||||
</Hidden>
|
||||
|
||||
<Hidden mdUp>
|
||||
|
||||
<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%' }} />}
|
||||
<Terms open={displayTerms(status)} onSubmit={() => { setStatus(null); formRef.current.dispatchEvent(new Event('submit')) }} form={formRef} />
|
||||
</div>
|
||||
</Hidden>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
||||
const RegisterSubDomain = withFormik({
|
||||
mapPropsToValues: props => ({ subDomain: '', domainName: '', price: '', statusAddress: props.statusContactCode || '', address: web3.eth.defaultAccount || '' }),
|
||||
validate(values, props) {
|
||||
const errors = {};
|
||||
const { address } = values;
|
||||
const { subDomain } = props || values;
|
||||
if (address && !web3.utils.isAddress(address)) errors.address = 'Not a valid address';
|
||||
if (!subDomain) errors.subDomain = 'Required';
|
||||
return errors;
|
||||
},
|
||||
async handleSubmit(values, { setSubmitting, props }) {
|
||||
const { editAccount, preRegisteredCallback } = props;
|
||||
const { address, statusAddress } = values;
|
||||
const { subDomain, domainName, domainPrice, registeredCallbackFn } = props || values;
|
||||
const { methods: { register } } = UsernameRegistrar;
|
||||
const { methods: { approveAndCall } } = TestToken;
|
||||
const subdomainHash = soliditySha3(subDomain);
|
||||
const domainNameHash = hash(domainName);
|
||||
const resolveToAddr = address || zeroAddress;
|
||||
const points = statusAddress ? generateXY(statusAddress) : null;
|
||||
const node = hash(subDomain.includes('eth') ? subDomain : `${subDomain}.${domainName}`);
|
||||
const { methods: { setAddr, setPubkey } } = await getResolver(node);
|
||||
|
||||
const funcsToSend = [];
|
||||
const args = [
|
||||
subdomainHash,
|
||||
resolveToAddr,
|
||||
points ? points.x : zeroBytes32,
|
||||
points ? points.y : zeroBytes32,
|
||||
];
|
||||
if (editAccount) {
|
||||
if (address !== web3.eth.defaultAccount) funcsToSend.push(setAddr(node, resolveToAddr));
|
||||
if (statusAddress && statusAddress !== props.statusContactCode) funcsToSend.push(setPubkey(node, args[3], args[4]));
|
||||
} else {
|
||||
funcsToSend.push(
|
||||
approveAndCall(UsernameRegistrar.address, domainPrice, register(...args).encodeABI())
|
||||
);
|
||||
}
|
||||
while (funcsToSend.length) {
|
||||
const toSend = funcsToSend.pop();
|
||||
toSend.estimateGas().then((gasEstimated) => {
|
||||
const gas = editAccount ? gasEstimated + 1000 : gasEstimated * 2;
|
||||
console.log("Register would work. :D Gas estimated: " + gasEstimated, { gas }, gasEstimated + 1000);
|
||||
console.log("Trying: register(\"" + subdomainHash + "\",\"" + domainNameHash + "\",\"" + resolveToAddr + "\",\"" + zeroBytes32 + "\",\"" + zeroBytes32 + "\")");
|
||||
if (preRegisteredCallback) preRegisteredCallback();
|
||||
toSend.send({ gas }).then((txId) => {
|
||||
if (txId.status == "0x1" || txId.status == "0x01"){
|
||||
console.log("Register send success. :)");
|
||||
} else {
|
||||
console.log("Register send errored. :( Out of gas? ");
|
||||
}
|
||||
console.dir(txId)
|
||||
}).catch(err => {
|
||||
console.log("Register send errored. :( Out of gas?");
|
||||
console.dir(err)
|
||||
}).finally(() => {
|
||||
// REQUIRED UNTIL THIS ISSUES IS RESOLVED: https://github.com/jaredpalmer/formik/issues/597
|
||||
setTimeout(() => { registeredCallbackFn(resolveToAddr, statusAddress || zeroBytes32); }, 200);
|
||||
setSubmitting(false);
|
||||
});
|
||||
}).catch(err => {
|
||||
console.log("Register would error. :/ Already Registered? Have Token Balance? Is Allowance set?")
|
||||
console.dir(err)
|
||||
setSubmitting(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
})(InnerForm);
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
statusContactCode: getStatusContactCode(state),
|
||||
SNTAllowance: getSNTAllowance(state),
|
||||
SNTBalance: getCurrentAccount(state) && getCurrentAccount(state).SNTBalance,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(RegisterSubDomain);
|
|
@ -0,0 +1,28 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import { Form, FormGroup, FormControl, HelpBlock, Button, ControlLabel } from 'react-bootstrap';
|
||||
import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar';
|
||||
import web3Utils from 'web3-utils'
|
||||
import { hash } from 'eth-ens-namehash'
|
||||
|
||||
const zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
||||
const getUserAddress = contract => contract._provider.publicConfigStore._state.selectedAddress;
|
||||
const dispatchSetup = (ENSRegistry) => {
|
||||
const { methods: { setSubnodeOwner } } = ENSRegistry;
|
||||
const { sha3 } = web3Utils
|
||||
setSubnodeOwner(zeroBytes32, sha3('eth'), getUserAddress(ENSRegistry))
|
||||
.send()
|
||||
.then(res => { console.log(res) })
|
||||
setSubnodeOwner(hash('eth'), sha3('stateofus'), UsernameRegistrar._address)
|
||||
.send()
|
||||
.then(res => { console.log(res) })
|
||||
setSubnodeOwner(hash('eth'), sha3('stateofus'), UsernameRegistrar._address)
|
||||
.send()
|
||||
.then(res => { console.log(res) })
|
||||
}
|
||||
const SetupEns = ({ ENSRegistry }) => (
|
||||
<Fragment>
|
||||
<Button bsStyle="primary" onClick={() => dispatchSetup(ENSRegistry)}>ADD INITIAL NODES TO ENS</Button>
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
export default SetupEns;
|
|
@ -0,0 +1,10 @@
|
|||
.ens-terms__title {
|
||||
padding: 40px 24px;
|
||||
background: #EEF2F5;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.ens-terms li {
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
|
||||
import './terms.css';
|
||||
|
||||
const styles = theme => ({
|
||||
button: {
|
||||
margin: theme.spacing.unit,
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'rgba(67, 96, 223, 0.1)',
|
||||
}
|
||||
});
|
||||
|
||||
const buttonText = { color: '#4360df', margin: '0 20px', fontWeight: 300 };
|
||||
|
||||
|
||||
const Terms = ({ classes, open, onSubmit }) => (
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={open}
|
||||
>
|
||||
<div className="ens-terms">
|
||||
<h2 className="ens-terms__title">Terms of name registration</h2>
|
||||
<ul>
|
||||
<li>Funds are deposited for 1 year. Your SNT
will be locked, but not spent.</li>
|
||||
<li>After 1 year, you can release the name and get your deposit back, or take no action to keep the name.</li>
|
||||
<li>If terms of the contract change — e.g. Status makes contract upgrades — user has the right to release the username regardless of time held.</li>
|
||||
<li>The contract controller cannot access your deposited funds. They can only be moved back to the address that sent them.</li>
|
||||
<li>Your address(es) will be publicly associated with your ENS name.</li>
|
||||
<li>Usernames are created as subdomain nodes of stateofus.eth and are subject to the ENS smart contract terms.</li>
|
||||
<li>You authorize the contract to transfer SNT on your behalf. This can only occur when you approve a transaction to authorize the transfer.</li>
|
||||
</ul>
|
||||
|
||||
<p>These terms are guaranteed by the smart contract logic at addresses:</p>
|
||||
|
||||
<ul>
|
||||
<li>0xb1C47B61CDaeee3fA85Fe8B93FcE6311165E6291 (ENSSubdomainRegistry — Status)</li>
|
||||
</ul>
|
||||
|
||||
|
||||
<ul>
|
||||
<li>0x112234455C3a32FD11230C42E7Bccd4A84e02010 (ENS).</li>
|
||||
</ul>
|
||||
|
||||
<Button type="submit" size="large" className={classNames(classes.button)} onClick={onSubmit}>
|
||||
<div style={buttonText}>Let's Go</div>
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
export default withStyles(styles)(Terms);
|
|
@ -0,0 +1,56 @@
|
|||
import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar';
|
||||
import web3 from 'web3';
|
||||
import React from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import FieldGroup from '../standard/FieldGroup';
|
||||
import { withFormik } from 'formik';
|
||||
|
||||
const InnerForm = ({
|
||||
values,
|
||||
errors,
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
isSubmitting,
|
||||
}) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FieldGroup
|
||||
id="newAddress"
|
||||
name="newAddress"
|
||||
type="text"
|
||||
label="New Controller Address"
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
value={values.newAddress}
|
||||
error={errors.newAddress}
|
||||
/>
|
||||
<Button bsStyle="primary" type="submit" disabled={isSubmitting || !!Object.keys(errors).length}>{!isSubmitting ? 'Submit' : 'Submitting to the Blockchain - (this may take awhile)'}</Button>
|
||||
</form>
|
||||
)
|
||||
|
||||
const UpdateController = withFormik({
|
||||
mapPropsToValues: props => ({ newAddress: '' }),
|
||||
async validate(values) {
|
||||
const { utils: { isAddress } } = web3;
|
||||
const { newAddress } = values;
|
||||
const errors = {};
|
||||
if (!isAddress(newAddress)) errors.newAddress = 'Please enter a valid address'
|
||||
if (Object.keys(errors).length) throw errors;
|
||||
},
|
||||
async handleSubmit(values, { setSubmitting }) {
|
||||
const { newAddress } = values;
|
||||
const { methods: { changeController } } = UsernameRegistrar;
|
||||
changeController(newAddress)
|
||||
.send()
|
||||
.then((res) => {
|
||||
setSubmitting(false);
|
||||
console.log(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
setSubmitting(false);
|
||||
console.log(err);
|
||||
})
|
||||
}
|
||||
})(InnerForm);
|
||||
|
||||
export default UpdateController;
|
|
@ -0,0 +1,3 @@
|
|||
export const zeroAddress = '0x0000000000000000000000000000000000000000';
|
||||
export const zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
||||
export const formatPrice = price => price.includes('.') ? price : Number(price).toLocaleString();
|
|
@ -0,0 +1,14 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import web3 from "Embark/web3";
|
||||
import ENSRegistry from 'Embark/contracts/ENSRegistry';
|
||||
import PublicResolver from 'Embark/contracts/PublicResolver';
|
||||
|
||||
const { methods: { owner, resolver } } = ENSRegistry;
|
||||
|
||||
export const nullAddress = '0x0000000000000000000000000000000000000000';
|
||||
export const getResolver = async node => {
|
||||
const resolverAddress = await resolver(node).call();
|
||||
return resolverAddress !== nullAddress
|
||||
? new EmbarkJS.Blockchain.Contract({ abi: PublicResolver._jsonInterface, address: resolverAddress, web3 })
|
||||
: PublicResolver
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
.ens-welcome {
|
||||
margin: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ens-welcome__img {
|
||||
margin: 20px 0 50px;
|
||||
}
|
||||
|
||||
.ens-welcome__title {
|
||||
font-weight: bold;
|
||||
font-size: 1.375rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.ens-welcome__guide {
|
||||
color: #adb5bd;
|
||||
margin: 20px 0 30px;
|
||||
}
|
||||
|
||||
.ens-welcome__guide span {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ens-welcome__guide span:after, .ens-welcome__guide span:before {
|
||||
content: '';
|
||||
border-bottom: solid 6px transparent;
|
||||
display: block;
|
||||
position: absolute;
|
||||
border-top: solid 6px #fff;
|
||||
border-left: solid 6px transparent;
|
||||
border-right: solid 6px transparent;
|
||||
top: 5px;
|
||||
right: -20px;
|
||||
}
|
||||
|
||||
.ens-welcome__guide span:before {
|
||||
border-top: solid 6px #adb5bd;
|
||||
top: 7px;
|
||||
}
|
||||
|
||||
.ens-welcome__list {
|
||||
margin-top: 20px;
|
||||
counter-reset: ens-counter;
|
||||
list-style: none;
|
||||
text-align: left;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.ens-welcome__list li {
|
||||
margin: 0 0 1rem 0;
|
||||
padding-left: 80px;
|
||||
counter-increment: ens-counter;
|
||||
position: relative;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.ens-welcome__list li:before {
|
||||
content: counter(ens-counter);
|
||||
color: #4360DF;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
line-height: 60px;
|
||||
width: 60px;
|
||||
height: 57px;
|
||||
top: 0;
|
||||
border-radius: 50%;
|
||||
border: solid 3px #ECEFFC;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ens-welcome__list .title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.ens-welcome__list .text {
|
||||
color: #adb5bd;
|
||||
line-height: 1.313rem;
|
||||
}
|
||||
|
||||
.ens-welcome__info {
|
||||
border-top: solid 1px #ccc;
|
||||
border-bottom: solid 1px #ccc;
|
||||
margin: 30px 0 20px;
|
||||
padding: 15px 0;
|
||||
text-align: center;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import StatusCards from '../../ui/icons/svg/intro_name.svg';
|
||||
|
||||
import './welcome.css';
|
||||
|
||||
const styles = theme => ({
|
||||
button: {
|
||||
margin: theme.spacing.unit,
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'rgba(67, 96, 223, 0.1)',
|
||||
}
|
||||
});
|
||||
|
||||
const buttonText = { color: '#4360df', margin: '0 20px', fontWeight: 300 };
|
||||
|
||||
const WelcomeContent = () => (
|
||||
<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="text-primary">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 }) => (
|
||||
<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 />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default withStyles(styles)(Welcome);
|
|
@ -0,0 +1,44 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import ENSRegistry from 'Embark/contracts/ENSRegistry';
|
||||
import UsernameRegistrar from 'Embark/contracts/UsernameRegistrar';
|
||||
import TestToken from 'Embark/contracts/TestToken';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Form, FormGroup, FormControl, HelpBlock, Button, ControlLabel } from 'react-bootstrap';
|
||||
import AddDomain from './ens/addDomain';
|
||||
import MoveDomain from './ens/moveDomain';
|
||||
import RegisterSubDomain from './ens/registerSubDomain';
|
||||
import TokenPermissions from './standard/TokenPermission';
|
||||
import SetupENS from './ens/setupENS';
|
||||
import UpdateController from './ens/updateController';
|
||||
|
||||
const FieldGroup = ({ id, label, help, ...props }) => (
|
||||
<FormGroup controlId={id}>
|
||||
<ControlLabel>{label}</ControlLabel>
|
||||
<FormControl {...props} />
|
||||
{help && <HelpBlock>{help}</HelpBlock>}
|
||||
</FormGroup>
|
||||
)
|
||||
|
||||
const ENSSubManagement = props => (
|
||||
<Fragment>
|
||||
<h2 style={{ textAlign: 'center' }}>Subdomain Management</h2>
|
||||
<h3>Change Registry Controller</h3>
|
||||
<UpdateController />
|
||||
<h3>Activate Registry/Update Registry Price</h3>
|
||||
<AddDomain />
|
||||
<h3>Move Domain To Another Registry</h3>
|
||||
<MoveDomain />
|
||||
<hr/>
|
||||
<h3>Register Sub-Domain</h3>
|
||||
<RegisterSubDomain />
|
||||
<hr/>
|
||||
<TokenPermissions
|
||||
symbol='SNT'
|
||||
spender={UsernameRegistrar._address}
|
||||
methods={TestToken.methods} />
|
||||
<hr/>
|
||||
<SetupENS ENSRegistry={ENSRegistry} />
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
export default ENSSubManagement;
|
|
@ -0,0 +1,132 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import ERC20Token from 'Embark/contracts/ERC20Token';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap';
|
||||
import { getCurrentAccount, accountsIsLoading } from '../reducers/accounts';
|
||||
|
||||
class ERC20TokenUI extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
ERC20Token.options.address = props.address;
|
||||
this.state = {
|
||||
balanceOf: 0,
|
||||
transferTo: "",
|
||||
transferAmount: 0,
|
||||
accountBalance: 0,
|
||||
accountB: web3.eth.defaultAccount,
|
||||
}
|
||||
}
|
||||
|
||||
update_transferTo(e){
|
||||
this.setState({transferTo: e.target.value});
|
||||
}
|
||||
|
||||
update_transferAmount(e){
|
||||
this.setState({transferAmount: e.target.value});
|
||||
}
|
||||
|
||||
transfer(e){
|
||||
var to = this.state.transferTo;
|
||||
var amount = this.state.transferAmount;
|
||||
var tx = ERC20Token.methods.transfer(to, amount).send({from: web3.eth.defaultAccount});
|
||||
this._addToLog(ERC20Token.options.address+".transfer(" + to + ", "+amount+")");
|
||||
}
|
||||
|
||||
approve(e){
|
||||
var to = this.state.transferTo;
|
||||
var amount = this.state.transferAmount;
|
||||
var tx = ERC20Token.methods.approve(to, amount).send({from: web3.eth.defaultAccount});
|
||||
this._addToLog(ERC20Token.options.address+".approve(" + to + ", "+amount+")");
|
||||
}
|
||||
|
||||
balanceOf(e){
|
||||
e.preventDefault();
|
||||
var who = e.target.value;
|
||||
if (EmbarkJS.isNewWeb3()) {
|
||||
ERC20Token.methods.balanceOf(who).call()
|
||||
.then(_value => this.setState({balanceOf: _value}))
|
||||
} else {
|
||||
ERC20Token.balanceOf(who)
|
||||
.then(_value => this.x({balanceOf: _value}));
|
||||
}
|
||||
this._addToLog(ERC20Token.options.address+".balanceOf(" + who + ")");
|
||||
}
|
||||
|
||||
getDefaultAccountBalance(){
|
||||
if (EmbarkJS.isNewWeb3()) {
|
||||
ERC20Token.methods.balanceOf(web3.eth.defaultAccount).call()
|
||||
.then(_value => this.setState({accountBalance: _value}))
|
||||
} else {
|
||||
ERC20Token.balanceOf(web3.eth.defaultAccount)
|
||||
.then(_value => this.x({valueGet: _value}))
|
||||
}
|
||||
this._addToLog(ERC20Token.options.address + ".balanceOf(" + web3.eth.defaultAccount + ")");
|
||||
}
|
||||
|
||||
_addToLog(txt){
|
||||
console.log(txt);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account, isLoading } = this.props;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h3> Read your account token balance </h3>
|
||||
<Form inline>
|
||||
<FormGroup>
|
||||
{!isLoading && <HelpBlock>Your test token balance is <span className="accountBalance">{account.SNTBalance}</span></HelpBlock>}
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
||||
<h3> Read account token balance</h3>
|
||||
<Form inline>
|
||||
<FormGroup>
|
||||
<label>
|
||||
Of:
|
||||
<FormControl
|
||||
type="text"
|
||||
defaultValue={this.state.accountB}
|
||||
onChange={(e) => this.balanceOf(e)} />
|
||||
</label>
|
||||
<label>
|
||||
<HelpBlock><span className="balanceOf">{this.state.balanceOf}</span></HelpBlock>
|
||||
</label>
|
||||
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
||||
<h3> Transfer/Approve token balance</h3>
|
||||
<Form inline>
|
||||
<FormGroup>
|
||||
<label>
|
||||
To:
|
||||
<FormControl
|
||||
type="text"
|
||||
defaultValue={this.state.transferTo}
|
||||
onChange={(e) => this.update_transferTo(e) } />
|
||||
</label>
|
||||
<label>
|
||||
Amount:
|
||||
<FormControl
|
||||
type="text"
|
||||
defaultValue={this.state.transferAmount}
|
||||
onChange={(e) => this.update_transferAmount(e) } />
|
||||
</label>
|
||||
<Button bsStyle="primary" onClick={(e) => this.transfer(e)}>Transfer</Button>
|
||||
<Button bsStyle="primary" onClick={(e) => this.approve(e)}>Approve</Button>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
account: getCurrentAccount(state),
|
||||
isLoading: accountsIsLoading(state),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(ERC20TokenUI);
|
|
@ -0,0 +1,46 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FormGroup, FormControl, HelpBlock, InputGroup } from 'react-bootstrap';
|
||||
import { Field } from '../../ui/components';
|
||||
import theme from '../../ui/theme';
|
||||
import { font } from '../../ui/utils/styles/font';
|
||||
|
||||
const StyledFormControl = styled(FormControl)`
|
||||
${font({ size: 'small', weight: 'normal' })};
|
||||
width: ${({ wide }) => (wide ? '100%' : 'auto')};
|
||||
|
||||
padding: 5px 10px;
|
||||
background: ${theme.contentBackground};
|
||||
border: 1px solid ${theme.contentBorder};
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
color: ${theme.textPrimary};
|
||||
appearance: none;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: ${theme.contentBorderActive};
|
||||
}
|
||||
&:read-only {
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 ${theme.textSecondary};
|
||||
}
|
||||
`;
|
||||
|
||||
const FieldGroup = ({ id, label, button, error, ...props }) => (
|
||||
<FormGroup controlId={id} validationState={error ? 'error' : null}>
|
||||
<Field label={label} validationState={error ? 'error' : null} wide>
|
||||
{button ?
|
||||
<InputGroup>
|
||||
<InputGroup.Button>
|
||||
{button}
|
||||
</InputGroup.Button>
|
||||
<StyledFormControl {...props} />
|
||||
</InputGroup>
|
||||
: <StyledFormControl {...props} />
|
||||
}
|
||||
{error && <HelpBlock>{error}</HelpBlock>}
|
||||
</Field>
|
||||
</FormGroup>
|
||||
);
|
||||
|
||||
export default FieldGroup;
|
|
@ -0,0 +1,96 @@
|
|||
import React from 'react';
|
||||
import styles from './style.js';
|
||||
import EthereumLogo from './../assets/ethereum-logo.svg';
|
||||
import MetamaskLogo from './../assets/metamask-logo.svg';
|
||||
|
||||
function capitalize(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
const NoConnection = ({ network }) => console.log(network) || (
|
||||
<div style={styles.NoEthereumSection}>
|
||||
<img style={styles.ImgHeaderLogo} alt="Status" src="images/logo.png" />
|
||||
<hr />
|
||||
<img
|
||||
style={styles.ImgEthereumLogo}
|
||||
alt="Ethereum"
|
||||
src={EthereumLogo}
|
||||
/>
|
||||
<h2>NOT CONNECTED TO ETHEREUM {network.toUpperCase()}</h2>
|
||||
<p style={styles.PNotFound}>
|
||||
This application requires an Ethereum client to be running and connected to {capitalize(network)}. A client
|
||||
could not be detected which probably means it's not
|
||||
installed, running or is misconfigured.
|
||||
</p>
|
||||
<p style={styles.PUseClients}>
|
||||
Please use one of the following clients to connect to Ethereum:
|
||||
</p>
|
||||
<div style={styles.DivClients}>
|
||||
<div style={styles.DivSubClients}>
|
||||
<img style={styles.ImgLogo} alt="Metamask" src={MetamaskLogo} />
|
||||
<h2>METAMASK</h2>
|
||||
<p>
|
||||
<span style={styles.AlignNumber}>
|
||||
<span style={styles.NumberCircle}>1</span>
|
||||
</span>Install
|
||||
<a
|
||||
href="https://metamask.io/"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
{" "}
|
||||
Metamask
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<span style={styles.AlignNumber}>
|
||||
<span style={styles.NumberCircle}>2</span>
|
||||
</span>Use Chrome to browse
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
href="https://vote.status.im"
|
||||
target="_blank"
|
||||
>
|
||||
{" "}
|
||||
https://vote.status.im
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<div style={styles.DivSubClients}>
|
||||
<img style={styles.ImgLogo} alt="Mist" src="images/mist.png" />
|
||||
<h2>MIST</h2>
|
||||
<p>
|
||||
<span style={styles.AlignNumber}>
|
||||
<span style={styles.NumberCircle}>1</span>
|
||||
</span>Install and run
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
href="https://github.com/ethereum/mist/releases"
|
||||
target="_blank"
|
||||
>
|
||||
{" "}
|
||||
Mist
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<span style={styles.AlignNumber}>
|
||||
<span style={styles.NumberCircle}>2</span>
|
||||
</span>
|
||||
Use Mist to browse
|
||||
<a
|
||||
rel="noopener noreferrer"
|
||||
href="https://vote.status.im"
|
||||
target="_blank"
|
||||
>
|
||||
{" "}
|
||||
https://vote.status.im
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
NoConnection.displayName = 'NoConnection';
|
||||
|
||||
export default NoConnection;
|
|
@ -0,0 +1,53 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import IPhone from '@material-ui/icons/PhoneIphone';
|
||||
import Android from '@material-ui/icons/Android';
|
||||
|
||||
const styles = {
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
width: '100%',
|
||||
zIndex: 1,
|
||||
},
|
||||
flex: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
menuButton: {
|
||||
marginLeft: -12,
|
||||
marginRight: 20,
|
||||
},
|
||||
};
|
||||
|
||||
function ButtonAppBar(props) {
|
||||
const { classes } = props;
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AppBar position="static">
|
||||
<Toolbar>
|
||||
<Typography variant="title" color="inherit" className={classes.flex}>
|
||||
This site is optimized for Status. Get the App to enable all features and get the best experience.
|
||||
</Typography>
|
||||
<IconButton color="inherit" aria-label="iPhone" href="https://status.im/success.html" target="_blank">
|
||||
<IPhone />
|
||||
</IconButton>
|
||||
<IconButton color="inherit" aria-label="Android" href="https://test.status.im/" target="_blank">
|
||||
<Android />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ButtonAppBar.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ButtonAppBar);
|
|
@ -0,0 +1,103 @@
|
|||
import React, { Fragment, PureComponent } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import web3 from "Embark/web3"
|
||||
import Toggle from 'react-toggle';
|
||||
import { BigNumber } from './utils'
|
||||
import "react-toggle/style.css";
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
import FormLabel from '@material-ui/core/FormLabel';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormGroup from '@material-ui/core/FormGroup';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import { actions, getSNTAllowance } from '../../reducers/accounts';
|
||||
|
||||
// We set an allowance to be "unlimited" by setting it to
|
||||
// it's maximum possible value -- namely, 2^256 - 1.
|
||||
const { fromWei } = web3.utils;
|
||||
const unlimitedAllowance = new BigNumber(2).pow(256).sub(1);
|
||||
const getDefaultAccount = () => web3.eth.defaultAccount;
|
||||
const SUPPORTED_TOKENS = ['SNT', 'STT'];
|
||||
const BALANCE_KEYS = { 'SNT': 'SNTBalance', 'STT': 'SNTBalance' };
|
||||
|
||||
class TokenHandle extends PureComponent {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
toggleApproved = () => {
|
||||
const { approved } = this.state;
|
||||
const { methods: { approve }, spender, receiveSntAllowance } = this.props;
|
||||
const isApproved = !!Number(approved);
|
||||
let amountToApprove = isApproved ? 0 : unlimitedAllowance;
|
||||
console.log("approve(\""+spender+"\",\"" +amountToApprove +"\")")
|
||||
this.setState({ updating: true });
|
||||
approve(
|
||||
spender,
|
||||
amountToApprove
|
||||
)
|
||||
.send()
|
||||
.then(approval => {
|
||||
const { events: { Approval: { returnValues: { _value } } } } = approval
|
||||
receiveSntAllowance(_value)
|
||||
this.setState({ ...this.state, updating: false })
|
||||
}).catch(err => {
|
||||
console.log("Approve failed: " + err);
|
||||
this.setState({ updating: false });
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { symbol, account, isLoading, mobile, SNTAllowance } = this.props;
|
||||
const { updating } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
{!updating && !isLoading && !!account && <div>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend" style={{ textAlign: 'center' }}>Token Permissions</FormLabel>
|
||||
<FormGroup style={{ alignItems: 'center' }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={!!Number(SNTAllowance)}
|
||||
onChange={this.toggleApproved}
|
||||
value={symbol}
|
||||
/>
|
||||
}
|
||||
label={`${Number(fromWei(account[BALANCE_KEYS[symbol]])).toLocaleString()} ${symbol}`}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormHelperText style={{ textAlign: 'center' }}>Registry needs permission to transfer SNT from your account prior to registration</FormHelperText>
|
||||
</FormControl>
|
||||
</div>}
|
||||
{isLoading || updating && <CircularProgress style={{ marginLeft: mobile ? '45%' : null }} />}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const TokenPermissions = (props) => (
|
||||
<Fragment>
|
||||
{!props.mobile && <Fragment>
|
||||
<Tooltip title="Turn on permissions for a token to enable its use with the ENS subdomain registry">
|
||||
<h3>Token Permissions</h3>
|
||||
</Tooltip>
|
||||
<hr/>
|
||||
</Fragment>}
|
||||
<TokenHandle {...props} />
|
||||
</Fragment>
|
||||
)
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
receiveSntAllowance(amount) {
|
||||
dispatch(actions.receiveSntAllowance(amount));
|
||||
},
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
SNTAllowance: getSNTAllowance(state),
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TokenPermissions);
|
|
@ -0,0 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import TokenPermissions from './TokenPermission';
|
||||
import { getCurrentAccount, accountsIsLoading } from '../../reducers/accounts';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
account: getCurrentAccount(state),
|
||||
isLoading: accountsIsLoading(state),
|
||||
});
|
||||
export default connect(mapStateToProps)(TokenPermissions);
|
|
@ -0,0 +1,10 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import NoConnection from './NoConnection';
|
||||
|
||||
const Web3Render = ({ ready, children, network }) => (
|
||||
<Fragment>
|
||||
{ready ? <Fragment>{children}</Fragment> : <NoConnection network={network} />}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default Web3Render;
|
|
@ -0,0 +1,68 @@
|
|||
export default {
|
||||
NoEthereumSection: {
|
||||
maxWidth: '980px',
|
||||
margin: '0 auto',
|
||||
textAlign: 'center',
|
||||
fontSize: '24px'
|
||||
},
|
||||
ImgHeaderLogo: {
|
||||
width: '264px',
|
||||
paddingBottom: '30px'
|
||||
},
|
||||
ImgEthereumLogo: {
|
||||
width: '61px',
|
||||
paddingTop: '30px',
|
||||
marginBottom: '40px'
|
||||
},
|
||||
PNotFound: {
|
||||
opacity: 0.50,
|
||||
display: 'inline-block',
|
||||
letterSpacing: '0.6px',
|
||||
margin: 10
|
||||
},
|
||||
PUseClients: {
|
||||
display: 'inline-block',
|
||||
color: '#546979',
|
||||
fontWeight: 700
|
||||
},
|
||||
DivClients: {
|
||||
height: '360px',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
fontSize: '21px',
|
||||
marginBottom: '50px',
|
||||
marginTop: '50px'
|
||||
},
|
||||
ImgLogo: {
|
||||
width: '85px'
|
||||
},
|
||||
AlignNumber: {
|
||||
display: 'table-cell',
|
||||
verticalAlign: 'middle',
|
||||
paddingRight: '20px'
|
||||
},
|
||||
NumberCircle: {
|
||||
display: 'inline-block',
|
||||
borderRadius: '50%',
|
||||
height: '28px',
|
||||
width: '28px',
|
||||
lineHeight: '28px',
|
||||
textAlign: 'center',
|
||||
border: 'solid 1px #CFD8DC',
|
||||
fontSize: '18px',
|
||||
color: '#222228',
|
||||
opacity: 0.68
|
||||
},
|
||||
DivSubClients: {
|
||||
backgroundColor: 'white',
|
||||
width: '340px',
|
||||
height: '360px',
|
||||
padding: '33px 10px 33px 27px',
|
||||
opacity: 0.89,
|
||||
border: '2px solid #EFF1F3',
|
||||
boxShadow: '0px 2px 6px 0px rgba(0, 0, 0, 0.13)',
|
||||
borderRadius: '4px',
|
||||
margin: '0 20px'
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import BigNumber from 'bignumber.js';
|
||||
|
||||
// By default BigNumber's `toString` method converts to exponential notation if the value has
|
||||
// more then 20 digits. We want to avoid this behavior, so we set EXPONENTIAL_AT to a high number
|
||||
BigNumber.config({
|
||||
EXPONENTIAL_AT: 1000,
|
||||
});
|
||||
|
||||
export { BigNumber };
|
|
@ -0,0 +1,64 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import TestToken from 'Embark/contracts/TestToken';
|
||||
import React from 'react';
|
||||
import { Form, FormGroup, FormControl, HelpBlock, Button } from 'react-bootstrap';
|
||||
import ERC20TokenUI from './erc20token';
|
||||
import { connect } from 'react-redux';
|
||||
import { actions as accountActions } from '../reducers/accounts';
|
||||
|
||||
class TestTokenUI extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
amountToMint: 100,
|
||||
}
|
||||
}
|
||||
|
||||
handleMintAmountChange(e){
|
||||
this.setState({amountToMint: e.target.value});
|
||||
}
|
||||
|
||||
mint(e){
|
||||
const { addToBalance } = this.props;
|
||||
e.preventDefault();
|
||||
|
||||
var value = parseInt(this.state.amountToMint, 10);
|
||||
|
||||
if (EmbarkJS.isNewWeb3()) {
|
||||
TestToken.methods.mint(value).send({from: web3.eth.defaultAccount})
|
||||
.then(r => { addToBalance(value) });
|
||||
} else {
|
||||
TestToken.mint(value).send({from: web3.eth.defaultAccount})
|
||||
.then(r => { addToBalance(value) });
|
||||
}
|
||||
console.log(TestToken.options.address +".mint("+value+").send({from: " + web3.eth.defaultAccount + "})");
|
||||
}
|
||||
|
||||
render(){
|
||||
return (<React.Fragment>
|
||||
<h3> Mint Test Token</h3>
|
||||
<Form inline>
|
||||
<FormGroup>
|
||||
<FormControl
|
||||
type="text"
|
||||
defaultValue={this.state.amountToMint}
|
||||
onChange={(e) => this.handleMintAmountChange(e)} />
|
||||
<Button bsStyle="primary" onClick={(e) => this.mint(e)}>Mint</Button>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
||||
<ERC20TokenUI address={ TestToken.options.address } />
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
addToBalance(amount) {
|
||||
dispatch(accountActions.addToSntTokenBalance(amount));
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(null, mapDispatchToProps)(TestTokenUI);
|
|
@ -0,0 +1,33 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import React from 'react';
|
||||
import { Navbar, NavItem, Nav, MenuItem , NavDropdown} from 'react-bootstrap';
|
||||
import AccountList from './accountList';
|
||||
|
||||
class TopNavbar extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
render(){
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Navbar>
|
||||
<Navbar.Header>
|
||||
<Navbar.Brand>
|
||||
<a href="#home">Status.im Demo</a>
|
||||
</Navbar.Brand>
|
||||
</Navbar.Header>
|
||||
<AccountList classNameNavDropdown="pull-right" />
|
||||
</Navbar>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TopNavbar;
|
|
@ -0,0 +1,64 @@
|
|||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.accounts {
|
||||
float: right;
|
||||
margin-right: 17px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.identicon {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
||||
.logs {
|
||||
background-color: black;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
border-left: 1px solid #ddd;
|
||||
border-right: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
vertical-align: middle;
|
||||
margin-left: 5px;
|
||||
margin-top: 4px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: red;
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
vertical-align: middle;
|
||||
margin-left: 5px;
|
||||
margin-top: 4px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: mediumseagreen;
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
input.form-control {
|
||||
margin: 5px;
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
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 './dapp.css';
|
||||
|
||||
const { getNetworkType } = web3.eth.net;
|
||||
|
||||
const symbols = {
|
||||
'ropsten': 'STT',
|
||||
'private': 'SNT',
|
||||
'main': 'SNT'
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
state = { admin: false, searching: false };
|
||||
|
||||
componentDidMount(){
|
||||
EmbarkJS.onReady((err) => {
|
||||
getNetworkType().then(network => { this.setState({ network })});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { admin, network, searching } = this.state;
|
||||
const toggleSearch = () => { this.setState({ searching: !searching }) };
|
||||
const isRopsten = network === 'ropsten';
|
||||
const isMainnet = network === 'main';
|
||||
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} />
|
||||
</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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
|
@ -0,0 +1,11 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Status.im Domain Registration</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body class="container">
|
||||
<div id="app">
|
||||
</div>
|
||||
<script src="js/index.js" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import store from './store/configureStore';
|
||||
import App from './dapp';
|
||||
import init from './store/init'
|
||||
import './dapp.css';
|
||||
|
||||
init();
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById('app')
|
||||
);
|
|
@ -0,0 +1,81 @@
|
|||
import { createTypes, actionCreator } from 'redux-action-creator'
|
||||
import { createSelector } from 'reselect'
|
||||
|
||||
export const types = createTypes([
|
||||
'RECEIVE_ACCOUNTS',
|
||||
'UPDATE_DEFAULT_ACCOUNT',
|
||||
'ADD_TO_SNT_TOKEN_BALANCE',
|
||||
'SUBTRACT_FROM_SNT_TOKEN_BALANCE',
|
||||
'RECEIVE_STATUS_CONTACT_CODE',
|
||||
'RECEIVE_SNT_ALLOWANCE'
|
||||
], 'ACCOUNTS')
|
||||
export const actions = {
|
||||
receiveAccounts: actionCreator(types.RECEIVE_ACCOUNTS, 'defaultAccount','accounts'),
|
||||
updateDefaultAccount: actionCreator(types.UPDATE_DEFAULT_ACCOUNT, 'defaultAccount'),
|
||||
addToSntTokenBalance: actionCreator(types.ADD_TO_SNT_TOKEN_BALANCE, 'amount'),
|
||||
subtractfromSntTokenBalance: actionCreator(types.SUBTRACT_FROM_SNT_TOKEN_BALANCE, 'amount'),
|
||||
receiveStatusContactCode: actionCreator(types.RECEIVE_STATUS_CONTACT_CODE, 'statusContactCode'),
|
||||
receiveSntAllowance: actionCreator(types.RECEIVE_SNT_ALLOWANCE, 'SNTAllowance')
|
||||
}
|
||||
|
||||
export default function(state = { loading: true, accounts: [] }, action) {
|
||||
switch (action.type) {
|
||||
case types.RECEIVE_ACCOUNTS: {
|
||||
const { defaultAccount, accounts } = action.payload
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
defaultAccount,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
case types.UPDATE_DEFAULT_ACCOUNT: {
|
||||
const { defaultAccount } = action.payload
|
||||
return { ...state, defaultAccount }
|
||||
}
|
||||
case types.ADD_TO_SNT_TOKEN_BALANCE: {
|
||||
const currentAccount = { ...getCurrentAccount({accounts: state}) }
|
||||
currentAccount.SNTBalance = Number(currentAccount.SNTBalance) + Number(action.payload.amount)
|
||||
const accounts = [ ...state.accounts ]
|
||||
const idx = accounts.findIndex(a => a.address === currentAccount.address)
|
||||
accounts[idx] = currentAccount
|
||||
return {
|
||||
...state,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
case types.SUBTRACT_FROM_SNT_TOKEN_BALANCE: {
|
||||
const currentAccount = { ...getCurrentAccount({accounts: state}) }
|
||||
currentAccount.SNTBalance = Number(currentAccount.SNTBalance) - Number(action.payload.amount)
|
||||
const accounts = [ ...state.accounts ]
|
||||
const idx = accounts.findIndex(a => a.address === currentAccount.address)
|
||||
accounts[idx] = currentAccount
|
||||
return {
|
||||
...state,
|
||||
accounts
|
||||
}
|
||||
}
|
||||
case types.RECEIVE_STATUS_CONTACT_CODE: {
|
||||
const { statusContactCode } = action.payload
|
||||
return { ...state, statusContactCode }
|
||||
}
|
||||
case types.RECEIVE_SNT_ALLOWANCE: {
|
||||
const { SNTAllowance } = action.payload
|
||||
return { ...state, SNTAllowance }
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export const getAccountState = state => state.acounts;
|
||||
export const getAccounts = state => state.accounts.accounts;
|
||||
export const getDefaultAccount = state => state.accounts.defaultAccount;
|
||||
export const accountsIsLoading = state => state.accounts.loading;
|
||||
export const getStatusContactCode = state => state.accounts.statusContactCode;
|
||||
export const getSNTAllowance = state => state.accounts.SNTAllowance;
|
||||
export const getCurrentAccount = createSelector(
|
||||
getDefaultAccount,
|
||||
getAccounts,
|
||||
(defaultAccount, accounts) => accounts.find(a => a.address === defaultAccount)
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import accounts from './accounts'
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
accounts
|
||||
});
|
||||
|
||||
export default rootReducer;
|
|
@ -0,0 +1,11 @@
|
|||
import { createStore, applyMiddleware } from 'redux';
|
||||
import rootReducer from '../reducers/rootReducer';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
const store = createStore(
|
||||
rootReducer,
|
||||
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
|
||||
applyMiddleware(thunk)
|
||||
);
|
||||
|
||||
export default store;
|
|
@ -0,0 +1,14 @@
|
|||
import web3 from "Embark/web3"
|
||||
import EmbarkJS from 'Embark/EmbarkJS'
|
||||
import store from './configureStore'
|
||||
import { fetchAndDispatchSNTAllowance, fetchAndDispatchAccountsWithBalances, checkAndDispatchStatusContactCode } from '../actions/accounts'
|
||||
|
||||
const dispatch = action => store.dispatch(action)
|
||||
|
||||
export default () => {
|
||||
EmbarkJS.onReady(async (err) => {
|
||||
fetchAndDispatchAccountsWithBalances(web3, dispatch)
|
||||
checkAndDispatchStatusContactCode(dispatch)
|
||||
fetchAndDispatchSNTAllowance(dispatch)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
import styled, { css } from 'styled-components'
|
||||
import SafeLink from '../SafeLink'
|
||||
import theme from '../../theme'
|
||||
import { font, unselectable } from '../../utils/styles'
|
||||
import PublicUrl, { styledUrl } from '../../providers/PublicUrl'
|
||||
import cross from './assets/cross.svg'
|
||||
import check from './assets/check.svg'
|
||||
import crossWhite from './assets/cross-white.svg'
|
||||
import checkWhite from './assets/check-white.svg'
|
||||
|
||||
const {
|
||||
gradientStart,
|
||||
gradientEnd,
|
||||
gradientStartActive,
|
||||
gradientEndActive,
|
||||
gradientText,
|
||||
contentBackground,
|
||||
contentBorder,
|
||||
contentBorderActive,
|
||||
secondaryBackground,
|
||||
textPrimary,
|
||||
textSecondary,
|
||||
disabled: disabledColor,
|
||||
disabledText,
|
||||
} = theme
|
||||
|
||||
// Plain button = normal or strong
|
||||
const plainButtonStyles = css`
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0);
|
||||
&:after {
|
||||
content: '';
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
${({ disabled }) =>
|
||||
disabled
|
||||
? ''
|
||||
: css`
|
||||
&:hover,
|
||||
&:focus {
|
||||
box-shadow: ${({ disabled }) =>
|
||||
disabled ? 'none' : '0 1px 1px rgba(0, 0, 0, 0.2)'};
|
||||
}
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 1px 1px rgba(0, 0, 0, 0);
|
||||
&:after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`};
|
||||
`
|
||||
|
||||
const modeNormal = css`
|
||||
${plainButtonStyles};
|
||||
&:active {
|
||||
color: ${textPrimary};
|
||||
}
|
||||
`
|
||||
|
||||
const modeSecondary = css`
|
||||
${plainButtonStyles};
|
||||
background: ${secondaryBackground};
|
||||
&:hover,
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
`
|
||||
|
||||
const modeStrong = css`
|
||||
${plainButtonStyles};
|
||||
${font({ size: 'small', weight: 'bold' })};
|
||||
|
||||
${({ disabled }) =>
|
||||
disabled
|
||||
? css`
|
||||
color: ${disabledText};
|
||||
background-color: ${disabledColor};
|
||||
background-image: none;
|
||||
`
|
||||
: css`
|
||||
color: ${gradientText};
|
||||
background-color: transparent;
|
||||
background-image: linear-gradient(
|
||||
130deg,
|
||||
${gradientStart},
|
||||
${gradientEnd}
|
||||
)};
|
||||
|
||||
&:after {
|
||||
background-image: linear-gradient(
|
||||
130deg,
|
||||
${gradientStartActive},
|
||||
${gradientEndActive}
|
||||
);
|
||||
}
|
||||
`};
|
||||
`
|
||||
|
||||
const modeOutline = css`
|
||||
background: transparent;
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
border: 1px solid ${contentBorder};
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: ${contentBorderActive};
|
||||
}
|
||||
&:active {
|
||||
color: ${textPrimary};
|
||||
border-color: ${textPrimary};
|
||||
}
|
||||
`
|
||||
|
||||
const modeText = css`
|
||||
padding: 10px;
|
||||
background: transparent;
|
||||
&:active,
|
||||
&:focus {
|
||||
color: ${textPrimary};
|
||||
}
|
||||
`
|
||||
|
||||
const compactStyle = css`
|
||||
padding: ${({ mode }) => (mode === 'outline' ? '4px 14px' : '5px 15px')};
|
||||
`
|
||||
|
||||
const positiveStyle = css`
|
||||
padding-left: 34px;
|
||||
background: url(${styledUrl(check)}) no-repeat 12px calc(50% - 1px);
|
||||
${({ mode }) => {
|
||||
if (mode !== 'strong') return ''
|
||||
return css`
|
||||
&,
|
||||
&:active {
|
||||
background-image: url(${styledUrl(checkWhite)});
|
||||
background-color: ${theme.positive};
|
||||
}
|
||||
&:after {
|
||||
background: none;
|
||||
}
|
||||
`
|
||||
}};
|
||||
`
|
||||
|
||||
const negativeStyle = css`
|
||||
padding-left: 30px;
|
||||
background: url(${styledUrl(cross)}) no-repeat 10px calc(50% - 1px);
|
||||
${({ mode }) => {
|
||||
if (mode !== 'strong') return ''
|
||||
return css`
|
||||
&,
|
||||
&:active {
|
||||
background-image: url(${styledUrl(crossWhite)});
|
||||
background-color: ${theme.negative};
|
||||
}
|
||||
&:after {
|
||||
background: none;
|
||||
}
|
||||
`
|
||||
}};
|
||||
`
|
||||
|
||||
const StyledButton = styled.button.attrs({ type: 'button' })`
|
||||
width: ${({ wide }) => (wide ? '100%' : 'auto')};
|
||||
padding: 10px 15px;
|
||||
white-space: nowrap;
|
||||
${font({ size: 'small', weight: 'normal' })};
|
||||
color: ${textSecondary};
|
||||
background: ${contentBackground};
|
||||
border: 0;
|
||||
border-radius: 3px;
|
||||
outline: 0;
|
||||
cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')};
|
||||
&,
|
||||
&:after {
|
||||
transition-property: all;
|
||||
transition-duration: 100ms;
|
||||
transition-timing-function: ease-in-out;
|
||||
}
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
${({ mode }) => {
|
||||
if (mode === 'secondary') return modeSecondary
|
||||
if (mode === 'strong') return modeStrong
|
||||
if (mode === 'outline') return modeOutline
|
||||
if (mode === 'text') return modeText
|
||||
return modeNormal
|
||||
}};
|
||||
|
||||
${({ compact }) => (compact ? compactStyle : '')};
|
||||
|
||||
${({ emphasis }) => {
|
||||
if (emphasis === 'positive') return positiveStyle
|
||||
if (emphasis === 'negative') return negativeStyle
|
||||
return ''
|
||||
}};
|
||||
`
|
||||
|
||||
const Button = PublicUrl.hocWrap(StyledButton)
|
||||
const Anchor = PublicUrl.hocWrap(
|
||||
StyledButton.withComponent(SafeLink).extend`
|
||||
${unselectable};
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
`
|
||||
)
|
||||
|
||||
Button.Anchor = Anchor
|
||||
|
||||
export default Button
|
|
@ -0,0 +1,97 @@
|
|||
# Button
|
||||
|
||||
A simple Button component.
|
||||
|
||||
## Usage
|
||||
|
||||
```jsx
|
||||
import { Button } from '@aragon/ui'
|
||||
|
||||
const App = () => (
|
||||
<Button>Hello World</Button>
|
||||
)
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
### `mode`
|
||||
|
||||
- Type: `String`
|
||||
- Values: `normal` (default), `secondary`, `outline`, `strong` or `text`.
|
||||
|
||||
Set this property to the desired visual variant.
|
||||
|
||||
#### Example:
|
||||
|
||||
```jsx
|
||||
const App = () => (
|
||||
<div>
|
||||
<Button mode="outline">Cancel</Button>
|
||||
<Button mode="strong">Accept</Button>
|
||||
</div>
|
||||
)
|
||||
```
|
||||
|
||||
### `emphasis`
|
||||
|
||||
- Type: `String`
|
||||
- Values: `positive` or `negative`.
|
||||
|
||||
Set this property to provide positive or negative visual cues.
|
||||
|
||||
#### Example:
|
||||
|
||||
```jsx
|
||||
const App = () => (
|
||||
<div>
|
||||
<Button emphasis="negative">Cancel</Button>
|
||||
<Button emphasis="positive">Accept</Button>
|
||||
</div>
|
||||
)
|
||||
```
|
||||
|
||||
### `compact`
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `false`
|
||||
|
||||
Set to true to obtain a button that contains less padding than normal buttons.
|
||||
|
||||
#### Example:
|
||||
|
||||
```jsx
|
||||
const MyButton = () => (
|
||||
<Button compact>Accept</Button>
|
||||
)
|
||||
```
|
||||
|
||||
### `wide`
|
||||
|
||||
- Type: `Boolean`
|
||||
- Default: `false`
|
||||
|
||||
Set to true to obtain a button that expands horizontally.
|
||||
|
||||
#### Example:
|
||||
|
||||
```jsx
|
||||
const MyButton = () => (
|
||||
<Button wide>Accept</Button>
|
||||
)
|
||||
```
|
||||
|
||||
## Attached Components
|
||||
|
||||
### `Anchor`
|
||||
|
||||
An `<a>` styled to be visually similar to Buttons, supporting the same properties.
|
||||
|
||||
#### Example:
|
||||
|
||||
```jsx
|
||||
const LinkButton = () => (
|
||||
<Button.Anchor mode="strong" href="https://aragon.one/" target="_blank">
|
||||
Aragon
|
||||
</Button.Anchor>
|
||||
)
|
||||
```
|
|
@ -0,0 +1 @@
|
|||
<svg width="14" height="10" viewBox="0 0 14 10" xmlns="http://www.w3.org/2000/svg"><path d="M4.176 7.956L12.114 0l1.062 1.062-9 9L0 5.886l1.044-1.062z" fill="#FFF" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 191 B |
|
@ -0,0 +1 @@
|
|||
<svg width="14" height="10" viewBox="0 0 14 10" xmlns="http://www.w3.org/2000/svg"><path d="M4.176 7.956L12.114 0l1.062 1.062-9 9L0 5.886l1.044-1.062z" fill="#21D48E" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 194 B |
|
@ -0,0 +1 @@
|
|||
<svg width="11" height="11" viewBox="0 0 11 11" xmlns="http://www.w3.org/2000/svg"><path d="M10.476 1.062L6.3 5.238l4.176 4.176-1.062 1.062L5.238 6.3l-4.176 4.176L0 9.414l4.176-4.176L0 1.062 1.062 0l4.176 4.176L9.414 0z" fill="#FFF" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 260 B |
|
@ -0,0 +1 @@
|
|||
<svg width="11" height="11" viewBox="0 0 11 11" xmlns="http://www.w3.org/2000/svg"><path d="M10.476 1.524L6.3 5.7l4.176 4.176-1.062 1.062-4.176-4.176-4.176 4.176L0 9.876 4.176 5.7 0 1.524 1.062.462l4.176 4.176L9.414.462z" fill="#FB7777" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 264 B |
|
@ -0,0 +1,2 @@
|
|||
// @create-index
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
// @create-index
|
||||
|
||||
export { default as Button } from './Button.js';
|
||||
export { default as assets } from './assets';
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import styled from 'styled-components'
|
||||
|
||||
import { theme } from '../theme';
|
||||
import { media } from '../utils/media';
|
||||
|
||||
const StyledCard = styled.div`
|
||||
width: ${({ width }) => width || '282px'};
|
||||
height: ${({ height }) => height || '322px'};
|
||||
background: ${theme.contentBackground};
|
||||
border: ${({ border }) => border || `1px solid ${theme.contentBorder}`};
|
||||
border-radius: 3px;
|
||||
min-height: 30vh;
|
||||
${media.desktop`margin-top: 5%;`}
|
||||
${media.giant`margin-top: 5%;`}
|
||||
${media.tablet`margin-top: 25%;`}
|
||||
${media.phone`margin-top: 25%;`}
|
||||
`
|
||||
|
||||
export default StyledCard
|
|
@ -0,0 +1,46 @@
|
|||
import { node, string } from 'prop-types'
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Text from './Text'
|
||||
import theme from '../theme'
|
||||
import { unselectable } from '../utils/styles'
|
||||
|
||||
const StyledField = styled.div`
|
||||
margin-bottom: 20px;
|
||||
`
|
||||
|
||||
const StyledAsterisk = styled.span`
|
||||
color: ${theme.accent};
|
||||
float: right;
|
||||
padding-top: 3px;
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
const StyledTextBlock = styled(Text.Block)`
|
||||
font-weight: 400;
|
||||
${unselectable()};
|
||||
`
|
||||
|
||||
const Field = ({ children, label, wide, ...props }) => {
|
||||
const isRequired = React.Children.toArray(children).some(
|
||||
({ props: childProps }) => childProps.required
|
||||
)
|
||||
return (
|
||||
<StyledField {...props}>
|
||||
<label style={{ width: wide ? '100%' : 'auto' }}>
|
||||
<StyledTextBlock color={theme.textSecondary} smallcaps>
|
||||
{label}
|
||||
{isRequired && <StyledAsterisk title="Required">*</StyledAsterisk>}
|
||||
</StyledTextBlock>
|
||||
{children}
|
||||
</label>
|
||||
</StyledField>
|
||||
)
|
||||
}
|
||||
|
||||
Field.propTypes = {
|
||||
children: node,
|
||||
label: string,
|
||||
}
|
||||
|
||||
export default Field
|
|
@ -0,0 +1,73 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import styled from 'styled-components'
|
||||
import Attention from '../icons/components/Attention'
|
||||
import Bylaw from '../icons/components/Bylaw'
|
||||
import theme from '../theme'
|
||||
import { font } from '../utils/styles'
|
||||
import Info from './Info'
|
||||
|
||||
const Icon = styled.span`
|
||||
margin-right: 10px;
|
||||
`
|
||||
|
||||
const Title = styled.div`
|
||||
color: ${theme.textSecondary};
|
||||
margin-bottom: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
${font({ size: 'small' })};
|
||||
`
|
||||
|
||||
const TitlelessBody = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const IconInfo = ({ children, icon, title, ...props }) => {
|
||||
let titleElm = title
|
||||
let bodyElm = (
|
||||
<TitlelessBody>
|
||||
{icon && <Icon>{icon}</Icon>}
|
||||
{children}
|
||||
</TitlelessBody>
|
||||
)
|
||||
if (title) {
|
||||
titleElm = (
|
||||
<Title>
|
||||
{icon && <Icon>{icon}</Icon>}
|
||||
{title}
|
||||
</Title>
|
||||
)
|
||||
bodyElm = children
|
||||
}
|
||||
return (
|
||||
<Info title={titleElm} {...props}>
|
||||
{bodyElm}
|
||||
</Info>
|
||||
)
|
||||
}
|
||||
IconInfo.propTypes = {
|
||||
children: PropTypes.node,
|
||||
icon: PropTypes.node,
|
||||
title: PropTypes.node,
|
||||
}
|
||||
|
||||
const Action = props => <IconInfo icon={<Attention />} {...props} />
|
||||
|
||||
const PermissionIconInfo = styled(IconInfo)`
|
||||
${Icon} {
|
||||
color: ${theme.infoPermissionsIcon};
|
||||
}
|
||||
`
|
||||
|
||||
const Permissions = props => (
|
||||
<PermissionIconInfo
|
||||
background={theme.infoPermissionsBackground}
|
||||
icon={<Bylaw />}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
export { Action, Permissions }
|
||||
export default IconInfo
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import PropTypes from 'prop-types'
|
||||
import theme from '../theme'
|
||||
import { Action, Permissions } from './IconInfo'
|
||||
|
||||
const Info = ({ children, title, ...props }) => (
|
||||
<Main {...props}>
|
||||
{title && <Title>{title}</Title>}
|
||||
{children}
|
||||
</Main>
|
||||
)
|
||||
Info.propTypes = {
|
||||
background: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
title: PropTypes.node,
|
||||
}
|
||||
Info.defaultProps = {
|
||||
background: theme.infoBackground,
|
||||
}
|
||||
|
||||
const Main = styled.section`
|
||||
background: ${({ background }) => background};
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
word-wrap: break-word;
|
||||
`
|
||||
|
||||
const Title = styled.h1`
|
||||
display: flex;
|
||||
margin-top: 0px; // needed to overwrite bootstrap.css
|
||||
`
|
||||
|
||||
Info.Action = Action
|
||||
Info.Permissions = Permissions
|
||||
|
||||
export default Info
|
|
@ -0,0 +1,21 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
const styles = theme => ({
|
||||
button: {
|
||||
borderRadius: '4px',
|
||||
backgroundColor: 'rgba(67, 96, 223, 0.1)',
|
||||
}
|
||||
});
|
||||
const buttonText = { color: '#4360df', margin: '0 20px', fontWeight: 300 };
|
||||
const MobileButton = ({ classes, text, type, style, ...props }) => (
|
||||
<Fragment>
|
||||
<Button type={type} size="large" className={classNames(classes.button)} style={style} {...props} >
|
||||
<div style={buttonText}>{text}</div>
|
||||
</Button>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default withStyles(styles)(MobileButton);
|
|
@ -0,0 +1,54 @@
|
|||
import React from 'react';
|
||||
import styled, { css } from 'styled-components';
|
||||
import theme from '../theme';
|
||||
import SearchIcon from '@material-ui/icons/Search';
|
||||
import Button from '@material-ui/core/Button';
|
||||
|
||||
const searchWrapper = {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
paddingLeft: '11px',
|
||||
boxSizing: 'border-box',
|
||||
};
|
||||
|
||||
const pasteStyle = {
|
||||
...searchWrapper,
|
||||
paddingLeft: '17em',
|
||||
color: theme.accent
|
||||
};
|
||||
|
||||
const MobileInput = styled.input`
|
||||
display: block;
|
||||
border-radius: 0;
|
||||
background-color: #eef2f5;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
height: 3.5em;
|
||||
width: ${({ wide }) => (wide ? '100%' : 'auto')};
|
||||
appearance: none;
|
||||
box-shadow: none;
|
||||
padding-left: ${({ search }) => (search ? '45px' : '15px')};
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: ${theme.contentBorderActive};
|
||||
}
|
||||
`;
|
||||
|
||||
const MobileSearch = props => (
|
||||
<div style={{ position: 'relative' }}>
|
||||
{props.search && <div style={searchWrapper}>
|
||||
<SearchIcon style={{ color: theme.accent }} />
|
||||
</div>}
|
||||
<div style={{ display: 'flex' }}>
|
||||
<MobileInput {...props} autoCapitalize="none" />
|
||||
{!props.value && props.paste && <Button style={{ color: theme.accent }} onClick={props.paste}>Paste</Button>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default MobileSearch;
|
|
@ -0,0 +1,8 @@
|
|||
import styled from 'styled-components'
|
||||
|
||||
const SafeLink = styled.a.attrs({
|
||||
// See https://mathiasbynens.github.io/rel-noopener
|
||||
rel: 'noopener noreferrer',
|
||||
})``
|
||||
|
||||
export default SafeLink
|
|
@ -0,0 +1,34 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { font } from '../utils/styles/font'
|
||||
|
||||
const StyledText = styled.span`
|
||||
${({ size, weight, smallcaps }) => (font({ size, weight, smallcaps }))};
|
||||
${({ color }) => (color ? `color: ${color}` : '')};
|
||||
`
|
||||
|
||||
const Text = props => <StyledText {...props} />
|
||||
|
||||
const createTextContainer = Element => {
|
||||
const Container = ({
|
||||
children,
|
||||
color,
|
||||
size,
|
||||
smallcaps,
|
||||
weight,
|
||||
...props
|
||||
}) => {
|
||||
const textProps = { color, size, smallcaps, weight }
|
||||
return (
|
||||
<Element {...props}>
|
||||
<Text {...textProps}>{children}</Text>
|
||||
</Element>
|
||||
)
|
||||
}
|
||||
return Container
|
||||
}
|
||||
|
||||
Text.Block = createTextContainer('div')
|
||||
Text.Paragraph = createTextContainer('p')
|
||||
|
||||
export default Text
|
|
@ -0,0 +1,69 @@
|
|||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import styled, { css } from 'styled-components'
|
||||
import theme from '../theme'
|
||||
import { font } from '../utils/styles/font'
|
||||
|
||||
const baseStyles = css`
|
||||
${font({ size: 'small', weight: 'normal' })};
|
||||
width: ${({ wide }) => (wide ? '100%' : 'auto')};
|
||||
|
||||
padding: 5px 10px;
|
||||
background: ${theme.contentBackground};
|
||||
border: 1px solid ${theme.contentBorder};
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
color: ${theme.textPrimary};
|
||||
appearance: none;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: ${theme.contentBorderActive};
|
||||
}
|
||||
&:read-only {
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 ${theme.textSecondary};
|
||||
}
|
||||
`
|
||||
|
||||
// Simple input
|
||||
const TextInput = styled.input`
|
||||
${baseStyles};
|
||||
`
|
||||
TextInput.propTypes = {
|
||||
required: PropTypes.bool,
|
||||
type: PropTypes.oneOf([
|
||||
'email',
|
||||
'number',
|
||||
'password',
|
||||
'search',
|
||||
'tel',
|
||||
'text',
|
||||
'url',
|
||||
]),
|
||||
}
|
||||
TextInput.defaultProps = {
|
||||
required: false,
|
||||
type: 'text',
|
||||
}
|
||||
|
||||
// <input type=number> (only for compat)
|
||||
const TextInputNumber = styled.input.attrs({ type: 'number' })`
|
||||
${baseStyles};
|
||||
`
|
||||
|
||||
// Multiline input (textarea element)
|
||||
const TextInputMultiline = styled.textarea`
|
||||
${baseStyles};
|
||||
resize: vertical;
|
||||
`
|
||||
TextInputMultiline.propTypes = {
|
||||
required: PropTypes.bool,
|
||||
}
|
||||
TextInputMultiline.defaultProps = {
|
||||
required: false,
|
||||
}
|
||||
|
||||
TextInput.Number = TextInputNumber
|
||||
TextInput.Multiline = TextInputMultiline
|
||||
|
||||
export default TextInput
|
|
@ -0,0 +1,11 @@
|
|||
// @create-index
|
||||
|
||||
export * from './Button';
|
||||
export { default as Card } from './Card.js';
|
||||
export { default as Field } from './Field.js';
|
||||
export { default as Text } from './Text.js';
|
||||
export { default as TextInput } from './TextInput.js';
|
||||
export { default as MobileSearch } from './MobileSearch.js';
|
||||
export { default as MobileButton } from './MobileButton.js';
|
||||
export { default as Info } from './Info.js';
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import React from "react"
|
||||
|
||||
const Add = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M0 0h22v22H0z" />
|
||||
<path
|
||||
d="M11 4.744c1.216 0 2.341.304 3.376.912a6.308 6.308 0 0 1 2.368 2.368 6.546 6.546 0 0 1 .912 3.376 6.546 6.546 0 0 1-.912 3.376 6.308 6.308 0 0 1-2.368 2.368 6.546 6.546 0 0 1-3.376.912 6.546 6.546 0 0 1-3.376-.912 6.428 6.428 0 0 1-2.368-2.384 6.517 6.517 0 0 1-.912-3.36c0-1.205.304-2.325.912-3.36A6.55 6.55 0 0 1 7.64 5.656 6.517 6.517 0 0 1 11 4.744z"
|
||||
stroke="currentColor"
|
||||
/>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M11.656 8.056v2.688h2.688v1.312h-2.688v2.688h-1.312v-2.688H7.656v-1.312h2.688V8.056z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Add
|
|
@ -0,0 +1,16 @@
|
|||
import React from "react"
|
||||
|
||||
const Apps = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M0 0h22v22H0z" />
|
||||
<path
|
||||
d="M4.157 3.07C3.523 3.07 3 3.592 3 4.226v5.012c0 .635.523 1.157 1.157 1.157h5.012c.634 0 1.156-.522 1.156-1.157V4.226c0-.634-.522-1.156-1.156-1.156H4.157zm8.482 0c-.635 0-1.157.522-1.157 1.156v5.012c0 .635.522 1.157 1.157 1.157h5.012c.634 0 1.156-.522 1.156-1.157V4.226c0-.634-.522-1.156-1.156-1.156h-5.012zm-8.482.77h5.012c.22 0 .385.166.385.386v5.012c0 .22-.165.386-.385.386H4.157a.377.377 0 0 1-.386-.386V4.226c0-.22.165-.385.386-.385zm8.482 0h5.012c.22 0 .385.166.385.386v5.012c0 .22-.165.386-.385.386h-5.012a.377.377 0 0 1-.386-.386V4.226c0-.22.165-.385.386-.385zm2.463 7.706a.386.386 0 0 0-.343.391v2.892h-2.892a.386.386 0 1 0 0 .77h2.892v2.893a.386.386 0 1 0 .771 0V15.6h2.892a.386.386 0 1 0 0-.771H15.53v-2.892a.386.386 0 0 0-.428-.391zm-10.945.006c-.634 0-1.157.522-1.157 1.156v5.012c0 .635.523 1.157 1.157 1.157h5.012c.634 0 1.156-.522 1.156-1.157v-5.012c0-.634-.522-1.156-1.156-1.156H4.157zm0 .77h5.012c.22 0 .385.166.385.386v5.012c0 .22-.165.386-.385.386H4.157a.377.377 0 0 1-.386-.386v-5.012c0-.22.165-.385.386-.385z"
|
||||
fill="currentColor"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Apps
|
|
@ -0,0 +1,9 @@
|
|||
import React from "react"
|
||||
|
||||
const ArrowDown = props => (
|
||||
<svg width={9} height={5} viewBox="0 0 9 5" {...props}>
|
||||
<path d="M0 0h8.36L4.18 4.18z" fill="currentColor" fillRule="evenodd" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default ArrowDown
|
|
@ -0,0 +1,16 @@
|
|||
import React from "react"
|
||||
|
||||
const Attention = props => (
|
||||
<svg width={14} height={14} viewBox="0 0 14 14" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<rect fill="#DAEAEF" width={14} height={14} rx={7} />
|
||||
<path
|
||||
d="M6.155 8.547h1.298V3.3H6.155v5.247zM6.045 11h1.529V9.537H6.045V11z"
|
||||
fill="#6D777B"
|
||||
opacity={0.7}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Attention
|
|
@ -0,0 +1,16 @@
|
|||
import React from "react"
|
||||
|
||||
const Blank = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M0 0h22v22H0z" />
|
||||
<path
|
||||
d="M17.655 2H5.345A.357.357 0 0 0 5 2.345v16.559c0 .181.163.345.345.345h9.768c.09 0 .2-.037.254-.11l2.542-2.85a.345.345 0 0 0 .091-.236V2.345A.357.357 0 0 0 17.655 2zM5.69 2.69h11.62v12.637h-2.25a.69.69 0 0 0-.69.69v2.542H5.69V2.689zm9.369 15.742v-2.433h2.16l-2.16 2.433z"
|
||||
fill="currentColor"
|
||||
fillRule="nonzero"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Blank
|
|
@ -0,0 +1,19 @@
|
|||
import React from "react"
|
||||
|
||||
const Bylaw = props => (
|
||||
<svg width={17} height={16} viewBox="0 0 17 16" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M-2-2h22v22H-2z" />
|
||||
<g opacity={0.8} stroke="currentColor">
|
||||
<path
|
||||
d="M9.036 1.143L1.578 4.357V5.43h14.916V4.357L9.036 1.143zm6.88 12.393H2.071c-.318 0-.577.242-.577.535v1.072h15V14.07c0-.293-.26-.535-.578-.535z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path d="M3 5v8.034M6 5v8.275M9 5v8.034M12 5v8.275M15 5v8.275" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Bylaw
|
|
@ -0,0 +1,13 @@
|
|||
import React from "react"
|
||||
|
||||
const Check = props => (
|
||||
<svg width={14} height={10} viewBox="0 0 14 10" {...props}>
|
||||
<path
|
||||
d="M4.176 7.956L12.114 0l1.062 1.062-9 9L0 5.886l1.044-1.062z"
|
||||
fill="#21D48E"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Check
|
|
@ -0,0 +1,13 @@
|
|||
import React from "react"
|
||||
|
||||
const Cross = props => (
|
||||
<svg width={11} height={11} viewBox="0 0 11 11" {...props}>
|
||||
<path
|
||||
d="M10.476 1.524L6.3 5.7l4.176 4.176-1.062 1.062-4.176-4.176-4.176 4.176L0 9.876 4.176 5.7 0 1.524 1.062.462l4.176 4.176L9.414.462z"
|
||||
fill="#FB7777"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Cross
|
|
@ -0,0 +1,13 @@
|
|||
import React from "react"
|
||||
|
||||
const Ellipsis = props => (
|
||||
<svg width={15} height={4} viewBox="0 0 15 4" {...props}>
|
||||
<path
|
||||
d="M7.5 3.213a1.42 1.42 0 0 1-.974-.37c-.278-.248-.418-.588-.418-1.021 0-.384.135-.71.404-.979S7.11.439 7.5.439s.722.135.997.404c.276.27.413.595.413.979 0 .439-.142.78-.427 1.025a1.465 1.465 0 0 1-.983.366zm-5.327 0c-.371 0-.694-.122-.97-.366C.928 2.603.791 2.26.791 1.822c0-.39.133-.718.398-.984.266-.266.594-.399.984-.399s.722.135.997.404c.275.27.413.595.413.979 0 .439-.142.78-.427 1.025a1.465 1.465 0 0 1-.983.366zm10.654 0c-.365 0-.688-.123-.97-.37-.28-.248-.421-.588-.421-1.021 0-.384.134-.71.403-.979.27-.269.598-.404.988-.404s.722.135.997.404c.276.27.413.595.413.979 0 .433-.14.773-.422 1.02a1.45 1.45 0 0 1-.988.371z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Ellipsis
|
|
@ -0,0 +1,25 @@
|
|||
import React from "react"
|
||||
|
||||
const Fundraising = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M0 0h22v22H0z" />
|
||||
<g stroke="currentColor">
|
||||
<path
|
||||
d="M6 12.26C6.402 13.75 9.137 15 12.475 15 16.089 15 19 13.534 19 11.875c0-.886-1.07-1.903-2.967-2.357"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path d="M16 6.88C16 8.536 13.1 10 9.5 10S3 8.536 3 6.88C3 5.224 5.9 4 9.5 4S16 5.224 16 6.88" />
|
||||
<path
|
||||
d="M6 12v2c0 1.667 2.9 3 6.5 3s6.5-1.333 6.5-3v-2c0 1.643-2.9 3.095-6.5 3.095S6 13.643 6 12z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path d="M9.5 10.095C5.9 10.095 3 8.643 3 7v2c0 1.667 2.9 3 6.5 3S16 10.667 16 9V7c0 1.643-2.9 3.095-6.5 3.095" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Fundraising
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react"
|
||||
|
||||
const Groups = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M0 0h22v22H0z" />
|
||||
<path
|
||||
d="M14.928 16.169l.395.22c.264.146.396.337.396.571v1.186a.753.753 0 0 1-.235.564.773.773 0 0 1-.556.227H4.79a.773.773 0 0 1-.557-.227.753.753 0 0 1-.234-.564V16.96c0-.215.132-.4.396-.557l.395-.234 3.032-1.64c-.556-.44-.942-1.026-1.157-1.758-.215-.733-.322-1.314-.322-1.744V9.05c0-.332.102-.662.307-.989.205-.327.474-.623.806-.886.332-.264.71-.479 1.135-.645a3.497 3.497 0 0 1 1.282-.249c.44 0 .87.083 1.29.25.419.165.793.38 1.12.644.327.263.59.559.79.886.2.327.301.657.301.989v1.977c0 .479-.095 1.075-.286 1.787-.19.713-.559 1.29-1.106 1.729l2.945 1.626zM14.78 18v-.835a.391.391 0 0 0-.102-.073l-.117-.059a.071.071 0 0 1-.037-.014.479.479 0 0 0-.051-.03l-2.945-1.626a.99.99 0 0 1-.337-.315.863.863 0 0 1-.146-.432.856.856 0 0 1 .066-.454.913.913 0 0 1 .285-.366c.4-.313.674-.76.82-1.34.147-.581.22-1.058.22-1.429V9.05c0-.342-.256-.733-.769-1.172a2.672 2.672 0 0 0-1.794-.66c-.664 0-1.262.22-1.794.66-.533.44-.799.83-.799 1.172v1.977c0 .371.088.848.264 1.429.176.58.464 1.027.864 1.34.117.098.208.22.271.366a.856.856 0 0 1 .066.454.863.863 0 0 1-.146.432.865.865 0 0 1-.337.3L5.23 17.005a.06.06 0 0 0-.029.007.115.115 0 0 0-.03.022.432.432 0 0 0-.124.059l-.11.073V18h9.844zm3.428-4.16l.395.22c.264.146.396.336.396.57v1.173a.753.753 0 0 1-.234.564.773.773 0 0 1-.557.227h-1.553a5.372 5.372 0 0 0-.183-.542.952.952 0 0 0-.3-.396h1.89v-.835a4.202 4.202 0 0 0-.103-.066.408.408 0 0 0-.117-.05.092.092 0 0 0-.037-.023.402.402 0 0 1-.051-.022l-2.988-1.626a.99.99 0 0 1-.337-.315.863.863 0 0 1-.147-.432.856.856 0 0 1 .066-.454.913.913 0 0 1 .286-.366c.4-.322.68-.774.842-1.355.161-.581.242-1.052.242-1.414V6.721c0-.352-.264-.747-.791-1.187a2.756 2.756 0 0 0-1.817-.659 2.981 2.981 0 0 0-1.787.6 2.301 2.301 0 0 0-.586-.102l-.6-.03c.341-.4.78-.737 1.318-1.01a3.608 3.608 0 0 1 1.655-.41c.44 0 .872.085 1.297.256a4.52 4.52 0 0 1 1.135.652c.332.264.6.562.806.894.205.332.307.664.307.996v1.977c0 .469-.102 1.062-.307 1.78-.205.718-.581 1.292-1.128 1.721l2.988 1.64z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Groups
|
|
@ -0,0 +1,12 @@
|
|||
import React from "react"
|
||||
|
||||
const Home = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<path
|
||||
d="M17.884 9.993c.08.085.121.198.115.314a.484.484 0 0 1-.129.312.667.667 0 0 1-.143.09.375.375 0 0 1-.157.033c-.06 0-.12-.01-.176-.033a.338.338 0 0 1-.137-.103l-.586-.614v6.53a.419.419 0 0 1-.13.307.419.419 0 0 1-.307.13h-3.052a.419.419 0 0 1-.307-.13.419.419 0 0 1-.13-.307V12.16H9.268l-.014 4.362a.419.419 0 0 1-.13.307.419.419 0 0 1-.307.13H5.78a.43.43 0 0 1-.437-.437V9.993l-.586.613a.484.484 0 0 1-.314.13.428.428 0 0 1-.312-.117.484.484 0 0 1-.13-.312.428.428 0 0 1 .116-.314L10.7 3.135a.463.463 0 0 1 .15-.102.456.456 0 0 1 .34 0 .339.339 0 0 1 .137.102l6.557 6.858zM15.8 9.147c0-.019.003-.036.013-.054l-4.799-5.016-4.81 5.016a.26.26 0 0 0 .005.054.223.223 0 0 1 .007.055v6.885h2.168l.014-4.363a.419.419 0 0 1 .129-.307.419.419 0 0 1 .307-.13h4.348a.419.419 0 0 1 .307.13c.083.08.13.191.13.307v4.363h2.18v-6.94z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Home
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react"
|
||||
|
||||
const Identity = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M0 0h22v22H0z" />
|
||||
<path
|
||||
d="M4.04 16.984V18h11.44v1H4.04a1.02 1.02 0 0 1-.731-.297A.943.943 0 0 1 3 18v-1.5c0-.281.173-.518.52-.71l.52-.29 4.566-2.078a3.891 3.891 0 0 1-.926-1.008 6.536 6.536 0 0 1-.61-1.21 6.431 6.431 0 0 1-.333-1.212A6.209 6.209 0 0 1 6.64 9V6.5c0-.417.135-.833.406-1.25a4.43 4.43 0 0 1 1.073-1.125 5.961 5.961 0 0 1 1.503-.813A4.82 4.82 0 0 1 11.32 3c.574 0 1.14.104 1.698.313a5.819 5.819 0 0 1 1.495.812c.439.333.796.708 1.073 1.125.276.417.414.833.414 1.25V9c0 .302-.032.651-.098 1.047a6.59 6.59 0 0 1-.324 1.21 5.86 5.86 0 0 1-.602 1.188 4.13 4.13 0 0 1-.91.992l1.381.61-.26 1-1.56-.703a1.045 1.045 0 0 1-.406-.32.958.958 0 0 1-.195-.477c-.01-.177.016-.344.082-.5a1.03 1.03 0 0 1 .308-.406c.564-.417.962-1.008 1.195-1.774.233-.765.349-1.388.349-1.867V6.5c0-.27-.108-.552-.325-.844a3.757 3.757 0 0 0-.837-.804 4.941 4.941 0 0 0-1.162-.61A3.744 3.744 0 0 0 11.32 4c-.444 0-.883.08-1.316.242a4.932 4.932 0 0 0-1.17.617 3.65 3.65 0 0 0-.837.813c-.211.292-.317.568-.317.828V9c0 .49.127 1.115.382 1.875.254.76.653 1.349 1.194 1.766a.969.969 0 0 1 .195 1.367c-.108.14-.243.247-.406.32l-4.566 2.078a1.041 1.041 0 0 0-.268.157c-.114.083-.171.223-.171.421zM18.5 16a.48.48 0 0 1 .352.148.48.48 0 0 1 .148.352.48.48 0 0 1-.148.352.48.48 0 0 1-.352.148H17v1.5a.48.48 0 0 1-.148.352.48.48 0 0 1-.352.148.48.48 0 0 1-.352-.148A.48.48 0 0 1 16 18.5V17h-1.5a.48.48 0 0 1-.352-.148A.48.48 0 0 1 14 16.5a.48.48 0 0 1 .148-.352A.48.48 0 0 1 14.5 16H16v-1.5a.48.48 0 0 1 .148-.352A.48.48 0 0 1 16.5 14a.48.48 0 0 1 .352.148.48.48 0 0 1 .148.352V16h1.5z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Identity
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react"
|
||||
|
||||
const Notifications = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M0 0h22v22H0z" />
|
||||
<path
|
||||
d="M17.271 13.367c.254.313.455.591.601.835a.731.731 0 0 1 .044.733.718.718 0 0 1-.571.424c-.264.04-.469.059-.616.059H13.99c0 .693-.244 1.284-.732 1.772a2.414 2.414 0 0 1-1.773.733 2.414 2.414 0 0 1-1.772-.733 2.414 2.414 0 0 1-.733-1.772H6.3c-.225 0-.457-.027-.696-.08a.732.732 0 0 1-.52-.403c-.117-.245-.107-.496.03-.755a6.56 6.56 0 0 1 .556-.857c.264-.342.552-.73.864-1.164.313-.435.469-.887.469-1.355V7.742c0-.664.115-1.286.344-1.867.23-.582.545-1.09.945-1.524.4-.434.874-.776 1.42-1.025a4.191 4.191 0 0 1 1.759-.374c.634 0 1.225.125 1.772.374a4.46 4.46 0 0 1 1.428 1.025c.406.435.723.942.953 1.524a5.04 5.04 0 0 1 .344 1.867v3.062c0 .478.149.942.447 1.391.297.45.583.84.856 1.172zm-5.786 3.574c.42 0 .78-.149 1.077-.446.298-.298.447-.657.447-1.077H9.962c0 .42.149.779.447 1.077.298.297.656.446 1.076.446zm5.347-2.52a8.139 8.139 0 0 0-.337-.425c-.322-.39-.657-.856-1.003-1.398a3.262 3.262 0 0 1-.52-1.794V7.742c0-.527-.09-1.02-.271-1.48a3.85 3.85 0 0 0-.74-1.2 3.511 3.511 0 0 0-1.106-.813c-.425-.2-.886-.3-1.384-.3-.489 0-.945.1-1.37.3-.425.2-.793.47-1.106.813a3.744 3.744 0 0 0-.732 1.2 4.102 4.102 0 0 0-.264 1.48v3.062c0 .634-.183 1.218-.55 1.75a46.606 46.606 0 0 1-1.157 1.619c-.063.088-.12.17-.168.249h10.708z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Notifications
|
|
@ -0,0 +1,19 @@
|
|||
import React from "react"
|
||||
|
||||
const Permissions = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M0 0h22v22H0z" />
|
||||
<g stroke="currentColor">
|
||||
<path
|
||||
d="M11.036 3.143L3.578 6.357V7.43h14.916V6.357l-7.458-3.214zm6.88 12.393H4.071c-.318 0-.577.242-.577.535v1.072h15V16.07c0-.293-.26-.535-.578-.535z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path d="M5 7v8.034M8 7v8.275M11 7v8.034M14 7v8.275M17 7v8.275" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Permissions
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react"
|
||||
|
||||
const Settings = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M0 0h22v22H0z" />
|
||||
<path
|
||||
d="M18.063 9.08c.224.038.437.148.637.329.2.18.3.403.3.666v.938c0 .254-.1.459-.3.615-.2.156-.413.264-.637.322l-1.216.293a6.84 6.84 0 0 1-.154.418 4.008 4.008 0 0 1-.183.388l.644 1.084c.127.195.205.42.235.674a.766.766 0 0 1-.235.659l-.659.659a.847.847 0 0 1-.674.256 1.38 1.38 0 0 1-.688-.212l-1.055-.674a7.697 7.697 0 0 1-.41.19 4.595 4.595 0 0 1-.44.162l-.263 1.216a1.29 1.29 0 0 1-.33.637c-.18.2-.403.3-.666.3h-.938a.743.743 0 0 1-.615-.3 1.749 1.749 0 0 1-.322-.637L9.8 15.86a6.001 6.001 0 0 1-.469-.168 4.816 4.816 0 0 1-.454-.213l-1.084.689a1.308 1.308 0 0 1-.681.212.813.813 0 0 1-.667-.256l-.674-.66a.785.785 0 0 1-.22-.658c.03-.254.103-.479.22-.674l.689-1.143a8.068 8.068 0 0 1-.169-.359 3.029 3.029 0 0 1-.139-.388l-1.215-.293a1.749 1.749 0 0 1-.638-.322.743.743 0 0 1-.3-.615v-.938c0-.263.1-.486.3-.666.2-.181.413-.29.638-.33l1.2-.264a3.44 3.44 0 0 1 .147-.41c.059-.136.117-.268.176-.395l-.689-1.143a1.664 1.664 0 0 1-.22-.674.785.785 0 0 1 .22-.659l.674-.659a.813.813 0 0 1 .667-.256c.268.014.495.085.68.212l1.085.689a5.325 5.325 0 0 1 .908-.381l.308-1.202a1.58 1.58 0 0 1 .307-.637.76.76 0 0 1 .63-.3h.938c.263 0 .483.1.659.3.176.2.288.408.337.623l.264 1.23a6.533 6.533 0 0 1 .85.352l1.054-.674a1.38 1.38 0 0 1 .688-.212.847.847 0 0 1 .674.256l.66.66a.766.766 0 0 1 .234.658 1.55 1.55 0 0 1-.235.674l-.644 1.084c.068.137.134.276.198.418.063.141.114.29.153.446l1.201.264zm0 1.86l.014-.836a.515.515 0 0 0-.205-.102l-1.743-.396-.161-.512a2.474 2.474 0 0 0-.117-.352 4.109 4.109 0 0 0-.176-.366l-.235-.469.923-1.538a.474.474 0 0 0 .066-.117.28.28 0 0 0 .022-.088l-.615-.6a.348.348 0 0 0-.19.058l-1.51.967-.483-.25a11.505 11.505 0 0 0-.351-.168 1.904 1.904 0 0 0-.366-.124l-.513-.176-.381-1.772a.431.431 0 0 0-.044-.11l-.03-.051h-.864a.34.34 0 0 0-.058.087.52.52 0 0 0-.044.147l-.425 1.7-.498.16a4.51 4.51 0 0 0-.762.322l-.483.25-1.567-.997-.074-.036a.671.671 0 0 0-.088-.037l-.615.615c0 .03.008.064.022.103a.898.898 0 0 0 .066.132l.952 1.582-.234.454a8.76 8.76 0 0 0-.154.351 2.79 2.79 0 0 0-.11.323l-.16.512-1.773.396a.537.537 0 0 0-.096.044.199.199 0 0 1-.066.03v.863c.02.02.05.037.088.052a.81.81 0 0 0 .147.036l1.714.44.16.498a3.144 3.144 0 0 0 .265.615l.22.454-.953 1.597a.474.474 0 0 0-.066.117.298.298 0 0 0-.022.103l.615.6c.03 0 .062-.007.096-.022a.59.59 0 0 0 .095-.051l1.538-.982.483.25a4.51 4.51 0 0 0 .762.322l.498.16.44 1.73c.01.048.022.09.036.124a.254.254 0 0 0 .051.08h.85a.515.515 0 0 0 .103-.19l.38-1.743.513-.176a5.14 5.14 0 0 0 .703-.293l.484-.249 1.538.982.073.036a.72.72 0 0 0 .088.037l.615-.615a.298.298 0 0 0-.022-.103.826.826 0 0 0-.066-.132l-.923-1.523.235-.469c.058-.107.11-.217.154-.33.044-.112.085-.227.124-.344l.161-.483 1.743-.44a.635.635 0 0 0 .125-.036.254.254 0 0 0 .08-.052zM11.5 7.687c.781 0 1.448.273 2 .82a2.7 2.7 0 0 1 .827 1.992 2.7 2.7 0 0 1-.827 1.992 2.737 2.737 0 0 1-2 .82 2.72 2.72 0 0 1-1.985-.82 2.7 2.7 0 0 1-.828-1.992 2.7 2.7 0 0 1 .828-1.992 2.72 2.72 0 0 1 1.985-.82zm0 4.687c.518 0 .96-.183 1.326-.55.366-.366.549-.807.549-1.325 0-.518-.183-.96-.55-1.326a1.806 1.806 0 0 0-1.325-.549c-.518 0-.96.183-1.326.55a1.804 1.804 0 0 0-.549 1.325c0 .518.183.96.55 1.326.366.366.807.549 1.325.549z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Settings
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react"
|
||||
|
||||
const Share = props => (
|
||||
<svg width={16} height={14} viewBox="0 0 16 14" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M-3-4h22v22H-3z" />
|
||||
<path
|
||||
d="M.531 13.719a.44.44 0 0 1-.312-.117.522.522 0 0 1-.157-.305c0-.042-.018-.3-.054-.774-.037-.474-.013-1.054.07-1.742.083-.687.258-1.43.524-2.226A6.74 6.74 0 0 1 1.89 6.313 6.89 6.89 0 0 1 3.125 5.18a7.514 7.514 0 0 1 1.523-.836 9.899 9.899 0 0 1 1.797-.54c.64-.13 1.326-.205 2.055-.226L8.516.781a.48.48 0 0 1 .078-.273.52.52 0 0 1 .203-.18.537.537 0 0 1 .281-.047c.094.01.177.047.25.11l6.485 5.312a.513.513 0 0 1 .187.39.498.498 0 0 1-.188.391L9.329 11.86a.453.453 0 0 1-.25.11.537.537 0 0 1-.281-.047.501.501 0 0 1-.203-.188.493.493 0 0 1-.078-.265L8.5 8.594c-1.75 0-3.125.234-4.125.703s-1.75.99-2.25 1.562c-.5.573-.815 1.107-.945 1.602-.13.495-.196.763-.196.805a.538.538 0 0 1-.132.32.387.387 0 0 1-.305.133H.53zm8.485-6.125c.062 0 .125.013.187.039a.72.72 0 0 1 .172.101.777.777 0 0 1 .102.164.473.473 0 0 1 .039.196v2.312l5.203-4.312-5.203-4.266v2.25a.48.48 0 0 1-.149.352.48.48 0 0 1-.351.148c-.709 0-1.375.05-2 .149a8.793 8.793 0 0 0-1.743.453 7.012 7.012 0 0 0-1.46.75 5.65 5.65 0 0 0-1.157 1.039 6.162 6.162 0 0 0-1.148 1.89 8.291 8.291 0 0 0-.492 1.922c.27-.375.604-.752 1-1.133.395-.38.903-.72 1.523-1.023.62-.302 1.375-.55 2.266-.742.89-.193 1.96-.29 3.21-.29z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Share
|
|
@ -0,0 +1,12 @@
|
|||
import React from "react"
|
||||
|
||||
const Status = props => (
|
||||
<svg width="330" height="124" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M72.458 61.429c-7.431.427-12.088-1.299-19.52-.871a31.245 31.245 0 0 0-5.47.796C48.565 47.65 58.292 35.662 71.519 34.9c8.117-.467 16.23 4.53 16.67 12.642.433 7.973-5.664 13.307-15.73 13.886M52.503 89.46c-7.776.438-15.547-4.24-15.969-11.831-.415-7.462 5.427-12.454 15.07-12.996 7.118-.4 11.58 1.216 18.698.815a30.589 30.589 0 0 0 5.24-.745C74.493 77.528 65.175 88.748 52.503 89.46M62 .181C27.758.18 0 27.857 0 62s27.758 61.82 62 61.82c34.242 0 62-27.678 62-61.82C124 27.858 96.242.18 62 .18" fill="#4360DF" />
|
||||
<path d="M152 71.11h9.464c0 1.389.452 2.386 1.356 2.991.905.605 2.066.908 3.484.908 1.172 0 2.132-.198 2.883-.596.75-.398 1.125-1.05 1.125-1.958 0-.536-.16-.976-.478-1.318-.319-.342-.807-.648-1.465-.916a34.317 34.317 0 0 0-2.517-.888l-3.71-1.18a61.345 61.345 0 0 1-3.653-1.304c-1.14-.45-2.142-1.012-3.005-1.683a7.086 7.086 0 0 1-2.02-2.453c-.483-.964-.724-2.159-.724-3.584 0-1.636.319-3.05.955-4.246a8.143 8.143 0 0 1 2.683-2.956c1.15-.776 2.528-1.358 4.13-1.745 1.604-.388 3.371-.582 5.303-.582 2.364 0 4.398.272 6.104.817 1.705.546 3.103 1.29 4.193 2.233 1.088.944 1.895 2.055 2.42 3.334.524 1.279.786 2.652.786 4.12h-9.526c0-1.28-.324-2.275-.972-2.988-.647-.713-1.628-1.07-2.943-1.07-1.007 0-1.87.226-2.59.677-.72.45-1.079 1.127-1.079 2.028 0 .605.17 1.09.51 1.454.34.364.839.693 1.497.984.66.292 1.472.595 2.44.907l3.326 1.076c1.355.356 2.619.75 3.79 1.185 1.17.435 2.203.964 3.096 1.586a6.936 6.936 0 0 1 2.127 2.34c.524.938.786 2.129.786 3.57 0 1.903-.37 3.523-1.11 4.861a9.342 9.342 0 0 1-2.975 3.267c-1.243.84-2.687 1.443-4.331 1.808-1.644.365-3.37.547-5.179.547-4.5 0-7.99-.959-10.466-2.877-2.477-1.918-3.715-4.702-3.715-8.35m28.768-12.938V50.86h4.7v-9.195h9.554v9.195h6.837v7.312h-6.837v11.015c0 1.144.102 2.054.305 2.73.203.676.493 1.207.87 1.591a2.64 2.64 0 0 0 1.374.749 8.744 8.744 0 0 0 1.816.171c.406 0 .926-.015 1.557-.047.63-.03 1.18-.088 1.648-.172v7.62a36.13 36.13 0 0 1-2.671.349c-1.068.106-2.325.159-3.77.159a26.9 26.9 0 0 1-3.548-.25 8.077 8.077 0 0 1-3.46-1.32c-1.062-.71-1.94-1.804-2.633-3.278-.695-1.475-1.042-3.5-1.042-6.073V58.171h-4.7zm33.723 8.773c0 1.073.185 2.099.554 3.078.368.978.875 1.82 1.517 2.525a7.436 7.436 0 0 0 2.323 1.705c.906.432 1.918.647 3.035.647 1.116 0 2.134-.215 3.05-.647.918-.432 1.697-1 2.34-1.705.643-.704 1.148-1.54 1.517-2.51a8.614 8.614 0 0 0 .554-3.093 8.273 8.273 0 0 0-.554-3.03 8.493 8.493 0 0 0-1.517-2.526 6.915 6.915 0 0 0-2.323-1.736c-.907-.42-1.93-.631-3.067-.631-1.117 0-2.129.21-3.035.63a6.92 6.92 0 0 0-2.323 1.737 8.5 8.5 0 0 0-1.517 2.526 8.272 8.272 0 0 0-.554 3.03m-9.915 0c0-2.336.376-4.504 1.129-6.503.752-2 1.8-3.72 3.147-5.161a15.203 15.203 0 0 1 4.72-3.425c1.802-.842 3.752-1.263 5.851-1.263 2.339 0 4.368.495 6.085 1.483 1.718.99 3.009 2.242 3.873 3.757v-4.451h9.915v31.158h-9.915V78.15c-.78 1.43-2.035 2.651-3.762 3.661-1.728 1.01-3.794 1.516-6.196 1.516-2.077 0-4.022-.421-5.834-1.263a15.017 15.017 0 0 1-4.737-3.441c-1.346-1.452-2.395-3.177-3.147-5.177-.753-1.999-1.129-4.167-1.129-6.503m36.704-8.773v-7.31h4.7v-9.195h9.554v9.195h6.837v7.312h-6.837v11.015c0 1.144.102 2.054.305 2.73.203.676.493 1.207.87 1.591a2.64 2.64 0 0 0 1.374.749 8.72 8.72 0 0 0 1.816.171c.406 0 .926-.015 1.557-.047.63-.03 1.18-.088 1.648-.172v7.62a36.13 36.13 0 0 1-2.671.349c-1.068.106-2.325.159-3.77.159a26.9 26.9 0 0 1-3.548-.25 8.077 8.077 0 0 1-3.46-1.32c-1.062-.71-1.94-1.804-2.633-3.278-.695-1.475-1.042-3.5-1.042-6.073V58.171h-4.7zm25.792 10.802v-18.38h9.723v15.872c0 2.217.392 4.02 1.177 5.411.784 1.39 2.188 2.086 4.212 2.086 2.024 0 3.517-.706 4.477-2.118.96-1.411 1.44-3.319 1.44-5.724V50.592h9.723v30.96h-9.723v-4.423c-.846 1.631-2.137 2.907-3.872 3.827-1.734.92-3.81 1.38-6.226 1.38-2.124 0-3.867-.335-5.231-1.004-1.364-.669-2.473-1.515-3.327-2.54-.978-1.193-1.618-2.573-1.92-4.14-.302-1.57-.453-3.462-.453-5.679m34.72 2.137h9.464c0 1.389.452 2.386 1.356 2.991.904.605 2.066.908 3.484.908 1.172 0 2.132-.198 2.882-.596.75-.398 1.126-1.05 1.126-1.958 0-.536-.16-.976-.478-1.318-.319-.342-.807-.648-1.465-.916a34.393 34.393 0 0 0-2.517-.888l-3.71-1.18a61.434 61.434 0 0 1-3.653-1.304c-1.14-.45-2.143-1.012-3.005-1.683a7.083 7.083 0 0 1-2.02-2.453c-.483-.964-.724-2.159-.724-3.584 0-1.636.318-3.05.955-4.246a8.143 8.143 0 0 1 2.683-2.956c1.15-.776 2.528-1.358 4.13-1.745 1.603-.388 3.37-.582 5.303-.582 2.363 0 4.398.272 6.104.817 1.705.546 3.103 1.29 4.193 2.233 1.088.944 1.895 2.055 2.42 3.334.523 1.279.786 2.652.786 4.12h-9.526c0-1.28-.324-2.275-.972-2.988-.647-.713-1.629-1.07-2.944-1.07-1.007 0-1.87.226-2.59.677-.719.45-1.078 1.127-1.078 2.028 0 .605.17 1.09.51 1.454.34.364.838.693 1.497.984.66.292 1.471.595 2.439.907l3.327 1.076a40.42 40.42 0 0 1 3.79 1.185c1.17.435 2.203.964 3.096 1.586a6.94 6.94 0 0 1 2.127 2.34c.524.938.786 2.129.786 3.57 0 1.903-.37 3.523-1.11 4.861a9.335 9.335 0 0 1-2.975 3.267c-1.243.84-2.687 1.443-4.331 1.808-1.645.365-3.37.547-5.179.547-4.5 0-7.99-.959-10.466-2.877-2.477-1.918-3.715-4.702-3.715-8.35" fill="#000" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default Status;
|
|
@ -0,0 +1,14 @@
|
|||
import React from "react"
|
||||
|
||||
const Time = props => (
|
||||
<svg width={13} height={13} viewBox="0 0 13 13" {...props}>
|
||||
<path
|
||||
d="M6.5 11.76c.8 0 1.535-.2 2.205-.6.66-.39 1.185-.92 1.575-1.59.39-.67.585-1.405.585-2.205S10.67 5.83 10.28 5.16a4.403 4.403 0 0 0-1.575-1.575A4.305 4.305 0 0 0 6.5 3c-.8 0-1.535.195-2.205.585-.66.39-1.185.915-1.575 1.575a4.305 4.305 0 0 0-.585 2.205c0 .8.195 1.535.585 2.205.39.67.915 1.2 1.575 1.59.67.4 1.405.6 2.205.6zm0-10.02c1.03 0 1.98.255 2.85.765.85.49 1.52 1.16 2.01 2.01.51.87.765 1.82.765 2.85s-.255 1.98-.765 2.85c-.49.85-1.16 1.52-2.01 2.01-.87.51-1.82.765-2.85.765s-1.98-.255-2.85-.765a5.386 5.386 0 0 1-2.01-2.01 5.535 5.535 0 0 1-.765-2.85c0-1.03.255-1.98.765-2.85.49-.85 1.16-1.52 2.01-2.01.87-.51 1.82-.765 2.85-.765zm.33 2.52v3.315L9.32 9.06l-.51.765L5.885 8.01V4.26h.945zM3.95 1.395l-2.895 2.37L.26 2.82 3.125.45l.825.945zm8.79 1.425l-.795.945-2.895-2.46.825-.945 2.865 2.46z"
|
||||
fill="#6D777B"
|
||||
fillRule="evenodd"
|
||||
opacity={0.75}
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Time
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react"
|
||||
|
||||
const Wallet = props => (
|
||||
<svg width={22} height={22} viewBox="0 0 22 22" {...props}>
|
||||
<g fill="none" fillRule="evenodd">
|
||||
<path d="M0 0h22v22H0z" />
|
||||
<path
|
||||
d="M19 7.186v8.642c0 .39-.137.723-.41.996-.274.274-.606.41-.996.41H5.406c-.39 0-.722-.136-.996-.41a1.356 1.356 0 0 1-.41-.996v-7.5c0-.38.137-.708.41-.981.274-.274.6-.415.982-.425h.468V5.047c0-.39.14-.723.418-.996a1.36 1.36 0 0 1 .989-.41l10.59 2.109c.528.146.85.38.967.703.117.322.176.567.176.733zM6.798 5.046v1.876h10.327c.186.01.308-.03.366-.117l.088-.132L7.237 4.578a.467.467 0 0 0-.44.469zm11.264 10.782V7.391c0 .156-.1.273-.3.351-.2.078-.412.117-.637.117H5.406a.45.45 0 0 0-.33.14.45.45 0 0 0-.138.33v7.5a.45.45 0 0 0 .139.329.45.45 0 0 0 .33.139h12.187a.45.45 0 0 0 .33-.14.45.45 0 0 0 .139-.329zm-11.25-4.687c.254 0 .474.092.66.278a.901.901 0 0 1 .278.66.894.894 0 0 1-.278.666.913.913 0 0 1-.66.27.906.906 0 0 1-.666-.27.906.906 0 0 1-.271-.667c0-.254.09-.474.271-.66a.894.894 0 0 1 .667-.277z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default Wallet
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,17 @@
|
|||
/* eslint-disable prettier/prettier */
|
||||
|
||||
export { default as IconAdd } from './components/Add'
|
||||
export { default as IconApps } from './components/Apps'
|
||||
export { default as IconBlank } from './components/Blank'
|
||||
export { default as IconCheck } from './components/Check'
|
||||
export { default as IconCross } from './components/Cross'
|
||||
export { default as IconFundraising } from './components/Fundraising'
|
||||
export { default as IconGroups } from './components/Groups'
|
||||
export { default as IconHome } from './components/Home'
|
||||
export { default as IconIdentity } from './components/Identity'
|
||||
export { default as IconNotifications } from './components/Notifications'
|
||||
export { default as IconPermissions } from './components/Permissions'
|
||||
export { default as IconSettings } from './components/Settings'
|
||||
export { default as IconShare } from './components/Share'
|
||||
export { default as IconTime } from './components/Time'
|
||||
export { default as IconWallet } from './components/Wallet'
|
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1 @@
|
|||
<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M0 0h22v22H0z"/><path d="M11 4.744c1.216 0 2.341.304 3.376.912a6.308 6.308 0 0 1 2.368 2.368 6.546 6.546 0 0 1 .912 3.376 6.546 6.546 0 0 1-.912 3.376 6.308 6.308 0 0 1-2.368 2.368 6.546 6.546 0 0 1-3.376.912 6.546 6.546 0 0 1-3.376-.912 6.428 6.428 0 0 1-2.368-2.384 6.517 6.517 0 0 1-.912-3.36c0-1.205.304-2.325.912-3.36A6.55 6.55 0 0 1 7.64 5.656 6.517 6.517 0 0 1 11 4.744z" stroke="#8B9396"/><path fill="#8B9396" d="M11.656 8.056v2.688h2.688v1.312h-2.688v2.688h-1.312v-2.688H7.656v-1.312h2.688V8.056z"/></g></svg>
|
After Width: | Height: | Size: 645 B |
|
@ -0,0 +1 @@
|
|||
<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M0 0h22v22H0z"/><path d="M4.157 3.07C3.523 3.07 3 3.592 3 4.226v5.012c0 .635.523 1.157 1.157 1.157h5.012c.634 0 1.156-.522 1.156-1.157V4.226c0-.634-.522-1.156-1.156-1.156H4.157zm8.482 0c-.635 0-1.157.522-1.157 1.156v5.012c0 .635.522 1.157 1.157 1.157h5.012c.634 0 1.156-.522 1.156-1.157V4.226c0-.634-.522-1.156-1.156-1.156h-5.012zm-8.482.77h5.012c.22 0 .385.166.385.386v5.012c0 .22-.165.386-.385.386H4.157a.377.377 0 0 1-.386-.386V4.226c0-.22.165-.385.386-.385zm8.482 0h5.012c.22 0 .385.166.385.386v5.012c0 .22-.165.386-.385.386h-5.012a.377.377 0 0 1-.386-.386V4.226c0-.22.165-.385.386-.385zm2.463 7.706a.386.386 0 0 0-.343.391v2.892h-2.892a.386.386 0 1 0 0 .77h2.892v2.893a.386.386 0 1 0 .771 0V15.6h2.892a.386.386 0 1 0 0-.771H15.53v-2.892a.386.386 0 0 0-.428-.391zm-10.945.006c-.634 0-1.157.522-1.157 1.156v5.012c0 .635.523 1.157 1.157 1.157h5.012c.634 0 1.156-.522 1.156-1.157v-5.012c0-.634-.522-1.156-1.156-1.156H4.157zm0 .77h5.012c.22 0 .385.166.385.386v5.012c0 .22-.165.386-.385.386H4.157a.377.377 0 0 1-.386-.386v-5.012c0-.22.165-.385.386-.385z" fill="#8B9396" fill-rule="nonzero"/></g></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg width="9" height="5" viewBox="0 0 9 5" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h8.36L4.18 4.18z" fill="#8B9396" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 152 B |
|
@ -0,0 +1 @@
|
|||
<svg width="14" height="14" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><rect fill="#DAEAEF" width="14" height="14" rx="7"/><path d="M6.155 8.547h1.298V3.3H6.155v5.247zM6.045 11h1.529V9.537H6.045V11z" fill="#6D777B" opacity=".7"/></g></svg>
|
After Width: | Height: | Size: 286 B |
|
@ -0,0 +1 @@
|
|||
<svg width="22" height="22" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M0 0h22v22H0z"/><path d="M17.655 2H5.345A.357.357 0 0 0 5 2.345v16.559c0 .181.163.345.345.345h9.768c.09 0 .2-.037.254-.11l2.542-2.85a.345.345 0 0 0 .091-.236V2.345A.357.357 0 0 0 17.655 2zM5.69 2.69h11.62v12.637H15.06a.69.69 0 0 0-.69.69v2.542h-8.68V2.689zm9.369 15.742v-2.433h2.16l-2.16 2.433z" fill="#8B9396" fill-rule="nonzero"/></g></svg>
|
After Width: | Height: | Size: 469 B |