Merge branch 'development' into master-pre-release
# Conflicts: # package.json # src/components/CookiesBanner/index.jsx # src/components/EtherscanBtn/index.jsx # src/components/EtherscanLink/index.jsx # src/components/Footer/index.jsx # src/components/Header/components/CircleDot.jsx # src/components/Header/components/Layout.jsx # src/components/Header/components/NetworkLabel.jsx # src/components/Header/components/SafeListHeader/index.jsx # src/components/Sidebar/LegalLinks.jsx # src/components/Sidebar/index.jsx # src/components/Sidebar/style.js # src/components/Spacer/index.jsx # src/components/layout/Hairline/index.js # src/config/index.js # src/logic/addressBook/model/addressBook.js # src/logic/addressBook/store/actions/addAddressBookEntry.js # src/logic/addressBook/store/actions/loadAddressBook.js # src/logic/addressBook/store/actions/loadAddressBookFromStorage.js # src/logic/addressBook/store/actions/removeAddressBookEntry.js # src/logic/addressBook/store/actions/updateAddressBookEntry.js # src/logic/addressBook/store/middleware/addressBookMiddleware.js # src/logic/addressBook/store/reducer/addressBook.js # src/logic/addressBook/store/selectors/index.js # src/logic/addressBook/utils/index.js # src/logic/notifications/notificationBuilder.js # src/logic/safe/transactions/awaitingTransactions.js # src/logic/safe/utils/safeVersion.js # src/logic/tokens/utils/tokenHelpers.js # src/logic/wallets/ethAddresses.js # src/routes/load/components/OwnerList/index.jsx # src/routes/load/components/ReviewInformation/index.jsx # src/routes/open/components/ReviewInformation/index.jsx # src/routes/open/components/SafeOwnersConfirmationsForm/style.js # src/routes/safe/components/AddressBook/CreateEditEntryModal/index.jsx # src/routes/safe/components/AddressBook/CreateEditEntryModal/style.js # src/routes/safe/components/AddressBook/DeleteEntryModal/index.jsx # src/routes/safe/components/AddressBook/DeleteEntryModal/style.js # src/routes/safe/components/AddressBook/EllipsisTransactionDetails/index.jsx # src/routes/safe/components/AddressBook/columns.js # src/routes/safe/components/AddressBook/index.jsx # src/routes/safe/components/AddressBook/style.js # src/routes/safe/components/Balances/Receive/index.jsx # src/routes/safe/components/Balances/SendModal/screens/AddressBookInput/index.jsx # src/routes/safe/components/Balances/SendModal/screens/AddressBookInput/style.js # src/routes/safe/components/Balances/SendModal/screens/SendCustomTx/index.jsx # src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.jsx # src/routes/safe/components/Balances/index.jsx # src/routes/safe/components/Layout.jsx # src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/index.jsx # src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.jsx # src/routes/safe/components/Settings/ManageOwners/index.jsx # src/routes/safe/components/Settings/RemoveSafeModal/index.jsx # src/routes/safe/components/Settings/SafeDetails/index.jsx # src/routes/safe/components/Settings/assets/icons/OwnersIcon.jsx # src/routes/safe/components/Settings/assets/icons/RequiredConfirmationsIcon.jsx # src/routes/safe/components/Settings/assets/icons/SafeDetailsIcon.jsx # src/routes/safe/components/Settings/style.js # src/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal/index.jsx # src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription/index.jsx # src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/OwnerComponent.jsx # src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/index.jsx # src/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/style.js # src/routes/safe/components/Transactions/TxsTable/ExpandedTx/RejectTxModal/index.jsx # src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.jsx # src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.jsx # src/routes/safe/components/Transactions/TxsTable/columns.js # src/routes/safe/components/Transactions/TxsTable/index.jsx # src/routes/safe/components/assets/AddressBookIcon.jsx # src/routes/safe/components/assets/BalancesIcon.jsx # src/routes/safe/components/assets/SettingsIcon.jsx # src/routes/safe/components/assets/TransactionsIcon.jsx # src/routes/safe/components/style.js # src/routes/safe/container/selector.js # src/routes/safe/store/actions/fetchSafe.js # src/routes/safe/store/actions/fetchTransactions.js # src/routes/safe/store/middleware/notificationsMiddleware.js # src/routes/safe/store/middleware/safeStorage.js # src/routes/safe/store/selectors/index.js # src/store/index.js # yarn.lock
This commit is contained in:
commit
c97cf75ba8
|
@ -1,7 +1,15 @@
|
|||
node_modules
|
||||
!.eslintrc.js
|
||||
build_webpack
|
||||
config
|
||||
contracts
|
||||
flow-typed
|
||||
flow-typed/npm
|
||||
config
|
||||
scripts
|
||||
migrations
|
||||
node_modules
|
||||
public
|
||||
scripts
|
||||
src/assets
|
||||
src/config
|
||||
test
|
||||
*.spec*
|
||||
*.test*
|
83
.eslintrc
83
.eslintrc
|
@ -1,33 +1,57 @@
|
|||
{
|
||||
"extends": ["airbnb", "plugin:flowtype/recommended", "plugin:jsx-a11y/recommended"],
|
||||
"settings": {
|
||||
"react": {
|
||||
"flowVersion": "0.112.0",
|
||||
"pragma": "React",
|
||||
"version": "16.12.0"
|
||||
},
|
||||
"import/extensions": [".js", ".jsx"]
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": ["jsx-a11y", "jest", "flowtype"],
|
||||
"plugins": ["react", "flowtype", "import", "jsx-a11y", "prettier"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:flowtype/recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:jsx-a11y/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier/react",
|
||||
"prettier/flowtype"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"amd": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
|
||||
"react/forbid-prop-types": [1, { "forbid": ["object", "any"] }],
|
||||
"class-methods-use-this": 0,
|
||||
"semi": ["error", "never"],
|
||||
"max-len": [
|
||||
"error",
|
||||
120,
|
||||
2,
|
||||
{
|
||||
"ignoreUrls": true,
|
||||
"ignoreComments": false,
|
||||
"ignoreRegExpLiterals": true,
|
||||
"ignoreStrings": true,
|
||||
"ignoreTemplateLiterals": true
|
||||
"ignoreTemplateLiterals": true,
|
||||
"ignoreUrls": true
|
||||
}
|
||||
],
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"import/extensions": 0,
|
||||
"import/prefer-default-export": 0,
|
||||
"react/default-props-match-prop-types": ["error", { "allowRequiredDefaults": true }],
|
||||
// https://github.com/yannickcr/eslint-plugin-react/issues/1593 ^
|
||||
"jsx-a11y/label-has-for": 0,
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||
"no-console": ["error", { "allow": ["warn", "error"] }],
|
||||
"no-console": [
|
||||
"error",
|
||||
{
|
||||
"allow": ["warn", "error"]
|
||||
}
|
||||
],
|
||||
"semi": ["error", "never"],
|
||||
"flowtype/require-valid-file-annotation": [
|
||||
2,
|
||||
"always",
|
||||
|
@ -35,6 +59,10 @@
|
|||
"annotationStyle": "line"
|
||||
}
|
||||
],
|
||||
"import/extensions": 0,
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/prefer-default-export": 0,
|
||||
"jsx-a11y/anchor-is-valid": [
|
||||
"error",
|
||||
{
|
||||
|
@ -43,13 +71,24 @@
|
|||
"aspects": ["noHref", "invalidHref", "preferButton"]
|
||||
}
|
||||
],
|
||||
"react/require-default-props": 0,
|
||||
"react/default-props-match-prop-types": ["error", { "allowRequiredDefaults": true }],
|
||||
"react/forbid-prop-types": [
|
||||
1,
|
||||
{
|
||||
"forbid": ["object", "any"]
|
||||
}
|
||||
],
|
||||
"react/no-array-index-key": 0,
|
||||
"react/require-default-props": 0,
|
||||
"react/state-in-constructor": 0,
|
||||
"react/jsx-filename-extension": [
|
||||
1,
|
||||
{
|
||||
"extensions": [".js", ".jsx"]
|
||||
}
|
||||
],
|
||||
"react/jsx-props-no-spreading": 0,
|
||||
"react/state-in-constructor": 0
|
||||
},
|
||||
"env": {
|
||||
"jest/globals": true,
|
||||
"browser": true
|
||||
"prettier/prettier": "error",
|
||||
"jsx-a11y/no-autofocus": "warn"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
!.eslintrc.js
|
||||
build_webpack
|
||||
config
|
||||
contracts
|
||||
flow-typed
|
||||
flow-typed/npm
|
||||
migrations
|
||||
node_modules
|
||||
public
|
||||
scripts
|
||||
src/assets
|
||||
src/config
|
||||
test
|
||||
*.spec*
|
||||
*.test*
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"semi": false
|
||||
}
|
55
package.json
55
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "safe-react",
|
||||
"version": "1.7.1",
|
||||
"version": "1.7.2",
|
||||
"description": "Allowing crypto users manage funds in a safer way",
|
||||
"homepage": "https://github.com/gnosis/safe-react#readme",
|
||||
"bugs": {
|
||||
|
@ -19,25 +19,33 @@
|
|||
"build": "REACT_APP_APP_VERSION=$npm_package_version node scripts/build.js",
|
||||
"build-mainnet": "REACT_APP_NETWORK=mainnet yarn build",
|
||||
"flow": "flow",
|
||||
"precommit": "./precommit.sh",
|
||||
"format:staged": "lint-staged",
|
||||
"lint:check": "eslint './src/**/*.{js,jsx}'",
|
||||
"lint:fix": "yarn lint:check --fix",
|
||||
"prettier": "prettier './src/**/*.{js,jsx}'",
|
||||
"prettier:check": "yarn prettier --check",
|
||||
"prettier:fix": "yarn prettier --write",
|
||||
"start": "node scripts/start.js",
|
||||
"start-mainnet": "REACT_APP_NETWORK=mainnet yarn start",
|
||||
"test": "NODE_ENV=test && node scripts/test.js --env=jsdom",
|
||||
"format": "prettier-eslint $PWD'/src/**/*.{js,jsx}' --write",
|
||||
"format:staged": "lint-staged"
|
||||
"test": "NODE_ENV=test && node scripts/test.js --env=jsdom"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged --allow-empty"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"./src/**/*.{js,jsx}": "prettier-eslint --write"
|
||||
"src/**/*.{js,jsx}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"pre-commit": [
|
||||
"precommit"
|
||||
],
|
||||
"dependencies": {
|
||||
"@gnosis.pm/safe-contracts": "1.0.0",
|
||||
"@gnosis.pm/util-contracts": "2.0.4",
|
||||
"@gnosis.pm/safe-contracts": "1.1.1-dev.1",
|
||||
"@gnosis.pm/util-contracts": "2.0.6",
|
||||
"@material-ui/core": "4.8.0",
|
||||
"@material-ui/icons": "4.5.1",
|
||||
"@material-ui/lab": "^4.0.0-alpha.39",
|
||||
"@material-ui/lab": "4.0.0-alpha.39",
|
||||
"@portis/web3": "^2.0.0-beta.45",
|
||||
"@testing-library/jest-dom": "4.2.4",
|
||||
"@toruslabs/torus-embed": "0.2.10",
|
||||
|
@ -50,7 +58,7 @@
|
|||
"currency-flags": "^2.1.1",
|
||||
"date-fns": "2.8.1",
|
||||
"dotenv": "^8.2.0",
|
||||
"ethereum-ens": "0.7.8",
|
||||
"ethereum-ens": "0.8.0",
|
||||
"final-form": "4.18.6",
|
||||
"fortmatic": "^1.0.1",
|
||||
"history": "4.10.1",
|
||||
|
@ -81,8 +89,9 @@
|
|||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0",
|
||||
"semver": "^7.1.1",
|
||||
"styled-components": "^5.0.1",
|
||||
"web3": "1.2.4",
|
||||
"web3connect": "^1.0.0-beta.25"
|
||||
"web3connect": "1.0.0-beta.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.7.5",
|
||||
|
@ -123,13 +132,13 @@
|
|||
"css-loader": "3.4.0",
|
||||
"detect-port": "^1.3.0",
|
||||
"dotenv-expand": "^5.1.0",
|
||||
"eslint": "6.7.2",
|
||||
"eslint-config-airbnb": "18.0.1",
|
||||
"eslint-plugin-flowtype": "4.5.2",
|
||||
"eslint-plugin-import": "2.19.1",
|
||||
"eslint-plugin-jest": "23.1.1",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-react": "7.17.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-plugin-flowtype": "^4.6.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-react": "^7.18.3",
|
||||
"ethereumjs-abi": "0.6.8",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "5.0.2",
|
||||
|
@ -137,6 +146,7 @@
|
|||
"fs-extra": "8.1.0",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^4.2.2",
|
||||
"jest": "24.9.0",
|
||||
"jest-dom": "4.0.0",
|
||||
"json-loader": "^0.5.7",
|
||||
|
@ -144,8 +154,7 @@
|
|||
"postcss-loader": "^3.0.0",
|
||||
"postcss-mixins": "6.2.3",
|
||||
"postcss-simple-vars": "^5.0.2",
|
||||
"pre-commit": "^1.2.2",
|
||||
"prettier-eslint-cli": "5.0.0",
|
||||
"prettier": "^1.19.1",
|
||||
"run-with-testrpc": "0.3.1",
|
||||
"style-loader": "1.0.2",
|
||||
"terser-webpack-plugin": "2.3.1",
|
||||
|
|
41
precommit.sh
41
precommit.sh
|
@ -1,41 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".jsx\{0,1\}$")
|
||||
ESLINT="$(git rev-parse --show-toplevel)/node_modules/.bin/eslint"
|
||||
|
||||
if [[ "$STAGED_FILES" = "" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PASS=true
|
||||
|
||||
echo -e "\nValidating Javascript:\n"
|
||||
|
||||
# Check for eslint
|
||||
if [[ ! -x "$ESLINT" ]]; then
|
||||
echo -e "\t\033[41mPlease install ESlint\033[0m (npm i --save-dev eslint)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for FILE in $STAGED_FILES
|
||||
do
|
||||
"$ESLINT" "$FILE" "--fix"
|
||||
|
||||
if [[ "$?" == 0 ]]; then
|
||||
echo -e "\t\033[32mESLint Passed: $FILE\033[0m"
|
||||
else
|
||||
echo -e "\t\033[41mESLint Failed: $FILE\033[0m"
|
||||
PASS=false
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "\nJavascript validation completed!\n"
|
||||
|
||||
if ! $PASS; then
|
||||
echo -e "\033[0;31mCOMMIT FAILED, PLEASE FIX ESLINT ERRORS:\033[0m Your commit contains files that should pass ESLint but do not. Please fix the ESLint errors in a new commit.\n"
|
||||
exit 1
|
||||
else
|
||||
echo -e "\033[42mCOMMIT SUCCEEDED\033[0m\n"
|
||||
fi
|
||||
|
||||
exit $?
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="21" viewBox="0 0 13 21">
|
||||
<path fill="#B2B5B2" fill-rule="evenodd" d="M8.7 11.266V0H4.27v11.266H0l6.484 9.172 6.493-9.172z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 195 B |
|
@ -0,0 +1,30 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Hairline from '~/components/layout/Hairline'
|
||||
import { sm, md } from '~/theme/variables'
|
||||
import ArrowDown from './arrow-down.svg'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: ${md} 0;
|
||||
|
||||
img {
|
||||
margin: 0 ${sm};
|
||||
}
|
||||
`
|
||||
|
||||
type Props = {
|
||||
withArrow: boolean,
|
||||
}
|
||||
|
||||
const DividerLine = ({ withArrow }: Props) => (
|
||||
<Wrapper>
|
||||
{withArrow && <img src={ArrowDown} alt="Arrow Down" />}
|
||||
<Hairline />
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
export default DividerLine
|
|
@ -0,0 +1,21 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { border } from '~/theme/variables'
|
||||
|
||||
type Props = {
|
||||
children: React.Node,
|
||||
}
|
||||
|
||||
const Box = styled.p`
|
||||
padding: 10px;
|
||||
word-wrap: break-word;
|
||||
border: solid 2px ${border};
|
||||
`
|
||||
|
||||
const TextBox = ({ children }: Props) => {
|
||||
return <Box>{children}</Box>
|
||||
}
|
||||
|
||||
export default TextBox
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
export { default as DividerLine } from './DividerLine'
|
||||
export { default as TextBox } from './TextBox'
|
|
@ -0,0 +1,20 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Loader = () => (
|
||||
<Wrapper>
|
||||
<CircularProgress size={60} />
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
export default Loader
|
|
@ -0,0 +1,2 @@
|
|||
// @flow
|
||||
export { default as Loader } from './Loader'
|
|
@ -0,0 +1,7 @@
|
|||
// @flow
|
||||
export * from './dataDisplay'
|
||||
export * from './feedback'
|
||||
export * from './layouts'
|
||||
export * from './safeUtils'
|
||||
export * from './surfaces'
|
||||
export * from './utils'
|
|
@ -0,0 +1,46 @@
|
|||
// @flow
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 245px auto;
|
||||
grid-template-rows: 62px auto 25px;
|
||||
min-height: 500px;
|
||||
|
||||
.background {
|
||||
box-shadow: 1px 2px 10px 0 rgba(212, 212, 211, 0.59);
|
||||
background-color: white;
|
||||
}
|
||||
`
|
||||
export const Nav = styled.div`
|
||||
grid-column: 1/3;
|
||||
grid-row: 1;
|
||||
margin: 8px 0;
|
||||
padding: 16px 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`
|
||||
|
||||
export const Menu = styled.div.attrs(() => ({ className: 'background' }))`
|
||||
grid-column: 1;
|
||||
grid-row: 2/4;
|
||||
border-right: solid 2px #e8e7e6;
|
||||
border-top-left-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
`
|
||||
|
||||
export const Content = styled.div.attrs(() => ({ className: 'background' }))`
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
border-top-right-radius: 8px;
|
||||
`
|
||||
|
||||
export const Footer = styled.div.attrs(() => ({ className: 'background' }))`
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
border-bottom-right-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
|
@ -0,0 +1,56 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import cn from 'classnames'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
// TODO: move these styles to a generic place
|
||||
import styles from './style'
|
||||
|
||||
const Wrapper = styled.div``
|
||||
|
||||
const Item = styled.div`
|
||||
border-bottom: solid 2px rgb(232, 231, 230);
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
`
|
||||
|
||||
const IconImg = styled.img`
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
`
|
||||
type Props = {
|
||||
items: Array<{
|
||||
id: string,
|
||||
name: string,
|
||||
iconUrl?: string,
|
||||
}>,
|
||||
activeItem: string,
|
||||
onItemClick: () => void,
|
||||
classes: Object,
|
||||
}
|
||||
|
||||
const List = ({ items, activeItem, onItemClick, classes }: Props) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
{items.map(i => (
|
||||
<Item
|
||||
key={i.id}
|
||||
className={cn(classes.menuOption, activeItem === i.id && classes.active)}
|
||||
onClick={() => onItemClick(i.id)}
|
||||
>
|
||||
<div className="container">
|
||||
{i.iconUrl && <IconImg src={i.iconUrl} alt={i.name} />}
|
||||
<span>{i.name}</span>
|
||||
</div>
|
||||
</Item>
|
||||
))}
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default withStyles(styles)(List)
|
|
@ -0,0 +1,10 @@
|
|||
// @flow
|
||||
import * as LayoutComponents from './Layout'
|
||||
import List from './List'
|
||||
|
||||
const ListContentLayout = {
|
||||
...LayoutComponents,
|
||||
List,
|
||||
}
|
||||
|
||||
export default ListContentLayout
|
|
@ -0,0 +1,66 @@
|
|||
// @flow
|
||||
import {
|
||||
xs,
|
||||
sm,
|
||||
md,
|
||||
border,
|
||||
secondary,
|
||||
bolderFont,
|
||||
background,
|
||||
largeFontSize,
|
||||
fontColor,
|
||||
screenSm,
|
||||
} from '~/theme/variables'
|
||||
|
||||
const styles = () => ({
|
||||
menuOption: {
|
||||
alignItems: 'center',
|
||||
borderRight: `solid 1px ${border}`,
|
||||
boxSizing: 'border-box',
|
||||
cursor: 'pointer',
|
||||
flexGrow: '1',
|
||||
flexShrink: '1',
|
||||
fontSize: '13px',
|
||||
justifyContent: 'center',
|
||||
lineHeight: '1.2',
|
||||
minWidth: '0',
|
||||
padding: `${md} ${sm}`,
|
||||
width: '100%',
|
||||
|
||||
[`@media (min-width: ${screenSm}px)`]: {
|
||||
borderRight: 'none',
|
||||
flexGrow: '0',
|
||||
fontSize: largeFontSize,
|
||||
justifyContent: 'flex-start',
|
||||
padding: `${md} 0 ${md} ${md}`,
|
||||
},
|
||||
'&:last-of-type': {
|
||||
borderRight: 'none',
|
||||
},
|
||||
'&:first-child': {
|
||||
borderTopLeftRadius: sm,
|
||||
},
|
||||
'& svg': {
|
||||
display: 'block',
|
||||
marginRight: xs,
|
||||
maxWidth: '16px',
|
||||
|
||||
[`@media (min-width: ${screenSm}px)`]: {
|
||||
marginRight: sm,
|
||||
},
|
||||
},
|
||||
'& .fill': {
|
||||
fill: fontColor,
|
||||
},
|
||||
},
|
||||
active: {
|
||||
backgroundColor: background,
|
||||
color: secondary,
|
||||
fontWeight: bolderFont,
|
||||
'& .fill': {
|
||||
fill: secondary,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default styles
|
|
@ -0,0 +1,2 @@
|
|||
// @flow
|
||||
export { default as ListContentLayout } from './ListContentLayout'
|
|
@ -0,0 +1,77 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||
import CopyBtn from '~/components/CopyBtn'
|
||||
import Identicon from '~/components/Identicon'
|
||||
import Bold from '~/components/layout/Bold'
|
||||
import { xs, border } from '~/theme/variables'
|
||||
import Block from '~/components/layout/Block'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.icon-section {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.data-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.address {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
`
|
||||
const StyledBlock = styled(Block)`
|
||||
font-size: 12px;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.5;
|
||||
background-color: ${border};
|
||||
width: fit-content;
|
||||
padding: 5px 10px;
|
||||
margin-top: ${xs};
|
||||
border-radius: 3px;
|
||||
`
|
||||
type Props = {
|
||||
safeName: string,
|
||||
safeAddress: string,
|
||||
ethBalance: string,
|
||||
}
|
||||
|
||||
const AddressInfo = ({ safeName, safeAddress, ethBalance }: Props) => {
|
||||
return (
|
||||
<Wrapper>
|
||||
<div className="icon-section">
|
||||
<Identicon address={safeAddress} diameter={32} />
|
||||
</div>
|
||||
<div className="data-section">
|
||||
{safeName && (
|
||||
<Paragraph weight="bolder" noMargin>
|
||||
{safeName}
|
||||
</Paragraph>
|
||||
)}
|
||||
<div className="address">
|
||||
<Paragraph weight="bolder" noMargin>
|
||||
{safeAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={safeAddress} />
|
||||
<EtherscanBtn type="address" value={safeAddress} />
|
||||
</div>
|
||||
{ethBalance && (
|
||||
<StyledBlock>
|
||||
<Paragraph noMargin>
|
||||
Balance: <Bold>{`${ethBalance} ETH`}</Bold>
|
||||
</Paragraph>
|
||||
</StyledBlock>
|
||||
)}
|
||||
</div>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddressInfo
|
|
@ -0,0 +1,2 @@
|
|||
// @flow
|
||||
export { default as AddressInfo } from './AddressInfo'
|
|
@ -0,0 +1,48 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import CollapseMUI from '@material-ui/core/Collapse'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import ExpandLess from '@material-ui/icons/ExpandLess'
|
||||
import ExpandMore from '@material-ui/icons/ExpandMore'
|
||||
|
||||
const Wrapper = styled.div``
|
||||
|
||||
const Header = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Title = styled.div``
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
children: React.Node,
|
||||
description: React.Node,
|
||||
}
|
||||
|
||||
const Collapse = ({ title, description, children }: Props) => {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
const handleClick = () => {
|
||||
setOpen(!open)
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Title>{title}</Title>
|
||||
<Header>
|
||||
<IconButton disableRipple size="small" onClick={handleClick}>
|
||||
{open ? <ExpandLess /> : <ExpandMore />}
|
||||
</IconButton>
|
||||
{description}
|
||||
</Header>
|
||||
|
||||
<CollapseMUI in={open} timeout="auto" unmountOnExit>
|
||||
{children}
|
||||
</CollapseMUI>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default Collapse
|
|
@ -0,0 +1,2 @@
|
|||
// @flow
|
||||
export { default as Collapse } from './Collapse'
|
|
@ -0,0 +1,2 @@
|
|||
// @flow
|
||||
export * from './modals'
|
|
@ -0,0 +1,73 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Close from '@material-ui/icons/Close'
|
||||
import { makeStyles, createStyles } from '@material-ui/core/styles'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
|
||||
import Modal from '~/components/Modal'
|
||||
import Hairline from '~/components/layout/Hairline'
|
||||
|
||||
const TitleSection = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 10px 20px;
|
||||
`
|
||||
const BodySection = styled.div`
|
||||
padding: 10px 20px;
|
||||
max-height: 460px;
|
||||
overflow-y: auto;
|
||||
`
|
||||
const FooterSection = styled.div`
|
||||
margin: 10px 20px;
|
||||
`
|
||||
|
||||
const StyledClose = styled(Close)`
|
||||
&& {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
}
|
||||
`
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
createStyles({
|
||||
paper: {
|
||||
height: 'auto',
|
||||
position: 'static',
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
body: React.Node,
|
||||
footer: React.Node,
|
||||
onClose: () => void,
|
||||
}
|
||||
|
||||
const GenericModal = ({ title, body, footer, onClose }: Props) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<Modal title="GenericModal" description="GenericModal" handleClose={onClose} paperClassName={classes.paper} open>
|
||||
<TitleSection>
|
||||
{title}
|
||||
<IconButton onClick={onClose} disableRipple>
|
||||
<StyledClose />
|
||||
</IconButton>
|
||||
</TitleSection>
|
||||
|
||||
<Hairline />
|
||||
<BodySection>{body}</BodySection>
|
||||
|
||||
{footer && (
|
||||
<>
|
||||
<Hairline />
|
||||
<FooterSection>{footer}</FooterSection>
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default GenericModal
|
|
@ -0,0 +1,3 @@
|
|||
// @flow
|
||||
export { default as GenericModal } from './GenericModal'
|
||||
export * from './utils'
|
|
@ -0,0 +1,60 @@
|
|||
// @flow
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Button from '~/components/layout/Button'
|
||||
import { lg } from '~/theme/variables'
|
||||
|
||||
const StyledParagraph = styled(Paragraph)`
|
||||
&& {
|
||||
font-size: ${lg};
|
||||
}
|
||||
`
|
||||
const IconImg = styled.img`
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
`
|
||||
const TitleWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export const ModalTitle = ({ title, iconUrl }: { title: string, iconUrl: string }) => {
|
||||
return (
|
||||
<TitleWrapper>
|
||||
{iconUrl && <IconImg src={iconUrl} alt={title} />}
|
||||
<StyledParagraph weight="bolder" noMargin>
|
||||
{title}
|
||||
</StyledParagraph>
|
||||
</TitleWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
const FooterWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
`
|
||||
|
||||
export const ModalFooterConfirmation = ({
|
||||
okText,
|
||||
cancelText,
|
||||
handleOk,
|
||||
handleCancel,
|
||||
}: {
|
||||
okText: string,
|
||||
cancelText: string,
|
||||
handleOk: () => void,
|
||||
handleCancel: () => void,
|
||||
}) => {
|
||||
return (
|
||||
<FooterWrapper>
|
||||
<Button minWidth={130} onClick={handleCancel}>
|
||||
{cancelText}
|
||||
</Button>
|
||||
<Button color="primary" minWidth={130} onClick={handleOk} variant="contained">
|
||||
{okText}
|
||||
</Button>
|
||||
</FooterWrapper>
|
||||
)
|
||||
}
|
|
@ -7,9 +7,7 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||
import cn from 'classnames'
|
||||
import Link from '~/components/layout/Link'
|
||||
import Button from '~/components/layout/Button'
|
||||
import {
|
||||
primary, mainFontFamily, md, screenSm,
|
||||
} from '~/theme/variables'
|
||||
import { primary, mainFontFamily, md, screenSm } from '~/theme/variables'
|
||||
import type { CookiesProps } from '~/logic/cookies/model/cookie'
|
||||
import { COOKIES_KEY } from '~/logic/cookies/model/cookie'
|
||||
import { loadFromCookie, saveCookie } from '~/logic/cookies/utils'
|
||||
|
@ -134,17 +132,21 @@ const CookiesBanner = () => {
|
|||
|
||||
const cookieBannerContent = (
|
||||
<div className={classes.container}>
|
||||
<span role="button" tabIndex="0" onClick={closeCookiesBannerHandler} onKeyDown={closeCookiesBannerHandler} className={cn(classes.acceptPreferences, classes.text)}>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
onClick={closeCookiesBannerHandler}
|
||||
onKeyDown={closeCookiesBannerHandler}
|
||||
className={cn(classes.acceptPreferences, classes.text)}
|
||||
>
|
||||
Accept preferences >
|
||||
</span>
|
||||
<div className={classes.content}>
|
||||
<p className={classes.text}>
|
||||
We use cookies to give you the best experience and to help improve our website. Please read our
|
||||
{' '}
|
||||
We use cookies to give you the best experience and to help improve our website. Please read our{' '}
|
||||
<Link className={classes.link} to="https://safe.gnosis.io/cookie">
|
||||
Cookie Policy
|
||||
</Link>
|
||||
{' '}
|
||||
</Link>{' '}
|
||||
for more information. By clicking "Accept all", you agree to the storing of cookies on your device
|
||||
to enhance site navigation, analyze site usage and provide customer support.
|
||||
</p>
|
||||
|
@ -155,7 +157,7 @@ const CookiesBanner = () => {
|
|||
disabled
|
||||
label="Necessary"
|
||||
name="Necessary"
|
||||
onChange={() => setLocalNecessary((prev) => !prev)}
|
||||
onChange={() => setLocalNecessary(prev => !prev)}
|
||||
value={localNecessary}
|
||||
control={<Checkbox disabled />}
|
||||
/>
|
||||
|
@ -164,7 +166,7 @@ const CookiesBanner = () => {
|
|||
<FormControlLabel
|
||||
label="Analytics"
|
||||
name="Analytics"
|
||||
onChange={() => setLocalAnalytics((prev) => !prev)}
|
||||
onChange={() => setLocalAnalytics(prev => !prev)}
|
||||
value={localAnalytics}
|
||||
control={<Checkbox checked={localAnalytics} />}
|
||||
/>
|
||||
|
|
|
@ -33,9 +33,7 @@ type EtherscanBtnProps = {
|
|||
value: string,
|
||||
}
|
||||
|
||||
const EtherscanBtn = ({
|
||||
type, value, className, increaseZindex = false,
|
||||
}: EtherscanBtnProps) => {
|
||||
const EtherscanBtn = ({ type, value, className, increaseZindex = false }: EtherscanBtnProps) => {
|
||||
const classes = useStyles()
|
||||
const customClasses = increaseZindex ? { popper: classes.increasedPopperZindex } : {}
|
||||
|
||||
|
|
|
@ -18,14 +18,9 @@ type EtherscanLinkProps = {
|
|||
value: string,
|
||||
}
|
||||
|
||||
const EtherscanLink = ({
|
||||
type, value, cut, classes, knownAddress,
|
||||
}: EtherscanLinkProps) => (
|
||||
const EtherscanLink = ({ type, value, cut, classes, knownAddress }: EtherscanLinkProps) => (
|
||||
<Block className={classes.etherscanLink}>
|
||||
<Span
|
||||
className={cn(knownAddress && classes.addressParagraph, classes.address)}
|
||||
size="md"
|
||||
>
|
||||
<Span className={cn(knownAddress && classes.addressParagraph, classes.address)} size="md">
|
||||
{cut ? shortVersionOf(value, cut) : value}
|
||||
</Span>
|
||||
<CopyBtn className={cn(classes.button, classes.firstButton)} content={value} />
|
||||
|
|
|
@ -55,12 +55,7 @@ const Footer = () => {
|
|||
|
||||
return (
|
||||
<footer className={classes.footer}>
|
||||
<span className={classes.item}>
|
||||
©
|
||||
{date.getFullYear()}
|
||||
{' '}
|
||||
Gnosis
|
||||
</span>
|
||||
<span className={classes.item}>©{date.getFullYear()} Gnosis</span>
|
||||
<span className={classes.sep}>|</span>
|
||||
<Link className={cn(classes.item, classes.link)} to="https://safe.gnosis.io/terms" target="_blank">
|
||||
Terms
|
||||
|
|
|
@ -4,9 +4,7 @@ import { withStyles } from '@material-ui/core/styles'
|
|||
import Dot from '@material-ui/icons/FiberManualRecord'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Img from '~/components/layout/Img'
|
||||
import {
|
||||
fancy, border, warning, screenSm,
|
||||
} from '~/theme/variables'
|
||||
import { fancy, border, warning, screenSm } from '~/theme/variables'
|
||||
|
||||
const key = require('../assets/key.svg')
|
||||
const triangle = require('../assets/triangle.svg')
|
||||
|
|
|
@ -12,9 +12,7 @@ import Col from '~/components/layout/Col'
|
|||
import Img from '~/components/layout/Img'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Spacer from '~/components/Spacer'
|
||||
import {
|
||||
border, sm, md, headerHeight, screenSm,
|
||||
} from '~/theme/variables'
|
||||
import { border, sm, md, headerHeight, screenSm } from '~/theme/variables'
|
||||
import Provider from './Provider'
|
||||
import NetworkLabel from './NetworkLabel'
|
||||
import SafeListHeader from './SafeListHeader'
|
||||
|
@ -24,7 +22,7 @@ const logo = require('../assets/gnosis-safe-multisig-logo.svg')
|
|||
type Props = Open & {
|
||||
classes: Object,
|
||||
providerDetails: React.Node,
|
||||
providerInfo: React.Node
|
||||
providerInfo: React.Node,
|
||||
}
|
||||
|
||||
const styles = () => ({
|
||||
|
@ -64,55 +62,42 @@ const styles = () => ({
|
|||
},
|
||||
})
|
||||
|
||||
const Layout = openHoc(
|
||||
({
|
||||
open,
|
||||
toggle,
|
||||
clickAway,
|
||||
classes,
|
||||
providerInfo,
|
||||
providerDetails,
|
||||
}: Props) => (
|
||||
<Row className={classes.summary}>
|
||||
<Col start="xs" middle="xs" className={classes.logo}>
|
||||
<Link to="/">
|
||||
<Img src={logo} height={32} alt="Gnosis Team Safe" />
|
||||
</Link>
|
||||
</Col>
|
||||
<Divider />
|
||||
<SafeListHeader />
|
||||
<Divider />
|
||||
<NetworkLabel />
|
||||
<Spacer />
|
||||
<Provider open={open} toggle={toggle} info={providerInfo}>
|
||||
{(providerRef) => (
|
||||
<Popper
|
||||
anchorEl={providerRef.current}
|
||||
className={classes.popper}
|
||||
open={open}
|
||||
placement="bottom"
|
||||
popperOptions={{ positionFixed: true }}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow {...TransitionProps}>
|
||||
<>
|
||||
<ClickAwayListener
|
||||
onClickAway={clickAway}
|
||||
mouseEvent="onClick"
|
||||
touchEvent={false}
|
||||
>
|
||||
<List className={classes.root} component="div">
|
||||
{providerDetails}
|
||||
</List>
|
||||
</ClickAwayListener>
|
||||
</>
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
const Layout = openHoc(({ open, toggle, clickAway, classes, providerInfo, providerDetails }: Props) => (
|
||||
<Row className={classes.summary}>
|
||||
<Col start="xs" middle="xs" className={classes.logo}>
|
||||
<Link to="/">
|
||||
<Img src={logo} height={32} alt="Gnosis Team Safe" />
|
||||
</Link>
|
||||
</Col>
|
||||
<Divider />
|
||||
<SafeListHeader />
|
||||
<Divider />
|
||||
<NetworkLabel />
|
||||
<Spacer />
|
||||
<Provider open={open} toggle={toggle} info={providerInfo}>
|
||||
{providerRef => (
|
||||
<Popper
|
||||
anchorEl={providerRef.current}
|
||||
className={classes.popper}
|
||||
open={open}
|
||||
placement="bottom"
|
||||
popperOptions={{ positionFixed: true }}
|
||||
>
|
||||
{({ TransitionProps }) => (
|
||||
<Grow {...TransitionProps}>
|
||||
<>
|
||||
<ClickAwayListener onClickAway={clickAway} mouseEvent="onClick" touchEvent={false}>
|
||||
<List className={classes.root} component="div">
|
||||
{providerDetails}
|
||||
</List>
|
||||
</ClickAwayListener>
|
||||
</>
|
||||
</Grow>
|
||||
)}
|
||||
</Provider>
|
||||
</Row>
|
||||
),
|
||||
)
|
||||
</Popper>
|
||||
)}
|
||||
</Provider>
|
||||
</Row>
|
||||
))
|
||||
|
||||
export default withStyles(styles)(Layout)
|
||||
|
|
|
@ -4,9 +4,7 @@ import { makeStyles } from '@material-ui/core/styles'
|
|||
import { getNetwork } from '~/config'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Col from '~/components/layout/Col'
|
||||
import {
|
||||
xs, sm, md, border, screenSm,
|
||||
} from '~/theme/variables'
|
||||
import { xs, sm, md, border, screenSm } from '~/theme/variables'
|
||||
|
||||
const network = getNetwork()
|
||||
const formattedNetwork = network[0].toUpperCase() + network.substring(1).toLowerCase()
|
||||
|
|
|
@ -56,9 +56,7 @@ class Provider extends React.Component<Props> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
open, toggle, children, classes, info,
|
||||
} = this.props
|
||||
const { open, toggle, children, classes, info } = this.props
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -13,9 +13,7 @@ import Img from '~/components/layout/Img'
|
|||
import Row from '~/components/layout/Row'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Spacer from '~/components/Spacer'
|
||||
import {
|
||||
xs, sm, md, lg, background, warning, connected as connectedBg,
|
||||
} from '~/theme/variables'
|
||||
import { xs, sm, md, lg, background, warning, connected as connectedBg } from '~/theme/variables'
|
||||
import { upperFirst } from '~/utils/css'
|
||||
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
||||
import CircleDot from '~/components/Header/components/CircleDot'
|
||||
|
@ -93,9 +91,7 @@ const styles = () => ({
|
|||
},
|
||||
})
|
||||
|
||||
const UserDetails = ({
|
||||
provider, connected, network, userAddress, classes, onDisconnect,
|
||||
}: Props) => {
|
||||
const UserDetails = ({ provider, connected, network, userAddress, classes, onDisconnect }: Props) => {
|
||||
const status = connected ? 'Connected' : 'Connection error'
|
||||
const address = userAddress ? shortVersionOf(userAddress, 4) : 'Address not available'
|
||||
const identiconAddress = userAddress || 'random'
|
||||
|
|
|
@ -54,9 +54,7 @@ const styles = () => ({
|
|||
},
|
||||
})
|
||||
|
||||
const ProviderInfo = ({
|
||||
provider, network, userAddress, connected, classes,
|
||||
}: Props) => {
|
||||
const ProviderInfo = ({ provider, network, userAddress, connected, classes }: Props) => {
|
||||
const providerText = `${provider} [${network}]`
|
||||
const cutAddress = connected ? shortVersionOf(userAddress, 4) : 'Connection Error'
|
||||
const color = connected ? 'primary' : 'warning'
|
||||
|
|
|
@ -7,9 +7,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
|
|||
import ExpandLessIcon from '@material-ui/icons/ExpandLess'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Col from '~/components/layout/Col'
|
||||
import {
|
||||
xs, sm, md, border, screenSm,
|
||||
} from '~/theme/variables'
|
||||
import { xs, sm, md, border, screenSm } from '~/theme/variables'
|
||||
import { safesCountSelector } from '~/routes/safe/store/selectors'
|
||||
import { SidebarContext } from '~/components/Sidebar'
|
||||
|
||||
|
@ -64,8 +62,8 @@ const SafeListHeader = ({ safesCount }: Props) => {
|
|||
)
|
||||
}
|
||||
|
||||
export default connect<Object, Object,?Function,?Object>(
|
||||
export default connect<Object, Object, ?Function, ?Object>(
|
||||
// $FlowFixMe
|
||||
(state) => ({ safesCount: safesCountSelector(state) }),
|
||||
state => ({ safesCount: safesCountSelector(state) }),
|
||||
null,
|
||||
)(SafeListHeader)
|
||||
|
|
|
@ -58,9 +58,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
|||
|
||||
getProviderInfoBased = () => {
|
||||
const { hasError } = this.state
|
||||
const {
|
||||
loaded, available, provider, network, userAddress,
|
||||
} = this.props
|
||||
const { loaded, available, provider, network, userAddress } = this.props
|
||||
|
||||
if (hasError || !loaded) {
|
||||
return <ProviderDisconnected />
|
||||
|
@ -71,9 +69,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
|||
|
||||
getProviderDetailsBased = () => {
|
||||
const { hasError } = this.state
|
||||
const {
|
||||
loaded, available, provider, network, userAddress,
|
||||
} = this.props
|
||||
const { loaded, available, provider, network, userAddress } = this.props
|
||||
|
||||
if (hasError || !loaded) {
|
||||
return <ConnectDetails />
|
||||
|
@ -98,7 +94,4 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
selector,
|
||||
actions,
|
||||
)(withSnackbar(HeaderComponent))
|
||||
export default connect(selector, actions)(withSnackbar(HeaderComponent))
|
||||
|
|
|
@ -62,13 +62,10 @@ export default class Identicon extends React.PureComponent<Props> {
|
|||
return image
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { diameter, className } = this.props
|
||||
const style = this.getStyleFrom(diameter)
|
||||
|
||||
return (
|
||||
<div className={className} style={style} ref={this.identicon} />
|
||||
)
|
||||
return <div className={className} style={style} ref={this.identicon} />
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,24 +20,16 @@ const styles = {
|
|||
|
||||
class ListItemText extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const {
|
||||
primary, secondary, classes, cut = false,
|
||||
} = this.props
|
||||
const { primary, secondary, classes, cut = false } = this.props
|
||||
|
||||
const cutStyle = cut ? {
|
||||
secondary: classes.itemTextSecondary,
|
||||
} : undefined
|
||||
const cutStyle = cut
|
||||
? {
|
||||
secondary: classes.itemTextSecondary,
|
||||
}
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<MuiListItemText
|
||||
classes={cutStyle}
|
||||
inset
|
||||
primary={primary}
|
||||
secondary={secondary}
|
||||
/>
|
||||
)
|
||||
return <MuiListItemText classes={cutStyle} inset primary={primary} secondary={secondary} />
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default withStyles(styles)(ListItemText)
|
||||
|
|
|
@ -40,7 +40,14 @@ const styles = () => ({
|
|||
})
|
||||
|
||||
const GnoModal = ({
|
||||
title, description, open, children, handleClose, modalClassName, classes, paperClassName,
|
||||
title,
|
||||
description,
|
||||
open,
|
||||
children,
|
||||
handleClose,
|
||||
modalClassName,
|
||||
classes,
|
||||
paperClassName,
|
||||
}: Props) => (
|
||||
<Modal
|
||||
aria-labelledby={title}
|
||||
|
|
|
@ -43,7 +43,7 @@ class Notifier extends Component<Props> {
|
|||
componentDidUpdate() {
|
||||
const { notifications = [], enqueueSnackbar, removeSnackbar } = this.props
|
||||
|
||||
notifications.forEach((notification) => {
|
||||
notifications.forEach(notification => {
|
||||
// Do nothing if snackbar is already displayed
|
||||
if (this.displayed.includes(notification.key)) {
|
||||
return
|
||||
|
@ -66,7 +66,7 @@ class Notifier extends Component<Props> {
|
|||
})
|
||||
}
|
||||
|
||||
storeDisplayed = (id) => {
|
||||
storeDisplayed = id => {
|
||||
this.displayed = [...this.displayed, id]
|
||||
}
|
||||
|
||||
|
|
|
@ -24,9 +24,7 @@ type Props = {
|
|||
isOpen: boolean,
|
||||
}
|
||||
|
||||
const ScanQRModal = ({
|
||||
classes, onClose, isOpen, onScan,
|
||||
}: Props) => {
|
||||
const ScanQRModal = ({ classes, onClose, isOpen, onScan }: Props) => {
|
||||
const [hasWebcam, setHasWebcam] = useState(null)
|
||||
const scannerRef: Object = React.createRef()
|
||||
const openImageDialog = () => {
|
||||
|
@ -73,10 +71,10 @@ const ScanQRModal = ({
|
|||
<QrReader
|
||||
ref={scannerRef}
|
||||
legacyMode={!hasWebcam}
|
||||
onScan={(data) => {
|
||||
onScan={data => {
|
||||
if (data) onScan(data)
|
||||
}}
|
||||
onError={(err) => {
|
||||
onError={err => {
|
||||
console.error(err)
|
||||
}}
|
||||
style={{ width: '400px', height: '400px' }}
|
||||
|
@ -85,12 +83,7 @@ const ScanQRModal = ({
|
|||
</Col>
|
||||
<Hairline />
|
||||
<Row align="center" className={classes.buttonRow}>
|
||||
<Button
|
||||
color="secondary"
|
||||
className={classes.button}
|
||||
minWidth={154}
|
||||
onClick={onClose}
|
||||
>
|
||||
<Button color="secondary" className={classes.button} minWidth={154} onClick={onClose}>
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
// @flow
|
||||
import {
|
||||
lg, sm, background, secondaryText,
|
||||
} from '~/theme/variables'
|
||||
import { lg, sm, background, secondaryText } from '~/theme/variables'
|
||||
|
||||
export const styles = () => ({
|
||||
heading: {
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
// @flow
|
||||
navigator.getMedia = navigator.getUserMedia // use the proper vendor prefix
|
||||
|| navigator.webkitGetUserMedia
|
||||
|| navigator.mozGetUserMedia
|
||||
|| navigator.msGetUserMedia
|
||||
navigator.getMedia =
|
||||
navigator.getUserMedia || // use the proper vendor prefix
|
||||
navigator.webkitGetUserMedia ||
|
||||
navigator.mozGetUserMedia ||
|
||||
navigator.msGetUserMedia
|
||||
|
||||
export const checkWebcam = (success: Function, err: Function) => navigator.getMedia(
|
||||
{ video: true },
|
||||
() => {
|
||||
success()
|
||||
},
|
||||
() => {
|
||||
err()
|
||||
},
|
||||
)
|
||||
export const checkWebcam = (success: Function, err: Function) =>
|
||||
navigator.getMedia(
|
||||
{ video: true },
|
||||
() => {
|
||||
success()
|
||||
},
|
||||
() => {
|
||||
err()
|
||||
},
|
||||
)
|
||||
|
|
|
@ -11,9 +11,7 @@ import Hairline from '~/components/layout/Hairline'
|
|||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import ButtonLink from '~/components/layout/ButtonLink'
|
||||
import Identicon from '~/components/Identicon'
|
||||
import {
|
||||
mediumFontSize, sm, primary, disabled, md,
|
||||
} from '~/theme/variables'
|
||||
import { mediumFontSize, sm, primary, disabled, md } from '~/theme/variables'
|
||||
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
||||
import { shortVersionOf, sameAddress } from '~/logic/wallets/ethAddresses'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
|
@ -74,14 +72,12 @@ const useStyles = makeStyles({
|
|||
},
|
||||
})
|
||||
|
||||
const SafeList = ({
|
||||
safes, onSafeClick, setDefaultSafe, defaultSafe, currentSafe,
|
||||
}: SafeListProps) => {
|
||||
const SafeList = ({ safes, onSafeClick, setDefaultSafe, defaultSafe, currentSafe }: SafeListProps) => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
<MuiList className={classes.list}>
|
||||
{safes.map((safe) => (
|
||||
{safes.map(safe => (
|
||||
<React.Fragment key={safe.address}>
|
||||
<Link
|
||||
to={`${SAFELIST_ADDRESS}/${safe.address}/balances`}
|
||||
|
@ -89,18 +85,23 @@ const SafeList = ({
|
|||
data-testid={SIDEBAR_SAFELIST_ROW_TESTID}
|
||||
>
|
||||
<ListItem classes={{ root: classes.listItemRoot }}>
|
||||
{ sameAddress(currentSafe, safe.address) ? (
|
||||
{sameAddress(currentSafe, safe.address) ? (
|
||||
<ListItemIcon>
|
||||
<Img src={check} alt="check" className={classes.checkIcon} />
|
||||
</ListItemIcon>
|
||||
) : <div className={classes.noIcon}>placeholder</div> }
|
||||
) : (
|
||||
<div className={classes.noIcon}>placeholder</div>
|
||||
)}
|
||||
<ListItemIcon>
|
||||
<Identicon address={safe.address} diameter={32} className={classes.icon} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={safe.name}
|
||||
secondary={shortVersionOf(safe.address, 4)}
|
||||
classes={{ primary: classes.safeName, secondary: classes.safeAddress }}
|
||||
classes={{
|
||||
primary: classes.safeName,
|
||||
secondary: classes.safeAddress,
|
||||
}}
|
||||
/>
|
||||
<Paragraph size="lg" color="primary">
|
||||
{`${formatAmount(safe.ethBalance)} ETH`}
|
||||
|
@ -111,7 +112,7 @@ const SafeList = ({
|
|||
<ButtonLink
|
||||
className={classes.makeDefaultBtn}
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
|
|
|
@ -14,10 +14,7 @@ import Hairline from '~/components/layout/Hairline'
|
|||
import Row from '~/components/layout/Row'
|
||||
import { WELCOME_ADDRESS } from '~/routes/routes'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import {
|
||||
defaultSafeSelector,
|
||||
safeParamAddressFromStateSelector,
|
||||
} from '~/routes/safe/store/selectors'
|
||||
import { defaultSafeSelector, safeParamAddressFromStateSelector } from '~/routes/safe/store/selectors'
|
||||
import setDefaultSafe from '~/routes/safe/store/actions/setDefaultSafe'
|
||||
import { sortedSafeListSelector } from './selectors'
|
||||
import SafeList from './SafeList'
|
||||
|
@ -27,7 +24,7 @@ const { useState, useEffect, useMemo } = React
|
|||
|
||||
type TSidebarContext = {
|
||||
isOpen: boolean,
|
||||
toggleSidebar: Function
|
||||
toggleSidebar: Function,
|
||||
}
|
||||
|
||||
export const SidebarContext = React.createContext<TSidebarContext>({
|
||||
|
@ -40,22 +37,18 @@ type SidebarProps = {
|
|||
safes: List<Safe>,
|
||||
setDefaultSafeAction: Function,
|
||||
defaultSafe: string,
|
||||
currentSafe: string
|
||||
currentSafe: string,
|
||||
}
|
||||
|
||||
const filterBy = (filter: string, safes: List<Safe>): List<Safe> => safes.filter(
|
||||
(safe: Safe) => !filter
|
||||
|| safe.address.toLowerCase().includes(filter.toLowerCase())
|
||||
|| safe.name.toLowerCase().includes(filter.toLowerCase()),
|
||||
)
|
||||
const filterBy = (filter: string, safes: List<Safe>): List<Safe> =>
|
||||
safes.filter(
|
||||
(safe: Safe) =>
|
||||
!filter ||
|
||||
safe.address.toLowerCase().includes(filter.toLowerCase()) ||
|
||||
safe.name.toLowerCase().includes(filter.toLowerCase()),
|
||||
)
|
||||
|
||||
const Sidebar = ({
|
||||
children,
|
||||
safes,
|
||||
setDefaultSafeAction,
|
||||
defaultSafe,
|
||||
currentSafe,
|
||||
}: SidebarProps) => {
|
||||
const Sidebar = ({ children, safes, setDefaultSafeAction, defaultSafe, currentSafe }: SidebarProps) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
||||
const [filter, setFilter] = useState<string>('')
|
||||
const classes = useSidebarStyles()
|
||||
|
@ -74,7 +67,7 @@ const Sidebar = ({
|
|||
}
|
||||
|
||||
const toggleSidebar = () => {
|
||||
setIsOpen((prevIsOpen) => !prevIsOpen)
|
||||
setIsOpen(prevIsOpen => !prevIsOpen)
|
||||
}
|
||||
|
||||
const handleFilterChange = (value: string) => {
|
||||
|
@ -147,7 +140,7 @@ const Sidebar = ({
|
|||
|
||||
export default connect<Object, Object, ?Function, ?Object>(
|
||||
// $FlowFixMe
|
||||
(state) => ({
|
||||
state => ({
|
||||
safes: sortedSafeListSelector(state),
|
||||
defaultSafe: defaultSafeSelector(state),
|
||||
currentSafe: safeParamAddressFromStateSelector(state),
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// @flow
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import {
|
||||
xs, mediumFontSize, secondaryText, md, headerHeight, screenSm,
|
||||
} from '~/theme/variables'
|
||||
import { xs, mediumFontSize, secondaryText, md, headerHeight, screenSm } from '~/theme/variables'
|
||||
|
||||
const sidebarWidth = '400px'
|
||||
const sidebarMarginLeft = '12px'
|
||||
|
|
|
@ -9,4 +9,5 @@ const style = {
|
|||
flexGrow: 1,
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
export default ({ className }: Props) => <div className={className} style={style} />
|
||||
|
|
|
@ -30,15 +30,7 @@ type Props = {
|
|||
buttonLabels?: Array<string>,
|
||||
}
|
||||
|
||||
const Controls = ({
|
||||
onPrevious,
|
||||
firstPage,
|
||||
penultimate,
|
||||
lastPage,
|
||||
disabled,
|
||||
currentStep,
|
||||
buttonLabels,
|
||||
}: Props) => {
|
||||
const Controls = ({ onPrevious, firstPage, penultimate, lastPage, disabled, currentStep, buttonLabels }: Props) => {
|
||||
const back = firstPage ? 'Cancel' : 'Back'
|
||||
|
||||
let next
|
||||
|
|
|
@ -23,9 +23,7 @@ type Props = {
|
|||
padding?: boolean,
|
||||
}
|
||||
|
||||
const OpenPaper = ({
|
||||
classes, children, controls, padding = true,
|
||||
}: Props) => (
|
||||
const OpenPaper = ({ classes, children, controls, padding = true }: Props) => (
|
||||
<Paper className={classes.root} elevation={1}>
|
||||
<Block className={padding ? classes.padding : ''}>{children}</Block>
|
||||
{controls}
|
||||
|
|
|
@ -5,10 +5,6 @@ type Props = {
|
|||
children: React.Node,
|
||||
}
|
||||
|
||||
const Step = ({ children }: Props) => (
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
const Step = ({ children }: Props) => <div>{children}</div>
|
||||
|
||||
export default Step
|
||||
|
|
|
@ -52,7 +52,7 @@ const GnoStepper = (props: Props) => {
|
|||
|
||||
const getPageProps = (pages: React.Node): PageProps => React.Children.toArray(pages)[page].props
|
||||
|
||||
const updateInitialProps = (newInitialProps) => {
|
||||
const updateInitialProps = newInitialProps => {
|
||||
setValues(newInitialProps)
|
||||
}
|
||||
|
||||
|
@ -105,14 +105,12 @@ const GnoStepper = (props: Props) => {
|
|||
return next(formValues)
|
||||
}
|
||||
|
||||
const isLastPage = (pageNumber) => {
|
||||
const isLastPage = pageNumber => {
|
||||
const { steps } = props
|
||||
return pageNumber === steps.length - 1
|
||||
}
|
||||
|
||||
const {
|
||||
steps, children, classes, disabledWhenValidating = false, testId, mutators, buttonLabels,
|
||||
} = props
|
||||
const { steps, children, classes, disabledWhenValidating = false, testId, mutators, buttonLabels } = props
|
||||
const activePage = getActivePageFrom(children)
|
||||
|
||||
const lastPage = isLastPage(page)
|
||||
|
|
|
@ -57,9 +57,7 @@ class GnoTableHead extends React.PureComponent<Props> {
|
|||
sortDirection={orderBy === column.id ? order : false}
|
||||
>
|
||||
{column.static ? (
|
||||
<div style={column.style}>
|
||||
{column.label}
|
||||
</div>
|
||||
<div style={column.style}>{column.label}</div>
|
||||
) : (
|
||||
<TableSortLabel
|
||||
active={orderBy === column.id}
|
||||
|
|
|
@ -157,9 +157,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
|
|||
noBorder,
|
||||
disableLoadingOnEmptyTable,
|
||||
} = this.props
|
||||
const {
|
||||
order, orderBy, page, orderProp, rowsPerPage, fixed,
|
||||
} = this.state
|
||||
const { order, orderBy, page, orderProp, rowsPerPage, fixed } = this.state
|
||||
const orderByParam = orderBy || defaultOrderBy
|
||||
const orderParam = order || defaultOrder
|
||||
const displayRows = rowsPerPage || defaultRowsPerPage
|
||||
|
@ -189,10 +187,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
|
|||
</Table>
|
||||
)}
|
||||
{isEmpty && (
|
||||
<Row
|
||||
className={classes.loader}
|
||||
style={this.getEmptyStyle(emptyRows + 1)}
|
||||
>
|
||||
<Row className={classes.loader} style={this.getEmptyStyle(emptyRows + 1)}>
|
||||
<CircularProgress size={60} />
|
||||
</Row>
|
||||
)}
|
||||
|
|
|
@ -39,13 +39,14 @@ export const stableSort = (dataArray: List<any>, cmp: any, fixed: boolean): List
|
|||
return a[1] - b[1]
|
||||
})
|
||||
|
||||
const sortedElems: List<any> = stabilizedThis.map((el) => el[0])
|
||||
const sortedElems: List<any> = stabilizedThis.map(el => el[0])
|
||||
|
||||
return fixedElems.concat(sortedElems)
|
||||
}
|
||||
|
||||
export type Order = 'asc' | 'desc'
|
||||
|
||||
export const getSorting = (order: Order, orderBy: string, orderProp: boolean) => (order === 'desc'
|
||||
? (a: any, b: any) => desc(a, b, orderBy, orderProp)
|
||||
: (a: any, b: any) => -desc(a, b, orderBy, orderProp))
|
||||
export const getSorting = (order: Order, orderBy: string, orderProp: boolean) =>
|
||||
order === 'desc'
|
||||
? (a: any, b: any) => desc(a, b, orderBy, orderProp)
|
||||
: (a: any, b: any) => -desc(a, b, orderBy, orderProp)
|
||||
|
|
|
@ -3,11 +3,7 @@ import * as React from 'react'
|
|||
import { Field } from 'react-final-form'
|
||||
import { OnChange } from 'react-final-form-listeners'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import {
|
||||
composeValidators,
|
||||
required,
|
||||
mustBeEthereumAddress,
|
||||
} from '~/components/forms/validator'
|
||||
import { composeValidators, required, mustBeEthereumAddress } from '~/components/forms/validator'
|
||||
import { getAddressFromENS } from '~/logic/wallets/getWeb3'
|
||||
|
||||
type Props = {
|
||||
|
@ -23,7 +19,7 @@ type Props = {
|
|||
disabled?: boolean,
|
||||
}
|
||||
|
||||
const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
|
||||
const isValidEnsName = name => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
|
||||
|
||||
// an idea for second field was taken from here
|
||||
// https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js
|
||||
|
@ -45,11 +41,7 @@ const AddressInput = ({
|
|||
name={name}
|
||||
component={TextField}
|
||||
type="text"
|
||||
validate={composeValidators(
|
||||
required,
|
||||
mustBeEthereumAddress,
|
||||
...validators,
|
||||
)}
|
||||
validate={composeValidators(required, mustBeEthereumAddress, ...validators)}
|
||||
inputAdornment={inputAdornment}
|
||||
placeholder={placeholder}
|
||||
text={text}
|
||||
|
@ -59,7 +51,7 @@ const AddressInput = ({
|
|||
disabled={disabled}
|
||||
/>
|
||||
<OnChange name={name}>
|
||||
{async (value) => {
|
||||
{async value => {
|
||||
if (isValidEnsName(value)) {
|
||||
try {
|
||||
const resolverAddr = await getAddressFromENS(value)
|
||||
|
|
|
@ -5,22 +5,11 @@ import Checkbox, { type CheckoxProps } from '@material-ui/core/Checkbox'
|
|||
class GnoCheckbox extends React.PureComponent<CheckoxProps> {
|
||||
render() {
|
||||
const {
|
||||
input: {
|
||||
checked, name, onChange, ...restInput
|
||||
},
|
||||
meta,
|
||||
input: { checked, name, onChange, ...restInput },
|
||||
...rest
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
{...rest}
|
||||
name={name}
|
||||
inputProps={restInput}
|
||||
onChange={onChange}
|
||||
checked={!!checked}
|
||||
/>
|
||||
)
|
||||
return <Checkbox {...rest} name={name} inputProps={restInput} onChange={onChange} checked={!!checked} />
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Form } from 'react-final-form'
|
|||
export type OnSubmit = (
|
||||
values: Object,
|
||||
form: FormApi,
|
||||
callback: ?(errors: ?Object) => ?Object
|
||||
callback: ?(errors: ?Object) => ?Object,
|
||||
) => ?Object | Promise<?Object> | void
|
||||
|
||||
type Props = {
|
||||
|
@ -25,9 +25,7 @@ const stylesBasedOn = (padding: number): $Shape<CSSStyleDeclaration> => ({
|
|||
flex: '1 0 auto',
|
||||
})
|
||||
|
||||
const GnoForm = ({
|
||||
onSubmit, validation, initialValues, children, padding = 0, formMutators, testId = '',
|
||||
}: Props) => (
|
||||
const GnoForm = ({ onSubmit, validation, initialValues, children, padding = 0, formMutators, testId = '' }: Props) => (
|
||||
<Form
|
||||
validate={validation}
|
||||
onSubmit={onSubmit}
|
||||
|
|
|
@ -10,9 +10,7 @@ const style = {
|
|||
}
|
||||
|
||||
const SelectInput = ({
|
||||
input: {
|
||||
name, value, onChange, ...restInput
|
||||
},
|
||||
input: { name, value, onChange, ...restInput },
|
||||
meta,
|
||||
label,
|
||||
formControlProps,
|
||||
|
|
|
@ -21,11 +21,8 @@ const styles = () => ({
|
|||
class TextField extends React.PureComponent<TextFieldProps> {
|
||||
render() {
|
||||
const {
|
||||
input: {
|
||||
name, onChange, value, ...restInput
|
||||
},
|
||||
input: { name, onChange, value, ...restInput },
|
||||
meta,
|
||||
render,
|
||||
text,
|
||||
inputAdornment,
|
||||
classes,
|
||||
|
|
|
@ -23,13 +23,7 @@ const styles = () => ({
|
|||
})
|
||||
|
||||
const TextareaField = ({ classes, ...props }: TextFieldProps) => (
|
||||
<Field
|
||||
{...props}
|
||||
component={TextField}
|
||||
multiline
|
||||
rows="5"
|
||||
className={classes.textarea}
|
||||
/>
|
||||
<Field {...props} component={TextField} multiline rows="5" className={classes.textarea} />
|
||||
)
|
||||
|
||||
export default withStyles(styles)(TextareaField)
|
||||
|
|
|
@ -20,7 +20,8 @@ type Field = boolean | string | null | typeof undefined
|
|||
|
||||
export const required = (value: Field) => (value ? undefined : 'Required')
|
||||
|
||||
export const mustBeInteger = (value: string) => (!Number.isInteger(Number(value)) || value.includes('.') ? 'Must be an integer' : undefined)
|
||||
export const mustBeInteger = (value: string) =>
|
||||
!Number.isInteger(Number(value)) || value.includes('.') ? 'Must be an integer' : undefined
|
||||
|
||||
export const mustBeFloat = (value: number) => (value && Number.isNaN(Number(value)) ? 'Must be a number' : undefined)
|
||||
|
||||
|
@ -75,16 +76,19 @@ export const mustBeEthereumContractAddress = simpleMemoize(async (address: strin
|
|||
: undefined
|
||||
})
|
||||
|
||||
export const minMaxLength = (minLen: string | number, maxLen: string | number) => (value: string) => (value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols`)
|
||||
export const minMaxLength = (minLen: string | number, maxLen: string | number) => (value: string) =>
|
||||
value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols`
|
||||
|
||||
export const ADDRESS_REPEATED_ERROR = 'Address already introduced'
|
||||
|
||||
export const uniqueAddress = (addresses: string[] | List<string>) => simpleMemoize((value: string) => {
|
||||
const addressAlreadyExists = addresses.some((address) => sameAddress(value, address))
|
||||
return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined
|
||||
})
|
||||
export const uniqueAddress = (addresses: string[] | List<string>) =>
|
||||
simpleMemoize((value: string) => {
|
||||
const addressAlreadyExists = addresses.some(address => sameAddress(value, address))
|
||||
return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined
|
||||
})
|
||||
|
||||
export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) => validators.reduce((error, validator) => error || validator(value), undefined)
|
||||
export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) =>
|
||||
validators.reduce((error, validator) => error || validator(value), undefined)
|
||||
|
||||
export const inLimit = (limit: number, base: number, baseText: string, symbol: string = 'ETH') => (value: string) => {
|
||||
const amount = Number(value)
|
||||
|
|
|
@ -7,10 +7,7 @@ export type Open = {
|
|||
clickAway: () => void,
|
||||
}
|
||||
|
||||
export default withStateHandlers(
|
||||
() => ({ open: false }),
|
||||
{
|
||||
toggle: ({ open }) => () => ({ open: !open }),
|
||||
clickAway: () => () => ({ open: false }),
|
||||
},
|
||||
)
|
||||
export default withStateHandlers(() => ({ open: false }), {
|
||||
toggle: ({ open }) => () => ({ open: !open }),
|
||||
clickAway: () => () => ({ open: false }),
|
||||
})
|
||||
|
|
|
@ -19,9 +19,7 @@ type Props = {
|
|||
|
||||
class Block extends PureComponent<Props> {
|
||||
render() {
|
||||
const {
|
||||
margin, padding, justify, children, className, ...props
|
||||
} = this.props
|
||||
const { margin, padding, justify, children, className, ...props } = this.props
|
||||
|
||||
const paddingStyle = padding ? capitalize(padding, 'padding') : undefined
|
||||
return (
|
||||
|
|
|
@ -14,9 +14,7 @@ const calculateStyleBased = (minWidth, minHeight) => ({
|
|||
minHeight: minHeight && `${minHeight}px`,
|
||||
})
|
||||
|
||||
const GnoButton = ({
|
||||
minWidth, minHeight = 35, testId = '', style = {}, ...props
|
||||
}: Props) => {
|
||||
const GnoButton = ({ minWidth, minHeight = 35, testId = '', style = {}, ...props }: Props) => {
|
||||
const calculatedStyle = calculateStyleBased(minWidth, minHeight)
|
||||
|
||||
return <Button style={{ ...calculatedStyle, ...style }} data-testid={testId} {...props} />
|
||||
|
|
|
@ -23,6 +23,8 @@ const GnoButtonLink = ({
|
|||
testId = '',
|
||||
className = '',
|
||||
...props
|
||||
}: Props) => <button type={type} className={cx(styles.btnLink, size, color, weight, className)} data-testid={testId} {...props} />
|
||||
}: Props) => (
|
||||
<button type={type} className={cx(styles.btnLink, size, color, weight, className)} data-testid={testId} {...props} />
|
||||
)
|
||||
|
||||
export default GnoButtonLink
|
||||
|
|
|
@ -18,9 +18,7 @@ type Props = {
|
|||
style?: Object,
|
||||
}
|
||||
|
||||
const Hairline = ({
|
||||
margin, color, style, className,
|
||||
}: Props) => {
|
||||
const Hairline = ({ margin, color, style, className }: Props) => {
|
||||
const calculatedStyles = calculateStyleFrom(color, margin)
|
||||
const mergedStyles = { ...calculatedStyles, ...(style || {}) }
|
||||
|
||||
|
|
|
@ -20,9 +20,7 @@ type Props = {
|
|||
}
|
||||
|
||||
const Heading = (props: Props) => {
|
||||
const {
|
||||
align, tag, truncate, margin, color, children, testId, className = '', ...rest
|
||||
} = props
|
||||
const { align, tag, truncate, margin, color, children, testId, className = '', ...rest } = props
|
||||
|
||||
const classes = cx(className, 'heading', align, tag, margin ? capitalize(margin, 'margin') : undefined, color, {
|
||||
truncate,
|
||||
|
|
|
@ -14,9 +14,7 @@ type Props = {
|
|||
testId?: string,
|
||||
}
|
||||
|
||||
const Img = ({
|
||||
fullwidth, alt, bordered, className, style, testId = '', ...props
|
||||
}: Props) => {
|
||||
const Img = ({ fullwidth, alt, bordered, className, style, testId = '', ...props }: Props) => {
|
||||
const classes = cx(styles.img, { fullwidth, bordered }, className)
|
||||
|
||||
return <img alt={alt} style={style} className={classes} data-testid={testId} {...props} />
|
||||
|
|
|
@ -16,9 +16,7 @@ type Props = {
|
|||
innerRef?: React.ElementRef<any>,
|
||||
}
|
||||
|
||||
const GnosisLink = ({
|
||||
to, children, color, className, padding, innerRef, ...props
|
||||
}: Props) => {
|
||||
const GnosisLink = ({ to, children, color, className, padding, innerRef, ...props }: Props) => {
|
||||
const internal = /^\/(?!\/)/.test(to)
|
||||
const classes = cx(styles.link, color || 'regular', padding ? capitalize(padding, 'padding') : undefined, className)
|
||||
const LinkElement = internal ? Link : 'a'
|
||||
|
@ -29,13 +27,7 @@ const GnosisLink = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<LinkElement
|
||||
className={classes}
|
||||
href={internal ? null : to}
|
||||
to={internal ? to : null}
|
||||
{...refs}
|
||||
{...props}
|
||||
>
|
||||
<LinkElement className={classes} href={internal ? null : to} to={internal ? to : null} {...refs} {...props}>
|
||||
{children}
|
||||
</LinkElement>
|
||||
)
|
||||
|
|
|
@ -8,13 +8,11 @@ const cx = classNames.bind(styles)
|
|||
type Props = {
|
||||
children: React.Node,
|
||||
align?: 'center',
|
||||
overflow?: boolean
|
||||
overflow?: boolean,
|
||||
}
|
||||
|
||||
const Page = ({ children, align, overflow }: Props) => (
|
||||
<main className={cx(styles.page, align, { overflow })}>
|
||||
{children}
|
||||
</main>
|
||||
<main className={cx(styles.page, align, { overflow })}>{children}</main>
|
||||
)
|
||||
|
||||
export default Page
|
||||
|
|
|
@ -78,7 +78,7 @@ const PageFrame = ({ children, classes, currentNetwork }: Props) => {
|
|||
|
||||
export default withStyles(notificationStyles)(
|
||||
connect(
|
||||
(state) => ({
|
||||
state => ({
|
||||
currentNetwork: networkSelector(state),
|
||||
}),
|
||||
null,
|
||||
|
|
|
@ -20,9 +20,7 @@ type Props = {
|
|||
|
||||
class Paragraph extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const {
|
||||
weight, children, color, align, size, transform, noMargin, className, dot, ...props
|
||||
} = this.props
|
||||
const { weight, children, color, align, size, transform, noMargin, className, dot, ...props } = this.props
|
||||
|
||||
return (
|
||||
<p
|
||||
|
|
|
@ -15,9 +15,7 @@ type Props = {
|
|||
testId?: string,
|
||||
}
|
||||
|
||||
const Row = ({
|
||||
children, className, margin, align, grow, testId = '', ...props
|
||||
}: Props) => {
|
||||
const Row = ({ children, className, margin, align, grow, testId = '', ...props }: Props) => {
|
||||
const rowClassNames = cx(
|
||||
styles.row,
|
||||
margin ? capitalize(margin, 'margin') : undefined,
|
||||
|
|
|
@ -6,13 +6,11 @@ import TableCell from '@material-ui/core/TableCell'
|
|||
import TableHead from '@material-ui/core/TableHead'
|
||||
import TableRow from '@material-ui/core/TableRow'
|
||||
|
||||
export {
|
||||
TableBody, TableCell, TableHead, TableRow,
|
||||
}
|
||||
export { TableBody, TableCell, TableHead, TableRow }
|
||||
|
||||
type Props = {
|
||||
children: React.Node,
|
||||
size?: number
|
||||
size?: number,
|
||||
}
|
||||
|
||||
const buildWidthFrom = (size: number) => ({
|
||||
|
@ -29,9 +27,7 @@ const GnoTable = ({ size, children }: Props) => {
|
|||
|
||||
return (
|
||||
<div style={overflowStyle}>
|
||||
<Table style={style}>
|
||||
{children}
|
||||
</Table>
|
||||
<Table style={style}>{children}</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -75,4 +75,4 @@ export const getIntercomId = () =>
|
|||
|
||||
export const getExchangeRatesUrl = () => 'https://api.exchangeratesapi.io/latest'
|
||||
|
||||
export const getSafeLastVersion = () => process.env.REACT_APP_LATEST_SAFE_VERSION
|
||||
export const getSafeLastVersion = () => process.env.REACT_APP_LATEST_SAFE_VERSION || '1.1.1'
|
|
@ -2,13 +2,13 @@
|
|||
import type { RecordOf } from 'immutable'
|
||||
|
||||
export type AddressBookEntry = {
|
||||
address: string;
|
||||
name: string;
|
||||
isOwner: boolean;
|
||||
address: string,
|
||||
name: string,
|
||||
isOwner: boolean,
|
||||
}
|
||||
|
||||
export type AddressBookProps = {
|
||||
addressBook: Map<string, AddressBookEntry>
|
||||
addressBook: Map<string, AddressBookEntry>,
|
||||
}
|
||||
|
||||
export type AddressBook = RecordOf<AddressBookProps>
|
||||
|
|
|
@ -4,7 +4,9 @@ import type { AddressBookEntryType } from '~/logic/addressBook/model/addressBook
|
|||
|
||||
export const ADD_ENTRY = 'ADD_ENTRY'
|
||||
|
||||
|
||||
export const addAddressBookEntry = createAction<string, *, *>(ADD_ENTRY, (entry: AddressBookEntryType): AddressBookEntryType => ({
|
||||
entry,
|
||||
}))
|
||||
export const addAddressBookEntry = createAction<string, *, *>(
|
||||
ADD_ENTRY,
|
||||
(entry: AddressBookEntryType): AddressBookEntryType => ({
|
||||
entry,
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -4,4 +4,6 @@ import type { AddressBook } from '~/logic/addressBook/model/addressBook'
|
|||
|
||||
export const LOAD_ADDRESS_BOOK = 'LOAD_ADDRESS_BOOK'
|
||||
|
||||
export const loadAddressBook = createAction<string, *, *>(LOAD_ADDRESS_BOOK, (addressBook: AddressBook) => ({ addressBook }))
|
||||
export const loadAddressBook = createAction<string, *, *>(LOAD_ADDRESS_BOOK, (addressBook: AddressBook) => ({
|
||||
addressBook,
|
||||
}))
|
||||
|
|
|
@ -18,7 +18,7 @@ const loadAddressBookFromStorage = () => async (dispatch: ReduxDispatch<GlobalSt
|
|||
// Fetch all the current safes, in case that we don't have a safe on the adbk, we add it
|
||||
const safes = safesListSelector(state)
|
||||
const adbkEntries = addressBook.keySeq().toArray()
|
||||
safes.forEach((safe) => {
|
||||
safes.forEach(safe => {
|
||||
const { address } = safe
|
||||
const found = adbkEntries.includes(address)
|
||||
if (!found) {
|
||||
|
|
|
@ -3,7 +3,6 @@ import { createAction } from 'redux-actions'
|
|||
|
||||
export const REMOVE_ENTRY = 'REMOVE_ENTRY'
|
||||
|
||||
|
||||
export const removeAddressBookEntry = createAction<string, *, *>(REMOVE_ENTRY, (entryAddress: string): void => ({
|
||||
entryAddress,
|
||||
}))
|
||||
|
|
|
@ -4,7 +4,9 @@ import type { AddressBookEntry } from '~/logic/addressBook/model/addressBook'
|
|||
|
||||
export const UPDATE_ENTRY = 'UPDATE_ENTRY'
|
||||
|
||||
|
||||
export const updateAddressBookEntry = createAction<string, *, *>(UPDATE_ENTRY, (entry: AddressBookEntry): AddressBookEntry => ({
|
||||
entry,
|
||||
}))
|
||||
export const updateAddressBookEntry = createAction<string, *, *>(
|
||||
UPDATE_ENTRY,
|
||||
(entry: AddressBookEntry): AddressBookEntry => ({
|
||||
entry,
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -11,12 +11,7 @@ import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar
|
|||
import { saveAddressBook } from '~/logic/addressBook/utils'
|
||||
import type { AddressBookProps } from '~/logic/addressBook/model/addressBook'
|
||||
|
||||
const watchedActions = [
|
||||
ADD_ENTRY,
|
||||
REMOVE_ENTRY,
|
||||
UPDATE_ENTRY,
|
||||
]
|
||||
|
||||
const watchedActions = [ADD_ENTRY, REMOVE_ENTRY, UPDATE_ENTRY]
|
||||
|
||||
const addressBookMiddleware = (store: Store<GlobalState>) => (next: Function) => async (action: AnyAction) => {
|
||||
const handledAction = next(action)
|
||||
|
|
|
@ -24,7 +24,6 @@ export const buildAddressBook = (storedAdbk: AddressBook): AddressBookProps => {
|
|||
return addressBookBuilt
|
||||
}
|
||||
|
||||
|
||||
export default handleActions<State, *>(
|
||||
{
|
||||
[LOAD_ADDRESS_BOOK]: (state: State, action: ActionType<Function>): State => {
|
||||
|
@ -44,23 +43,22 @@ export default handleActions<State, *>(
|
|||
const { entry } = action.payload
|
||||
|
||||
// Adds the entry to all the safes (if it does not already exists)
|
||||
const newState = state.withMutations((map) => {
|
||||
const newState = state.withMutations(map => {
|
||||
const adbkMap = map.get('addressBook')
|
||||
|
||||
if (adbkMap) {
|
||||
adbkMap.keySeq()
|
||||
.forEach((safeAddress) => {
|
||||
const safeAddressBook = state.getIn(['addressBook', safeAddress])
|
||||
adbkMap.keySeq().forEach(safeAddress => {
|
||||
const safeAddressBook = state.getIn(['addressBook', safeAddress])
|
||||
|
||||
if (safeAddressBook) {
|
||||
const adbkAddressList = getAddressesListFromAdbk(safeAddressBook)
|
||||
const found = adbkAddressList.includes(entry.address)
|
||||
if (!found) {
|
||||
const updatedSafeAdbkList = safeAddressBook.push(entry)
|
||||
map.setIn(['addressBook', safeAddress], updatedSafeAdbkList)
|
||||
}
|
||||
if (safeAddressBook) {
|
||||
const adbkAddressList = getAddressesListFromAdbk(safeAddressBook)
|
||||
const found = adbkAddressList.includes(entry.address)
|
||||
if (!found) {
|
||||
const updatedSafeAdbkList = safeAddressBook.push(entry)
|
||||
map.setIn(['addressBook', safeAddress], updatedSafeAdbkList)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return newState
|
||||
|
@ -69,13 +67,13 @@ export default handleActions<State, *>(
|
|||
const { entry } = action.payload
|
||||
|
||||
// Updates the entry from all the safes
|
||||
const newState = state.withMutations((map) => {
|
||||
const newState = state.withMutations(map => {
|
||||
map
|
||||
.get('addressBook')
|
||||
.keySeq()
|
||||
.forEach((safeAddress) => {
|
||||
.forEach(safeAddress => {
|
||||
const entriesList: List<AddressBookEntry> = state.getIn(['addressBook', safeAddress])
|
||||
const entryIndex = entriesList.findIndex((entryItem) => sameAddress(entryItem.address, entry.address))
|
||||
const entryIndex = entriesList.findIndex(entryItem => sameAddress(entryItem.address, entry.address))
|
||||
const updatedEntriesList = entriesList.set(entryIndex, entry)
|
||||
map.setIn(['addressBook', safeAddress], updatedEntriesList)
|
||||
})
|
||||
|
@ -86,13 +84,13 @@ export default handleActions<State, *>(
|
|||
[REMOVE_ENTRY]: (state: State, action: ActionType<Function>): State => {
|
||||
const { entryAddress } = action.payload
|
||||
// Removes the entry from all the safes
|
||||
const newState = state.withMutations((map) => {
|
||||
const newState = state.withMutations(map => {
|
||||
map
|
||||
.get('addressBook')
|
||||
.keySeq()
|
||||
.forEach((safeAddress) => {
|
||||
.forEach(safeAddress => {
|
||||
const entriesList = state.getIn(['addressBook', safeAddress])
|
||||
const entryIndex = entriesList.findIndex((entry) => sameAddress(entry.address, entryAddress))
|
||||
const entryIndex = entriesList.findIndex(entry => sameAddress(entry.address, entryAddress))
|
||||
const updatedEntriesList = entriesList.remove(entryIndex)
|
||||
map.setIn(['addressBook', safeAddress], updatedEntriesList)
|
||||
})
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable import/named */
|
||||
// @flow
|
||||
import { List, Map } from 'immutable'
|
||||
import { createSelector, Selector } from 'reselect'
|
||||
|
@ -7,7 +8,8 @@ import type { GlobalState } from '~/store'
|
|||
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
|
||||
import { safeParamAddressFromStateSelector } from '~/routes/safe/store/selectors'
|
||||
|
||||
export const addressBookMapSelector = (state: GlobalState): Map<string, AddressBook> => state[ADDRESS_BOOK_REDUCER_ID].get('addressBook')
|
||||
export const addressBookMapSelector = (state: GlobalState): Map<string, AddressBook> =>
|
||||
state[ADDRESS_BOOK_REDUCER_ID].get('addressBook')
|
||||
|
||||
export const getAddressBook: Selector<GlobalState, AddressBook> = createSelector(
|
||||
addressBookMapSelector,
|
||||
|
@ -38,7 +40,7 @@ export const getNameFromAddressBook = (userAddress: string): string | null => {
|
|||
return null
|
||||
}
|
||||
const addressBook = useSelector(getAddressBook)
|
||||
const result = addressBook.filter((addressBookItem) => addressBookItem.address === userAddress)
|
||||
const result = addressBook.filter(addressBookItem => addressBookItem.address === userAddress)
|
||||
if (result.size > 0) {
|
||||
return result.get(0).name
|
||||
}
|
||||
|
|
|
@ -21,10 +21,11 @@ export const saveAddressBook = async (addressBook: AddressBook) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const getAddressesListFromAdbk = (addressBook: AddressBook) => Array.from(addressBook).map((entry) => entry.address)
|
||||
export const getAddressesListFromAdbk = (addressBook: AddressBook) =>
|
||||
Array.from(addressBook).map(entry => entry.address)
|
||||
|
||||
const getNameFromAdbk = (addressBook: AddressBook, userAddress: string): string | null => {
|
||||
const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress)
|
||||
const entry = addressBook.find(addressBookItem => addressBookItem.address === userAddress)
|
||||
if (entry) {
|
||||
return entry.name
|
||||
}
|
||||
|
@ -39,12 +40,11 @@ export const getNameFromAddressBook = (userAddress: string): string | null => {
|
|||
return getNameFromAdbk(addressBook, userAddress)
|
||||
}
|
||||
|
||||
|
||||
export const getOwnersWithNameFromAddressBook = (addressBook: AddressBook, ownerList: List<Owner>) => {
|
||||
if (!ownerList) {
|
||||
return []
|
||||
}
|
||||
const ownersListWithAdbkNames = ownerList.map((owner) => {
|
||||
const ownersListWithAdbkNames = ownerList.map(owner => {
|
||||
const ownerName = getNameFromAdbk(addressBook, owner.address)
|
||||
return {
|
||||
address: owner.address,
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// @flow
|
||||
|
||||
// Code of the safe v1.0.0
|
||||
const proxyCodeV10 =
|
||||
'0x608060405273ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415603d573d6000fd5b3d6000f3fe'
|
||||
// Old PayingProxyCode
|
||||
const oldProxyCode =
|
||||
'0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600'
|
||||
|
||||
export const isProxyCode = codeToCheck =>
|
||||
codeToCheck === oldProxyCode || codeToCheck === proxyCodeV10
|
|
@ -1,15 +1,21 @@
|
|||
// @flow
|
||||
import contract from 'truffle-contract'
|
||||
import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/ProxyFactory.json'
|
||||
import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxyFactory.json'
|
||||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
||||
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/Proxy.json'
|
||||
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxy.json'
|
||||
import { ensureOnce } from '~/utils/singleton'
|
||||
import { simpleMemoize } from '~/components/forms/validator'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
||||
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
||||
import { isProxyCode } from '~/logic/contracts/historicProxyCode'
|
||||
|
||||
export const SENTINEL_ADDRESS = '0x0000000000000000000000000000000000000001'
|
||||
export const MULTI_SEND_ADDRESS = '0xB522a9f781924eD250A11C54105E51840B138AdD'
|
||||
export const SAFE_MASTER_COPY_ADDRESS = '0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F'
|
||||
export const DEFAULT_FALLBACK_HANDLER_ADDRESS = '0xd5D82B6aDDc9027B22dCA772Aa68D5d74cdBdF44'
|
||||
export const SAFE_MASTER_COPY_ADDRESS_V10 = '0xb6029EA3B2c51D09a50B53CA8012FeEB05bDa35A'
|
||||
|
||||
|
||||
let proxyFactoryMaster
|
||||
let safeMaster
|
||||
|
@ -66,7 +72,7 @@ export const getSafeMasterContract = async () => {
|
|||
|
||||
export const deploySafeContract = async (safeAccounts: string[], numConfirmations: number, userAccount: string) => {
|
||||
const gnosisSafeData = await safeMaster.contract.methods
|
||||
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', ZERO_ADDRESS, 0, ZERO_ADDRESS)
|
||||
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', DEFAULT_FALLBACK_HANDLER_ADDRESS, ZERO_ADDRESS, 0, ZERO_ADDRESS)
|
||||
.encodeABI()
|
||||
const proxyFactoryData = proxyFactoryMaster.contract.methods
|
||||
.createProxy(safeMaster.address, gnosisSafeData)
|
||||
|
@ -88,7 +94,7 @@ export const estimateGasForDeployingSafe = async (
|
|||
userAccount: string,
|
||||
) => {
|
||||
const gnosisSafeData = await safeMaster.contract.methods
|
||||
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', ZERO_ADDRESS, 0, ZERO_ADDRESS)
|
||||
.setup(safeAccounts, numConfirmations, ZERO_ADDRESS, '0x', DEFAULT_FALLBACK_HANDLER_ADDRESS, ZERO_ADDRESS, 0, ZERO_ADDRESS)
|
||||
.encodeABI()
|
||||
const proxyFactoryData = proxyFactoryMaster.contract.methods
|
||||
.createProxy(safeMaster.address, gnosisSafeData)
|
||||
|
@ -126,7 +132,49 @@ export const validateProxy = async (safeAddress: string): Promise<boolean> => {
|
|||
return true
|
||||
}
|
||||
}
|
||||
// Old PayingProxyCode
|
||||
const oldProxyCode = '0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600'
|
||||
return codeWithoutMetadata === oldProxyCode
|
||||
|
||||
|
||||
return isProxyCode(codeWithoutMetadata)
|
||||
}
|
||||
|
||||
export type MultiSendTransactionInstanceType = {
|
||||
operation: number,
|
||||
to: string,
|
||||
value: number,
|
||||
data: string,
|
||||
}
|
||||
|
||||
export const getEncodedMultiSendCallData = (txs: Array<MultiSendTransactionInstanceType>, web3: Object) => {
|
||||
const multiSendAbi = [
|
||||
{
|
||||
type: 'function',
|
||||
name: 'multiSend',
|
||||
constant: false,
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [{ type: 'bytes', name: 'transactions' }],
|
||||
outputs: [],
|
||||
},
|
||||
]
|
||||
const multiSend = new web3.eth.Contract(multiSendAbi, MULTI_SEND_ADDRESS)
|
||||
const encodeMultiSendCallData = multiSend.methods
|
||||
.multiSend(
|
||||
`0x${txs
|
||||
.map((tx) => [
|
||||
web3.eth.abi.encodeParameter('uint8', 0).slice(-2),
|
||||
web3.eth.abi.encodeParameter('address', tx.to).slice(-40),
|
||||
web3.eth.abi.encodeParameter('uint256', tx.value).slice(-64),
|
||||
web3.eth.abi
|
||||
.encodeParameter(
|
||||
'uint256',
|
||||
web3.utils.hexToBytes(tx.data).length,
|
||||
)
|
||||
.slice(-64),
|
||||
tx.data.replace(/^0x/, ''),
|
||||
].join(''))
|
||||
.join('')}`,
|
||||
)
|
||||
.encodeABI()
|
||||
|
||||
return encodeMultiSendCallData
|
||||
}
|
||||
|
|
|
@ -3,4 +3,6 @@ import { createAction } from 'redux-actions'
|
|||
|
||||
export const OPEN_COOKIE_BANNER = 'OPEN_COOKIE_BANNER'
|
||||
|
||||
export const openCookieBanner = createAction<string, *, *>(OPEN_COOKIE_BANNER, (cookieBannerOpen: boolean) => ({ cookieBannerOpen }))
|
||||
export const openCookieBanner = createAction<string, *, *>(OPEN_COOKIE_BANNER, (cookieBannerOpen: boolean) => ({
|
||||
cookieBannerOpen,
|
||||
}))
|
||||
|
|
|
@ -3,7 +3,10 @@ import axios from 'axios'
|
|||
import { getExchangeRatesUrl } from '~/config'
|
||||
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
|
||||
|
||||
const fetchCurrenciesRates = async (baseCurrency: AVAILABLE_CURRENCIES, targetCurrencyValue: AVAILABLE_CURRENCIES): Promise<number> => {
|
||||
const fetchCurrenciesRates = async (
|
||||
baseCurrency: AVAILABLE_CURRENCIES,
|
||||
targetCurrencyValue: AVAILABLE_CURRENCIES,
|
||||
): Promise<number> => {
|
||||
let rate = 0
|
||||
const url = `${getExchangeRatesUrl()}?base=${baseCurrency}&symbols=${targetCurrencyValue}`
|
||||
|
||||
|
|
|
@ -7,14 +7,15 @@ import fetchCurrenciesRates from '~/logic/currencyValues/api/fetchCurrenciesRate
|
|||
import { currencyValuesListSelector } from '~/logic/currencyValues/store/selectors'
|
||||
import { setCurrencyBalances } from '~/logic/currencyValues/store/actions/setCurrencyBalances'
|
||||
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const fetchCurrencySelectedValue = (currencyValueSelected: AVAILABLE_CURRENCIES) => async (dispatch: ReduxDispatch<GlobalState>, getState: Function) => {
|
||||
const fetchCurrencySelectedValue = (currencyValueSelected: AVAILABLE_CURRENCIES) => async (
|
||||
dispatch: ReduxDispatch<GlobalState>,
|
||||
getState: Function,
|
||||
) => {
|
||||
const state = getState()
|
||||
const currencyBalancesList = currencyValuesListSelector(state)
|
||||
const selectedCurrencyRateInBaseCurrency = await fetchCurrenciesRates(AVAILABLE_CURRENCIES.USD, currencyValueSelected)
|
||||
|
||||
|
||||
const newList = []
|
||||
for (const currencyValue of currencyBalancesList) {
|
||||
const { balanceInBaseCurrency } = currencyValue
|
||||
|
|
|
@ -10,21 +10,24 @@ import { loadFromStorage } from '~/utils/storage'
|
|||
import fetchCurrencySelectedValue from '~/logic/currencyValues/store/actions/fetchCurrencySelectedValue'
|
||||
import { CURRENCY_SELECTED_KEY } from '~/logic/currencyValues/store/actions/saveCurrencySelected'
|
||||
|
||||
|
||||
export const fetchCurrencyValues = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
try {
|
||||
const tokensFetched = await fetchTokenCurrenciesBalances(safeAddress)
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const currencyList = List(tokensFetched.data.filter((currencyBalance) => currencyBalance.balanceUsd).map((currencyBalance) => {
|
||||
const { balanceUsd, tokenAddress } = currencyBalance
|
||||
return makeBalanceCurrency({
|
||||
currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : null,
|
||||
tokenAddress,
|
||||
balanceInBaseCurrency: balanceUsd,
|
||||
balanceInSelectedCurrency: balanceUsd,
|
||||
})
|
||||
}))
|
||||
const currencyList = List(
|
||||
tokensFetched.data
|
||||
.filter(currencyBalance => currencyBalance.balanceUsd)
|
||||
.map(currencyBalance => {
|
||||
const { balanceUsd, tokenAddress } = currencyBalance
|
||||
return makeBalanceCurrency({
|
||||
currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : null,
|
||||
tokenAddress,
|
||||
balanceInBaseCurrency: balanceUsd,
|
||||
balanceInSelectedCurrency: balanceUsd,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
dispatch(setCurrencyBalances(currencyList))
|
||||
const currencyStored = await loadFromStorage(CURRENCY_SELECTED_KEY)
|
||||
|
|
|
@ -7,10 +7,11 @@ import type { GlobalState } from '~/store'
|
|||
import { saveToStorage } from '~/utils/storage'
|
||||
import { setCurrencySelected } from '~/logic/currencyValues/store/actions/setCurrencySelected'
|
||||
|
||||
|
||||
export const CURRENCY_SELECTED_KEY = 'CURRENCY_SELECTED_KEY'
|
||||
|
||||
const saveCurrencySelected = (currencySelected: AVAILABLE_CURRENCIES) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const saveCurrencySelected = (currencySelected: AVAILABLE_CURRENCIES) => async (
|
||||
dispatch: ReduxDispatch<GlobalState>,
|
||||
) => {
|
||||
await saveToStorage(CURRENCY_SELECTED_KEY, { currencyValueSelected: currencySelected })
|
||||
dispatch(setCurrencySelected(currencySelected))
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import type { CurrencyValues, CurrencyValuesProps } from '~/logic/currencyValues
|
|||
|
||||
export const SET_CURRENCY_BALANCES = 'SET_CURRENCY_BALANCES'
|
||||
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export const setCurrencyBalances = createAction<string, *>(SET_CURRENCY_BALANCES, (currencyBalances: Map<string, CurrencyValues>): CurrencyValuesProps => ({ currencyBalances }))
|
||||
export const setCurrencyBalances = createAction<string, *>(
|
||||
SET_CURRENCY_BALANCES,
|
||||
(currencyBalances: Map<string, CurrencyValues>): CurrencyValuesProps => ({ currencyBalances }),
|
||||
)
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
// @flow
|
||||
import { createAction } from 'redux-actions'
|
||||
import type {
|
||||
CurrencyValuesProps,
|
||||
} from '~/logic/currencyValues/store/model/currencyValues'
|
||||
import type { CurrencyValuesProps } from '~/logic/currencyValues/store/model/currencyValues'
|
||||
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
|
||||
|
||||
|
||||
export const SET_CURRENT_CURRENCY = 'SET_CURRENT_CURRENCY'
|
||||
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export const setCurrencySelected = createAction<string, *>(SET_CURRENT_CURRENCY, (currencyValueSelected: AVAILABLE_CURRENCIES): CurrencyValuesProps => ({ currencyValueSelected }))
|
||||
export const setCurrencySelected = createAction<string, *>(
|
||||
SET_CURRENT_CURRENCY,
|
||||
(currencyValueSelected: AVAILABLE_CURRENCIES): CurrencyValuesProps => ({ currencyValueSelected }),
|
||||
)
|
||||
|
|
|
@ -38,9 +38,8 @@ export const AVAILABLE_CURRENCIES = {
|
|||
MYR: 'MYR',
|
||||
}
|
||||
|
||||
|
||||
export type BalanceCurrencyType = {
|
||||
currencyName: AVAILABLE_CURRENCIES;
|
||||
currencyName: AVAILABLE_CURRENCIES,
|
||||
tokenAddress: string,
|
||||
balanceInBaseCurrency: string,
|
||||
balanceInSelectedCurrency: string,
|
||||
|
@ -54,8 +53,8 @@ export const makeBalanceCurrency = Record({
|
|||
})
|
||||
|
||||
export type CurrencyValuesProps = {
|
||||
currencyValueSelected: AVAILABLE_CURRENCIES;
|
||||
currencyValuesList: BalanceCurrencyType[]
|
||||
currencyValueSelected: AVAILABLE_CURRENCIES,
|
||||
currencyValuesList: BalanceCurrencyType[],
|
||||
}
|
||||
|
||||
export type CurrencyValues = RecordOf<CurrencyValuesProps>
|
||||
|
|
|
@ -4,6 +4,6 @@ import { List } from 'immutable'
|
|||
import { type GlobalState } from '~/store'
|
||||
import { CURRENCY_VALUES_KEY } from '~/logic/currencyValues/store/reducer/currencyValues'
|
||||
|
||||
|
||||
export const currencyValuesListSelector = (state: GlobalState) => (state[CURRENCY_VALUES_KEY].get('currencyBalances') ? state[CURRENCY_VALUES_KEY].get('currencyBalances') : List([]))
|
||||
export const currencyValuesListSelector = (state: GlobalState) =>
|
||||
state[CURRENCY_VALUES_KEY].get('currencyBalances') ? state[CURRENCY_VALUES_KEY].get('currencyBalances') : List([])
|
||||
export const currentCurrencySelector = (state: GlobalState) => state[CURRENCY_VALUES_KEY].get('currencyValueSelected')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue