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:
fernandomg 2020-02-19 14:15:17 -03:00
commit c97cf75ba8
257 changed files with 4620 additions and 3758 deletions

View File

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

View File

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

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
10

15
.prettierignore Normal file
View File

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

7
.prettierrc Normal file
View File

@ -0,0 +1,7 @@
{
"tabWidth": 2,
"printWidth": 120,
"trailingComma": "all",
"singleQuote": true,
"semi": false
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
// @flow
export { default as DividerLine } from './DividerLine'
export { default as TextBox } from './TextBox'

View File

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

View File

@ -0,0 +1,2 @@
// @flow
export { default as Loader } from './Loader'

View File

@ -0,0 +1,7 @@
// @flow
export * from './dataDisplay'
export * from './feedback'
export * from './layouts'
export * from './safeUtils'
export * from './surfaces'
export * from './utils'

View File

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

View File

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

View File

@ -0,0 +1,10 @@
// @flow
import * as LayoutComponents from './Layout'
import List from './List'
const ListContentLayout = {
...LayoutComponents,
List,
}
export default ListContentLayout

View File

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

View File

@ -0,0 +1,2 @@
// @flow
export { default as ListContentLayout } from './ListContentLayout'

View File

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

View File

@ -0,0 +1,2 @@
// @flow
export { default as AddressInfo } from './AddressInfo'

View File

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

View File

@ -0,0 +1,2 @@
// @flow
export { default as Collapse } from './Collapse'

View File

@ -0,0 +1,2 @@
// @flow
export * from './modals'

View File

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

View File

@ -0,0 +1,3 @@
// @flow
export { default as GenericModal } from './GenericModal'
export * from './utils'

View File

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

View File

@ -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 &gt; Accept preferences &gt;
</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 &quot;Accept all&quot;, you agree to the storing of cookies on your device for more information. By clicking &quot;Accept all&quot;, 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} />}
/> />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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