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
|
build_webpack
|
||||||
|
config
|
||||||
|
contracts
|
||||||
flow-typed
|
flow-typed
|
||||||
flow-typed/npm
|
flow-typed/npm
|
||||||
config
|
|
||||||
scripts
|
|
||||||
migrations
|
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",
|
"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": {
|
"rules": {
|
||||||
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
|
|
||||||
"react/forbid-prop-types": [1, { "forbid": ["object", "any"] }],
|
|
||||||
"class-methods-use-this": 0,
|
"class-methods-use-this": 0,
|
||||||
"semi": ["error", "never"],
|
|
||||||
"max-len": [
|
"max-len": [
|
||||||
"error",
|
"error",
|
||||||
120,
|
120,
|
||||||
2,
|
2,
|
||||||
{
|
{
|
||||||
"ignoreUrls": true,
|
|
||||||
"ignoreComments": false,
|
"ignoreComments": false,
|
||||||
"ignoreRegExpLiterals": true,
|
"ignoreRegExpLiterals": true,
|
||||||
"ignoreStrings": true,
|
"ignoreStrings": true,
|
||||||
"ignoreTemplateLiterals": true
|
"ignoreTemplateLiterals": true,
|
||||||
|
"ignoreUrls": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"import/no-unresolved": 0,
|
"no-console": [
|
||||||
"import/no-extraneous-dependencies": 0,
|
"error",
|
||||||
"import/extensions": 0,
|
{
|
||||||
"import/prefer-default-export": 0,
|
"allow": ["warn", "error"]
|
||||||
"react/default-props-match-prop-types": ["error", { "allowRequiredDefaults": true }],
|
}
|
||||||
// https://github.com/yannickcr/eslint-plugin-react/issues/1593 ^
|
],
|
||||||
"jsx-a11y/label-has-for": 0,
|
"semi": ["error", "never"],
|
||||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
|
||||||
"no-console": ["error", { "allow": ["warn", "error"] }],
|
|
||||||
"flowtype/require-valid-file-annotation": [
|
"flowtype/require-valid-file-annotation": [
|
||||||
2,
|
2,
|
||||||
"always",
|
"always",
|
||||||
|
@ -35,6 +59,10 @@
|
||||||
"annotationStyle": "line"
|
"annotationStyle": "line"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"import/extensions": 0,
|
||||||
|
"import/no-extraneous-dependencies": 0,
|
||||||
|
"import/no-unresolved": 0,
|
||||||
|
"import/prefer-default-export": 0,
|
||||||
"jsx-a11y/anchor-is-valid": [
|
"jsx-a11y/anchor-is-valid": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
@ -43,13 +71,24 @@
|
||||||
"aspects": ["noHref", "invalidHref", "preferButton"]
|
"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/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/jsx-props-no-spreading": 0,
|
||||||
"react/state-in-constructor": 0
|
"prettier/prettier": "error",
|
||||||
},
|
"jsx-a11y/no-autofocus": "warn"
|
||||||
"env": {
|
|
||||||
"jest/globals": true,
|
|
||||||
"browser": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
"name": "safe-react",
|
||||||
"version": "1.7.1",
|
"version": "1.7.2",
|
||||||
"description": "Allowing crypto users manage funds in a safer way",
|
"description": "Allowing crypto users manage funds in a safer way",
|
||||||
"homepage": "https://github.com/gnosis/safe-react#readme",
|
"homepage": "https://github.com/gnosis/safe-react#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -19,25 +19,33 @@
|
||||||
"build": "REACT_APP_APP_VERSION=$npm_package_version node scripts/build.js",
|
"build": "REACT_APP_APP_VERSION=$npm_package_version node scripts/build.js",
|
||||||
"build-mainnet": "REACT_APP_NETWORK=mainnet yarn build",
|
"build-mainnet": "REACT_APP_NETWORK=mainnet yarn build",
|
||||||
"flow": "flow",
|
"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": "node scripts/start.js",
|
||||||
"start-mainnet": "REACT_APP_NETWORK=mainnet yarn start",
|
"start-mainnet": "REACT_APP_NETWORK=mainnet yarn start",
|
||||||
"test": "NODE_ENV=test && node scripts/test.js --env=jsdom",
|
"test": "NODE_ENV=test && node scripts/test.js --env=jsdom"
|
||||||
"format": "prettier-eslint $PWD'/src/**/*.{js,jsx}' --write",
|
},
|
||||||
"format:staged": "lint-staged"
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "lint-staged --allow-empty"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"./src/**/*.{js,jsx}": "prettier-eslint --write"
|
"src/**/*.{js,jsx}": [
|
||||||
|
"eslint --fix",
|
||||||
|
"prettier --write"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"pre-commit": [
|
|
||||||
"precommit"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gnosis.pm/safe-contracts": "1.0.0",
|
"@gnosis.pm/safe-contracts": "1.1.1-dev.1",
|
||||||
"@gnosis.pm/util-contracts": "2.0.4",
|
"@gnosis.pm/util-contracts": "2.0.6",
|
||||||
"@material-ui/core": "4.8.0",
|
"@material-ui/core": "4.8.0",
|
||||||
"@material-ui/icons": "4.5.1",
|
"@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",
|
"@portis/web3": "^2.0.0-beta.45",
|
||||||
"@testing-library/jest-dom": "4.2.4",
|
"@testing-library/jest-dom": "4.2.4",
|
||||||
"@toruslabs/torus-embed": "0.2.10",
|
"@toruslabs/torus-embed": "0.2.10",
|
||||||
|
@ -50,7 +58,7 @@
|
||||||
"currency-flags": "^2.1.1",
|
"currency-flags": "^2.1.1",
|
||||||
"date-fns": "2.8.1",
|
"date-fns": "2.8.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"ethereum-ens": "0.7.8",
|
"ethereum-ens": "0.8.0",
|
||||||
"final-form": "4.18.6",
|
"final-form": "4.18.6",
|
||||||
"fortmatic": "^1.0.1",
|
"fortmatic": "^1.0.1",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
|
@ -81,8 +89,9 @@
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"semver": "^7.1.1",
|
"semver": "^7.1.1",
|
||||||
|
"styled-components": "^5.0.1",
|
||||||
"web3": "1.2.4",
|
"web3": "1.2.4",
|
||||||
"web3connect": "^1.0.0-beta.25"
|
"web3connect": "1.0.0-beta.25"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/cli": "7.7.5",
|
"@babel/cli": "7.7.5",
|
||||||
|
@ -123,13 +132,13 @@
|
||||||
"css-loader": "3.4.0",
|
"css-loader": "3.4.0",
|
||||||
"detect-port": "^1.3.0",
|
"detect-port": "^1.3.0",
|
||||||
"dotenv-expand": "^5.1.0",
|
"dotenv-expand": "^5.1.0",
|
||||||
"eslint": "6.7.2",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-airbnb": "18.0.1",
|
"eslint-config-prettier": "^6.10.0",
|
||||||
"eslint-plugin-flowtype": "4.5.2",
|
"eslint-plugin-flowtype": "^4.6.0",
|
||||||
"eslint-plugin-import": "2.19.1",
|
"eslint-plugin-import": "^2.20.1",
|
||||||
"eslint-plugin-jest": "23.1.1",
|
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
"eslint-plugin-react": "7.17.0",
|
"eslint-plugin-react": "^7.18.3",
|
||||||
"ethereumjs-abi": "0.6.8",
|
"ethereumjs-abi": "0.6.8",
|
||||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||||
"file-loader": "5.0.2",
|
"file-loader": "5.0.2",
|
||||||
|
@ -137,6 +146,7 @@
|
||||||
"fs-extra": "8.1.0",
|
"fs-extra": "8.1.0",
|
||||||
"html-loader": "^0.5.5",
|
"html-loader": "^0.5.5",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"husky": "^4.2.2",
|
||||||
"jest": "24.9.0",
|
"jest": "24.9.0",
|
||||||
"jest-dom": "4.0.0",
|
"jest-dom": "4.0.0",
|
||||||
"json-loader": "^0.5.7",
|
"json-loader": "^0.5.7",
|
||||||
|
@ -144,8 +154,7 @@
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
"postcss-mixins": "6.2.3",
|
"postcss-mixins": "6.2.3",
|
||||||
"postcss-simple-vars": "^5.0.2",
|
"postcss-simple-vars": "^5.0.2",
|
||||||
"pre-commit": "^1.2.2",
|
"prettier": "^1.19.1",
|
||||||
"prettier-eslint-cli": "5.0.0",
|
|
||||||
"run-with-testrpc": "0.3.1",
|
"run-with-testrpc": "0.3.1",
|
||||||
"style-loader": "1.0.2",
|
"style-loader": "1.0.2",
|
||||||
"terser-webpack-plugin": "2.3.1",
|
"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 cn from 'classnames'
|
||||||
import Link from '~/components/layout/Link'
|
import Link from '~/components/layout/Link'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import {
|
import { primary, mainFontFamily, md, screenSm } from '~/theme/variables'
|
||||||
primary, mainFontFamily, md, screenSm,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
import type { CookiesProps } from '~/logic/cookies/model/cookie'
|
import type { CookiesProps } from '~/logic/cookies/model/cookie'
|
||||||
import { COOKIES_KEY } from '~/logic/cookies/model/cookie'
|
import { COOKIES_KEY } from '~/logic/cookies/model/cookie'
|
||||||
import { loadFromCookie, saveCookie } from '~/logic/cookies/utils'
|
import { loadFromCookie, saveCookie } from '~/logic/cookies/utils'
|
||||||
|
@ -134,17 +132,21 @@ const CookiesBanner = () => {
|
||||||
|
|
||||||
const cookieBannerContent = (
|
const cookieBannerContent = (
|
||||||
<div className={classes.container}>
|
<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 >
|
Accept preferences >
|
||||||
</span>
|
</span>
|
||||||
<div className={classes.content}>
|
<div className={classes.content}>
|
||||||
<p className={classes.text}>
|
<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">
|
<Link className={classes.link} to="https://safe.gnosis.io/cookie">
|
||||||
Cookie Policy
|
Cookie Policy
|
||||||
</Link>
|
</Link>{' '}
|
||||||
{' '}
|
|
||||||
for more information. By clicking "Accept all", you agree to the storing of cookies on your device
|
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.
|
to enhance site navigation, analyze site usage and provide customer support.
|
||||||
</p>
|
</p>
|
||||||
|
@ -155,7 +157,7 @@ const CookiesBanner = () => {
|
||||||
disabled
|
disabled
|
||||||
label="Necessary"
|
label="Necessary"
|
||||||
name="Necessary"
|
name="Necessary"
|
||||||
onChange={() => setLocalNecessary((prev) => !prev)}
|
onChange={() => setLocalNecessary(prev => !prev)}
|
||||||
value={localNecessary}
|
value={localNecessary}
|
||||||
control={<Checkbox disabled />}
|
control={<Checkbox disabled />}
|
||||||
/>
|
/>
|
||||||
|
@ -164,7 +166,7 @@ const CookiesBanner = () => {
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
label="Analytics"
|
label="Analytics"
|
||||||
name="Analytics"
|
name="Analytics"
|
||||||
onChange={() => setLocalAnalytics((prev) => !prev)}
|
onChange={() => setLocalAnalytics(prev => !prev)}
|
||||||
value={localAnalytics}
|
value={localAnalytics}
|
||||||
control={<Checkbox checked={localAnalytics} />}
|
control={<Checkbox checked={localAnalytics} />}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -33,9 +33,7 @@ type EtherscanBtnProps = {
|
||||||
value: string,
|
value: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const EtherscanBtn = ({
|
const EtherscanBtn = ({ type, value, className, increaseZindex = false }: EtherscanBtnProps) => {
|
||||||
type, value, className, increaseZindex = false,
|
|
||||||
}: EtherscanBtnProps) => {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const customClasses = increaseZindex ? { popper: classes.increasedPopperZindex } : {}
|
const customClasses = increaseZindex ? { popper: classes.increasedPopperZindex } : {}
|
||||||
|
|
||||||
|
|
|
@ -18,14 +18,9 @@ type EtherscanLinkProps = {
|
||||||
value: string,
|
value: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const EtherscanLink = ({
|
const EtherscanLink = ({ type, value, cut, classes, knownAddress }: EtherscanLinkProps) => (
|
||||||
type, value, cut, classes, knownAddress,
|
|
||||||
}: EtherscanLinkProps) => (
|
|
||||||
<Block className={classes.etherscanLink}>
|
<Block className={classes.etherscanLink}>
|
||||||
<Span
|
<Span className={cn(knownAddress && classes.addressParagraph, classes.address)} size="md">
|
||||||
className={cn(knownAddress && classes.addressParagraph, classes.address)}
|
|
||||||
size="md"
|
|
||||||
>
|
|
||||||
{cut ? shortVersionOf(value, cut) : value}
|
{cut ? shortVersionOf(value, cut) : value}
|
||||||
</Span>
|
</Span>
|
||||||
<CopyBtn className={cn(classes.button, classes.firstButton)} content={value} />
|
<CopyBtn className={cn(classes.button, classes.firstButton)} content={value} />
|
||||||
|
|
|
@ -55,12 +55,7 @@ const Footer = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className={classes.footer}>
|
<footer className={classes.footer}>
|
||||||
<span className={classes.item}>
|
<span className={classes.item}>©{date.getFullYear()} Gnosis</span>
|
||||||
©
|
|
||||||
{date.getFullYear()}
|
|
||||||
{' '}
|
|
||||||
Gnosis
|
|
||||||
</span>
|
|
||||||
<span className={classes.sep}>|</span>
|
<span className={classes.sep}>|</span>
|
||||||
<Link className={cn(classes.item, classes.link)} to="https://safe.gnosis.io/terms" target="_blank">
|
<Link className={cn(classes.item, classes.link)} to="https://safe.gnosis.io/terms" target="_blank">
|
||||||
Terms
|
Terms
|
||||||
|
|
|
@ -4,9 +4,7 @@ import { withStyles } from '@material-ui/core/styles'
|
||||||
import Dot from '@material-ui/icons/FiberManualRecord'
|
import Dot from '@material-ui/icons/FiberManualRecord'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Img from '~/components/layout/Img'
|
import Img from '~/components/layout/Img'
|
||||||
import {
|
import { fancy, border, warning, screenSm } from '~/theme/variables'
|
||||||
fancy, border, warning, screenSm,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
|
|
||||||
const key = require('../assets/key.svg')
|
const key = require('../assets/key.svg')
|
||||||
const triangle = require('../assets/triangle.svg')
|
const triangle = require('../assets/triangle.svg')
|
||||||
|
|
|
@ -12,9 +12,7 @@ import Col from '~/components/layout/Col'
|
||||||
import Img from '~/components/layout/Img'
|
import Img from '~/components/layout/Img'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Spacer from '~/components/Spacer'
|
import Spacer from '~/components/Spacer'
|
||||||
import {
|
import { border, sm, md, headerHeight, screenSm } from '~/theme/variables'
|
||||||
border, sm, md, headerHeight, screenSm,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
import Provider from './Provider'
|
import Provider from './Provider'
|
||||||
import NetworkLabel from './NetworkLabel'
|
import NetworkLabel from './NetworkLabel'
|
||||||
import SafeListHeader from './SafeListHeader'
|
import SafeListHeader from './SafeListHeader'
|
||||||
|
@ -24,7 +22,7 @@ const logo = require('../assets/gnosis-safe-multisig-logo.svg')
|
||||||
type Props = Open & {
|
type Props = Open & {
|
||||||
classes: Object,
|
classes: Object,
|
||||||
providerDetails: React.Node,
|
providerDetails: React.Node,
|
||||||
providerInfo: React.Node
|
providerInfo: React.Node,
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = () => ({
|
const styles = () => ({
|
||||||
|
@ -64,15 +62,7 @@ const styles = () => ({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const Layout = openHoc(
|
const Layout = openHoc(({ open, toggle, clickAway, classes, providerInfo, providerDetails }: Props) => (
|
||||||
({
|
|
||||||
open,
|
|
||||||
toggle,
|
|
||||||
clickAway,
|
|
||||||
classes,
|
|
||||||
providerInfo,
|
|
||||||
providerDetails,
|
|
||||||
}: Props) => (
|
|
||||||
<Row className={classes.summary}>
|
<Row className={classes.summary}>
|
||||||
<Col start="xs" middle="xs" className={classes.logo}>
|
<Col start="xs" middle="xs" className={classes.logo}>
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
|
@ -85,7 +75,7 @@ const Layout = openHoc(
|
||||||
<NetworkLabel />
|
<NetworkLabel />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Provider open={open} toggle={toggle} info={providerInfo}>
|
<Provider open={open} toggle={toggle} info={providerInfo}>
|
||||||
{(providerRef) => (
|
{providerRef => (
|
||||||
<Popper
|
<Popper
|
||||||
anchorEl={providerRef.current}
|
anchorEl={providerRef.current}
|
||||||
className={classes.popper}
|
className={classes.popper}
|
||||||
|
@ -96,11 +86,7 @@ const Layout = openHoc(
|
||||||
{({ TransitionProps }) => (
|
{({ TransitionProps }) => (
|
||||||
<Grow {...TransitionProps}>
|
<Grow {...TransitionProps}>
|
||||||
<>
|
<>
|
||||||
<ClickAwayListener
|
<ClickAwayListener onClickAway={clickAway} mouseEvent="onClick" touchEvent={false}>
|
||||||
onClickAway={clickAway}
|
|
||||||
mouseEvent="onClick"
|
|
||||||
touchEvent={false}
|
|
||||||
>
|
|
||||||
<List className={classes.root} component="div">
|
<List className={classes.root} component="div">
|
||||||
{providerDetails}
|
{providerDetails}
|
||||||
</List>
|
</List>
|
||||||
|
@ -112,7 +98,6 @@ const Layout = openHoc(
|
||||||
)}
|
)}
|
||||||
</Provider>
|
</Provider>
|
||||||
</Row>
|
</Row>
|
||||||
),
|
))
|
||||||
)
|
|
||||||
|
|
||||||
export default withStyles(styles)(Layout)
|
export default withStyles(styles)(Layout)
|
||||||
|
|
|
@ -4,9 +4,7 @@ import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { getNetwork } from '~/config'
|
import { getNetwork } from '~/config'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import {
|
import { xs, sm, md, border, screenSm } from '~/theme/variables'
|
||||||
xs, sm, md, border, screenSm,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
|
|
||||||
const network = getNetwork()
|
const network = getNetwork()
|
||||||
const formattedNetwork = network[0].toUpperCase() + network.substring(1).toLowerCase()
|
const formattedNetwork = network[0].toUpperCase() + network.substring(1).toLowerCase()
|
||||||
|
|
|
@ -56,9 +56,7 @@ class Provider extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { open, toggle, children, classes, info } = this.props
|
||||||
open, toggle, children, classes, info,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -13,9 +13,7 @@ import Img from '~/components/layout/Img'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Spacer from '~/components/Spacer'
|
import Spacer from '~/components/Spacer'
|
||||||
import {
|
import { xs, sm, md, lg, background, warning, connected as connectedBg } from '~/theme/variables'
|
||||||
xs, sm, md, lg, background, warning, connected as connectedBg,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
import { upperFirst } from '~/utils/css'
|
import { upperFirst } from '~/utils/css'
|
||||||
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
||||||
import CircleDot from '~/components/Header/components/CircleDot'
|
import CircleDot from '~/components/Header/components/CircleDot'
|
||||||
|
@ -93,9 +91,7 @@ const styles = () => ({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const UserDetails = ({
|
const UserDetails = ({ provider, connected, network, userAddress, classes, onDisconnect }: Props) => {
|
||||||
provider, connected, network, userAddress, classes, onDisconnect,
|
|
||||||
}: Props) => {
|
|
||||||
const status = connected ? 'Connected' : 'Connection error'
|
const status = connected ? 'Connected' : 'Connection error'
|
||||||
const address = userAddress ? shortVersionOf(userAddress, 4) : 'Address not available'
|
const address = userAddress ? shortVersionOf(userAddress, 4) : 'Address not available'
|
||||||
const identiconAddress = userAddress || 'random'
|
const identiconAddress = userAddress || 'random'
|
||||||
|
|
|
@ -54,9 +54,7 @@ const styles = () => ({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const ProviderInfo = ({
|
const ProviderInfo = ({ provider, network, userAddress, connected, classes }: Props) => {
|
||||||
provider, network, userAddress, connected, classes,
|
|
||||||
}: Props) => {
|
|
||||||
const providerText = `${provider} [${network}]`
|
const providerText = `${provider} [${network}]`
|
||||||
const cutAddress = connected ? shortVersionOf(userAddress, 4) : 'Connection Error'
|
const cutAddress = connected ? shortVersionOf(userAddress, 4) : 'Connection Error'
|
||||||
const color = connected ? 'primary' : 'warning'
|
const color = connected ? 'primary' : 'warning'
|
||||||
|
|
|
@ -7,9 +7,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
|
||||||
import ExpandLessIcon from '@material-ui/icons/ExpandLess'
|
import ExpandLessIcon from '@material-ui/icons/ExpandLess'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import {
|
import { xs, sm, md, border, screenSm } from '~/theme/variables'
|
||||||
xs, sm, md, border, screenSm,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
import { safesCountSelector } from '~/routes/safe/store/selectors'
|
import { safesCountSelector } from '~/routes/safe/store/selectors'
|
||||||
import { SidebarContext } from '~/components/Sidebar'
|
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
|
// $FlowFixMe
|
||||||
(state) => ({ safesCount: safesCountSelector(state) }),
|
state => ({ safesCount: safesCountSelector(state) }),
|
||||||
null,
|
null,
|
||||||
)(SafeListHeader)
|
)(SafeListHeader)
|
||||||
|
|
|
@ -58,9 +58,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
getProviderInfoBased = () => {
|
getProviderInfoBased = () => {
|
||||||
const { hasError } = this.state
|
const { hasError } = this.state
|
||||||
const {
|
const { loaded, available, provider, network, userAddress } = this.props
|
||||||
loaded, available, provider, network, userAddress,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
if (hasError || !loaded) {
|
if (hasError || !loaded) {
|
||||||
return <ProviderDisconnected />
|
return <ProviderDisconnected />
|
||||||
|
@ -71,9 +69,7 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
getProviderDetailsBased = () => {
|
getProviderDetailsBased = () => {
|
||||||
const { hasError } = this.state
|
const { hasError } = this.state
|
||||||
const {
|
const { loaded, available, provider, network, userAddress } = this.props
|
||||||
loaded, available, provider, network, userAddress,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
if (hasError || !loaded) {
|
if (hasError || !loaded) {
|
||||||
return <ConnectDetails />
|
return <ConnectDetails />
|
||||||
|
@ -98,7 +94,4 @@ class HeaderComponent extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(selector, actions)(withSnackbar(HeaderComponent))
|
||||||
selector,
|
|
||||||
actions,
|
|
||||||
)(withSnackbar(HeaderComponent))
|
|
||||||
|
|
|
@ -62,13 +62,10 @@ export default class Identicon extends React.PureComponent<Props> {
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { diameter, className } = this.props
|
const { diameter, className } = this.props
|
||||||
const style = this.getStyleFrom(diameter)
|
const style = this.getStyleFrom(diameter)
|
||||||
|
|
||||||
return (
|
return <div className={className} style={style} ref={this.identicon} />
|
||||||
<div className={className} style={style} ref={this.identicon} />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,24 +20,16 @@ const styles = {
|
||||||
|
|
||||||
class ListItemText extends React.PureComponent<Props> {
|
class ListItemText extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { primary, secondary, classes, cut = false } = this.props
|
||||||
primary, secondary, classes, cut = false,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
const cutStyle = cut ? {
|
const cutStyle = cut
|
||||||
|
? {
|
||||||
secondary: classes.itemTextSecondary,
|
secondary: classes.itemTextSecondary,
|
||||||
} : undefined
|
}
|
||||||
|
: undefined
|
||||||
|
|
||||||
return (
|
return <MuiListItemText classes={cutStyle} inset primary={primary} secondary={secondary} />
|
||||||
<MuiListItemText
|
|
||||||
classes={cutStyle}
|
|
||||||
inset
|
|
||||||
primary={primary}
|
|
||||||
secondary={secondary}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default withStyles(styles)(ListItemText)
|
export default withStyles(styles)(ListItemText)
|
||||||
|
|
|
@ -40,7 +40,14 @@ const styles = () => ({
|
||||||
})
|
})
|
||||||
|
|
||||||
const GnoModal = ({
|
const GnoModal = ({
|
||||||
title, description, open, children, handleClose, modalClassName, classes, paperClassName,
|
title,
|
||||||
|
description,
|
||||||
|
open,
|
||||||
|
children,
|
||||||
|
handleClose,
|
||||||
|
modalClassName,
|
||||||
|
classes,
|
||||||
|
paperClassName,
|
||||||
}: Props) => (
|
}: Props) => (
|
||||||
<Modal
|
<Modal
|
||||||
aria-labelledby={title}
|
aria-labelledby={title}
|
||||||
|
|
|
@ -43,7 +43,7 @@ class Notifier extends Component<Props> {
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
const { notifications = [], enqueueSnackbar, removeSnackbar } = this.props
|
const { notifications = [], enqueueSnackbar, removeSnackbar } = this.props
|
||||||
|
|
||||||
notifications.forEach((notification) => {
|
notifications.forEach(notification => {
|
||||||
// Do nothing if snackbar is already displayed
|
// Do nothing if snackbar is already displayed
|
||||||
if (this.displayed.includes(notification.key)) {
|
if (this.displayed.includes(notification.key)) {
|
||||||
return
|
return
|
||||||
|
@ -66,7 +66,7 @@ class Notifier extends Component<Props> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
storeDisplayed = (id) => {
|
storeDisplayed = id => {
|
||||||
this.displayed = [...this.displayed, id]
|
this.displayed = [...this.displayed, id]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,7 @@ type Props = {
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScanQRModal = ({
|
const ScanQRModal = ({ classes, onClose, isOpen, onScan }: Props) => {
|
||||||
classes, onClose, isOpen, onScan,
|
|
||||||
}: Props) => {
|
|
||||||
const [hasWebcam, setHasWebcam] = useState(null)
|
const [hasWebcam, setHasWebcam] = useState(null)
|
||||||
const scannerRef: Object = React.createRef()
|
const scannerRef: Object = React.createRef()
|
||||||
const openImageDialog = () => {
|
const openImageDialog = () => {
|
||||||
|
@ -73,10 +71,10 @@ const ScanQRModal = ({
|
||||||
<QrReader
|
<QrReader
|
||||||
ref={scannerRef}
|
ref={scannerRef}
|
||||||
legacyMode={!hasWebcam}
|
legacyMode={!hasWebcam}
|
||||||
onScan={(data) => {
|
onScan={data => {
|
||||||
if (data) onScan(data)
|
if (data) onScan(data)
|
||||||
}}
|
}}
|
||||||
onError={(err) => {
|
onError={err => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}}
|
}}
|
||||||
style={{ width: '400px', height: '400px' }}
|
style={{ width: '400px', height: '400px' }}
|
||||||
|
@ -85,12 +83,7 @@ const ScanQRModal = ({
|
||||||
</Col>
|
</Col>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
<Row align="center" className={classes.buttonRow}>
|
<Row align="center" className={classes.buttonRow}>
|
||||||
<Button
|
<Button color="secondary" className={classes.button} minWidth={154} onClick={onClose}>
|
||||||
color="secondary"
|
|
||||||
className={classes.button}
|
|
||||||
minWidth={154}
|
|
||||||
onClick={onClose}
|
|
||||||
>
|
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
// @flow
|
// @flow
|
||||||
import {
|
import { lg, sm, background, secondaryText } from '~/theme/variables'
|
||||||
lg, sm, background, secondaryText,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
|
|
||||||
export const styles = () => ({
|
export const styles = () => ({
|
||||||
heading: {
|
heading: {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
navigator.getMedia = navigator.getUserMedia // use the proper vendor prefix
|
navigator.getMedia =
|
||||||
|| navigator.webkitGetUserMedia
|
navigator.getUserMedia || // use the proper vendor prefix
|
||||||
|| navigator.mozGetUserMedia
|
navigator.webkitGetUserMedia ||
|
||||||
|| navigator.msGetUserMedia
|
navigator.mozGetUserMedia ||
|
||||||
|
navigator.msGetUserMedia
|
||||||
|
|
||||||
export const checkWebcam = (success: Function, err: Function) => navigator.getMedia(
|
export const checkWebcam = (success: Function, err: Function) =>
|
||||||
|
navigator.getMedia(
|
||||||
{ video: true },
|
{ video: true },
|
||||||
() => {
|
() => {
|
||||||
success()
|
success()
|
||||||
|
@ -12,4 +14,4 @@ export const checkWebcam = (success: Function, err: Function) => navigator.getMe
|
||||||
() => {
|
() => {
|
||||||
err()
|
err()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,9 +11,7 @@ import Hairline from '~/components/layout/Hairline'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import ButtonLink from '~/components/layout/ButtonLink'
|
import ButtonLink from '~/components/layout/ButtonLink'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import {
|
import { mediumFontSize, sm, primary, disabled, md } from '~/theme/variables'
|
||||||
mediumFontSize, sm, primary, disabled, md,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
import { formatAmount } from '~/logic/tokens/utils/formatAmount'
|
||||||
import { shortVersionOf, sameAddress } from '~/logic/wallets/ethAddresses'
|
import { shortVersionOf, sameAddress } from '~/logic/wallets/ethAddresses'
|
||||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||||
|
@ -74,14 +72,12 @@ const useStyles = makeStyles({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const SafeList = ({
|
const SafeList = ({ safes, onSafeClick, setDefaultSafe, defaultSafe, currentSafe }: SafeListProps) => {
|
||||||
safes, onSafeClick, setDefaultSafe, defaultSafe, currentSafe,
|
|
||||||
}: SafeListProps) => {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MuiList className={classes.list}>
|
<MuiList className={classes.list}>
|
||||||
{safes.map((safe) => (
|
{safes.map(safe => (
|
||||||
<React.Fragment key={safe.address}>
|
<React.Fragment key={safe.address}>
|
||||||
<Link
|
<Link
|
||||||
to={`${SAFELIST_ADDRESS}/${safe.address}/balances`}
|
to={`${SAFELIST_ADDRESS}/${safe.address}/balances`}
|
||||||
|
@ -89,18 +85,23 @@ const SafeList = ({
|
||||||
data-testid={SIDEBAR_SAFELIST_ROW_TESTID}
|
data-testid={SIDEBAR_SAFELIST_ROW_TESTID}
|
||||||
>
|
>
|
||||||
<ListItem classes={{ root: classes.listItemRoot }}>
|
<ListItem classes={{ root: classes.listItemRoot }}>
|
||||||
{ sameAddress(currentSafe, safe.address) ? (
|
{sameAddress(currentSafe, safe.address) ? (
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Img src={check} alt="check" className={classes.checkIcon} />
|
<Img src={check} alt="check" className={classes.checkIcon} />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
) : <div className={classes.noIcon}>placeholder</div> }
|
) : (
|
||||||
|
<div className={classes.noIcon}>placeholder</div>
|
||||||
|
)}
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<Identicon address={safe.address} diameter={32} className={classes.icon} />
|
<Identicon address={safe.address} diameter={32} className={classes.icon} />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={safe.name}
|
primary={safe.name}
|
||||||
secondary={shortVersionOf(safe.address, 4)}
|
secondary={shortVersionOf(safe.address, 4)}
|
||||||
classes={{ primary: classes.safeName, secondary: classes.safeAddress }}
|
classes={{
|
||||||
|
primary: classes.safeName,
|
||||||
|
secondary: classes.safeAddress,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Paragraph size="lg" color="primary">
|
<Paragraph size="lg" color="primary">
|
||||||
{`${formatAmount(safe.ethBalance)} ETH`}
|
{`${formatAmount(safe.ethBalance)} ETH`}
|
||||||
|
@ -111,7 +112,7 @@ const SafeList = ({
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
className={classes.makeDefaultBtn}
|
className={classes.makeDefaultBtn}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={(e) => {
|
onClick={e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,7 @@ import Hairline from '~/components/layout/Hairline'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
import { WELCOME_ADDRESS } from '~/routes/routes'
|
import { WELCOME_ADDRESS } from '~/routes/routes'
|
||||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||||
import {
|
import { defaultSafeSelector, safeParamAddressFromStateSelector } from '~/routes/safe/store/selectors'
|
||||||
defaultSafeSelector,
|
|
||||||
safeParamAddressFromStateSelector,
|
|
||||||
} from '~/routes/safe/store/selectors'
|
|
||||||
import setDefaultSafe from '~/routes/safe/store/actions/setDefaultSafe'
|
import setDefaultSafe from '~/routes/safe/store/actions/setDefaultSafe'
|
||||||
import { sortedSafeListSelector } from './selectors'
|
import { sortedSafeListSelector } from './selectors'
|
||||||
import SafeList from './SafeList'
|
import SafeList from './SafeList'
|
||||||
|
@ -27,7 +24,7 @@ const { useState, useEffect, useMemo } = React
|
||||||
|
|
||||||
type TSidebarContext = {
|
type TSidebarContext = {
|
||||||
isOpen: boolean,
|
isOpen: boolean,
|
||||||
toggleSidebar: Function
|
toggleSidebar: Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SidebarContext = React.createContext<TSidebarContext>({
|
export const SidebarContext = React.createContext<TSidebarContext>({
|
||||||
|
@ -40,22 +37,18 @@ type SidebarProps = {
|
||||||
safes: List<Safe>,
|
safes: List<Safe>,
|
||||||
setDefaultSafeAction: Function,
|
setDefaultSafeAction: Function,
|
||||||
defaultSafe: string,
|
defaultSafe: string,
|
||||||
currentSafe: string
|
currentSafe: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterBy = (filter: string, safes: List<Safe>): List<Safe> => safes.filter(
|
const filterBy = (filter: string, safes: List<Safe>): List<Safe> =>
|
||||||
(safe: Safe) => !filter
|
safes.filter(
|
||||||
|| safe.address.toLowerCase().includes(filter.toLowerCase())
|
(safe: Safe) =>
|
||||||
|| safe.name.toLowerCase().includes(filter.toLowerCase()),
|
!filter ||
|
||||||
)
|
safe.address.toLowerCase().includes(filter.toLowerCase()) ||
|
||||||
|
safe.name.toLowerCase().includes(filter.toLowerCase()),
|
||||||
|
)
|
||||||
|
|
||||||
const Sidebar = ({
|
const Sidebar = ({ children, safes, setDefaultSafeAction, defaultSafe, currentSafe }: SidebarProps) => {
|
||||||
children,
|
|
||||||
safes,
|
|
||||||
setDefaultSafeAction,
|
|
||||||
defaultSafe,
|
|
||||||
currentSafe,
|
|
||||||
}: SidebarProps) => {
|
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false)
|
const [isOpen, setIsOpen] = useState<boolean>(false)
|
||||||
const [filter, setFilter] = useState<string>('')
|
const [filter, setFilter] = useState<string>('')
|
||||||
const classes = useSidebarStyles()
|
const classes = useSidebarStyles()
|
||||||
|
@ -74,7 +67,7 @@ const Sidebar = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
setIsOpen((prevIsOpen) => !prevIsOpen)
|
setIsOpen(prevIsOpen => !prevIsOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFilterChange = (value: string) => {
|
const handleFilterChange = (value: string) => {
|
||||||
|
@ -147,7 +140,7 @@ const Sidebar = ({
|
||||||
|
|
||||||
export default connect<Object, Object, ?Function, ?Object>(
|
export default connect<Object, Object, ?Function, ?Object>(
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
(state) => ({
|
state => ({
|
||||||
safes: sortedSafeListSelector(state),
|
safes: sortedSafeListSelector(state),
|
||||||
defaultSafe: defaultSafeSelector(state),
|
defaultSafe: defaultSafeSelector(state),
|
||||||
currentSafe: safeParamAddressFromStateSelector(state),
|
currentSafe: safeParamAddressFromStateSelector(state),
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import {
|
import { xs, mediumFontSize, secondaryText, md, headerHeight, screenSm } from '~/theme/variables'
|
||||||
xs, mediumFontSize, secondaryText, md, headerHeight, screenSm,
|
|
||||||
} from '~/theme/variables'
|
|
||||||
|
|
||||||
const sidebarWidth = '400px'
|
const sidebarWidth = '400px'
|
||||||
const sidebarMarginLeft = '12px'
|
const sidebarMarginLeft = '12px'
|
||||||
|
|
|
@ -9,4 +9,5 @@ const style = {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react/display-name
|
||||||
export default ({ className }: Props) => <div className={className} style={style} />
|
export default ({ className }: Props) => <div className={className} style={style} />
|
||||||
|
|
|
@ -30,15 +30,7 @@ type Props = {
|
||||||
buttonLabels?: Array<string>,
|
buttonLabels?: Array<string>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Controls = ({
|
const Controls = ({ onPrevious, firstPage, penultimate, lastPage, disabled, currentStep, buttonLabels }: Props) => {
|
||||||
onPrevious,
|
|
||||||
firstPage,
|
|
||||||
penultimate,
|
|
||||||
lastPage,
|
|
||||||
disabled,
|
|
||||||
currentStep,
|
|
||||||
buttonLabels,
|
|
||||||
}: Props) => {
|
|
||||||
const back = firstPage ? 'Cancel' : 'Back'
|
const back = firstPage ? 'Cancel' : 'Back'
|
||||||
|
|
||||||
let next
|
let next
|
||||||
|
|
|
@ -23,9 +23,7 @@ type Props = {
|
||||||
padding?: boolean,
|
padding?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const OpenPaper = ({
|
const OpenPaper = ({ classes, children, controls, padding = true }: Props) => (
|
||||||
classes, children, controls, padding = true,
|
|
||||||
}: Props) => (
|
|
||||||
<Paper className={classes.root} elevation={1}>
|
<Paper className={classes.root} elevation={1}>
|
||||||
<Block className={padding ? classes.padding : ''}>{children}</Block>
|
<Block className={padding ? classes.padding : ''}>{children}</Block>
|
||||||
{controls}
|
{controls}
|
||||||
|
|
|
@ -5,10 +5,6 @@ type Props = {
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Step = ({ children }: Props) => (
|
const Step = ({ children }: Props) => <div>{children}</div>
|
||||||
<div>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default Step
|
export default Step
|
||||||
|
|
|
@ -52,7 +52,7 @@ const GnoStepper = (props: Props) => {
|
||||||
|
|
||||||
const getPageProps = (pages: React.Node): PageProps => React.Children.toArray(pages)[page].props
|
const getPageProps = (pages: React.Node): PageProps => React.Children.toArray(pages)[page].props
|
||||||
|
|
||||||
const updateInitialProps = (newInitialProps) => {
|
const updateInitialProps = newInitialProps => {
|
||||||
setValues(newInitialProps)
|
setValues(newInitialProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,14 +105,12 @@ const GnoStepper = (props: Props) => {
|
||||||
return next(formValues)
|
return next(formValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLastPage = (pageNumber) => {
|
const isLastPage = pageNumber => {
|
||||||
const { steps } = props
|
const { steps } = props
|
||||||
return pageNumber === steps.length - 1
|
return pageNumber === steps.length - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { steps, children, classes, disabledWhenValidating = false, testId, mutators, buttonLabels } = props
|
||||||
steps, children, classes, disabledWhenValidating = false, testId, mutators, buttonLabels,
|
|
||||||
} = props
|
|
||||||
const activePage = getActivePageFrom(children)
|
const activePage = getActivePageFrom(children)
|
||||||
|
|
||||||
const lastPage = isLastPage(page)
|
const lastPage = isLastPage(page)
|
||||||
|
|
|
@ -57,9 +57,7 @@ class GnoTableHead extends React.PureComponent<Props> {
|
||||||
sortDirection={orderBy === column.id ? order : false}
|
sortDirection={orderBy === column.id ? order : false}
|
||||||
>
|
>
|
||||||
{column.static ? (
|
{column.static ? (
|
||||||
<div style={column.style}>
|
<div style={column.style}>{column.label}</div>
|
||||||
{column.label}
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<TableSortLabel
|
<TableSortLabel
|
||||||
active={orderBy === column.id}
|
active={orderBy === column.id}
|
||||||
|
|
|
@ -157,9 +157,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
|
||||||
noBorder,
|
noBorder,
|
||||||
disableLoadingOnEmptyTable,
|
disableLoadingOnEmptyTable,
|
||||||
} = this.props
|
} = this.props
|
||||||
const {
|
const { order, orderBy, page, orderProp, rowsPerPage, fixed } = this.state
|
||||||
order, orderBy, page, orderProp, rowsPerPage, fixed,
|
|
||||||
} = this.state
|
|
||||||
const orderByParam = orderBy || defaultOrderBy
|
const orderByParam = orderBy || defaultOrderBy
|
||||||
const orderParam = order || defaultOrder
|
const orderParam = order || defaultOrder
|
||||||
const displayRows = rowsPerPage || defaultRowsPerPage
|
const displayRows = rowsPerPage || defaultRowsPerPage
|
||||||
|
@ -189,10 +187,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
|
||||||
</Table>
|
</Table>
|
||||||
)}
|
)}
|
||||||
{isEmpty && (
|
{isEmpty && (
|
||||||
<Row
|
<Row className={classes.loader} style={this.getEmptyStyle(emptyRows + 1)}>
|
||||||
className={classes.loader}
|
|
||||||
style={this.getEmptyStyle(emptyRows + 1)}
|
|
||||||
>
|
|
||||||
<CircularProgress size={60} />
|
<CircularProgress size={60} />
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -39,13 +39,14 @@ export const stableSort = (dataArray: List<any>, cmp: any, fixed: boolean): List
|
||||||
return a[1] - b[1]
|
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)
|
return fixedElems.concat(sortedElems)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Order = 'asc' | 'desc'
|
export type Order = 'asc' | 'desc'
|
||||||
|
|
||||||
export const getSorting = (order: Order, orderBy: string, orderProp: boolean) => (order === '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)
|
||||||
: (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 { Field } from 'react-final-form'
|
||||||
import { OnChange } from 'react-final-form-listeners'
|
import { OnChange } from 'react-final-form-listeners'
|
||||||
import TextField from '~/components/forms/TextField'
|
import TextField from '~/components/forms/TextField'
|
||||||
import {
|
import { composeValidators, required, mustBeEthereumAddress } from '~/components/forms/validator'
|
||||||
composeValidators,
|
|
||||||
required,
|
|
||||||
mustBeEthereumAddress,
|
|
||||||
} from '~/components/forms/validator'
|
|
||||||
import { getAddressFromENS } from '~/logic/wallets/getWeb3'
|
import { getAddressFromENS } from '~/logic/wallets/getWeb3'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -23,7 +19,7 @@ type Props = {
|
||||||
disabled?: boolean,
|
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
|
// an idea for second field was taken from here
|
||||||
// https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js
|
// https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js
|
||||||
|
@ -45,11 +41,7 @@ const AddressInput = ({
|
||||||
name={name}
|
name={name}
|
||||||
component={TextField}
|
component={TextField}
|
||||||
type="text"
|
type="text"
|
||||||
validate={composeValidators(
|
validate={composeValidators(required, mustBeEthereumAddress, ...validators)}
|
||||||
required,
|
|
||||||
mustBeEthereumAddress,
|
|
||||||
...validators,
|
|
||||||
)}
|
|
||||||
inputAdornment={inputAdornment}
|
inputAdornment={inputAdornment}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
text={text}
|
text={text}
|
||||||
|
@ -59,7 +51,7 @@ const AddressInput = ({
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<OnChange name={name}>
|
<OnChange name={name}>
|
||||||
{async (value) => {
|
{async value => {
|
||||||
if (isValidEnsName(value)) {
|
if (isValidEnsName(value)) {
|
||||||
try {
|
try {
|
||||||
const resolverAddr = await getAddressFromENS(value)
|
const resolverAddr = await getAddressFromENS(value)
|
||||||
|
|
|
@ -5,22 +5,11 @@ import Checkbox, { type CheckoxProps } from '@material-ui/core/Checkbox'
|
||||||
class GnoCheckbox extends React.PureComponent<CheckoxProps> {
|
class GnoCheckbox extends React.PureComponent<CheckoxProps> {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
input: {
|
input: { checked, name, onChange, ...restInput },
|
||||||
checked, name, onChange, ...restInput
|
|
||||||
},
|
|
||||||
meta,
|
|
||||||
...rest
|
...rest
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
return (
|
return <Checkbox {...rest} name={name} inputProps={restInput} onChange={onChange} checked={!!checked} />
|
||||||
<Checkbox
|
|
||||||
{...rest}
|
|
||||||
name={name}
|
|
||||||
inputProps={restInput}
|
|
||||||
onChange={onChange}
|
|
||||||
checked={!!checked}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Form } from 'react-final-form'
|
||||||
export type OnSubmit = (
|
export type OnSubmit = (
|
||||||
values: Object,
|
values: Object,
|
||||||
form: FormApi,
|
form: FormApi,
|
||||||
callback: ?(errors: ?Object) => ?Object
|
callback: ?(errors: ?Object) => ?Object,
|
||||||
) => ?Object | Promise<?Object> | void
|
) => ?Object | Promise<?Object> | void
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -25,9 +25,7 @@ const stylesBasedOn = (padding: number): $Shape<CSSStyleDeclaration> => ({
|
||||||
flex: '1 0 auto',
|
flex: '1 0 auto',
|
||||||
})
|
})
|
||||||
|
|
||||||
const GnoForm = ({
|
const GnoForm = ({ onSubmit, validation, initialValues, children, padding = 0, formMutators, testId = '' }: Props) => (
|
||||||
onSubmit, validation, initialValues, children, padding = 0, formMutators, testId = '',
|
|
||||||
}: Props) => (
|
|
||||||
<Form
|
<Form
|
||||||
validate={validation}
|
validate={validation}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
|
|
|
@ -10,9 +10,7 @@ const style = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const SelectInput = ({
|
const SelectInput = ({
|
||||||
input: {
|
input: { name, value, onChange, ...restInput },
|
||||||
name, value, onChange, ...restInput
|
|
||||||
},
|
|
||||||
meta,
|
meta,
|
||||||
label,
|
label,
|
||||||
formControlProps,
|
formControlProps,
|
||||||
|
|
|
@ -21,11 +21,8 @@ const styles = () => ({
|
||||||
class TextField extends React.PureComponent<TextFieldProps> {
|
class TextField extends React.PureComponent<TextFieldProps> {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
input: {
|
input: { name, onChange, value, ...restInput },
|
||||||
name, onChange, value, ...restInput
|
|
||||||
},
|
|
||||||
meta,
|
meta,
|
||||||
render,
|
|
||||||
text,
|
text,
|
||||||
inputAdornment,
|
inputAdornment,
|
||||||
classes,
|
classes,
|
||||||
|
|
|
@ -23,13 +23,7 @@ const styles = () => ({
|
||||||
})
|
})
|
||||||
|
|
||||||
const TextareaField = ({ classes, ...props }: TextFieldProps) => (
|
const TextareaField = ({ classes, ...props }: TextFieldProps) => (
|
||||||
<Field
|
<Field {...props} component={TextField} multiline rows="5" className={classes.textarea} />
|
||||||
{...props}
|
|
||||||
component={TextField}
|
|
||||||
multiline
|
|
||||||
rows="5"
|
|
||||||
className={classes.textarea}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export default withStyles(styles)(TextareaField)
|
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 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)
|
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
|
: 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 ADDRESS_REPEATED_ERROR = 'Address already introduced'
|
||||||
|
|
||||||
export const uniqueAddress = (addresses: string[] | List<string>) => simpleMemoize((value: string) => {
|
export const uniqueAddress = (addresses: string[] | List<string>) =>
|
||||||
const addressAlreadyExists = addresses.some((address) => sameAddress(value, address))
|
simpleMemoize((value: string) => {
|
||||||
|
const addressAlreadyExists = addresses.some(address => sameAddress(value, address))
|
||||||
return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined
|
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) => {
|
export const inLimit = (limit: number, base: number, baseText: string, symbol: string = 'ETH') => (value: string) => {
|
||||||
const amount = Number(value)
|
const amount = Number(value)
|
||||||
|
|
|
@ -7,10 +7,7 @@ export type Open = {
|
||||||
clickAway: () => void,
|
clickAway: () => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withStateHandlers(
|
export default withStateHandlers(() => ({ open: false }), {
|
||||||
() => ({ open: false }),
|
|
||||||
{
|
|
||||||
toggle: ({ open }) => () => ({ open: !open }),
|
toggle: ({ open }) => () => ({ open: !open }),
|
||||||
clickAway: () => () => ({ open: false }),
|
clickAway: () => () => ({ open: false }),
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
|
@ -19,9 +19,7 @@ type Props = {
|
||||||
|
|
||||||
class Block extends PureComponent<Props> {
|
class Block extends PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { margin, padding, justify, children, className, ...props } = this.props
|
||||||
margin, padding, justify, children, className, ...props
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
const paddingStyle = padding ? capitalize(padding, 'padding') : undefined
|
const paddingStyle = padding ? capitalize(padding, 'padding') : undefined
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -14,9 +14,7 @@ const calculateStyleBased = (minWidth, minHeight) => ({
|
||||||
minHeight: minHeight && `${minHeight}px`,
|
minHeight: minHeight && `${minHeight}px`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const GnoButton = ({
|
const GnoButton = ({ minWidth, minHeight = 35, testId = '', style = {}, ...props }: Props) => {
|
||||||
minWidth, minHeight = 35, testId = '', style = {}, ...props
|
|
||||||
}: Props) => {
|
|
||||||
const calculatedStyle = calculateStyleBased(minWidth, minHeight)
|
const calculatedStyle = calculateStyleBased(minWidth, minHeight)
|
||||||
|
|
||||||
return <Button style={{ ...calculatedStyle, ...style }} data-testid={testId} {...props} />
|
return <Button style={{ ...calculatedStyle, ...style }} data-testid={testId} {...props} />
|
||||||
|
|
|
@ -23,6 +23,8 @@ const GnoButtonLink = ({
|
||||||
testId = '',
|
testId = '',
|
||||||
className = '',
|
className = '',
|
||||||
...props
|
...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
|
export default GnoButtonLink
|
||||||
|
|
|
@ -18,9 +18,7 @@ type Props = {
|
||||||
style?: Object,
|
style?: Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Hairline = ({
|
const Hairline = ({ margin, color, style, className }: Props) => {
|
||||||
margin, color, style, className,
|
|
||||||
}: Props) => {
|
|
||||||
const calculatedStyles = calculateStyleFrom(color, margin)
|
const calculatedStyles = calculateStyleFrom(color, margin)
|
||||||
const mergedStyles = { ...calculatedStyles, ...(style || {}) }
|
const mergedStyles = { ...calculatedStyles, ...(style || {}) }
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,7 @@ type Props = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Heading = (props: Props) => {
|
const Heading = (props: Props) => {
|
||||||
const {
|
const { align, tag, truncate, margin, color, children, testId, className = '', ...rest } = props
|
||||||
align, tag, truncate, margin, color, children, testId, className = '', ...rest
|
|
||||||
} = props
|
|
||||||
|
|
||||||
const classes = cx(className, 'heading', align, tag, margin ? capitalize(margin, 'margin') : undefined, color, {
|
const classes = cx(className, 'heading', align, tag, margin ? capitalize(margin, 'margin') : undefined, color, {
|
||||||
truncate,
|
truncate,
|
||||||
|
|
|
@ -14,9 +14,7 @@ type Props = {
|
||||||
testId?: string,
|
testId?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Img = ({
|
const Img = ({ fullwidth, alt, bordered, className, style, testId = '', ...props }: Props) => {
|
||||||
fullwidth, alt, bordered, className, style, testId = '', ...props
|
|
||||||
}: Props) => {
|
|
||||||
const classes = cx(styles.img, { fullwidth, bordered }, className)
|
const classes = cx(styles.img, { fullwidth, bordered }, className)
|
||||||
|
|
||||||
return <img alt={alt} style={style} className={classes} data-testid={testId} {...props} />
|
return <img alt={alt} style={style} className={classes} data-testid={testId} {...props} />
|
||||||
|
|
|
@ -16,9 +16,7 @@ type Props = {
|
||||||
innerRef?: React.ElementRef<any>,
|
innerRef?: React.ElementRef<any>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const GnosisLink = ({
|
const GnosisLink = ({ to, children, color, className, padding, innerRef, ...props }: Props) => {
|
||||||
to, children, color, className, padding, innerRef, ...props
|
|
||||||
}: Props) => {
|
|
||||||
const internal = /^\/(?!\/)/.test(to)
|
const internal = /^\/(?!\/)/.test(to)
|
||||||
const classes = cx(styles.link, color || 'regular', padding ? capitalize(padding, 'padding') : undefined, className)
|
const classes = cx(styles.link, color || 'regular', padding ? capitalize(padding, 'padding') : undefined, className)
|
||||||
const LinkElement = internal ? Link : 'a'
|
const LinkElement = internal ? Link : 'a'
|
||||||
|
@ -29,13 +27,7 @@ const GnosisLink = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LinkElement
|
<LinkElement className={classes} href={internal ? null : to} to={internal ? to : null} {...refs} {...props}>
|
||||||
className={classes}
|
|
||||||
href={internal ? null : to}
|
|
||||||
to={internal ? to : null}
|
|
||||||
{...refs}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</LinkElement>
|
</LinkElement>
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,13 +8,11 @@ const cx = classNames.bind(styles)
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
align?: 'center',
|
align?: 'center',
|
||||||
overflow?: boolean
|
overflow?: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Page = ({ children, align, overflow }: Props) => (
|
const Page = ({ children, align, overflow }: Props) => (
|
||||||
<main className={cx(styles.page, align, { overflow })}>
|
<main className={cx(styles.page, align, { overflow })}>{children}</main>
|
||||||
{children}
|
|
||||||
</main>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export default Page
|
export default Page
|
||||||
|
|
|
@ -78,7 +78,7 @@ const PageFrame = ({ children, classes, currentNetwork }: Props) => {
|
||||||
|
|
||||||
export default withStyles(notificationStyles)(
|
export default withStyles(notificationStyles)(
|
||||||
connect(
|
connect(
|
||||||
(state) => ({
|
state => ({
|
||||||
currentNetwork: networkSelector(state),
|
currentNetwork: networkSelector(state),
|
||||||
}),
|
}),
|
||||||
null,
|
null,
|
||||||
|
|
|
@ -20,9 +20,7 @@ type Props = {
|
||||||
|
|
||||||
class Paragraph extends React.PureComponent<Props> {
|
class Paragraph extends React.PureComponent<Props> {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { weight, children, color, align, size, transform, noMargin, className, dot, ...props } = this.props
|
||||||
weight, children, color, align, size, transform, noMargin, className, dot, ...props
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
|
|
|
@ -15,9 +15,7 @@ type Props = {
|
||||||
testId?: string,
|
testId?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Row = ({
|
const Row = ({ children, className, margin, align, grow, testId = '', ...props }: Props) => {
|
||||||
children, className, margin, align, grow, testId = '', ...props
|
|
||||||
}: Props) => {
|
|
||||||
const rowClassNames = cx(
|
const rowClassNames = cx(
|
||||||
styles.row,
|
styles.row,
|
||||||
margin ? capitalize(margin, 'margin') : undefined,
|
margin ? capitalize(margin, 'margin') : undefined,
|
||||||
|
|
|
@ -6,13 +6,11 @@ import TableCell from '@material-ui/core/TableCell'
|
||||||
import TableHead from '@material-ui/core/TableHead'
|
import TableHead from '@material-ui/core/TableHead'
|
||||||
import TableRow from '@material-ui/core/TableRow'
|
import TableRow from '@material-ui/core/TableRow'
|
||||||
|
|
||||||
export {
|
export { TableBody, TableCell, TableHead, TableRow }
|
||||||
TableBody, TableCell, TableHead, TableRow,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
size?: number
|
size?: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildWidthFrom = (size: number) => ({
|
const buildWidthFrom = (size: number) => ({
|
||||||
|
@ -29,9 +27,7 @@ const GnoTable = ({ size, children }: Props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={overflowStyle}>
|
<div style={overflowStyle}>
|
||||||
<Table style={style}>
|
<Table style={style}>{children}</Table>
|
||||||
{children}
|
|
||||||
</Table>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,4 +75,4 @@ export const getIntercomId = () =>
|
||||||
|
|
||||||
export const getExchangeRatesUrl = () => 'https://api.exchangeratesapi.io/latest'
|
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'
|
import type { RecordOf } from 'immutable'
|
||||||
|
|
||||||
export type AddressBookEntry = {
|
export type AddressBookEntry = {
|
||||||
address: string;
|
address: string,
|
||||||
name: string;
|
name: string,
|
||||||
isOwner: boolean;
|
isOwner: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressBookProps = {
|
export type AddressBookProps = {
|
||||||
addressBook: Map<string, AddressBookEntry>
|
addressBook: Map<string, AddressBookEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AddressBook = RecordOf<AddressBookProps>
|
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 ADD_ENTRY = 'ADD_ENTRY'
|
||||||
|
|
||||||
|
export const addAddressBookEntry = createAction<string, *, *>(
|
||||||
export const addAddressBookEntry = createAction<string, *, *>(ADD_ENTRY, (entry: AddressBookEntryType): AddressBookEntryType => ({
|
ADD_ENTRY,
|
||||||
|
(entry: AddressBookEntryType): AddressBookEntryType => ({
|
||||||
entry,
|
entry,
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
|
|
|
@ -4,4 +4,6 @@ import type { AddressBook } from '~/logic/addressBook/model/addressBook'
|
||||||
|
|
||||||
export const LOAD_ADDRESS_BOOK = 'LOAD_ADDRESS_BOOK'
|
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
|
// 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 safes = safesListSelector(state)
|
||||||
const adbkEntries = addressBook.keySeq().toArray()
|
const adbkEntries = addressBook.keySeq().toArray()
|
||||||
safes.forEach((safe) => {
|
safes.forEach(safe => {
|
||||||
const { address } = safe
|
const { address } = safe
|
||||||
const found = adbkEntries.includes(address)
|
const found = adbkEntries.includes(address)
|
||||||
if (!found) {
|
if (!found) {
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { createAction } from 'redux-actions'
|
||||||
|
|
||||||
export const REMOVE_ENTRY = 'REMOVE_ENTRY'
|
export const REMOVE_ENTRY = 'REMOVE_ENTRY'
|
||||||
|
|
||||||
|
|
||||||
export const removeAddressBookEntry = createAction<string, *, *>(REMOVE_ENTRY, (entryAddress: string): void => ({
|
export const removeAddressBookEntry = createAction<string, *, *>(REMOVE_ENTRY, (entryAddress: string): void => ({
|
||||||
entryAddress,
|
entryAddress,
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -4,7 +4,9 @@ import type { AddressBookEntry } from '~/logic/addressBook/model/addressBook'
|
||||||
|
|
||||||
export const UPDATE_ENTRY = 'UPDATE_ENTRY'
|
export const UPDATE_ENTRY = 'UPDATE_ENTRY'
|
||||||
|
|
||||||
|
export const updateAddressBookEntry = createAction<string, *, *>(
|
||||||
export const updateAddressBookEntry = createAction<string, *, *>(UPDATE_ENTRY, (entry: AddressBookEntry): AddressBookEntry => ({
|
UPDATE_ENTRY,
|
||||||
|
(entry: AddressBookEntry): AddressBookEntry => ({
|
||||||
entry,
|
entry,
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
|
|
|
@ -11,12 +11,7 @@ import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar
|
||||||
import { saveAddressBook } from '~/logic/addressBook/utils'
|
import { saveAddressBook } from '~/logic/addressBook/utils'
|
||||||
import type { AddressBookProps } from '~/logic/addressBook/model/addressBook'
|
import type { AddressBookProps } from '~/logic/addressBook/model/addressBook'
|
||||||
|
|
||||||
const watchedActions = [
|
const watchedActions = [ADD_ENTRY, REMOVE_ENTRY, UPDATE_ENTRY]
|
||||||
ADD_ENTRY,
|
|
||||||
REMOVE_ENTRY,
|
|
||||||
UPDATE_ENTRY,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
const addressBookMiddleware = (store: Store<GlobalState>) => (next: Function) => async (action: AnyAction) => {
|
const addressBookMiddleware = (store: Store<GlobalState>) => (next: Function) => async (action: AnyAction) => {
|
||||||
const handledAction = next(action)
|
const handledAction = next(action)
|
||||||
|
|
|
@ -24,7 +24,6 @@ export const buildAddressBook = (storedAdbk: AddressBook): AddressBookProps => {
|
||||||
return addressBookBuilt
|
return addressBookBuilt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default handleActions<State, *>(
|
export default handleActions<State, *>(
|
||||||
{
|
{
|
||||||
[LOAD_ADDRESS_BOOK]: (state: State, action: ActionType<Function>): State => {
|
[LOAD_ADDRESS_BOOK]: (state: State, action: ActionType<Function>): State => {
|
||||||
|
@ -44,12 +43,11 @@ export default handleActions<State, *>(
|
||||||
const { entry } = action.payload
|
const { entry } = action.payload
|
||||||
|
|
||||||
// Adds the entry to all the safes (if it does not already exists)
|
// 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')
|
const adbkMap = map.get('addressBook')
|
||||||
|
|
||||||
if (adbkMap) {
|
if (adbkMap) {
|
||||||
adbkMap.keySeq()
|
adbkMap.keySeq().forEach(safeAddress => {
|
||||||
.forEach((safeAddress) => {
|
|
||||||
const safeAddressBook = state.getIn(['addressBook', safeAddress])
|
const safeAddressBook = state.getIn(['addressBook', safeAddress])
|
||||||
|
|
||||||
if (safeAddressBook) {
|
if (safeAddressBook) {
|
||||||
|
@ -69,13 +67,13 @@ export default handleActions<State, *>(
|
||||||
const { entry } = action.payload
|
const { entry } = action.payload
|
||||||
|
|
||||||
// Updates the entry from all the safes
|
// Updates the entry from all the safes
|
||||||
const newState = state.withMutations((map) => {
|
const newState = state.withMutations(map => {
|
||||||
map
|
map
|
||||||
.get('addressBook')
|
.get('addressBook')
|
||||||
.keySeq()
|
.keySeq()
|
||||||
.forEach((safeAddress) => {
|
.forEach(safeAddress => {
|
||||||
const entriesList: List<AddressBookEntry> = state.getIn(['addressBook', 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)
|
const updatedEntriesList = entriesList.set(entryIndex, entry)
|
||||||
map.setIn(['addressBook', safeAddress], updatedEntriesList)
|
map.setIn(['addressBook', safeAddress], updatedEntriesList)
|
||||||
})
|
})
|
||||||
|
@ -86,13 +84,13 @@ export default handleActions<State, *>(
|
||||||
[REMOVE_ENTRY]: (state: State, action: ActionType<Function>): State => {
|
[REMOVE_ENTRY]: (state: State, action: ActionType<Function>): State => {
|
||||||
const { entryAddress } = action.payload
|
const { entryAddress } = action.payload
|
||||||
// Removes the entry from all the safes
|
// Removes the entry from all the safes
|
||||||
const newState = state.withMutations((map) => {
|
const newState = state.withMutations(map => {
|
||||||
map
|
map
|
||||||
.get('addressBook')
|
.get('addressBook')
|
||||||
.keySeq()
|
.keySeq()
|
||||||
.forEach((safeAddress) => {
|
.forEach(safeAddress => {
|
||||||
const entriesList = state.getIn(['addressBook', 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)
|
const updatedEntriesList = entriesList.remove(entryIndex)
|
||||||
map.setIn(['addressBook', safeAddress], updatedEntriesList)
|
map.setIn(['addressBook', safeAddress], updatedEntriesList)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable import/named */
|
||||||
// @flow
|
// @flow
|
||||||
import { List, Map } from 'immutable'
|
import { List, Map } from 'immutable'
|
||||||
import { createSelector, Selector } from 'reselect'
|
import { createSelector, Selector } from 'reselect'
|
||||||
|
@ -7,7 +8,8 @@ import type { GlobalState } from '~/store'
|
||||||
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
|
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
|
||||||
import { safeParamAddressFromStateSelector } from '~/routes/safe/store/selectors'
|
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(
|
export const getAddressBook: Selector<GlobalState, AddressBook> = createSelector(
|
||||||
addressBookMapSelector,
|
addressBookMapSelector,
|
||||||
|
@ -38,7 +40,7 @@ export const getNameFromAddressBook = (userAddress: string): string | null => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const addressBook = useSelector(getAddressBook)
|
const addressBook = useSelector(getAddressBook)
|
||||||
const result = addressBook.filter((addressBookItem) => addressBookItem.address === userAddress)
|
const result = addressBook.filter(addressBookItem => addressBookItem.address === userAddress)
|
||||||
if (result.size > 0) {
|
if (result.size > 0) {
|
||||||
return result.get(0).name
|
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 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) {
|
if (entry) {
|
||||||
return entry.name
|
return entry.name
|
||||||
}
|
}
|
||||||
|
@ -39,12 +40,11 @@ export const getNameFromAddressBook = (userAddress: string): string | null => {
|
||||||
return getNameFromAdbk(addressBook, userAddress)
|
return getNameFromAdbk(addressBook, userAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getOwnersWithNameFromAddressBook = (addressBook: AddressBook, ownerList: List<Owner>) => {
|
export const getOwnersWithNameFromAddressBook = (addressBook: AddressBook, ownerList: List<Owner>) => {
|
||||||
if (!ownerList) {
|
if (!ownerList) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
const ownersListWithAdbkNames = ownerList.map((owner) => {
|
const ownersListWithAdbkNames = ownerList.map(owner => {
|
||||||
const ownerName = getNameFromAdbk(addressBook, owner.address)
|
const ownerName = getNameFromAdbk(addressBook, owner.address)
|
||||||
return {
|
return {
|
||||||
address: owner.address,
|
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
|
// @flow
|
||||||
import contract from 'truffle-contract'
|
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 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 { ensureOnce } from '~/utils/singleton'
|
||||||
import { simpleMemoize } from '~/components/forms/validator'
|
import { simpleMemoize } from '~/components/forms/validator'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
||||||
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
|
||||||
|
import { isProxyCode } from '~/logic/contracts/historicProxyCode'
|
||||||
|
|
||||||
export const SENTINEL_ADDRESS = '0x0000000000000000000000000000000000000001'
|
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 proxyFactoryMaster
|
||||||
let safeMaster
|
let safeMaster
|
||||||
|
@ -66,7 +72,7 @@ export const getSafeMasterContract = async () => {
|
||||||
|
|
||||||
export const deploySafeContract = async (safeAccounts: string[], numConfirmations: number, userAccount: string) => {
|
export const deploySafeContract = async (safeAccounts: string[], numConfirmations: number, userAccount: string) => {
|
||||||
const gnosisSafeData = await safeMaster.contract.methods
|
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()
|
.encodeABI()
|
||||||
const proxyFactoryData = proxyFactoryMaster.contract.methods
|
const proxyFactoryData = proxyFactoryMaster.contract.methods
|
||||||
.createProxy(safeMaster.address, gnosisSafeData)
|
.createProxy(safeMaster.address, gnosisSafeData)
|
||||||
|
@ -88,7 +94,7 @@ export const estimateGasForDeployingSafe = async (
|
||||||
userAccount: string,
|
userAccount: string,
|
||||||
) => {
|
) => {
|
||||||
const gnosisSafeData = await safeMaster.contract.methods
|
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()
|
.encodeABI()
|
||||||
const proxyFactoryData = proxyFactoryMaster.contract.methods
|
const proxyFactoryData = proxyFactoryMaster.contract.methods
|
||||||
.createProxy(safeMaster.address, gnosisSafeData)
|
.createProxy(safeMaster.address, gnosisSafeData)
|
||||||
|
@ -126,7 +132,49 @@ export const validateProxy = async (safeAddress: string): Promise<boolean> => {
|
||||||
return true
|
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 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 { getExchangeRatesUrl } from '~/config'
|
||||||
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
|
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
|
let rate = 0
|
||||||
const url = `${getExchangeRatesUrl()}?base=${baseCurrency}&symbols=${targetCurrencyValue}`
|
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 { currencyValuesListSelector } from '~/logic/currencyValues/store/selectors'
|
||||||
import { setCurrencyBalances } from '~/logic/currencyValues/store/actions/setCurrencyBalances'
|
import { setCurrencyBalances } from '~/logic/currencyValues/store/actions/setCurrencyBalances'
|
||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// 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 state = getState()
|
||||||
const currencyBalancesList = currencyValuesListSelector(state)
|
const currencyBalancesList = currencyValuesListSelector(state)
|
||||||
const selectedCurrencyRateInBaseCurrency = await fetchCurrenciesRates(AVAILABLE_CURRENCIES.USD, currencyValueSelected)
|
const selectedCurrencyRateInBaseCurrency = await fetchCurrenciesRates(AVAILABLE_CURRENCIES.USD, currencyValueSelected)
|
||||||
|
|
||||||
|
|
||||||
const newList = []
|
const newList = []
|
||||||
for (const currencyValue of currencyBalancesList) {
|
for (const currencyValue of currencyBalancesList) {
|
||||||
const { balanceInBaseCurrency } = currencyValue
|
const { balanceInBaseCurrency } = currencyValue
|
||||||
|
|
|
@ -10,13 +10,15 @@ import { loadFromStorage } from '~/utils/storage'
|
||||||
import fetchCurrencySelectedValue from '~/logic/currencyValues/store/actions/fetchCurrencySelectedValue'
|
import fetchCurrencySelectedValue from '~/logic/currencyValues/store/actions/fetchCurrencySelectedValue'
|
||||||
import { CURRENCY_SELECTED_KEY } from '~/logic/currencyValues/store/actions/saveCurrencySelected'
|
import { CURRENCY_SELECTED_KEY } from '~/logic/currencyValues/store/actions/saveCurrencySelected'
|
||||||
|
|
||||||
|
|
||||||
export const fetchCurrencyValues = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
export const fetchCurrencyValues = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||||
try {
|
try {
|
||||||
const tokensFetched = await fetchTokenCurrenciesBalances(safeAddress)
|
const tokensFetched = await fetchTokenCurrenciesBalances(safeAddress)
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
const currencyList = List(tokensFetched.data.filter((currencyBalance) => currencyBalance.balanceUsd).map((currencyBalance) => {
|
const currencyList = List(
|
||||||
|
tokensFetched.data
|
||||||
|
.filter(currencyBalance => currencyBalance.balanceUsd)
|
||||||
|
.map(currencyBalance => {
|
||||||
const { balanceUsd, tokenAddress } = currencyBalance
|
const { balanceUsd, tokenAddress } = currencyBalance
|
||||||
return makeBalanceCurrency({
|
return makeBalanceCurrency({
|
||||||
currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : null,
|
currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : null,
|
||||||
|
@ -24,7 +26,8 @@ export const fetchCurrencyValues = (safeAddress: string) => async (dispatch: Red
|
||||||
balanceInBaseCurrency: balanceUsd,
|
balanceInBaseCurrency: balanceUsd,
|
||||||
balanceInSelectedCurrency: balanceUsd,
|
balanceInSelectedCurrency: balanceUsd,
|
||||||
})
|
})
|
||||||
}))
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
dispatch(setCurrencyBalances(currencyList))
|
dispatch(setCurrencyBalances(currencyList))
|
||||||
const currencyStored = await loadFromStorage(CURRENCY_SELECTED_KEY)
|
const currencyStored = await loadFromStorage(CURRENCY_SELECTED_KEY)
|
||||||
|
|
|
@ -7,10 +7,11 @@ import type { GlobalState } from '~/store'
|
||||||
import { saveToStorage } from '~/utils/storage'
|
import { saveToStorage } from '~/utils/storage'
|
||||||
import { setCurrencySelected } from '~/logic/currencyValues/store/actions/setCurrencySelected'
|
import { setCurrencySelected } from '~/logic/currencyValues/store/actions/setCurrencySelected'
|
||||||
|
|
||||||
|
|
||||||
export const CURRENCY_SELECTED_KEY = 'CURRENCY_SELECTED_KEY'
|
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 })
|
await saveToStorage(CURRENCY_SELECTED_KEY, { currencyValueSelected: currencySelected })
|
||||||
dispatch(setCurrencySelected(currencySelected))
|
dispatch(setCurrencySelected(currencySelected))
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ import type { CurrencyValues, CurrencyValuesProps } from '~/logic/currencyValues
|
||||||
|
|
||||||
export const SET_CURRENCY_BALANCES = 'SET_CURRENCY_BALANCES'
|
export const SET_CURRENCY_BALANCES = 'SET_CURRENCY_BALANCES'
|
||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// 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
|
// @flow
|
||||||
import { createAction } from 'redux-actions'
|
import { createAction } from 'redux-actions'
|
||||||
import type {
|
import type { CurrencyValuesProps } from '~/logic/currencyValues/store/model/currencyValues'
|
||||||
CurrencyValuesProps,
|
|
||||||
} from '~/logic/currencyValues/store/model/currencyValues'
|
|
||||||
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
|
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
|
||||||
|
|
||||||
|
|
||||||
export const SET_CURRENT_CURRENCY = 'SET_CURRENT_CURRENCY'
|
export const SET_CURRENT_CURRENCY = 'SET_CURRENT_CURRENCY'
|
||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
// 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',
|
MYR: 'MYR',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type BalanceCurrencyType = {
|
export type BalanceCurrencyType = {
|
||||||
currencyName: AVAILABLE_CURRENCIES;
|
currencyName: AVAILABLE_CURRENCIES,
|
||||||
tokenAddress: string,
|
tokenAddress: string,
|
||||||
balanceInBaseCurrency: string,
|
balanceInBaseCurrency: string,
|
||||||
balanceInSelectedCurrency: string,
|
balanceInSelectedCurrency: string,
|
||||||
|
@ -54,8 +53,8 @@ export const makeBalanceCurrency = Record({
|
||||||
})
|
})
|
||||||
|
|
||||||
export type CurrencyValuesProps = {
|
export type CurrencyValuesProps = {
|
||||||
currencyValueSelected: AVAILABLE_CURRENCIES;
|
currencyValueSelected: AVAILABLE_CURRENCIES,
|
||||||
currencyValuesList: BalanceCurrencyType[]
|
currencyValuesList: BalanceCurrencyType[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CurrencyValues = RecordOf<CurrencyValuesProps>
|
export type CurrencyValues = RecordOf<CurrencyValuesProps>
|
||||||
|
|
|
@ -4,6 +4,6 @@ import { List } from 'immutable'
|
||||||
import { type GlobalState } from '~/store'
|
import { type GlobalState } from '~/store'
|
||||||
import { CURRENCY_VALUES_KEY } from '~/logic/currencyValues/store/reducer/currencyValues'
|
import { CURRENCY_VALUES_KEY } from '~/logic/currencyValues/store/reducer/currencyValues'
|
||||||
|
|
||||||
|
export const currencyValuesListSelector = (state: GlobalState) =>
|
||||||
export const currencyValuesListSelector = (state: GlobalState) => (state[CURRENCY_VALUES_KEY].get('currencyBalances') ? state[CURRENCY_VALUES_KEY].get('currencyBalances') : List([]))
|
state[CURRENCY_VALUES_KEY].get('currencyBalances') ? state[CURRENCY_VALUES_KEY].get('currencyBalances') : List([])
|
||||||
export const currentCurrencySelector = (state: GlobalState) => state[CURRENCY_VALUES_KEY].get('currencyValueSelected')
|
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