sigma prime and deja vu audits

This commit is contained in:
Corey 2019-12-10 11:44:40 -05:00
parent d90a7ddfbe
commit 1219dc7316
168 changed files with 15517 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@ -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)

View File

@ -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)
-

View File

@ -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 |

View File

@ -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"
]
}

View File

@ -0,0 +1,9 @@
{
"extends": "airbnb",
"plugins": [
"react"
],
"rules": {
"react/prop-types": 0
}
}

View File

@ -0,0 +1 @@
gitdir: ../../.git/modules/code/ens-usernames

View File

@ -0,0 +1 @@
*.sol linguist-language=Solidity

View File

@ -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/

View File

@ -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))
})
}

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 123 KiB

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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.'
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -0,0 +1,10 @@
.ens-terms__title {
padding: 40px 24px;
background: #EEF2F5;
border-radius: 4px;
margin-bottom: 30px;
}
.ens-terms li {
}

View File

@ -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);

View File

@ -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;

View File

@ -0,0 +1,3 @@
export const zeroAddress = '0x0000000000000000000000000000000000000000';
export const zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
export const formatPrice = price => price.includes('.') ? price : Number(price).toLocaleString();

View File

@ -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
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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&#39;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;

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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'
}
}

View File

@ -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 };

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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>

View File

@ -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')
);

View File

@ -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)
)

View File

@ -0,0 +1,8 @@
import { combineReducers } from 'redux';
import accounts from './accounts'
const rootReducer = combineReducers({
accounts
});
export default rootReducer;

View File

@ -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;

View File

@ -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)
})
}

View File

@ -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

View File

@ -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>
)
```

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,5 @@
// @create-index
export { default as Button } from './Button.js';
export { default as assets } from './assets';

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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';

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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'

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More