Development (#355)

* Adds cookie permissions to localStorage/redux state

* Adds action

* Adds files to git

* (fix) linting issues

* (update) flow-typed

* (update) .eslint and .flowconfig

* (add) cookie banner

* Finish cookie banner implementation

* (Add) checkbox's disabled style.

* Removes redux for cookiesStorage

* Fix cookieStore deletion

* Fixs cookies acceptance

* Fixs cookies banner verbiage
Fix "x" in wrong place for snackbar messages

* (remove) unused library

* Adds cookies utils
Replaces localStorage with cookies
Adds js-cookie

* (fix) added correct polished library and import, updated flow-typed

* (update) removed polish flow type, added js-cookie flow type

* Add link to cookie policy, use generic links for legal docs

* Remove link to cookie policy from sidebar, link cookie policy in the banner

* Let the user re-open the cookie banner

* remove withMutations from cookies reducer, move utils/cookies to logic/cookies

* Now the sidebar closes when the cookie banner is toggled

* Feature #169: Intercom (#301)

* Implements intercom
Adds REACT_APP_INTERCOM_ID_MAINNET and REACT_APP_INTERCOM_ID_RINKEBY env vars

* Adds .env.example

* Adds intercom env vars

* Updates env vars
Replaces "rinkeby" and "mainnet" with "non-production" and "production"

* Now loads intercom after the user accepted the analytics

* Add env variable for production intercom id

* Update .env.example

* Removes react-intercom
Fixs getIntercomId with default dev appID
Now loads intercom as script

* Renegerate flow-types

* Remove 'Hide zero balances' (#310)

* Use medium font size for 'select an asset' label (#312)

* Feature #272: Google Analytics (#299)

* Adds google analytics tracking for every route

* Adds cookies acceptance check before tracking

* Fix react-ga dependency

* Fix cookieStore deletion

* Merge with #189-cookie-banner

* Fixs react ga version
Refactored HOC with hooks

* Fix TYPO

* Fix path for cookies utils

* Fix imports

* remove flow type definition for polish

* Add GA ID log

* Fix load GA After cookies acceptance

* Feature #224: Activate tokens automatically (#300)

* Replace 'Manage Tokens' with 'Manage List'

* prevent 301 redirects

* Add `BLACKLISTED_TOKENS` key to persist through immortal

* Add store/action to extract _activate tokens by its balance_

- keeps already activated tokens
- discards blacklisted tokens
- adds tokens whose vales are bigger than zero and are not blacklisted

* Add `blacklistedTokens` list to safe's store

* Display activeTokensByBalance in 'Balances' screen

* Enable token's blacklisting functionality in Tokens List

* Retrieve balance from API

* Rename action to `activateTokensByBalance`

* Fix linting errors

- line too long
- required return

* Do not persist a separate list into `BLACKLISTED_TOKENS`

* Typo fix (#326)

* Fix security vulnerability: Remove uglifyjs, use terser plugin (#327)

* Remove uglifyjs, use terser plugin

* fix css-loader config

* Feature #256: Sticky header (#308)

* Add sticky header

* Remove react-headroom, set position to fixed for header

* Regenerate yarn lock

* Remove unused headroom style from root.scss

* Pull from dev, conflict fixes

* Update welcome text (#323)

* Feature #137: Tx list improvements (#222)

* Fix swapOwners threshold displayed as hex in tx list

* Refactor spinner in empty table

* Fix number of rows per page in table pagination

* Add use of EtherscanLink component

* Set short version of strings in tx list

* Adjust styles in tx list

* Add more styles to table

* WIP

* An attempt to fix #204 by showing UNKNOWN instead of failed to fetch token symbol

* Table pagination style fixes

* Show confirm transaction button in owner list

* Update dependencies

* Add confirmation icons to owner list in tx list

* exclude unneeded stuff from travis.yml

* Adds cookie permissions to localStorage/redux state

* Update dependencies

* Adds action

* Adds files to git

* (fix) linting issues

* (update) flow-typed

* (update) .eslint and .flowconfig

* (add) cookie banner

* Finish cookie banner implementation

* (Add) checkbox's disabled style.

* Removes redux for cookiesStorage

* Fix cookieStore deletion

* Increase TO_EXP for bignumber.js

* Fixs cookies acceptance

* Fixs cookies banner verbiage
Fix "x" in wrong place for snackbar messages

* (fix) added correct polished library and import, updated flow-typed

* (update) removed polish flow type, added js-cookie flow type

* Add link to cookie policy, use generic links for legal docs

* Remove link to cookie policy from sidebar, link cookie policy in the banner

* Mock Safe creation transaction

* Format code

* Fix break statement

* Remove deployment of storybook

* Let the user re-open the cookie banner

* Update tx status messages and visual confirmation progress

* Fix svg in tx confirmation progress

* Add styles to tx type in tx list

* Replace nonce in tx list with tx id

* Update opacity of cancelled tx

* Fix short version of address

* remove withMutations from cookies reducer, move utils/cookies to logic/cookies

* Now the sidebar closes when the cookie banner is toggled

* Fix styles in tx list

* Add Pending status in tx description

* (remove) unused library

* Adds cookies utils
Replaces localStorage with cookies
Adds js-cookie

* Set 25 rows per page in tx list by default

* Align tx table

* Adjust tx table and tx details borders

* Fix fetching transactions to show Safe creation tx alone

* Fix failed Safe creation transaction

* Add styles to tx data

* Refactor and fix owner list in transaction

* Refactor use of theme variables

* Remove storybook files

* Update dependencies

* Fix warnings

* Fix dependencies

* Update file-loader config

* Fix owner colors in the tx confirmation progress

* Fix transaction type icon height

* Tx list adjustments

* Update readme

* (Feature) Etherscan button icon (#331)

* (add) new open on etherscan button icon

* (remove) unused asset

* (fix) icon background

* Feature #239: Replace early access label with network label (#311)

* Remove early access label

* Revert "Remove early access label"

This reverts commit 34682f0f6d9c1974a6e45c2a31358864931d9c1e.

* Replace early access label with network label

* Capitalzie first letter of the network name

* Adds threshold update on checkAndUpdateSafe (#320)

* Feature #159: Pending transaction that requires user confirmation (#330)

* Creates a new notification: waitingConfirmation
Adds key as optional parameter for notification
Implemented getAwaitingTransactions to get the transactions that needs to be confirmed by the current user
Not fetchTransactions action also dispatch a notification for awaiting transactions
Improved performance of routes/safe/container/index to avoid re-rendering

* Removes notification logic on fetchTransactions
Adds notificationsMiddleware

* Moves fetchTransaction to container

* Removes unused param on fetchTransactions

* Fixs null safe check

* Fixs middleware declaration

* Removes lodash

* Changes cancelled transaction detection logic

* Feature #122: Multisig migration (#315)

* Adds query-string package.json
Parses query string on open layout

* Implements load all the values on openSafe view from param querys

* Adds query params validation

* Moves query parse logic to open.jsx

* Changes default no metamask component on open page

* Replaces global isNaN

* Fix threshold parsing validation

* Updates the welcome component with new verbiage for open

* Renames isOpenSafe to isOldMultisigMigration

* Merge branch 'development' of https://github.com/gnosis/safe-react into 122-multisig-migration

# Conflicts:
#	src/routes/open/components/Layout.jsx

* Merge branch 'development' of https://github.com/gnosis/safe-react into 159-pending-transactions

# Conflicts:
#	src/routes/safe/components/Transactions/index.jsx
#	yarn.lock

* set anonymizeIp to true (#335)

* Feature #180: Predict transaction nonce (#293)

* Dep bump

* Fetch transactions when safe view is mounted

* eslint fix

* Calculate new tx nonce from latest tx in service

* Fix tx cancellation, allow passing nonce to createTransaction

* dep bump

* Refactor createTransaction/processTransaction to use object as argument

* Adopting transactions table to new send tx flow with predicted nonces

* dep bump, disable esModule in file-loader options after new v5 release

* Don't show older tx annotation for already executed txs

* sort tx by nonce

* get new safe nonce after tx execution

* Bugfixes

* remove whitespace for showOlderTxAnnotation

* Feature #329: Rename to Multisig (#334)

* Rename to Multisig

* migration text fix

* replace safe for teams with multisig

* Fixs race condition (#341)

Fixs typo

* (Feature) Incoming transactions (#333)

* Add `blockNumber` to transactions model

* Create `incomingTransaction` node in store and load it along with `transactions`

* Add incoming transfers to the Transactions table

* Rename `transactionHash` to `executionTxHash` for better incoming/outgoing txs unification in Transactions table

* Add incoming transactions details

* Add transaction type icon in table row

* Add snackbar notification for incoming txs

* Make incoming transaction snackbar to show on any tab

* Use makeStyles hooks

* Fix incoming amounts conversion from wei

* Make concurrent promise calls

* Use date to calculate transactions ids

* Prevent repeating messages

- also move logic to display snack bar into the notifications middleware

* Merge transactions and incomingTxs to the transactions selector

* Show 'Multiple incoming transfers' if they are more than 3

* Prevent incoming transactions snack bar for first-timer users

* Set ID as the default order

* Use constant for _incoming_ type

* Feature #154: Fiat Balances (#290)

* Adds DropdownCurrency
Adds redux store for currencyValues
Adds Value column on the assets table
Adds mocked currency values

* (add) base currency dropdown

* (add) dropdown styles

* Refactors data fetching of the balances list
Now uses the endpoint

* Fix column value styling

* Adds support for ECB currency values

* Fixs list overflow

* Changes endpoint url
Adds decimals for balance values

* (fix) remove inline style

* (add) currencies dropdown search field

* (fix) list items' hover color

* Implements filter search

* Fix warning on dropdown template

* Saves selected currency in localStorage

* Remove spaces on curly braces
Add alt
Renames rowItem to cellItem
Improves fetchCurrenciesRates handling

* Removes withMutations

* Removes middleware
Export style to another file for dropdownCurrency

* Adds classNames

* Fix incomming transactions fetching (#346)

* Feature: Activate fortmatic (#339)

* Add fortmatic integration to web3connect

* add fortmatic

* Safe open form improvements: limit calling initContracts to 1 time

* update .env.example

* Feature #336: Confirmation required notification for non-owners fix (#338)

* Refactors grantedSelector with isUserOwner function
Checks if the user is owner of the safe before sending notification

* Adds safeParamAddressFromStateSelector
Refactors notificationsMiddleware with new selector

* Remove old size check

* safe notifications middleware fixes

* add apt-get update to travis yml

* (Fix) Incoming transactions inline-styles (#344)

* Remove inline styles

* Replace ternary with logical && operator

* use cn as shortcut for classnames
This commit is contained in:
Mikhail Mikheev 2019-12-16 17:04:42 +04:00 committed by GitHub
parent 405d8ba336
commit fff057d8e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
168 changed files with 3546 additions and 4535 deletions

View File

@ -9,3 +9,7 @@ REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET=
# For production environments # For production environments
REACT_APP_INTERCOM_ID= REACT_APP_INTERCOM_ID=
REACT_APP_PORTIS_ID=
REACT_APP_SQUARELINK_ID=
REACT_APP_FORTMATIC_KEY=

View File

@ -3,7 +3,6 @@
<PROJECT_ROOT>/contracts/**/.* <PROJECT_ROOT>/contracts/**/.*
<PROJECT_ROOT>/scripts/**/.* <PROJECT_ROOT>/scripts/**/.*
<PROJECT_ROOT>/public/**/.* <PROJECT_ROOT>/public/**/.*
<PROJECT_ROOT>/src/test/**/.*
<PROJECT_ROOT>/babel.config.js <PROJECT_ROOT>/babel.config.js
<PROJECT_ROOT>/jest.config.js <PROJECT_ROOT>/jest.config.js
<PROJECT_ROOT>/truffle.js <PROJECT_ROOT>/truffle.js

1
.gitignore vendored
View File

@ -1,6 +1,5 @@
node_modules/ node_modules/
build_webpack/ build_webpack/
build_storybook/
.DS_Store .DS_Store
build/ build/
yarn-error.log yarn-error.log

View File

@ -1,3 +0,0 @@
import '@storybook/addon-actions/register'
import '@storybook/addon-links/register'
import '@storybook/addon-knobs/register'

View File

@ -1,35 +0,0 @@
import 'babel-polyfill'
import { addDecorator, configure } from '@storybook/react'
import { withKnobs } from '@storybook/addon-knobs'
import { MuiThemeProvider } from '@material-ui/core/styles'
import * as React from 'react'
import { Provider } from 'react-redux'
import StoryRouter from 'storybook-router'
import { store } from '~/store'
import theme from '~/theme/mui'
import 'index.scss'
(function (global) {
//Useful for adding data and libraries to window object.
})(typeof window !== 'undefined' ? window : {});
addDecorator(withKnobs);
addDecorator(StoryRouter())
addDecorator((story) => (
<Provider store={store}>
<MuiThemeProvider theme={theme}>
{ story() }
</MuiThemeProvider>
</Provider>
))
const components = require.context('../src/components', true, /\.stories\.((js|ts)x?)$/)
const routes = require.context('../src/routes', true, /\.stories\.((js|ts)x?)$/)
function loadStories() {
components.keys().forEach((filename) => components(filename))
routes.keys().forEach((filename) => routes(filename))
}
configure(loadStories, module)

View File

@ -1 +0,0 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">

View File

@ -1,13 +0,0 @@
process.env.NODE_ENV = 'development'
const prodConfig = require('../config/webpack.config.dev');
module.exports = function(storybookConfig, configType) {
const config = Object.assign({}, prodConfig);
storybookConfig.module.rules = storybookConfig.module.rules.concat(config.module.rules)
storybookConfig.resolve = config.resolve;
return storybookConfig;
};

View File

@ -21,6 +21,7 @@ matrix:
- REACT_APP_NETWORK='rinkeby' - REACT_APP_NETWORK='rinkeby'
before_install: before_install:
# Needed to deploy pull request and releases # Needed to deploy pull request and releases
- sudo apt-get update
- sudo apt-get -y install python-pip python-dev - sudo apt-get -y install python-pip python-dev
- pip install awscli --upgrade --user - pip install awscli --upgrade --user
# Install truffle # Install truffle

View File

@ -133,6 +133,7 @@ module.exports = {
{ {
loader: 'file-loader', loader: 'file-loader',
options: { options: {
esModule: false,
name: 'img/[hash].[ext]', name: 'img/[hash].[ext]',
esModule: false esModule: false
}, },

View File

@ -1,122 +0,0 @@
// flow-typed signature: 9e597e3161a1342a9e77ec9437783354
// flow-typed version: <<STUB>>/@sambego/storybook-state_v^1.3.6/flow_v0.112.0
/**
* This is an autogenerated libdef stub for:
*
* '@sambego/storybook-state'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module '@sambego/storybook-state' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module '@sambego/storybook-state/dist' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/dist/State' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/dist/StateDecorator' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/dist/Store' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/enzyme.config' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/jest.config' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/src' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/src/State' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/src/StateDecorator' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/src/Store' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/tests/State.test' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/tests/StateDecorator.test' {
declare module.exports: any;
}
declare module '@sambego/storybook-state/tests/Store.test' {
declare module.exports: any;
}
// Filename aliases
declare module '@sambego/storybook-state/dist/index' {
declare module.exports: $Exports<'@sambego/storybook-state/dist'>;
}
declare module '@sambego/storybook-state/dist/index.js' {
declare module.exports: $Exports<'@sambego/storybook-state/dist'>;
}
declare module '@sambego/storybook-state/dist/State.js' {
declare module.exports: $Exports<'@sambego/storybook-state/dist/State'>;
}
declare module '@sambego/storybook-state/dist/StateDecorator.js' {
declare module.exports: $Exports<'@sambego/storybook-state/dist/StateDecorator'>;
}
declare module '@sambego/storybook-state/dist/Store.js' {
declare module.exports: $Exports<'@sambego/storybook-state/dist/Store'>;
}
declare module '@sambego/storybook-state/enzyme.config.js' {
declare module.exports: $Exports<'@sambego/storybook-state/enzyme.config'>;
}
declare module '@sambego/storybook-state/jest.config.js' {
declare module.exports: $Exports<'@sambego/storybook-state/jest.config'>;
}
declare module '@sambego/storybook-state/src/index' {
declare module.exports: $Exports<'@sambego/storybook-state/src'>;
}
declare module '@sambego/storybook-state/src/index.js' {
declare module.exports: $Exports<'@sambego/storybook-state/src'>;
}
declare module '@sambego/storybook-state/src/State.js' {
declare module.exports: $Exports<'@sambego/storybook-state/src/State'>;
}
declare module '@sambego/storybook-state/src/StateDecorator.js' {
declare module.exports: $Exports<'@sambego/storybook-state/src/StateDecorator'>;
}
declare module '@sambego/storybook-state/src/Store.js' {
declare module.exports: $Exports<'@sambego/storybook-state/src/Store'>;
}
declare module '@sambego/storybook-state/tests/State.test.js' {
declare module.exports: $Exports<'@sambego/storybook-state/tests/State.test'>;
}
declare module '@sambego/storybook-state/tests/StateDecorator.test.js' {
declare module.exports: $Exports<'@sambego/storybook-state/tests/StateDecorator.test'>;
}
declare module '@sambego/storybook-state/tests/Store.test.js' {
declare module.exports: $Exports<'@sambego/storybook-state/tests/Store.test'>;
}

View File

@ -1,187 +0,0 @@
// flow-typed signature: 64c369e4ed0c4a705b2f36b522874cac
// flow-typed version: <<STUB>>/@storybook/addon-actions_v5.2.6/flow_v0.112.0
/**
* This is an autogenerated libdef stub for:
*
* '@storybook/addon-actions'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module '@storybook/addon-actions' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module '@storybook/addon-actions/dist/components/ActionLogger' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/components/ActionLogger/style' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/constants' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/containers/ActionLogger' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/manager' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/models/ActionDisplay' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/models/ActionOptions' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/models/ActionsFunction' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/models/ActionsMap' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/models/DecoratorFunction' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/models/HandlerFunction' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/models' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/preview/action' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/preview/actions' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/preview/configureActions' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/preview/decorateAction' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/preview' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/preview/withActions' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/dist/typings.d' {
declare module.exports: any;
}
declare module '@storybook/addon-actions/register' {
declare module.exports: any;
}
// Filename aliases
declare module '@storybook/addon-actions/dist/components/ActionLogger/index' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/components/ActionLogger'>;
}
declare module '@storybook/addon-actions/dist/components/ActionLogger/index.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/components/ActionLogger'>;
}
declare module '@storybook/addon-actions/dist/components/ActionLogger/style.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/components/ActionLogger/style'>;
}
declare module '@storybook/addon-actions/dist/constants.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/constants'>;
}
declare module '@storybook/addon-actions/dist/containers/ActionLogger/index' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/containers/ActionLogger'>;
}
declare module '@storybook/addon-actions/dist/containers/ActionLogger/index.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/containers/ActionLogger'>;
}
declare module '@storybook/addon-actions/dist/index' {
declare module.exports: $Exports<'@storybook/addon-actions/dist'>;
}
declare module '@storybook/addon-actions/dist/index.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist'>;
}
declare module '@storybook/addon-actions/dist/manager.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/manager'>;
}
declare module '@storybook/addon-actions/dist/models/ActionDisplay.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/models/ActionDisplay'>;
}
declare module '@storybook/addon-actions/dist/models/ActionOptions.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/models/ActionOptions'>;
}
declare module '@storybook/addon-actions/dist/models/ActionsFunction.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/models/ActionsFunction'>;
}
declare module '@storybook/addon-actions/dist/models/ActionsMap.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/models/ActionsMap'>;
}
declare module '@storybook/addon-actions/dist/models/DecoratorFunction.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/models/DecoratorFunction'>;
}
declare module '@storybook/addon-actions/dist/models/HandlerFunction.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/models/HandlerFunction'>;
}
declare module '@storybook/addon-actions/dist/models/index' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/models'>;
}
declare module '@storybook/addon-actions/dist/models/index.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/models'>;
}
declare module '@storybook/addon-actions/dist/preview/action.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/preview/action'>;
}
declare module '@storybook/addon-actions/dist/preview/actions.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/preview/actions'>;
}
declare module '@storybook/addon-actions/dist/preview/configureActions.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/preview/configureActions'>;
}
declare module '@storybook/addon-actions/dist/preview/decorateAction.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/preview/decorateAction'>;
}
declare module '@storybook/addon-actions/dist/preview/index' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/preview'>;
}
declare module '@storybook/addon-actions/dist/preview/index.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/preview'>;
}
declare module '@storybook/addon-actions/dist/preview/withActions.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/preview/withActions'>;
}
declare module '@storybook/addon-actions/dist/typings.d.js' {
declare module.exports: $Exports<'@storybook/addon-actions/dist/typings.d'>;
}
declare module '@storybook/addon-actions/register.js' {
declare module.exports: $Exports<'@storybook/addon-actions/register'>;
}

View File

@ -1,283 +0,0 @@
// flow-typed signature: 90624ef390fe4c5befd27eedd08d7ad2
// flow-typed version: <<STUB>>/@storybook/addon-knobs_v5.2.6/flow_v0.112.0
/**
* This is an autogenerated libdef stub for:
*
* '@storybook/addon-knobs'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module '@storybook/addon-knobs' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module '@storybook/addon-knobs/angular' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/__types__/knob-test-cases' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/Panel' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/PropForm' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Array' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Boolean' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Button' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Checkboxes' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Color' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Date' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Files' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Number' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Object' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Options' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Radio' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Select' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/Text' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/components/types/types' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/converters' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/deprecated' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/KnobManager' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/KnobStore' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/register' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/registerKnobs' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/shared' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/type-defs' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/dist/typings.d' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/html' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/marko' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/mithril' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/polymer' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/react' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/register' {
declare module.exports: any;
}
declare module '@storybook/addon-knobs/vue' {
declare module.exports: any;
}
// Filename aliases
declare module '@storybook/addon-knobs/angular.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/angular'>;
}
declare module '@storybook/addon-knobs/dist/__types__/knob-test-cases.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/__types__/knob-test-cases'>;
}
declare module '@storybook/addon-knobs/dist/components/Panel.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/Panel'>;
}
declare module '@storybook/addon-knobs/dist/components/PropForm.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/PropForm'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Array.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Array'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Boolean.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Boolean'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Button.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Button'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Checkboxes.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Checkboxes'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Color.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Color'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Date.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Date'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Files.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Files'>;
}
declare module '@storybook/addon-knobs/dist/components/types/index' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types'>;
}
declare module '@storybook/addon-knobs/dist/components/types/index.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Number.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Number'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Object.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Object'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Options.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Options'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Radio.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Radio'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Select.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Select'>;
}
declare module '@storybook/addon-knobs/dist/components/types/Text.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/Text'>;
}
declare module '@storybook/addon-knobs/dist/components/types/types.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/components/types/types'>;
}
declare module '@storybook/addon-knobs/dist/converters.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/converters'>;
}
declare module '@storybook/addon-knobs/dist/deprecated.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/deprecated'>;
}
declare module '@storybook/addon-knobs/dist/index' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist'>;
}
declare module '@storybook/addon-knobs/dist/index.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist'>;
}
declare module '@storybook/addon-knobs/dist/KnobManager.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/KnobManager'>;
}
declare module '@storybook/addon-knobs/dist/KnobStore.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/KnobStore'>;
}
declare module '@storybook/addon-knobs/dist/register.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/register'>;
}
declare module '@storybook/addon-knobs/dist/registerKnobs.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/registerKnobs'>;
}
declare module '@storybook/addon-knobs/dist/shared.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/shared'>;
}
declare module '@storybook/addon-knobs/dist/type-defs.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/type-defs'>;
}
declare module '@storybook/addon-knobs/dist/typings.d.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/dist/typings.d'>;
}
declare module '@storybook/addon-knobs/html.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/html'>;
}
declare module '@storybook/addon-knobs/marko.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/marko'>;
}
declare module '@storybook/addon-knobs/mithril.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/mithril'>;
}
declare module '@storybook/addon-knobs/polymer.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/polymer'>;
}
declare module '@storybook/addon-knobs/react.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/react'>;
}
declare module '@storybook/addon-knobs/register.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/register'>;
}
declare module '@storybook/addon-knobs/vue.js' {
declare module.exports: $Exports<'@storybook/addon-knobs/vue'>;
}

View File

@ -1,101 +0,0 @@
// flow-typed signature: f6f4916ab4d700d3db6ac07b4264aa7e
// flow-typed version: <<STUB>>/@storybook/addon-links_v5.2.6/flow_v0.112.0
/**
* This is an autogenerated libdef stub for:
*
* '@storybook/addon-links'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module '@storybook/addon-links' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module '@storybook/addon-links/dist/constants' {
declare module.exports: any;
}
declare module '@storybook/addon-links/dist' {
declare module.exports: any;
}
declare module '@storybook/addon-links/dist/manager' {
declare module.exports: any;
}
declare module '@storybook/addon-links/dist/preview' {
declare module.exports: any;
}
declare module '@storybook/addon-links/dist/react/components/link' {
declare module.exports: any;
}
declare module '@storybook/addon-links/dist/react/components/RoutedLink' {
declare module.exports: any;
}
declare module '@storybook/addon-links/dist/react' {
declare module.exports: any;
}
declare module '@storybook/addon-links/dist/typings.d' {
declare module.exports: any;
}
declare module '@storybook/addon-links/react' {
declare module.exports: any;
}
declare module '@storybook/addon-links/register' {
declare module.exports: any;
}
// Filename aliases
declare module '@storybook/addon-links/dist/constants.js' {
declare module.exports: $Exports<'@storybook/addon-links/dist/constants'>;
}
declare module '@storybook/addon-links/dist/index' {
declare module.exports: $Exports<'@storybook/addon-links/dist'>;
}
declare module '@storybook/addon-links/dist/index.js' {
declare module.exports: $Exports<'@storybook/addon-links/dist'>;
}
declare module '@storybook/addon-links/dist/manager.js' {
declare module.exports: $Exports<'@storybook/addon-links/dist/manager'>;
}
declare module '@storybook/addon-links/dist/preview.js' {
declare module.exports: $Exports<'@storybook/addon-links/dist/preview'>;
}
declare module '@storybook/addon-links/dist/react/components/link.js' {
declare module.exports: $Exports<'@storybook/addon-links/dist/react/components/link'>;
}
declare module '@storybook/addon-links/dist/react/components/RoutedLink.js' {
declare module.exports: $Exports<'@storybook/addon-links/dist/react/components/RoutedLink'>;
}
declare module '@storybook/addon-links/dist/react/index' {
declare module.exports: $Exports<'@storybook/addon-links/dist/react'>;
}
declare module '@storybook/addon-links/dist/react/index.js' {
declare module.exports: $Exports<'@storybook/addon-links/dist/react'>;
}
declare module '@storybook/addon-links/dist/typings.d.js' {
declare module.exports: $Exports<'@storybook/addon-links/dist/typings.d'>;
}
declare module '@storybook/addon-links/react.js' {
declare module.exports: $Exports<'@storybook/addon-links/react'>;
}
declare module '@storybook/addon-links/register.js' {
declare module.exports: $Exports<'@storybook/addon-links/register'>;
}

View File

@ -1,61 +0,0 @@
// flow-typed signature: e484579841f3cb1e8f57a768abc4642d
// flow-typed version: c6154227d1/@storybook/react_v5.x.x/flow_>=v0.104.x
type NodeModule = typeof module;
declare module '@storybook/react' {
declare type Context = {
kind: string,
story: string,
...
};
declare type Renderable =
| string
| number
| React$Element<any>
| Iterable<?Renderable>;
declare type RenderCallback = (
context: Context
) => Renderable;
declare type RenderFunction = () => Renderable;
declare type StoryDecorator = (
story: RenderFunction,
context: Context
) => Renderable;
declare type DecoratorParameters = { [key: string]: any, ... };
declare interface Story {
+kind: string;
add(
storyName: string,
callback: RenderCallback,
parameters?: DecoratorParameters
): Story;
addDecorator(decorator: StoryDecorator): Story;
addParameters(parameters: DecoratorParameters): Story;
}
declare interface StoryObject {
name: string;
render: RenderFunction;
}
declare interface StoryBucket {
kind: string;
filename: string;
stories: Array<StoryObject>;
}
declare function addDecorator(decorator: StoryDecorator): void;
declare function addParameters(parameters: DecoratorParameters): void;
declare function clearDecorators(): void;
declare function configure(fn: () => void, module: NodeModule): void;
declare function setAddon(addon: Object): void;
declare function storiesOf(name: string, module: NodeModule): Story;
declare function storiesOf<T>(name: string, module: NodeModule): Story & T;
declare function forceReRender(): void;
declare function getStorybook(): Array<StoryBucket>;
}

View File

@ -1,249 +0,0 @@
// flow-typed signature: 48ec61af419d1ece506839678031f356
// flow-typed version: <<STUB>>/storybook-host_v5.1.0/flow_v0.112.0
/**
* This is an autogenerated libdef stub for:
*
* 'storybook-host'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/
declare module 'storybook-host' {
declare module.exports: any;
}
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module 'storybook-host/lib/common/alignment' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/color' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/css/css' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/css/glamor' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/css' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/css/test/css-flex.test' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/css/test/css-image.test' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/css/test/css-positioning.test' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/css/test/css-spacing.test' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/css/test/css.test' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/css/types' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/libs' {
declare module.exports: any;
}
declare module 'storybook-host/lib/common/util' {
declare module.exports: any;
}
declare module 'storybook-host/lib/components/AlignmentContainer/AlignmentContainer' {
declare module.exports: any;
}
declare module 'storybook-host/lib/components/AlignmentContainer/ComponentHost.stories' {
declare module.exports: any;
}
declare module 'storybook-host/lib/components/AlignmentContainer' {
declare module.exports: any;
}
declare module 'storybook-host/lib/components/ComponentHost/ComponentHost' {
declare module.exports: any;
}
declare module 'storybook-host/lib/components/ComponentHost/ComponentHost.stories' {
declare module.exports: any;
}
declare module 'storybook-host/lib/components/ComponentHost' {
declare module.exports: any;
}
declare module 'storybook-host/lib/components/CropMarks/CropMark' {
declare module.exports: any;
}
declare module 'storybook-host/lib/components/CropMarks/CropMarks' {
declare module.exports: any;
}
declare module 'storybook-host/lib/components/CropMarks' {
declare module.exports: any;
}
declare module 'storybook-host/lib/decorators/host' {
declare module.exports: any;
}
declare module 'storybook-host/lib' {
declare module.exports: any;
}
declare module 'storybook-host/lib/index.test' {
declare module.exports: any;
}
declare module 'storybook-host/lib/test/Foo' {
declare module.exports: any;
}
declare module 'storybook-host/lib/test' {
declare module.exports: any;
}
declare module 'storybook-host/lib/types' {
declare module.exports: any;
}
// Filename aliases
declare module 'storybook-host/lib/common/alignment.js' {
declare module.exports: $Exports<'storybook-host/lib/common/alignment'>;
}
declare module 'storybook-host/lib/common/color.js' {
declare module.exports: $Exports<'storybook-host/lib/common/color'>;
}
declare module 'storybook-host/lib/common/css/css.js' {
declare module.exports: $Exports<'storybook-host/lib/common/css/css'>;
}
declare module 'storybook-host/lib/common/css/glamor.js' {
declare module.exports: $Exports<'storybook-host/lib/common/css/glamor'>;
}
declare module 'storybook-host/lib/common/css/index' {
declare module.exports: $Exports<'storybook-host/lib/common/css'>;
}
declare module 'storybook-host/lib/common/css/index.js' {
declare module.exports: $Exports<'storybook-host/lib/common/css'>;
}
declare module 'storybook-host/lib/common/css/test/css-flex.test.js' {
declare module.exports: $Exports<'storybook-host/lib/common/css/test/css-flex.test'>;
}
declare module 'storybook-host/lib/common/css/test/css-image.test.js' {
declare module.exports: $Exports<'storybook-host/lib/common/css/test/css-image.test'>;
}
declare module 'storybook-host/lib/common/css/test/css-positioning.test.js' {
declare module.exports: $Exports<'storybook-host/lib/common/css/test/css-positioning.test'>;
}
declare module 'storybook-host/lib/common/css/test/css-spacing.test.js' {
declare module.exports: $Exports<'storybook-host/lib/common/css/test/css-spacing.test'>;
}
declare module 'storybook-host/lib/common/css/test/css.test.js' {
declare module.exports: $Exports<'storybook-host/lib/common/css/test/css.test'>;
}
declare module 'storybook-host/lib/common/css/types.js' {
declare module.exports: $Exports<'storybook-host/lib/common/css/types'>;
}
declare module 'storybook-host/lib/common/index' {
declare module.exports: $Exports<'storybook-host/lib/common'>;
}
declare module 'storybook-host/lib/common/index.js' {
declare module.exports: $Exports<'storybook-host/lib/common'>;
}
declare module 'storybook-host/lib/common/libs.js' {
declare module.exports: $Exports<'storybook-host/lib/common/libs'>;
}
declare module 'storybook-host/lib/common/util.js' {
declare module.exports: $Exports<'storybook-host/lib/common/util'>;
}
declare module 'storybook-host/lib/components/AlignmentContainer/AlignmentContainer.js' {
declare module.exports: $Exports<'storybook-host/lib/components/AlignmentContainer/AlignmentContainer'>;
}
declare module 'storybook-host/lib/components/AlignmentContainer/ComponentHost.stories.js' {
declare module.exports: $Exports<'storybook-host/lib/components/AlignmentContainer/ComponentHost.stories'>;
}
declare module 'storybook-host/lib/components/AlignmentContainer/index' {
declare module.exports: $Exports<'storybook-host/lib/components/AlignmentContainer'>;
}
declare module 'storybook-host/lib/components/AlignmentContainer/index.js' {
declare module.exports: $Exports<'storybook-host/lib/components/AlignmentContainer'>;
}
declare module 'storybook-host/lib/components/ComponentHost/ComponentHost.js' {
declare module.exports: $Exports<'storybook-host/lib/components/ComponentHost/ComponentHost'>;
}
declare module 'storybook-host/lib/components/ComponentHost/ComponentHost.stories.js' {
declare module.exports: $Exports<'storybook-host/lib/components/ComponentHost/ComponentHost.stories'>;
}
declare module 'storybook-host/lib/components/ComponentHost/index' {
declare module.exports: $Exports<'storybook-host/lib/components/ComponentHost'>;
}
declare module 'storybook-host/lib/components/ComponentHost/index.js' {
declare module.exports: $Exports<'storybook-host/lib/components/ComponentHost'>;
}
declare module 'storybook-host/lib/components/CropMarks/CropMark.js' {
declare module.exports: $Exports<'storybook-host/lib/components/CropMarks/CropMark'>;
}
declare module 'storybook-host/lib/components/CropMarks/CropMarks.js' {
declare module.exports: $Exports<'storybook-host/lib/components/CropMarks/CropMarks'>;
}
declare module 'storybook-host/lib/components/CropMarks/index' {
declare module.exports: $Exports<'storybook-host/lib/components/CropMarks'>;
}
declare module 'storybook-host/lib/components/CropMarks/index.js' {
declare module.exports: $Exports<'storybook-host/lib/components/CropMarks'>;
}
declare module 'storybook-host/lib/decorators/host.js' {
declare module.exports: $Exports<'storybook-host/lib/decorators/host'>;
}
declare module 'storybook-host/lib/index' {
declare module.exports: $Exports<'storybook-host/lib'>;
}
declare module 'storybook-host/lib/index.js' {
declare module.exports: $Exports<'storybook-host/lib'>;
}
declare module 'storybook-host/lib/index.test.js' {
declare module.exports: $Exports<'storybook-host/lib/index.test'>;
}
declare module 'storybook-host/lib/test/Foo.js' {
declare module.exports: $Exports<'storybook-host/lib/test/Foo'>;
}
declare module 'storybook-host/lib/test/index' {
declare module.exports: $Exports<'storybook-host/lib/test'>;
}
declare module 'storybook-host/lib/test/index.js' {
declare module.exports: $Exports<'storybook-host/lib/test'>;
}
declare module 'storybook-host/lib/types.js' {
declare module.exports: $Exports<'storybook-host/lib/types'>;
}

View File

@ -1,45 +0,0 @@
// flow-typed signature: c2d42e89f2eeb1cdff2d4d7dea9fd97b
// flow-typed version: c6154227d1/storybook-router_v0.x.x/flow_>=v0.104.x
type LocationShape = {
pathname?: string,
search?: string,
hash?: string,
state?: any,
...
};
type GetUserConfirmation = (
message: string,
callback: (confirmed: boolean) => void
) => void;
declare module "storybook-router" {
declare type Context = {
kind: string,
story: string,
...
};
declare type Renderable = React$Element<*>;
declare type RenderFunction = () => Renderable | Array<Renderable>;
declare type StoryDecorator = (
story: RenderFunction,
context: Context
) => Renderable | null;
declare type Links = { [key: string]: (kind: string, story: string) => Function, ... };
declare type RouterProps = {
initialEntry?: Array<string>,
autoRoute?: boolean,
initialEntries?: Array<LocationShape | string>,
initialIndex?: number,
getUserConfirmation?: GetUserConfirmation,
keyLength?: number,
children?: React$Element<*>,
...
};
declare module.exports: { (links?: Links, routerProps?: RouterProps): StoryDecorator, ... };
}

View File

@ -18,12 +18,10 @@
"scripts": { "scripts": {
"build": "node scripts/build.js", "build": "node scripts/build.js",
"build-mainnet": "REACT_APP_NETWORK=mainnet yarn build", "build-mainnet": "REACT_APP_NETWORK=mainnet yarn build",
"build-storybook": "build-storybook -o build_storybook",
"flow": "flow", "flow": "flow",
"precommit": "./precommit.sh", "precommit": "./precommit.sh",
"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",
"storybook": "start-storybook -p 6006",
"test": "NODE_ENV=test && node scripts/test.js --env=jsdom", "test": "NODE_ENV=test && node scripts/test.js --env=jsdom",
"format": "prettier-eslint \"src/**/*.js\" --write" "format": "prettier-eslint \"src/**/*.js\" --write"
}, },
@ -44,8 +42,11 @@
"bignumber.js": "9.0.0", "bignumber.js": "9.0.0",
"connected-react-router": "6.6.1", "connected-react-router": "6.6.1",
"date-fns": "2.8.1", "date-fns": "2.8.1",
"currency-flags": "^2.1.1",
"dotenv": "^8.2.0",
"ethereum-ens": "0.7.8", "ethereum-ens": "0.7.8",
"final-form": "4.18.6", "final-form": "4.18.6",
"fortmatic": "^1.0.1",
"history": "4.10.1", "history": "4.10.1",
"immortal-db": "^1.0.2", "immortal-db": "^1.0.2",
"immutable": "^4.0.0-rc.9", "immutable": "^4.0.0-rc.9",
@ -55,7 +56,9 @@
"optimize-css-assets-webpack-plugin": "5.0.3", "optimize-css-assets-webpack-plugin": "5.0.3",
"polished": "^3.4.2", "polished": "^3.4.2",
"qrcode.react": "1.0.0", "qrcode.react": "1.0.0",
"query-string": "6.9.0",
"react": "16.12.0", "react": "16.12.0",
"react-dev-utils": "^10.0.0",
"react-dom": "16.12.0", "react-dom": "16.12.0",
"react-final-form": "6.3.3", "react-final-form": "6.3.3",
"react-final-form-listeners": "^1.0.2", "react-final-form-listeners": "^1.0.2",
@ -70,10 +73,9 @@
"redux-actions": "^2.6.5", "redux-actions": "^2.6.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"reselect": "^4.0.0", "reselect": "^4.0.0",
"squarelink": "^1.1.3", "squarelink": "^1.1.4",
"web3": "1.2.4", "web3": "1.2.4",
"web3connect": "^1.0.0-beta.23", "web3connect": "^1.0.0-beta.23"
"react-ga": "^2.7.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/cli": "7.7.5", "@babel/cli": "7.7.5",
@ -100,12 +102,7 @@
"@babel/preset-env": "7.7.6", "@babel/preset-env": "7.7.6",
"@babel/preset-flow": "7.7.4", "@babel/preset-flow": "7.7.4",
"@babel/preset-react": "7.7.4", "@babel/preset-react": "7.7.4",
"@sambego/storybook-state": "^1.3.6", "@testing-library/react": "9.3.3",
"@storybook/addon-actions": "5.2.8",
"@storybook/addon-knobs": "5.2.8",
"@storybook/addon-links": "5.2.8",
"@storybook/react": "5.2.8",
"@testing-library/react": "9.3.2",
"autoprefixer": "9.7.3", "autoprefixer": "9.7.3",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-eslint": "10.0.3", "babel-eslint": "10.0.3",
@ -116,9 +113,9 @@
"babel-plugin-transform-es3-property-literals": "^6.22.0", "babel-plugin-transform-es3-property-literals": "^6.22.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"css-loader": "3.2.1", "css-loader": "3.3.0",
"detect-port": "^1.3.0", "detect-port": "^1.3.0",
"eslint": "5.16.0", "eslint": "6.7.2",
"eslint-config-airbnb": "18.0.1", "eslint-config-airbnb": "18.0.1",
"eslint-plugin-flowtype": "4.5.2", "eslint-plugin-flowtype": "4.5.2",
"eslint-plugin-import": "2.19.1", "eslint-plugin-import": "2.19.1",
@ -142,10 +139,8 @@
"pre-commit": "^1.2.2", "pre-commit": "^1.2.2",
"prettier-eslint-cli": "5.0.0", "prettier-eslint-cli": "5.0.0",
"run-with-testrpc": "0.3.1", "run-with-testrpc": "0.3.1",
"storybook-host": "5.1.0",
"storybook-router": "^0.3.4",
"style-loader": "1.0.1", "style-loader": "1.0.1",
"terser-webpack-plugin": "^2.2.2", "terser-webpack-plugin": "2.2.3",
"truffle": "5.1.3", "truffle": "5.1.3",
"truffle-contract": "4.0.31", "truffle-contract": "4.0.31",
"truffle-solidity-loader": "0.1.32", "truffle-solidity-loader": "0.1.32",

View File

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="favicon.ico"> <link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<title>Gnosis Safe For Teams</title> <title>Gnosis Safe Multisig</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@ -57,6 +57,8 @@ git clone https://github.com/gnosis/safe-transaction-service.git
cd safe-transaction-service cd safe-transaction-service
git checkout develop git checkout develop
docker-compose build docker-compose build
# it comes enabled by default in docker-compose
sudo service postgresql stop
docker-compose up -d docker-compose up -d
``` ```
Check that the service is running at https://localhost:8000 Check that the service is running at https://localhost:8000

View File

@ -3,6 +3,7 @@ import React from 'react'
import Web3Connect from 'web3connect' import Web3Connect from 'web3connect'
import Torus from '@toruslabs/torus-embed' import Torus from '@toruslabs/torus-embed'
import WalletConnectProvider from '@walletconnect/web3-provider' import WalletConnectProvider from '@walletconnect/web3-provider'
import Fortmatic from 'fortmatic'
import Portis from '@portis/web3' import Portis from '@portis/web3'
import Squarelink from 'squarelink' import Squarelink from 'squarelink'
import Button from '~/components/layout/Button' import Button from '~/components/layout/Button'
@ -10,8 +11,11 @@ import { fetchProvider } from '~/logic/wallets/store/actions'
import { getNetwork } from '~/config' import { getNetwork } from '~/config'
import { store } from '~/store' import { store } from '~/store'
const PORTIS_DAPP_ID = process.env.REACT_APP_NETWORK === 'mainnet' ? process.env.REACT_APP_PORTIS_ID : '852b763d-f28b-4463-80cb-846d7ec5806b' const isMainnet = process.env.REACT_APP_NETWORK === 'mainnet'
const SQUARELINK_CLIENT_ID = process.env.REACT_APP_NETWORK === 'mainnet' ? process.env.REACT_APP_SQUARELINK_ID : '46ce08fe50913cfa1b78'
const PORTIS_DAPP_ID = isMainnet ? process.env.REACT_APP_PORTIS_ID : '852b763d-f28b-4463-80cb-846d7ec5806b'
const SQUARELINK_CLIENT_ID = isMainnet ? process.env.REACT_APP_SQUARELINK_ID : '46ce08fe50913cfa1b78'
const FORTMATIC_API_KEY = isMainnet ? process.env.REACT_APP_FORTMATIC_KEY : 'pk_test_CAD437AA29BE0A40'
export const web3Connect = new Web3Connect.Core({ export const web3Connect = new Web3Connect.Core({
network: getNetwork().toLowerCase(), network: getNetwork().toLowerCase(),
@ -34,6 +38,12 @@ export const web3Connect = new Web3Connect.Core({
id: SQUARELINK_CLIENT_ID, id: SQUARELINK_CLIENT_ID,
}, },
}, },
fortmatic: {
package: Fortmatic,
options: {
key: FORTMATIC_API_KEY,
},
},
torus: { torus: {
package: Torus, package: Torus,
options: { options: {

View File

@ -8,7 +8,7 @@ import { makeStyles } from '@material-ui/core/styles'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
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 { primary, mainFontFamily } from '~/theme/variables' import { primary, mainFontFamily, md } 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'
@ -38,7 +38,7 @@ const useStyles = makeStyles({
text: { text: {
color: primary, color: primary,
fontFamily: mainFontFamily, fontFamily: mainFontFamily,
fontSize: '16px', fontSize: md,
fontWeight: 'normal', fontWeight: 'normal',
lineHeight: '1.38', lineHeight: '1.38',
margin: '0 0 25px', margin: '0 0 25px',

View File

@ -13,14 +13,14 @@ const useStyles = makeStyles({
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
cursor: 'pointer', cursor: 'pointer',
padding: xs, margin: `0 ${xs}`,
borderRadius: '50%', borderRadius: '50%',
transition: 'background-color .2s ease-in-out', transition: 'background-color .2s ease-in-out',
'&:hover': { '&:hover': {
backgroundColor: '#F0EFEE', backgroundColor: '#F0EFEE',
}, },
}, },
inreasedPopperZindex: { increasedPopperZindex: {
zIndex: 2001, zIndex: 2001,
}, },
}) })
@ -33,7 +33,7 @@ type CopyBtnProps = {
const CopyBtn = ({ content, increaseZindex = false }: CopyBtnProps) => { const CopyBtn = ({ content, increaseZindex = false }: CopyBtnProps) => {
const [clicked, setClicked] = useState<boolean>(false) const [clicked, setClicked] = useState<boolean>(false)
const classes = useStyles() const classes = useStyles()
const customClasses = increaseZindex ? { popper: classes.inreasedPopperZindex } : {} const customClasses = increaseZindex ? { popper: classes.increasedPopperZindex } : {}
return ( return (
<Tooltip <Tooltip

View File

@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" fill-rule="evenodd">
<path fill="#B2B5B2" fill-rule="nonzero" d="M17 17v-2a1 1 0 0 1 2 0v2a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2a1 1 0 1 1 0 2H7v10h10z"/>
<path fill="#B2B5B2" d="M15.586 7H13a1 1 0 0 1 0-2h5a.997.997 0 0 1 1 1v5a1 1 0 0 1-2 0V8.414l-6.243 6.243a1 1 0 1 1-1.414-1.414L15.586 7z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 442 B

View File

@ -3,23 +3,24 @@ import React from 'react'
import Tooltip from '@material-ui/core/Tooltip' import Tooltip from '@material-ui/core/Tooltip'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import Img from '~/components/layout/Img' import Img from '~/components/layout/Img'
import EtherscanOpenIcon from './img/etherscan-open.svg'
import { getEtherScanLink } from '~/logic/wallets/getWeb3' import { getEtherScanLink } from '~/logic/wallets/getWeb3'
import { xs } from '~/theme/variables' import { xs } from '~/theme/variables'
import SearchIcon from './search.svg'
const useStyles = makeStyles({ const useStyles = makeStyles({
container: { container: {
alignItems: 'center',
borderRadius: '50%',
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', margin: `0 ${xs}`,
padding: xs, padding: '0',
borderRadius: '50%',
transition: 'background-color .2s ease-in-out', transition: 'background-color .2s ease-in-out',
'&:hover': { '&:hover': {
backgroundColor: '#F0EFEE', backgroundColor: '#F0EFEE',
}, },
}, },
inreasedPopperZindex: { increasedPopperZindex: {
zIndex: 2001, zIndex: 2001,
}, },
}) })
@ -32,18 +33,18 @@ type EtherscanBtnProps = {
const EtherscanBtn = ({ type, value, increaseZindex = false }: EtherscanBtnProps) => { const EtherscanBtn = ({ type, value, increaseZindex = false }: EtherscanBtnProps) => {
const classes = useStyles() const classes = useStyles()
const customClasses = increaseZindex ? { popper: classes.inreasedPopperZindex } : {} const customClasses = increaseZindex ? { popper: classes.increasedPopperZindex } : {}
return ( return (
<Tooltip title="Show details on Etherscan" placement="top" classes={customClasses}> <Tooltip title="Show details on Etherscan" placement="top" classes={customClasses}>
<a <a
aria-label="Show details on Etherscan"
className={classes.container} className={classes.container}
href={getEtherScanLink(type, value)} href={getEtherScanLink(type, value)}
target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
aria-label="Show details on Etherscan" target="_blank"
> >
<Img src={SearchIcon} height={20} alt="Etherscan" /> <Img src={EtherscanOpenIcon} height={20} alt="Show on Etherscan" />
</a> </a>
</Tooltip> </Tooltip>
) )

View File

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" fill-rule="evenodd">
<path fill="#B2B5B2" fill-rule="nonzero" d="M18.671 17.085a1.119 1.119 0 0 1-.002 1.587 1.126 1.126 0 0 1-1.586.003l-2.68-2.68a6.6 6.6 0 1 1 .862-10.061 6.603 6.603 0 0 1 .727 8.471l2.68 2.68zm-4.923-3.335a4.455 4.455 0 0 0-6.298-6.3 4.456 4.456 0 0 0 0 6.3 4.452 4.452 0 0 0 6.298 0z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 436 B

View File

@ -1,25 +1,30 @@
// @flow // @flow
import React from 'react' import React from 'react'
import OpenInNew from '@material-ui/icons/OpenInNew' import { withStyles } from '@material-ui/core/styles'
import { getEtherScanLink } from '~/logic/wallets/getWeb3' import Block from '~/components/layout/Block'
import Paragraph from '~/components/layout/Paragraph'
import CopyBtn from '~/components/CopyBtn'
import EtherscanBtn from '~/components/EtherscanBtn'
import { shortVersionOf } from '~/logic/wallets/ethAddresses' import { shortVersionOf } from '~/logic/wallets/ethAddresses'
import { secondary } from '~/theme/variables' import { styles } from './style.js'
const openIconStyle = {
height: '13px',
color: secondary,
}
type EtherscanLinkProps = { type EtherscanLinkProps = {
type: 'tx' | 'address', type: 'tx' | 'address',
value: string, value: string,
cut?: number,
classes: Object,
} }
const EtherscanLink = ({ type, value }: EtherscanLinkProps) => ( const EtherscanLink = ({
<a href={getEtherScanLink(type, value)} target="_blank" rel="noopener noreferrer"> type, value, cut, classes,
{shortVersionOf(value, 4)} }: EtherscanLinkProps) => (
<OpenInNew style={openIconStyle} /> <Block className={classes.etherscanLink}>
</a> <Paragraph size="md" noMargin>
{cut ? shortVersionOf(value, cut) : value}
</Paragraph>
<CopyBtn content={value} />
<EtherscanBtn type={type} value={value} />
</Block>
) )
export default EtherscanLink export default withStyles(styles)(EtherscanLink)

View File

@ -0,0 +1,8 @@
// @flow
export const styles = () => ({
etherscanLink: {
display: 'flex',
alignItems: 'center',
},
})

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 330"><title>gnosis_safe_teams_2019_logo_all_rgb</title><path d="M161,14.2A146.54,146.54,0,1,0,307.56,160.74,146.54,146.54,0,0,0,161,14.2ZM268.27,168.74h-68a41.41,41.41,0,1,1,.32-14.41h67.68a7.21,7.21,0,1,1,0,14.41Z"/><path d="M347.28,203.9h9.86l29.27,51.46h.22l29.26-51.46h9.76v79.6h-9.76V222.84h-.22l-25.9,44.62h-6.62l-25.89-44.62h-.12V283.5h-9.86Z"/><path d="M442.8,261.41V228.9h9.08v29.93c0,12.33,4.71,17,13.34,17s16.26-7.51,16.26-20.51V228.9h9.08v54.6h-9.08v-9.42h-.11a21.28,21.28,0,0,1-18.05,10.09C450.76,284.17,442.8,277,442.8,261.41Z"/><path d="M507.71,201h9.08V283.5h-9.08Z"/><path d="M535.74,267.24V236.75h-7.06V228.9h7.06V210.29l9.08-1V228.9h18v7.85h-18V265.9c0,6.61,1.46,9.75,6.62,9.75a20.58,20.58,0,0,0,9.3-2.8l2.36,8c-3,1.79-6.73,3.36-13.23,3.36C540.22,284.17,535.74,278.23,535.74,267.24Z"/><path d="M573.3,212.42a6.62,6.62,0,1,1,6.61,6.5A6.53,6.53,0,0,1,573.3,212.42Zm2,16.48h9.08v54.6h-9.08Z"/><path d="M597.29,276.43l4.6-6.95c4.26,4.15,9.08,6.62,16.7,6.62,6.62,0,10.43-3.25,10.43-7.4,0-4.82-5-6.17-13-9.08-8.85-3.14-16.36-6.73-16.36-16.71,0-8.41,7.62-14.68,17.93-14.68A28.46,28.46,0,0,1,636.42,235l-4.71,7.18a20.61,20.61,0,0,0-14.46-5.83c-4.26,0-8.41,2.46-8.41,6.61s4.37,6.39,10.31,8.52c11.55,4.49,19.17,6.51,19.06,17.16,0,8.52-6.5,15.58-19.62,15.58A31.37,31.37,0,0,1,597.29,276.43Z"/><path d="M649.76,212.42a6.62,6.62,0,1,1,6.61,6.5A6.53,6.53,0,0,1,649.76,212.42Zm2,16.48h9.08v54.6h-9.08Z"/><path d="M680.48,300.43l2.24-8.08c6.28,3.59,11.77,5.72,19.62,5.72,12.33,0,18.27-7.17,18.27-16.37V273h-.22c-4,5.83-10.54,9.53-18.95,9.53-14.91,0-26.46-10.76-26.46-27,0-15.58,10.77-27.24,26.13-27.24a23.6,23.6,0,0,1,19.17,9.41h.22V228.9h9.08v50.56c0,15.7-7.85,26.91-26.9,26.91A42.46,42.46,0,0,1,680.48,300.43ZM720.84,255c0-11-8-18.39-18.16-18.39-11.1,0-18.5,8-18.5,18.61,0,11.55,8.07,18.84,18.38,18.84C713.55,274.08,720.84,265.9,720.84,255Z"/><path d="M341.67,132.53c0-23.1,14.91-41,40.36-41,11.55,0,24.11,4.82,32.29,14.24l-13.23,11.21A26.79,26.79,0,0,0,382,109.09c-11.32,0-20.74,8.64-20.74,23.44,0,12.89,8,23.43,21.53,23.43a27.69,27.69,0,0,0,13.79-3.48v-9.42H381.14V127.59h33.41v35.77c-7.29,5.6-19.4,9.75-31.73,9.75C359.84,173.11,341.67,158.42,341.67,132.53Z"/><path d="M426.09,117.39h17.49v6.39h.23a21.26,21.26,0,0,1,16.48-7.4c11.32,0,19.62,5.61,19.62,23v32.84H462.42V143.63c0-9.09-3-11.66-8.75-11.66-6.05,0-10.09,4.14-10.09,12.66v27.58H426.09Z"/><path d="M488.65,144.86c0-16.71,12-28.48,29.71-28.48,17.27,0,29.6,11.55,29.6,28.48s-12.33,28.36-29.6,28.36C500.65,173.22,488.65,161.45,488.65,144.86Zm41.6,0c0-7.63-5-12.89-11.89-12.89-7.06,0-12,5.38-12,12.89,0,7.29,5,12.89,12,12.89C525,157.75,530.25,152.37,530.25,144.86Z"/><path d="M553.12,165.15l8.18-11.32c4.6,4.15,9.31,6.16,14.35,6.16,3.48,0,5.27-1.23,5.27-3.25,0-1.79-1.68-2.91-8.52-5-9.2-2.81-17.27-7.29-17.27-17.83,0-11.1,9.09-17.49,20.52-17.49a32.3,32.3,0,0,1,20.74,6.84l-8.3,12.22c-3.81-3.81-8.52-5.72-12.22-5.72-2,0-4.37.9-4.37,3,0,1.68,2,2.92,7.51,4.82,12.11,4.15,18.73,7.29,18.73,18.05,0,10.2-7.29,17.6-22.31,17.6C566.79,173.22,559.17,170.53,553.12,165.15Z"/><path d="M605.58,100.46c0-5.38,4.38-9.86,10.43-9.86s10.54,4.37,10.54,9.86c0,5.72-4.49,10.09-10.54,10.09S605.58,106.18,605.58,100.46Zm1.69,16.93h17.48v54.82H607.27Z"/><path d="M633.16,165.15l8.19-11.32c4.59,4.15,9.3,6.16,14.35,6.16,3.47,0,5.27-1.23,5.27-3.25,0-1.79-1.69-2.91-8.52-5-9.2-2.81-17.27-7.29-17.27-17.83,0-11.1,9.08-17.49,20.52-17.49a32.31,32.31,0,0,1,20.74,6.84l-8.3,12.22c-3.81-3.81-8.52-5.72-12.22-5.72-2,0-4.37.9-4.37,3,0,1.68,2,2.92,7.51,4.82,12.11,4.15,18.72,7.29,18.72,18.05,0,10.2-7.28,17.6-22.31,17.6C646.84,173.22,639.22,170.53,633.16,165.15Z"/><path d="M710,160.55l11-13.34c6.06,6.62,13.35,9.42,18.84,9.42,6.28,0,9-3.14,9-6.73,0-4.37-2.8-6.27-11.77-9.3-12.11-4.15-24.22-9.87-24.22-25.56,0-13.23,11.32-23.21,26.12-23.44,10.43-.22,20,3.82,27.58,10.32L755.92,115.6c-6.84-5.38-11.77-7.51-16.25-7.51s-7.4,2.35-7.4,6.16,2.8,5.83,11.43,9.19c14,5.39,24.78,9,24.78,24.33,0,18.73-16,25.45-29.26,25.45A40.52,40.52,0,0,1,710,160.55Z"/><path d="M774.87,144.86c0-16.82,10.87-28.48,25-28.48A19.66,19.66,0,0,1,815,123.22l.22-.11v-5.72h17.6v54.82h-16.7V166.5l-.23-.12c-3.58,4.6-8.52,6.84-15.13,6.84C785.74,173.22,774.87,161.79,774.87,144.86Zm41.15,0c0-7.51-5-13-11.55-13-6.73,0-11.77,5.16-11.77,13s4.82,12.89,11.77,12.89C810.75,157.75,816,152.71,816,144.86Z"/><path d="M846.51,131.52h-5.27V117.39h5.27v-8.63c0-13.57,7.06-20.18,18-20.18a27.42,27.42,0,0,1,14.8,4.26l-3.25,12.67a17.51,17.51,0,0,0-6.84-2c-3.14,0-5.16,1.57-5.16,5.94v8h10.43v14.13H864.11v40.69h-17.6Z"/><path d="M932.84,149.45H895.5c1.12,6.28,6.39,9.53,13.12,9.53A17.29,17.29,0,0,0,921.4,153l10.09,10.32c-4.6,5.72-12.22,9.86-24.66,9.86-16.6,0-28.71-11.43-28.71-28.47,0-16.6,11.66-28.37,28.26-28.37,15.47,0,26.79,11.55,26.79,27.81C933.17,145.76,933,148.33,932.84,149.45ZM895.62,139h20.51c-1.12-5.38-4.71-8.75-10-8.75S896.74,133.54,895.62,139Z"/></svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -16,15 +16,15 @@ import {
border, sm, md, headerHeight, border, sm, md, headerHeight,
} from '~/theme/variables' } from '~/theme/variables'
import Provider from './Provider' import Provider from './Provider'
import EarlyAccessLabel from './EarlyAccessLabel' import NetworkLabel from './NetworkLabel'
import SafeListHeader from './SafeListHeader' import SafeListHeader from './SafeListHeader'
const logo = require('../assets/gnosis-safe-logo.svg') 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 = () => ({
@ -33,7 +33,7 @@ const styles = () => ({
padding: 0, padding: 0,
boxShadow: '0 0 10px 0 rgba(33, 48, 77, 0.1)', boxShadow: '0 0 10px 0 rgba(33, 48, 77, 0.1)',
minWidth: '280px', minWidth: '280px',
borderRadius: '8px', borderRadius: sm,
marginTop: '11px', marginTop: '11px',
}, },
summary: { summary: {
@ -43,6 +43,8 @@ const styles = () => ({
boxShadow: '0 2px 4px 0 rgba(212, 212, 211, 0.59)', boxShadow: '0 2px 4px 0 rgba(212, 212, 211, 0.59)',
backgroundColor: 'white', backgroundColor: 'white',
zIndex: 1301, zIndex: 1301,
position: 'fixed',
width: '100%',
}, },
logo: { logo: {
padding: `${sm} ${md}`, padding: `${sm} ${md}`,
@ -54,10 +56,15 @@ 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="/">
@ -67,15 +74,25 @@ const Layout = openHoc(({
<Divider /> <Divider />
<SafeListHeader /> <SafeListHeader />
<Divider /> <Divider />
<EarlyAccessLabel /> <NetworkLabel />
<Spacer /> <Spacer />
<Provider open={open} toggle={toggle} info={providerInfo}> <Provider open={open} toggle={toggle} info={providerInfo}>
{(providerRef) => ( {(providerRef) => (
<Popper open={open} anchorEl={providerRef.current} placement="bottom" className={classes.popper}> <Popper
open={open}
anchorEl={providerRef.current}
placement="bottom"
className={classes.popper}
popperOptions={{ positionFixed: true }}
>
{({ TransitionProps }) => ( {({ TransitionProps }) => (
<Grow {...TransitionProps}> <Grow {...TransitionProps}>
<> <>
<ClickAwayListener onClickAway={clickAway} mouseEvent="onClick" touchEvent={false}> <ClickAwayListener
onClickAway={clickAway}
mouseEvent="onClick"
touchEvent={false}
>
<List className={classes.root} component="div"> <List className={classes.root} component="div">
{providerDetails} {providerDetails}
</List> </List>
@ -87,7 +104,7 @@ const Layout = openHoc(({
)} )}
</Provider> </Provider>
</Row> </Row>
</> ),
)) )
export default withStyles(styles)(Layout) export default withStyles(styles)(Layout)

View File

@ -1,38 +0,0 @@
// @flow
import { storiesOf } from '@storybook/react'
import * as React from 'react'
import styles from '~/components/layout/PageFrame/index.scss'
import Layout from './Layout'
import ProviderAccesible from './ProviderInfo/ProviderAccesible'
import UserDetails from './ProviderDetails/UserDetails'
import ProviderDisconnected from './ProviderInfo/ProviderDisconnected'
import ConnectDetails from './ProviderDetails/ConnectDetails'
const FrameDecorator = (story) => <div className={styles.frame}>{story()}</div>
storiesOf('Components /Header', module)
.addDecorator(FrameDecorator)
.add('Connected', () => {
const provider = 'Metamask'
const userAddress = '0x873faa4cddd5b157e8e5a57e7a5479afc5d30moe'
const network = 'RINKEBY'
const info = <ProviderAccesible provider={provider} network={network} userAddress={userAddress} connected />
const details = <UserDetails provider={provider} network={network} userAddress={userAddress} connected />
return <Layout providerInfo={info} providerDetails={details} />
})
.add('Disconnected', () => {
const info = <ProviderDisconnected />
const details = <ConnectDetails />
return <Layout providerInfo={info} providerDetails={details} />
})
.add('Connection Error', () => {
const provider = 'Metamask'
const userAddress = '0x873faa4cddd5b157e8e5a57e7a5479afc5d30moe'
const network = 'RINKEBY'
const info = <ProviderAccesible provider={provider} network={network} userAddress={userAddress} connected={false} />
const details = <UserDetails provider={provider} network={network} userAddress={userAddress} connected={false} />
return <Layout providerInfo={info} providerDetails={details} />
})

View File

@ -1,20 +1,22 @@
// @flow // @flow
import * as React from 'react' import * as React from 'react'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
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, xs, sm, md, border,
} from '~/theme/variables' } from '~/theme/variables'
export const TOGGLE_SIDEBAR_BTN_TESTID = 'TOGGLE_SIDEBAR_BTN' const network = getNetwork()
const formattedNetwork = network[0].toUpperCase() + network.substring(1).toLowerCase()
const useStyles = makeStyles({ const useStyles = makeStyles({
container: { container: {
flexGrow: 0, flexGrow: 0,
padding: `0 ${md}`, padding: `0 ${md}`,
}, },
counter: { text: {
background: border, background: border,
padding: `${xs} ${sm}`, padding: `${xs} ${sm}`,
borderRadius: '3px', borderRadius: '3px',
@ -28,8 +30,8 @@ const EarlyAccessLabel = () => {
return ( return (
<Col start="xs" middle="xs" className={classes.container}> <Col start="xs" middle="xs" className={classes.container}>
<Paragraph size="xs" className={classes.counter}> <Paragraph size="xs" className={classes.text}>
Early access {formattedNetwork}
</Paragraph> </Paragraph>
</Col> </Col>
) )

View File

@ -1,11 +0,0 @@
// @flow
import { storiesOf } from '@storybook/react'
import * as React from 'react'
import styles from '~/components/layout/PageFrame/index.scss'
import Component from './index'
const FrameDecorator = (story) => <div className={styles.frame}>{story()}</div>
storiesOf('Components', module)
.addDecorator(FrameDecorator)
.add('Loader', () => <Component />)

View File

@ -3,6 +3,7 @@ import * as React from 'react'
import cn from 'classnames' import cn from 'classnames'
import Modal from '@material-ui/core/Modal' import Modal from '@material-ui/core/Modal'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import { sm } from '~/theme/variables'
type Props = { type Props = {
title: string, title: string,
@ -27,7 +28,7 @@ const styles = () => ({
top: '120px', top: '120px',
width: '500px', width: '500px',
height: '530px', height: '530px',
borderRadius: '8px', borderRadius: sm,
backgroundColor: '#ffffff', backgroundColor: '#ffffff',
boxShadow: '0 0 5px 0 rgba(74, 85, 121, 0.5)', boxShadow: '0 0 5px 0 rgba(74, 85, 121, 0.5)',
'&:focus': { '&:focus': {

View File

@ -16,6 +16,7 @@ export type Column = {
custom: boolean, // If content will be rendered by user manually custom: boolean, // If content will be rendered by user manually
width?: number, width?: number,
static?: boolean, // If content can't be sorted by values in the column static?: boolean, // If content can't be sorted by values in the column
style?: Object, // if you want to add some custom styling to the column
} }
export const cellWidth = (width: number | typeof undefined) => { export const cellWidth = (width: number | typeof undefined) => {
@ -56,12 +57,15 @@ class GnoTableHead extends React.PureComponent<Props> {
sortDirection={orderBy === column.id ? order : false} sortDirection={orderBy === column.id ? order : false}
> >
{column.static ? ( {column.static ? (
column.label <div style={column.style}>
{column.label}
</div>
) : ( ) : (
<TableSortLabel <TableSortLabel
active={orderBy === column.id} active={orderBy === column.id}
direction={order} direction={order}
onClick={this.changeSort(column.id, column.order)} onClick={this.changeSort(column.id, column.order)}
style={column.style}
> >
{column.label} {column.label}
</TableSortLabel> </TableSortLabel>

View File

@ -1,6 +1,5 @@
// @flow // @flow
import * as React from 'react' import * as React from 'react'
import classNames from 'classnames'
import { List } from 'immutable' import { List } from 'immutable'
import Table from '@material-ui/core/Table' import Table from '@material-ui/core/Table'
import TableBody from '@material-ui/core/TableBody' import TableBody from '@material-ui/core/TableBody'
@ -10,7 +9,7 @@ import TablePagination from '@material-ui/core/TablePagination'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import { type Order, stableSort, getSorting } from '~/components/Table/sorting' import { type Order, stableSort, getSorting } from '~/components/Table/sorting'
import TableHead, { type Column } from '~/components/Table/TableHead' import TableHead, { type Column } from '~/components/Table/TableHead'
import { xl } from '~/theme/variables' import { xxl, xl, sm } from '~/theme/variables'
type Props<K> = { type Props<K> = {
label: string, label: string,
@ -39,11 +38,12 @@ type State = {
const styles = { const styles = {
root: { root: {
backgroundColor: 'white', backgroundColor: 'white',
borderRadius: '8px', borderTopRightRadius: sm,
borderTopLeftRadius: sm,
boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)',
}, },
selectRoot: { selectRoot: {
lineHeight: '40px', lineHeight: xxl,
backgroundColor: 'white', backgroundColor: 'white',
}, },
white: { white: {
@ -53,14 +53,11 @@ const styles = {
backgroundColor: 'white', backgroundColor: 'white',
boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)',
marginBottom: xl, marginBottom: xl,
borderRadius: '8px', borderBottomRightRadius: sm,
borderTopLeftRadius: 0, borderBottomLeftRadius: sm,
borderTopRightRadius: 0,
}, },
loader: { loader: {
alignItems: 'center', boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)',
justifyContent: 'center',
backgroundColor: 'white',
}, },
} }
@ -125,6 +122,13 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
getEmptyStyle = (emptyRows: number) => ({ getEmptyStyle = (emptyRows: number) => ({
height: FIXED_HEIGHT * emptyRows, height: FIXED_HEIGHT * emptyRows,
borderTopRightRadius: sm,
borderTopLeftRadius: sm,
backgroundColor: 'white',
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}) })
handleChangePage = (e: SyntheticInputEvent<HTMLInputElement>, page: number) => { handleChangePage = (e: SyntheticInputEvent<HTMLInputElement>, page: number) => {
@ -171,7 +175,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
sortedData = sortedData.slice(page * displayRows, page * displayRows + displayRows) sortedData = sortedData.slice(page * displayRows, page * displayRows + displayRows)
} }
const emptyRows = displayRows - Math.min(displayRows, data.length - page * displayRows) const emptyRows = displayRows - Math.min(displayRows, data.size - page * displayRows)
const isEmpty = size === 0 const isEmpty = size === 0
return ( return (
@ -184,7 +188,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
)} )}
{isEmpty && ( {isEmpty && (
<Row <Row
className={classNames(classes.loader, !noBorder && classes.root)} className={classes.loader}
style={this.getEmptyStyle(emptyRows + 1)} style={this.getEmptyStyle(emptyRows + 1)}
> >
<CircularProgress size={60} /> <CircularProgress size={60} />

View File

@ -19,6 +19,7 @@ type Props = {
testId?: string, testId?: string,
validators?: Function[], validators?: Function[],
inputAdornment?: React.Element, inputAdornment?: React.Element,
defaultValue?: string,
} }
const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name) const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
@ -35,6 +36,7 @@ const AddressInput = ({
testId, testId,
inputAdornment, inputAdornment,
validators = [], validators = [],
defaultValue,
}: Props): React.Element<*> => ( }: Props): React.Element<*> => (
<> <>
<Field <Field
@ -51,6 +53,7 @@ const AddressInput = ({
text={text} text={text}
className={className} className={className}
testId={testId} testId={testId}
defaultValue={defaultValue}
/> />
<OnChange name={name}> <OnChange name={name}>
{async (value) => { {async (value) => {

View File

@ -13,7 +13,7 @@ type Props = {
margin?: Size, margin?: Size,
padding?: Size, padding?: Size,
justify?: 'center' | 'right' | 'left' | 'space-around', justify?: 'center' | 'right' | 'left' | 'space-around',
children: React.Node, children?: React.Node,
className?: string, className?: string,
} }

View File

@ -1,16 +0,0 @@
// @flow
import { storiesOf } from '@storybook/react'
import * as React from 'react'
import { host } from 'storybook-host'
import Component from './index'
storiesOf('Components', module)
.addDecorator(
host({
title: 'Hairline',
align: 'center',
height: 5,
width: '100%',
}),
)
.add('Hairline', () => <Component />)

View File

@ -2,12 +2,12 @@
display: flex; display: flex;
flex: 1 0 auto; flex: 1 0 auto;
flex-direction: column; flex-direction: column;
padding: 80px 200px 0px 200px; padding: 135px 200px 0px 200px;
} }
@media only screen and (max-width: $(screenLg)px) { @media only screen and (max-width: $(screenLg)px) {
.page { .page {
padding: 80px $lg 0px $lg; padding: 135px $lg 0px $lg;
} }
} }

View File

@ -17,6 +17,7 @@ import ErrorIcon from './assets/error.svg'
import InfoIcon from './assets/info.svg' import InfoIcon from './assets/info.svg'
import CookiesBanner from '~/components/CookiesBanner' import CookiesBanner from '~/components/CookiesBanner'
import styles from './index.scss' import styles from './index.scss'
import { fontColor } from '~/theme/variables'
const notificationStyles = { const notificationStyles = {
success: { success: {
@ -24,7 +25,7 @@ const notificationStyles = {
fontFamily: 'Averta', fontFamily: 'Averta',
fontSize: '14px', fontSize: '14px',
lineHeight: 1.43, lineHeight: 1.43,
color: '#001428', color: fontColor,
minHeight: '58px', minHeight: '58px',
boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)', boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)',
}, },
@ -33,7 +34,7 @@ const notificationStyles = {
fontFamily: 'Averta', fontFamily: 'Averta',
fontSize: '14px', fontSize: '14px',
lineHeight: 1.43, lineHeight: 1.43,
color: '#001428', color: fontColor,
minHeight: '58px', minHeight: '58px',
boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)', boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)',
}, },
@ -42,7 +43,7 @@ const notificationStyles = {
fontFamily: 'Averta', fontFamily: 'Averta',
fontSize: '14px', fontSize: '14px',
lineHeight: 1.43, lineHeight: 1.43,
color: '#001428', color: fontColor,
minHeight: '58px', minHeight: '58px',
boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)', boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)',
}, },
@ -51,7 +52,7 @@ const notificationStyles = {
fontFamily: 'Averta', fontFamily: 'Averta',
fontSize: '14px', fontSize: '14px',
lineHeight: 1.43, lineHeight: 1.43,
color: '#001428', color: fontColor,
minHeight: '58px', minHeight: '58px',
boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)', boxShadow: '0 0 10px 0 rgba(212, 212, 211, 0.59)',
}, },

View File

@ -52,6 +52,9 @@ export const getTxServiceHost = () => {
export const getTxServiceUriFrom = (safeAddress: string) => export const getTxServiceUriFrom = (safeAddress: string) =>
`safes/${safeAddress}/transactions/` `safes/${safeAddress}/transactions/`
export const getIncomingTxServiceUriTo = (safeAddress: string) =>
`safes/${safeAddress}/incoming-transactions/`
export const getRelayUrl = () => getConfig()[RELAY_API_URL] export const getRelayUrl = () => getConfig()[RELAY_API_URL]
export const signaturesViaMetamask = () => { export const signaturesViaMetamask = () => {
@ -69,3 +72,5 @@ export const getIntercomId = () =>
process.env.REACT_APP_ENV === "production" process.env.REACT_APP_ENV === "production"
? process.env.REACT_APP_INTERCOM_ID ? process.env.REACT_APP_INTERCOM_ID
: "plssl1fl" : "plssl1fl"
export const getExchangeRatesUrl = () => 'https://api.exchangeratesapi.io/latest'

View File

@ -8,6 +8,7 @@ import loadActiveTokens from '~/logic/tokens/store/actions/loadActiveTokens'
import loadDefaultSafe from '~/routes/safe/store/actions/loadDefaultSafe' import loadDefaultSafe from '~/routes/safe/store/actions/loadDefaultSafe'
import loadSafesFromStorage from '~/routes/safe/store/actions/loadSafesFromStorage' import loadSafesFromStorage from '~/routes/safe/store/actions/loadSafesFromStorage'
import { store } from '~/store' import { store } from '~/store'
import verifyRecurringUser from '~/utils/verifyRecurringUser'
BigNumber.set({ EXPONENTIAL_AT: [-7, 255] }) BigNumber.set({ EXPONENTIAL_AT: [-7, 255] })
@ -21,6 +22,7 @@ if (process.env.NODE_ENV !== 'production') {
store.dispatch(loadActiveTokens()) store.dispatch(loadActiveTokens())
store.dispatch(loadSafesFromStorage()) store.dispatch(loadSafesFromStorage())
store.dispatch(loadDefaultSafe()) store.dispatch(loadDefaultSafe())
verifyRecurringUser()
const root = document.getElementById('root') const root = document.getElementById('root')

View File

@ -55,7 +55,7 @@ export const decodeParamsFromSafeMethod = async (data: string) => {
case '0xe318b52b': case '0xe318b52b':
return { return {
methodName: METHOD_TO_ID[methodId], methodName: METHOD_TO_ID[methodId],
args: web3.eth.abi.decodeParameters(['address', 'address', 'address'], params), args: web3.eth.abi.decodeParameters(['uint', 'address', 'address'], params),
} }
// addOwnerWithThreshold // addOwnerWithThreshold

View File

@ -0,0 +1,19 @@
// @flow
import axios from 'axios'
import { getExchangeRatesUrl } from '~/config'
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
const fetchCurrenciesRates = async (baseCurrency: AVAILABLE_CURRENCIES, targetCurrencyValue: AVAILABLE_CURRENCIES): Promise<number> => {
let rate = 0
const url = `${getExchangeRatesUrl()}?base=${baseCurrency}&symbols=${targetCurrencyValue}`
const result = await axios.get(url)
if (result && result.data) {
const { rates } = result.data
rate = rates[targetCurrencyValue] ? rates[targetCurrencyValue] : 0
}
return rate
}
export default fetchCurrenciesRates

View File

@ -0,0 +1,15 @@
// @flow
import axios from 'axios'
import { getTxServiceHost } from '~/config'
const fetchTokenCurrenciesBalances = (safeAddress: string) => {
if (!safeAddress) {
return null
}
const apiUrl = getTxServiceHost()
const url = `${apiUrl}safes/${safeAddress}/balances/usd`
return axios.get(url)
}
export default fetchTokenCurrenciesBalances

View File

@ -0,0 +1,35 @@
// @flow
import { Dispatch as ReduxDispatch } from 'redux'
import { List } from 'immutable'
import type { GlobalState } from '~/store'
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
import fetchCurrenciesRates from '~/logic/currencyValues/api/fetchCurrenciesRates'
import { currencyValuesListSelector } from '~/logic/currencyValues/store/selectors'
import { setCurrencyBalances } from '~/logic/currencyValues/store/actions/setCurrencyBalances'
// eslint-disable-next-line max-len
const fetchCurrencySelectedValue = (currencyValueSelected: AVAILABLE_CURRENCIES) => async (dispatch: ReduxDispatch<GlobalState>, getState: Function) => {
const state = getState()
const currencyBalancesList = currencyValuesListSelector(state)
const selectedCurrencyRateInBaseCurrency = await fetchCurrenciesRates(AVAILABLE_CURRENCIES.USD, currencyValueSelected)
const newList = []
for (const currencyValue of currencyBalancesList) {
const { balanceInBaseCurrency } = currencyValue
const balanceInSelectedCurrency = balanceInBaseCurrency * selectedCurrencyRateInBaseCurrency
const updatedValue = currencyValue.merge({
currencyName: currencyValueSelected,
balanceInSelectedCurrency,
})
newList.push(updatedValue)
}
dispatch(setCurrencyBalances(List(newList)))
}
export default fetchCurrencySelectedValue

View File

@ -0,0 +1,43 @@
// @flow
import type { Dispatch as ReduxDispatch } from 'redux'
import { List } from 'immutable'
import type { GlobalState } from '~/store'
import { setCurrencyBalances } from '~/logic/currencyValues/store/actions/setCurrencyBalances'
import { AVAILABLE_CURRENCIES, makeBalanceCurrency } from '~/logic/currencyValues/store/model/currencyValues'
import { setCurrencySelected } from '~/logic/currencyValues/store/actions/setCurrencySelected'
import fetchTokenCurrenciesBalances from '~/logic/currencyValues/api/fetchTokenCurrenciesBalances'
import { loadFromStorage } from '~/utils/storage'
import fetchCurrencySelectedValue from '~/logic/currencyValues/store/actions/fetchCurrencySelectedValue'
import { CURRENCY_SELECTED_KEY } from '~/logic/currencyValues/store/actions/saveCurrencySelected'
export const fetchCurrencyValues = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
try {
const tokensFetched = await fetchTokenCurrenciesBalances(safeAddress)
// eslint-disable-next-line max-len
const currencyList = List(tokensFetched.data.filter((currencyBalance) => currencyBalance.balanceUsd).map((currencyBalance) => {
const { balanceUsd, tokenAddress } = currencyBalance
return makeBalanceCurrency({
currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : null,
tokenAddress,
balanceInBaseCurrency: balanceUsd,
balanceInSelectedCurrency: balanceUsd,
})
}))
dispatch(setCurrencyBalances(currencyList))
const currencyStored = await loadFromStorage(CURRENCY_SELECTED_KEY)
if (!currencyStored) {
return dispatch(setCurrencySelected(AVAILABLE_CURRENCIES.USD))
}
const { currencyValueSelected } = currencyStored
dispatch(fetchCurrencySelectedValue(currencyValueSelected))
dispatch(setCurrencySelected(currencyValueSelected))
} catch (err) {
console.error('Error fetching tokens price list', err)
}
return Promise.resolve()
}
export default fetchCurrencyValues

View File

@ -0,0 +1,18 @@
// @flow
import { Dispatch as ReduxDispatch } from 'redux'
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
import type { GlobalState } from '~/store'
import { saveToStorage } from '~/utils/storage'
import { setCurrencySelected } from '~/logic/currencyValues/store/actions/setCurrencySelected'
export const CURRENCY_SELECTED_KEY = 'CURRENCY_SELECTED_KEY'
const saveCurrencySelected = (currencySelected: AVAILABLE_CURRENCIES) => async (dispatch: ReduxDispatch<GlobalState>) => {
await saveToStorage(CURRENCY_SELECTED_KEY, { currencyValueSelected: currencySelected })
dispatch(setCurrencySelected(currencySelected))
}
export default saveCurrencySelected

View File

@ -0,0 +1,10 @@
// @flow
import { Map } from 'immutable'
import { createAction } from 'redux-actions'
import type { CurrencyValues, CurrencyValuesProps } from '~/logic/currencyValues/store/model/currencyValues'
export const SET_CURRENCY_BALANCES = 'SET_CURRENCY_BALANCES'
// eslint-disable-next-line max-len
export const setCurrencyBalances = createAction<string, *>(SET_CURRENCY_BALANCES, (currencyBalances: Map<string, CurrencyValues>): CurrencyValuesProps => ({ currencyBalances }))

View File

@ -0,0 +1,13 @@
// @flow
import { createAction } from 'redux-actions'
import type {
CurrencyValuesProps,
} from '~/logic/currencyValues/store/model/currencyValues'
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
export const SET_CURRENT_CURRENCY = 'SET_CURRENT_CURRENCY'
// eslint-disable-next-line max-len
export const setCurrencySelected = createAction<string, *>(SET_CURRENT_CURRENCY, (currencyValueSelected: AVAILABLE_CURRENCIES): CurrencyValuesProps => ({ currencyValueSelected }))

View File

@ -0,0 +1,61 @@
// @flow
import type { RecordOf } from 'immutable'
import { Record } from 'immutable'
export const AVAILABLE_CURRENCIES = {
USD: 'USD',
EUR: 'EUR',
CAD: 'CAD',
HKD: 'HKD',
ISK: 'ISK',
PHP: 'PHP',
DKK: 'DKK',
HUF: 'HUF',
CZK: 'CZK',
AUD: 'AUD',
RON: 'RON',
SEK: 'SEK',
IDR: 'IDR',
INR: 'INR',
BRL: 'BRL',
RUB: 'RUB',
HRK: 'HRK',
JPY: 'JPY',
THB: 'THB',
CHF: 'CHF',
SGD: 'SGD',
PLN: 'PLN',
BGN: 'BGN',
TRY: 'TRY',
CNY: 'CNY',
NOK: 'NOK',
NZD: 'NZD',
ZAR: 'ZAR',
MXN: 'MXN',
ILS: 'ILS',
GBP: 'GBP',
KRW: 'KRW',
MYR: 'MYR',
}
export type BalanceCurrencyType = {
currencyName: AVAILABLE_CURRENCIES;
tokenAddress: string,
balanceInBaseCurrency: string,
balanceInSelectedCurrency: string,
}
export const makeBalanceCurrency = Record({
currencyName: '',
tokenAddress: '',
balanceInBaseCurrency: '',
balanceInSelectedCurrency: '',
})
export type CurrencyValuesProps = {
currencyValueSelected: AVAILABLE_CURRENCIES;
currencyValuesList: BalanceCurrencyType[]
}
export type CurrencyValues = RecordOf<CurrencyValuesProps>

View File

@ -0,0 +1,28 @@
// @flow
import { Map } from 'immutable'
import { handleActions, type ActionType } from 'redux-actions'
import { SET_CURRENCY_BALANCES } from '../actions/setCurrencyBalances'
import type { State } from '~/logic/tokens/store/reducer/tokens'
import { SET_CURRENT_CURRENCY } from '~/logic/currencyValues/store/actions/setCurrencySelected'
export const CURRENCY_VALUES_KEY = 'currencyValues'
export default handleActions<State, *>(
{
[SET_CURRENCY_BALANCES]: (state: State, action: ActionType<Function>): State => {
const { currencyBalances } = action.payload
const newState = state.set('currencyBalances', currencyBalances)
return newState
},
[SET_CURRENT_CURRENCY]: (state: State, action: ActionType<Function>): State => {
const { currencyValueSelected } = action.payload
const newState = state.set('currencyValueSelected', currencyValueSelected)
return newState
},
},
Map(),
)

View File

@ -0,0 +1,9 @@
// @flow
import { List } from 'immutable'
import { type GlobalState } from '~/store'
import { CURRENCY_VALUES_KEY } from '~/logic/currencyValues/store/reducer/currencyValues'
export const currencyValuesListSelector = (state: GlobalState) => (state[CURRENCY_VALUES_KEY].get('currencyBalances') ? state[CURRENCY_VALUES_KEY].get('currencyBalances') : List([]))
export const currentCurrencySelector = (state: GlobalState) => state[CURRENCY_VALUES_KEY].get('currencyValueSelected')

View File

@ -10,6 +10,7 @@ import { type Notification, NOTIFICATIONS } from './notificationTypes'
export type NotificationsQueue = { export type NotificationsQueue = {
beforeExecution: Notification | null, beforeExecution: Notification | null,
pendingExecution: Notification | null, pendingExecution: Notification | null,
waitingConfirmation: Notification | null,
afterExecution: { afterExecution: {
noMoreConfirmationsNeeded: Notification | null, noMoreConfirmationsNeeded: Notification | null,
moreConfirmationsNeeded: Notification | null, moreConfirmationsNeeded: Notification | null,
@ -29,6 +30,15 @@ const standardTxNotificationsQueue: NotificationsQueue = {
afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG, afterExecutionError: NOTIFICATIONS.TX_FAILED_MSG,
} }
const waitingTransactionNotificationsQueue: NotificationsQueue = {
beforeExecution: null,
pendingExecution: null,
afterRejection: null,
waitingConfirmation: NOTIFICATIONS.TX_WAITING_MSG,
afterExecution: null,
afterExecutionError: null,
}
const confirmationTxNotificationsQueue: NotificationsQueue = { const confirmationTxNotificationsQueue: NotificationsQueue = {
beforeExecution: NOTIFICATIONS.SIGN_TX_MSG, beforeExecution: NOTIFICATIONS.SIGN_TX_MSG,
pendingExecution: NOTIFICATIONS.TX_CONFIRMATION_PENDING_MSG, pendingExecution: NOTIFICATIONS.TX_CONFIRMATION_PENDING_MSG,
@ -123,6 +133,10 @@ export const getNotificationsFromTxType = (txType: string) => {
notificationsQueue = ownerNameChangeNotificationsQueue notificationsQueue = ownerNameChangeNotificationsQueue
break break
} }
case TX_NOTIFICATION_TYPES.WAITING_TX: {
notificationsQueue = waitingTransactionNotificationsQueue
break
}
default: { default: {
notificationsQueue = defaultNotificationsQueue notificationsQueue = defaultNotificationsQueue
break break
@ -132,10 +146,12 @@ export const getNotificationsFromTxType = (txType: string) => {
return notificationsQueue return notificationsQueue
} }
export const enhanceSnackbarForAction = (notification: Notification) => ({ export const enhanceSnackbarForAction = (notification: Notification, key?: string, onClick?: Function) => ({
...notification, ...notification,
key,
options: { options: {
...notification.options, ...notification.options,
onClick,
action: (key: number) => ( action: (key: number) => (
<IconButton onClick={() => store.dispatch(closeSnackbarAction({ key }))}> <IconButton onClick={() => store.dispatch(closeSnackbarAction({ key }))}>
<IconClose /> <IconClose />

View File

@ -14,6 +14,7 @@ export type Variant = 'success' | 'error' | 'warning' | 'info'
export type Notification = { export type Notification = {
message: string, message: string,
key?: string,
options: { options: {
variant: Variant, variant: Variant,
persist: boolean, persist: boolean,
@ -38,6 +39,8 @@ export type Notifications = {
TX_EXECUTED_MSG: Notification, TX_EXECUTED_MSG: Notification,
TX_EXECUTED_MORE_CONFIRMATIONS_MSG: Notification, TX_EXECUTED_MORE_CONFIRMATIONS_MSG: Notification,
TX_FAILED_MSG: Notification, TX_FAILED_MSG: Notification,
TX_WAITING_MSG: Notification,
TX_INCOMING_MSG: Notification,
// Approval Transactions // Approval Transactions
TX_CONFIRMATION_PENDING_MSG: Notification, TX_CONFIRMATION_PENDING_MSG: Notification,
@ -122,6 +125,20 @@ export const NOTIFICATIONS: Notifications = {
message: 'Transaction failed', message: 'Transaction failed',
options: { variant: ERROR, persist: false, autoHideDuration: longDuration }, options: { variant: ERROR, persist: false, autoHideDuration: longDuration },
}, },
TX_WAITING_MSG: {
message: 'A pending transaction requires your confirmation!',
key: 'TX_WAITING_MSG',
options: {
variant: WARNING, persist: true, preventDuplicate: true,
},
},
TX_INCOMING_MSG: {
message: 'Incoming transfer: ',
key: 'TX_INCOMING_MSG',
options: {
variant: SUCCESS, persist: false, autoHideDuration: longDuration, preventDuplicate: true,
},
},
// Approval Transactions // Approval Transactions
TX_CONFIRMATION_PENDING_MSG: { TX_CONFIRMATION_PENDING_MSG: {

View File

@ -11,9 +11,8 @@ const addSnackbar = createAction<string, *>(ENQUEUE_SNACKBAR)
const enqueueSnackbar = (notification: NotificationProps) => (dispatch: ReduxDispatch<GlobalState>) => { const enqueueSnackbar = (notification: NotificationProps) => (dispatch: ReduxDispatch<GlobalState>) => {
const newNotification = { const newNotification = {
...notification, ...notification,
key: new Date().getTime(), key: notification.key || new Date().getTime(),
} }
dispatch(addSnackbar(newNotification)) dispatch(addSnackbar(newNotification))
} }

View File

@ -0,0 +1,43 @@
// @flow
import { Map, List } from 'immutable'
import type { Transaction } from '~/routes/safe/store/models/transaction'
export const getAwaitingTransactions = (
allTransactions: Map<string, List<Transaction>>,
userAccount: string,
): Map<string, List<Transaction>> => {
if (!allTransactions) {
return Map({})
}
const allAwaitingTransactions = allTransactions.map((safeTransactions) => {
const nonCancelledTransactions = safeTransactions.filter(
(transaction: Transaction) => {
// If transactions are not executed, but there's a transaction with the same nonce EXECUTED later
// it means that the transaction was cancelled (Replaced) and shouldn't get executed
if (!transaction.isExecuted) {
const replacementTransaction = safeTransactions.findLast(
(tx) => tx.isExecuted && tx.nonce === transaction.nonce,
)
if (replacementTransaction) {
// eslint-disable-next-line no-param-reassign
transaction = transaction.set('cancelled', true)
}
}
// The transaction is not executed and is not cancelled, so it's still waiting confirmations
if (!transaction.executionTxHash && !transaction.cancelled) {
// Then we check if the waiting confirmations are not from the current user, otherwise, filters this transaction
const transactionWaitingUser = transaction.confirmations.filter(
(confirmation) => confirmation.owner && confirmation.owner.address !== userAccount,
)
return transactionWaitingUser.size > 0
}
return false
},
)
return nonCancelledTransactions
})
return allAwaitingTransactions
}

View File

@ -0,0 +1,11 @@
// @flow
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { getIncomingTxServiceUriTo, getTxServiceHost } from '~/config'
export const buildIncomingTxServiceUrl = (safeAddress: string) => {
const host = getTxServiceHost()
const address = getWeb3().utils.toChecksumAddress(safeAddress)
const base = getIncomingTxServiceUriTo(address)
return `${host}${base}`
}

View File

@ -4,6 +4,7 @@ export type NotifiedTransaction = {
STANDARD_TX: string, STANDARD_TX: string,
CONFIRMATION_TX: string, CONFIRMATION_TX: string,
CANCELLATION_TX: string, CANCELLATION_TX: string,
WAITING_TX: string,
SETTINGS_CHANGE_TX: string, SETTINGS_CHANGE_TX: string,
SAFE_NAME_CHANGE_TX: string, SAFE_NAME_CHANGE_TX: string,
OWNER_NAME_CHANGE_TX: string, OWNER_NAME_CHANGE_TX: string,
@ -13,6 +14,7 @@ export const TX_NOTIFICATION_TYPES: NotifiedTransaction = {
STANDARD_TX: 'STANDARD_TX', STANDARD_TX: 'STANDARD_TX',
CONFIRMATION_TX: 'CONFIRMATION_TX', CONFIRMATION_TX: 'CONFIRMATION_TX',
CANCELLATION_TX: 'CANCELLATION_TX', CANCELLATION_TX: 'CANCELLATION_TX',
WAITING_TX: 'WAITING_TX',
SETTINGS_CHANGE_TX: 'SETTINGS_CHANGE_TX', SETTINGS_CHANGE_TX: 'SETTINGS_CHANGE_TX',
SAFE_NAME_CHANGE_TX: 'SAFE_NAME_CHANGE_TX', SAFE_NAME_CHANGE_TX: 'SAFE_NAME_CHANGE_TX',
OWNER_NAME_CHANGE_TX: 'OWNER_NAME_CHANGE_TX', OWNER_NAME_CHANGE_TX: 'OWNER_NAME_CHANGE_TX',

View File

@ -2,7 +2,6 @@
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json' import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
import { getWeb3 } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { type Operation } from '~/logic/safe/transactions' import { type Operation } from '~/logic/safe/transactions'
import { ZERO_ADDRESS } from '~/logic/wallets/ethAddresses'
export const CALL = 0 export const CALL = 0
export const TX_TYPE_EXECUTION = 'execution' export const TX_TYPE_EXECUTION = 'execution'

View File

@ -1,4 +1,8 @@
// @flow // @flow
import { List } from 'immutable'
import type { Safe } from '~/routes/safe/store/models/safe'
import type { Owner } from '~/routes/safe/store/models/owner'
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
export const sameAddress = (firstAddress: string, secondAddress: string): boolean => { export const sameAddress = (firstAddress: string, secondAddress: string): boolean => {
@ -13,10 +17,32 @@ export const sameAddress = (firstAddress: string, secondAddress: string): boolea
return firstAddress.toLowerCase() === secondAddress.toLowerCase() return firstAddress.toLowerCase() === secondAddress.toLowerCase()
} }
export const shortVersionOf = (address: string, cut: number) => { export const shortVersionOf = (value: string, cut: number) => {
const final = 42 - cut if (!value) {
return 'Unknown'
}
if (!address) return 'Unknown address' const final = value.length - cut
if (address.length < final) return address if (value.length < final) {
return `${address.substring(0, cut)}...${address.substring(final)}` return value
}
return `${value.substring(0, cut)}...${value.substring(final)}`
}
export const isUserOwner = (safe: Safe, userAccount: string): boolean => {
if (!safe) {
return false
}
if (!userAccount) {
return false
}
const { owners }: List<Owner> = safe
if (!owners) {
return false
}
return owners.find((owner: Owner) => sameAddress(owner.address, userAccount)) !== undefined
} }

View File

@ -10,7 +10,7 @@ import ReviewInformation from '~/routes/load/components/ReviewInformation'
import OwnerList from '~/routes/load/components/OwnerList' import OwnerList from '~/routes/load/components/OwnerList'
import DetailsForm, { safeFieldsValidation } from '~/routes/load/components/DetailsForm' import DetailsForm, { safeFieldsValidation } from '~/routes/load/components/DetailsForm'
import { history } from '~/store' import { history } from '~/store'
import { secondary } from '~/theme/variables' import { secondary, sm } from '~/theme/variables'
import { type SelectorProps } from '~/routes/load/container/selector' import { type SelectorProps } from '~/routes/load/container/selector'
const getSteps = () => ['Name and address', 'Owners', 'Review'] const getSteps = () => ['Name and address', 'Owners', 'Review']
@ -21,7 +21,7 @@ export type LayoutProps = SelectorProps & {
const iconStyle = { const iconStyle = {
color: secondary, color: secondary,
padding: '8px', padding: sm,
marginRight: '5px', marginRight: '5px',
} }

View File

@ -9,28 +9,62 @@ import Row from '~/components/layout/Row'
import Review from '~/routes/open/components/ReviewInformation' import Review from '~/routes/open/components/ReviewInformation'
import SafeNameField from '~/routes/open/components/SafeNameForm' import SafeNameField from '~/routes/open/components/SafeNameForm'
import SafeOwnersFields from '~/routes/open/components/SafeOwnersConfirmationsForm' import SafeOwnersFields from '~/routes/open/components/SafeOwnersConfirmationsForm'
import { getOwnerNameBy, getOwnerAddressBy, FIELD_CONFIRMATIONS } from '~/routes/open/components/fields' import {
getOwnerNameBy,
getOwnerAddressBy,
FIELD_CONFIRMATIONS,
FIELD_SAFE_NAME,
} from '~/routes/open/components/fields'
import { initContracts } from '~/logic/contracts/safeContracts'
import { history } from '~/store' import { history } from '~/store'
import { secondary } from '~/theme/variables' import { secondary, sm } from '~/theme/variables'
import type { SafePropsType } from '~/routes/open/container/Open'
import Welcome from '~/routes/welcome/components/Layout'
const { useEffect } = React
const getSteps = () => ['Name', 'Owners and confirmations', 'Review'] const getSteps = () => ['Name', 'Owners and confirmations', 'Review']
const initialValuesFrom = (userAccount: string) => ({
[getOwnerNameBy(0)]: 'My Wallet', const initialValuesFrom = (userAccount: string, safeProps?: SafePropsType) => {
[getOwnerAddressBy(0)]: userAccount, if (!safeProps) {
[FIELD_CONFIRMATIONS]: '1', return ({
}) [getOwnerNameBy(0)]: 'My Wallet',
[getOwnerAddressBy(0)]: userAccount,
[FIELD_CONFIRMATIONS]: '1',
})
}
let obj = {}
const {
ownerAddresses, ownerNames, threshold, name,
} = safeProps
// eslint-disable-next-line no-restricted-syntax
for (const [index, value] of ownerAddresses.entries()) {
const safeName = ownerNames[index] ? ownerNames[index] : 'My Wallet'
obj = {
...obj,
[getOwnerAddressBy(index)]: value,
[getOwnerNameBy(index)]: safeName,
}
}
return ({
...obj,
[FIELD_CONFIRMATIONS]: threshold || '1',
[FIELD_SAFE_NAME]: name,
})
}
type Props = { type Props = {
provider: string, provider: string,
userAccount: string, userAccount: string,
network: string, network: string,
onCallSafeContractSubmit: (values: Object) => Promise<void>, onCallSafeContractSubmit: (values: Object) => Promise<void>,
safeProps?: SafePropsType,
} }
const iconStyle = { const iconStyle = {
color: secondary, color: secondary,
padding: '8px', padding: sm,
marginRight: '5px', marginRight: '5px',
} }
@ -44,11 +78,21 @@ const formMutators = {
}, },
} }
const Layout = ({
provider, userAccount, onCallSafeContractSubmit, network, const Layout = (props: Props) => {
}: Props) => { const {
provider, userAccount, onCallSafeContractSubmit, network, safeProps,
} = props
useEffect(() => {
if (provider) {
initContracts()
}
}, [provider])
const steps = getSteps() const steps = getSteps()
const initialValues = initialValuesFrom(userAccount)
const initialValues = initialValuesFrom(userAccount, safeProps)
return ( return (
<> <>
@ -75,7 +119,7 @@ const Layout = ({
</Stepper> </Stepper>
</Block> </Block>
) : ( ) : (
<div>No web3 provider detected</div> <Welcome provider={provider} isOldMultisigMigration />
)} )}
</> </>
) )

View File

@ -1,68 +0,0 @@
// @flow
import { storiesOf } from '@storybook/react'
import { State, Store } from '@sambego/storybook-state'
import * as React from 'react'
import styles from '~/components/layout/PageFrame/index.scss'
import { getAccountsFrom, getThresholdFrom } from '~/routes/open/utils/safeDataExtractor'
import { getProviderInfo } from '~/logic/wallets/getWeb3'
import { sleep } from '~/utils/timer'
import Component from './Layout'
const FrameDecorator = (story) => <div className={styles.frame}>{story()}</div>
const store = new Store({
safeAddress: '',
safeTx: '',
})
storiesOf('Routes /open', module)
.addDecorator(FrameDecorator)
.add('Open Safe with all props set', () => {
getProviderInfo()
const provider = 'METAMASK'
const userAccount = '0x03db1a8b26d08df23337e9276a36b474510f0023'
const onCallSafeContractSubmit = async (values: Object): Promise<void> => {
const accounts = getAccountsFrom(values)
const numConfirmations = getThresholdFrom(values)
const data = {
userAccount,
accounts,
requiredConfirmations: numConfirmations,
}
// eslint-disable-next-line
console.log(`Generating and sending a eth tx based on: ${JSON.stringify(data, null, 2)}`)
await sleep(3000)
store.set({
safeAddress: '0x03db1a8b26d08df23337e9276a36b474510f0025',
// eslint-disable-next-line
safeTx: {
transactionHash: '0x4603de1ab6a92b4ee1fd67189089f5c02f5df5d135bf85af84083c27808c0544',
transactionIndex: 0,
blockHash: '0x593ce7d85fef2a492e8f759f485c8b66ff803773e77182c68dd45c439b7a956d',
blockNumber: 19,
gasUsed: 3034193,
cumulativeGasUsed: 3034193,
contractAddress: '0xfddda33736fb95b587cbfecc1ff4a50f717adc00',
logs: [],
status: '0x01',
logsBloom:
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
},
})
}
return (
<State store={store}>
<Component
network="rinkeby"
provider={provider}
userAccount={userAccount}
safeAddress={store.get('safeAddress')}
safeTx={store.get('safeTx')}
onCallSafeContractSubmit={onCallSafeContractSubmit}
/>
</State>
)
})

View File

@ -12,6 +12,7 @@ import { sm, secondary } from '~/theme/variables'
type Props = { type Props = {
classes: Object, classes: Object,
safeName?: string,
} }
const styles = () => ({ const styles = () => ({
@ -32,12 +33,13 @@ const styles = () => ({
}, },
}) })
const SafeName = ({ classes }: Props) => ( const SafeName = ({ classes, safeName }: Props) => (
<> <>
<Block margin="lg"> <Block margin="lg">
<Paragraph noMargin size="md" color="primary"> <Paragraph noMargin size="md" color="primary">
You are about to create a new Gnosis Safe wallet with one or more owners. First, let&apos;s give your new wallet You are about to create a new Gnosis Safe wallet with one or more owners. First, let&apos;s give your new
a name. This name is only stored locally and will never be shared with Gnosis or any third parties. wallet
a name. This name is only stored locally and will never be shared with Gnosis or any third parties.
</Paragraph> </Paragraph>
</Block> </Block>
<Block margin="lg" className={classes.root}> <Block margin="lg" className={classes.root}>
@ -48,23 +50,24 @@ const SafeName = ({ classes }: Props) => (
validate={required} validate={required}
placeholder="Name of the new Safe" placeholder="Name of the new Safe"
text="Safe name" text="Safe name"
defaultValue={safeName}
/> />
</Block> </Block>
<Block margin="lg"> <Block margin="lg">
<Paragraph noMargin size="md" color="primary" className={classes.links}> <Paragraph noMargin size="md" color="primary" className={classes.links}>
By continuing you consent with the By continuing you consent with the
{' '} {' '}
<a rel="noopener noreferrer" href="https://safe.gnosis.io/terms" target="_blank"> <a rel="noopener noreferrer" href="https://safe.gnosis.io/terms" target="_blank">
terms of use terms of use
</a> </a>
{' '} {' '}
and and
{' '} {' '}
<a rel="noopener noreferrer" href="https://safe.gnosis.io/privacy" target="_blank"> <a rel="noopener noreferrer" href="https://safe.gnosis.io/privacy" target="_blank">
privacy policy privacy policy
</a> </a>
. Most importantly, you confirm that your funds are held securely in the Gnosis Safe, a smart contract on the . Most importantly, you confirm that your funds are held securely in the Gnosis Safe, a smart contract on the
Ethereum blockchain. These funds cannot be accessed by Gnosis at any point. Ethereum blockchain. These funds cannot be accessed by Gnosis at any point.
</Paragraph> </Paragraph>
</Block> </Block>
</> </>
@ -72,10 +75,13 @@ const SafeName = ({ classes }: Props) => (
const SafeNameForm = withStyles(styles)(SafeName) const SafeNameForm = withStyles(styles)(SafeName)
const SafeNamePage = () => (controls: React.Node) => ( const SafeNamePage = () => (controls: React.Node, { values }) => {
<OpenPaper controls={controls}> const { safeName } = values
<SafeNameForm /> return (
</OpenPaper> <OpenPaper controls={controls}>
) <SafeNameForm safeName={safeName} />
</OpenPaper>
)
}
export default SafeNamePage export default SafeNamePage

View File

@ -4,6 +4,7 @@ import { withStyles } from '@material-ui/core/styles'
import InputAdornment from '@material-ui/core/InputAdornment' import InputAdornment from '@material-ui/core/InputAdornment'
import CheckCircle from '@material-ui/icons/CheckCircle' import CheckCircle from '@material-ui/icons/CheckCircle'
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem'
import { withRouter } from 'react-router-dom'
import Field from '~/components/forms/Field' import Field from '~/components/forms/Field'
import TextField from '~/components/forms/TextField' import TextField from '~/components/forms/TextField'
import SelectField from '~/components/forms/SelectField' import SelectField from '~/components/forms/SelectField'
@ -70,6 +71,7 @@ const SafeOwners = (props: Props) => {
} = props } = props
const validOwners = getNumOwnersFrom(values) const validOwners = getNumOwnersFrom(values)
const [numOwners, setNumOwners] = useState<number>(validOwners) const [numOwners, setNumOwners] = useState<number>(validOwners)
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false) const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
const [scanQrForOwnerName, setScanQrForOwnerName] = useState<string | null>(null) const [scanQrForOwnerName, setScanQrForOwnerName] = useState<string | null>(null)
@ -222,7 +224,7 @@ owner(s)
) )
} }
const SafeOwnersForm = withStyles(styles)(SafeOwners) const SafeOwnersForm = withStyles(styles)(withRouter(SafeOwners))
const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node, { values, errors, form }: Object) => ( const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node, { values, errors, form }: Object) => (
<> <>

View File

@ -2,6 +2,7 @@
export const FIELD_NAME: string = 'name' export const FIELD_NAME: string = 'name'
export const FIELD_CONFIRMATIONS: string = 'confirmations' export const FIELD_CONFIRMATIONS: string = 'confirmations'
export const FIELD_OWNERS: string = 'owners' export const FIELD_OWNERS: string = 'owners'
export const FIELD_SAFE_NAME: string = 'safeName'
export const getOwnerNameBy = (index: number) => `owner${index}Name` export const getOwnerNameBy = (index: number) => `owner${index}Name`
export const getOwnerAddressBy = (index: number) => `owner${index}Address` export const getOwnerAddressBy = (index: number) => `owner${index}Address`

View File

@ -1,12 +1,14 @@
// @flow // @flow
import * as React from 'react' import * as React from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import queryString from 'query-string'
import { withRouter } from 'react-router-dom'
import Page from '~/components/layout/Page' import Page from '~/components/layout/Page'
import { import {
getAccountsFrom, getThresholdFrom, getNamesFrom, getSafeNameFrom, getOwnersFrom, getAccountsFrom, getThresholdFrom, getNamesFrom, getSafeNameFrom, getOwnersFrom,
} from '~/routes/open/utils/safeDataExtractor' } from '~/routes/open/utils/safeDataExtractor'
import { buildSafe } from '~/routes/safe/store/actions/fetchSafe' import { buildSafe } from '~/routes/safe/store/actions/fetchSafe'
import { getGnosisSafeInstanceAt, deploySafeContract, initContracts } from '~/logic/contracts/safeContracts' import { getGnosisSafeInstanceAt, deploySafeContract } from '~/logic/contracts/safeContracts'
import { checkReceiptStatus } from '~/logic/wallets/ethTransactions' import { checkReceiptStatus } from '~/logic/wallets/ethTransactions'
import { history } from '~/store' import { history } from '~/store'
import { OPENING_ADDRESS, stillInOpeningView, SAFELIST_ADDRESS } from '~/routes/routes' import { OPENING_ADDRESS, stillInOpeningView, SAFELIST_ADDRESS } from '~/routes/routes'
@ -24,14 +26,36 @@ export type OpenState = {
safeAddress: string, safeAddress: string,
} }
export type SafePropsType = {
name: string,
ownerAddresses: string[],
ownerNames: string[],
threshold: string,
}
const validateQueryParams = (ownerAddresses?: string[], ownerNames?: string[], threshold?: string, safeName?: string) => {
if (!ownerAddresses || !ownerNames || !threshold || !safeName) {
return false
}
if (!ownerAddresses.length === 0 || ownerNames.length === 0) {
return false
}
if (Number.isNaN(Number(threshold))) {
return false
}
if (threshold > ownerAddresses.length) {
return false
}
return true
}
export const createSafe = async (values: Object, userAccount: string, addSafe: AddSafe): Promise<OpenState> => { export const createSafe = async (values: Object, userAccount: string, addSafe: AddSafe): Promise<OpenState> => {
const numConfirmations = getThresholdFrom(values) const numConfirmations = getThresholdFrom(values)
const name = getSafeNameFrom(values) const name = getSafeNameFrom(values)
const ownersNames = getNamesFrom(values) const ownersNames = getNamesFrom(values)
const ownerAddresses = getAccountsFrom(values) const ownerAddresses = getAccountsFrom(values)
await initContracts()
const safe = await deploySafeContract(ownerAddresses, numConfirmations, userAccount) const safe = await deploySafeContract(ownerAddresses, numConfirmations, userAccount)
await checkReceiptStatus(safe.tx) await checkReceiptStatus(safe.tx)
@ -59,10 +83,6 @@ export const createSafe = async (values: Object, userAccount: string, addSafe: A
} }
class Open extends React.Component<Props> { class Open extends React.Component<Props> {
async componentDidMount() {
await initContracts()
}
onCallSafeContractSubmit = async (values) => { onCallSafeContractSubmit = async (values) => {
try { try {
const { userAccount, addSafe } = this.props const { userAccount, addSafe } = this.props
@ -75,8 +95,23 @@ class Open extends React.Component<Props> {
} }
render() { render() {
const { provider, userAccount, network } = this.props const {
provider, userAccount, network, location,
} = this.props
const query: SafePropsType = queryString.parse(location.search, { arrayFormat: 'comma' })
const {
name, owneraddresses, ownernames, threshold,
} = query
let safeProps = null
if (validateQueryParams(owneraddresses, ownernames, threshold, name)) {
safeProps = {
name,
ownerAddresses: owneraddresses,
ownerNames: ownernames,
threshold,
}
}
return ( return (
<Page> <Page>
<Layout <Layout
@ -84,10 +119,11 @@ class Open extends React.Component<Props> {
provider={provider} provider={provider}
userAccount={userAccount} userAccount={userAccount}
onCallSafeContractSubmit={this.onCallSafeContractSubmit} onCallSafeContractSubmit={this.onCallSafeContractSubmit}
safeProps={safeProps}
/> />
</Page> </Page>
) )
} }
} }
export default connect(selector, actions)(Open) export default connect(selector, actions)(withRouter(Open))

View File

@ -1,19 +0,0 @@
// @flow
import { storiesOf } from '@storybook/react'
import * as React from 'react'
import styles from '~/components/layout/PageFrame/index.scss'
import { ETHEREUM_NETWORK } from '~/logic/wallets/getWeb3'
import Component from './component'
const FrameDecorator = (story) => <div className={styles.frame}>{story()}</div>
storiesOf('Routes /opening', module)
.addDecorator(FrameDecorator)
.add('View while Safe is being deployed', () => (
<Component
name="Super Vault 2000"
tx="0xed163e50e2e85695f5edafeba51d6be1758549858d12611ed4dcc96feaa19fc9"
network={ETHEREUM_NETWORK.RINKEBY}
/>
))
.add('Load this view without a tx', () => <Component network={ETHEREUM_NETWORK.UNKNOWN} />)

View File

@ -26,8 +26,8 @@ const styles = () => ({
boxSizing: 'border-box', boxSizing: 'border-box',
}, },
close: { close: {
height: '24px', height: lg,
width: '24px', width: lg,
fill: secondaryText, fill: secondaryText,
}, },
qrContainer: { qrContainer: {
@ -48,7 +48,7 @@ const styles = () => ({
justifyContent: 'center', justifyContent: 'center',
'& > button': { '& > button': {
fontFamily: 'Averta', fontFamily: 'Averta',
fontSize: '16px', fontSize: md,
boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)', boxShadow: '1px 2px 10px 0 rgba(212, 212, 211, 0.59)',
}, },
}, },

View File

@ -22,7 +22,7 @@ const styles = () => ({
maxHeight: '75px', maxHeight: '75px',
}, },
manage: { manage: {
fontSize: '24px', fontSize: lg,
}, },
closeIcon: { closeIcon: {
height: '35px', height: '35px',
@ -31,7 +31,7 @@ const styles = () => ({
buttonColumn: { buttonColumn: {
padding: '52px 0', padding: '52px 0',
'& > button': { '& > button': {
fontSize: '16px', fontSize: md,
fontFamily: 'Averta', fontFamily: 'Averta',
}, },
}, },

View File

@ -23,6 +23,7 @@ import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
import { formatAmount } from '~/logic/tokens/utils/formatAmount' import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import ArrowDown from '../assets/arrow-down.svg' import ArrowDown from '../assets/arrow-down.svg'
import { styles } from './style' import { styles } from './style'
import { sm } from '~/theme/variables'
type Props = { type Props = {
onClose: () => void, onClose: () => void,
@ -78,15 +79,15 @@ const ReviewCustomTx = ({
const txData = tx.data.trim() const txData = tx.data.trim()
const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0 const txValue = tx.value ? web3.utils.toWei(tx.value, 'ether') : 0
createTransaction( createTransaction({
safeAddress, safeAddress,
txRecipient, to: txRecipient,
txValue, valueInWei: txValue,
txData, txData,
TX_NOTIFICATION_TYPES.STANDARD_TX, notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
enqueueSnackbar, enqueueSnackbar,
closeSnackbar, closeSnackbar,
) })
onClose() onClose()
} }
@ -106,7 +107,7 @@ const ReviewCustomTx = ({
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} /> <SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
<Row margin="md"> <Row margin="md">
<Col xs={1}> <Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} /> <img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: sm }} />
</Col> </Col>
<Col xs={11} center="xs" layout="column"> <Col xs={11} center="xs" layout="column">
<Hairline /> <Hairline />

View File

@ -17,7 +17,7 @@ export const styles = () => ({
marginLeft: '20px', marginLeft: '20px',
}, },
headingText: { headingText: {
fontSize: '24px', fontSize: lg,
}, },
closeIcon: { closeIcon: {
height: '35px', height: '35px',
@ -50,7 +50,7 @@ export const styles = () => ({
justifyContent: 'center', justifyContent: 'center',
'& > button': { '& > button': {
fontFamily: 'Averta', fontFamily: 'Averta',
fontSize: '16px', fontSize: md,
}, },
}, },
submitButton: { submitButton: {

View File

@ -28,6 +28,7 @@ import { TX_NOTIFICATION_TYPES } from '~/logic/safe/transactions'
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers' import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
import ArrowDown from '../assets/arrow-down.svg' import ArrowDown from '../assets/arrow-down.svg'
import { styles } from './style' import { styles } from './style'
import { sm } from '~/theme/variables'
type Props = { type Props = {
onClose: () => void, onClose: () => void,
@ -108,15 +109,15 @@ const ReviewTx = ({
txAmount = 0 txAmount = 0
} }
createTransaction( createTransaction({
safeAddress, safeAddress,
txRecipient, to: txRecipient,
txAmount, valueInWei: txAmount,
txData, txData,
TX_NOTIFICATION_TYPES.STANDARD_TX, notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
enqueueSnackbar, enqueueSnackbar,
closeSnackbar, closeSnackbar,
) })
onClose() onClose()
} }
@ -136,7 +137,7 @@ const ReviewTx = ({
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} /> <SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
<Row margin="md"> <Row margin="md">
<Col xs={1}> <Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} /> <img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: sm }} />
</Col> </Col>
<Col xs={11} center="xs" layout="column"> <Col xs={11} center="xs" layout="column">
<Hairline /> <Hairline />

View File

@ -17,7 +17,7 @@ export const styles = () => ({
marginLeft: '20px', marginLeft: '20px',
}, },
headingText: { headingText: {
fontSize: '24px', fontSize: lg,
}, },
closeIcon: { closeIcon: {
height: '35px', height: '35px',
@ -37,7 +37,7 @@ export const styles = () => ({
justifyContent: 'center', justifyContent: 'center',
'& > button': { '& > button': {
fontFamily: 'Averta', fontFamily: 'Averta',
fontSize: '16px', fontSize: md,
}, },
}, },
submitButton: { submitButton: {

View File

@ -25,6 +25,7 @@ import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
import QRIcon from '~/assets/icons/qrcode.svg' import QRIcon from '~/assets/icons/qrcode.svg'
import ArrowDown from '../assets/arrow-down.svg' import ArrowDown from '../assets/arrow-down.svg'
import { styles } from './style' import { styles } from './style'
import { sm } from '~/theme/variables'
type Props = { type Props = {
onClose: () => void, onClose: () => void,
@ -102,7 +103,7 @@ const SendCustomTx = ({
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} /> <SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
<Row margin="md"> <Row margin="md">
<Col xs={1}> <Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} /> <img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: sm }} />
</Col> </Col>
<Col xs={11} center="xs" layout="column"> <Col xs={11} center="xs" layout="column">
<Hairline /> <Hairline />

View File

@ -15,7 +15,7 @@ export const styles = () => ({
marginLeft: '20px', marginLeft: '20px',
}, },
manage: { manage: {
fontSize: '24px', fontSize: lg,
}, },
closeIcon: { closeIcon: {
height: '35px', height: '35px',
@ -32,7 +32,7 @@ export const styles = () => ({
justifyContent: 'center', justifyContent: 'center',
'& > button': { '& > button': {
fontFamily: 'Averta', fontFamily: 'Averta',
fontSize: '16px', fontSize: md,
}, },
}, },
submitButton: { submitButton: {

View File

@ -28,6 +28,7 @@ import ScanQRModal from '~/components/ScanQRModal'
import ArrowDown from '../assets/arrow-down.svg' import ArrowDown from '../assets/arrow-down.svg'
import QRIcon from '~/assets/icons/qrcode.svg' import QRIcon from '~/assets/icons/qrcode.svg'
import { styles } from './style' import { styles } from './style'
import { sm } from '~/theme/variables'
type Props = { type Props = {
onClose: () => void, onClose: () => void,
@ -114,7 +115,7 @@ const SendFunds = ({
<SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} /> <SafeInfo safeAddress={safeAddress} safeName={safeName} ethBalance={ethBalance} />
<Row margin="md"> <Row margin="md">
<Col xs={1}> <Col xs={1}>
<img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: '8px' }} /> <img src={ArrowDown} alt="Arrow Down" style={{ marginLeft: sm }} />
</Col> </Col>
<Col xs={11} center="xs" layout="column"> <Col xs={11} center="xs" layout="column">
<Hairline /> <Hairline />

View File

@ -15,7 +15,7 @@ export const styles = () => ({
marginLeft: '20px', marginLeft: '20px',
}, },
manage: { manage: {
fontSize: '24px', fontSize: lg,
}, },
closeIcon: { closeIcon: {
height: '35px', height: '35px',
@ -32,7 +32,7 @@ export const styles = () => ({
justifyContent: 'center', justifyContent: 'center',
'& > button': { '& > button': {
fontFamily: 'Averta', fontFamily: 'Averta',
fontSize: '16px', fontSize: md,
}, },
}, },
submitButton: { submitButton: {

View File

@ -1,10 +1,10 @@
// @flow // @flow
import { lg } from '~/theme/variables' import { lg, md } from '~/theme/variables'
export const styles = () => ({ export const styles = () => ({
title: { title: {
padding: `${lg} 0 20px`, padding: `${lg} 0 20px`,
fontSize: '16px', fontSize: md,
}, },
formContainer: { formContainer: {
padding: '0 20px', padding: '0 20px',

View File

@ -4,6 +4,8 @@ import { type Token } from '~/logic/tokens/store/model/token'
import { buildOrderFieldFrom, FIXED, type SortRow } from '~/components/Table/sorting' import { buildOrderFieldFrom, FIXED, type SortRow } from '~/components/Table/sorting'
import { type Column } from '~/components/Table/TableHead' import { type Column } from '~/components/Table/TableHead'
import { formatAmount } from '~/logic/tokens/utils/formatAmount' import { formatAmount } from '~/logic/tokens/utils/formatAmount'
import type { BalanceCurrencyType } from '~/logic/currencyValues/store/model/currencyValues'
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
export const BALANCE_TABLE_ASSET_ID = 'asset' export const BALANCE_TABLE_ASSET_ID = 'asset'
export const BALANCE_TABLE_BALANCE_ID = 'balance' export const BALANCE_TABLE_BALANCE_ID = 'balance'
@ -16,13 +18,29 @@ type BalanceData = {
export type BalanceRow = SortRow<BalanceData> export type BalanceRow = SortRow<BalanceData>
export const getBalanceData = (activeTokens: List<Token>): List<BalanceRow> => { // eslint-disable-next-line max-len
const getTokenPriceInCurrency = (token: Token, currencySelected: AVAILABLE_CURRENCIES, currencyValues: List<BalanceCurrencyType>): string => {
// eslint-disable-next-line no-restricted-syntax
for (const tokenPriceIterator of currencyValues) {
const { tokenAddress, balanceInSelectedCurrency, currencyName } = tokenPriceIterator
if (token.address === tokenAddress && currencySelected === currencyName) {
const balance = balanceInSelectedCurrency ? parseFloat(balanceInSelectedCurrency, 10).toFixed(2) : balanceInSelectedCurrency
return `${balance} ${currencySelected}`
}
}
return null
}
// eslint-disable-next-line max-len
export const getBalanceData = (activeTokens: List<Token>, currencySelected: string, currencyValues: List<BalanceCurrencyType>): List<BalanceRow> => {
const rows = activeTokens.map((token: Token) => ({ const rows = activeTokens.map((token: Token) => ({
[BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri, address: token.address }, [BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri, address: token.address },
[buildOrderFieldFrom(BALANCE_TABLE_ASSET_ID)]: token.name, [buildOrderFieldFrom(BALANCE_TABLE_ASSET_ID)]: token.name,
[BALANCE_TABLE_BALANCE_ID]: `${formatAmount(token.balance)} ${token.symbol}`, [BALANCE_TABLE_BALANCE_ID]: `${formatAmount(token.balance)} ${token.symbol}`,
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance), [buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance),
[FIXED]: token.get('symbol') === 'ETH', [FIXED]: token.get('symbol') === 'ETH',
[BALANCE_TABLE_VALUE_ID]: getTokenPriceInCurrency(token, currencySelected, currencyValues),
})) }))
return rows return rows
@ -56,5 +74,26 @@ export const generateColumns = () => {
static: true, static: true,
} }
return List([assetColumn, balanceColumn, actions]) const value: Column = {
id: BALANCE_TABLE_VALUE_ID,
order: false,
label: 'Value',
custom: false,
static: true,
style: {
fontSize: '11px',
color: '#5d6d74',
borderBottomWidth: '2px',
width: '125px',
fontFamily: 'Averta',
fontWeight: 'normal',
fontStyle: 'normal',
textAlign: 'right',
},
}
return List([assetColumn, balanceColumn, value, actions])
} }
// eslint-disable-next-line max-len
export const filterByZero = (data: List<BalanceRow>, hideZero: boolean): List<BalanceRow> => data.filter((row: BalanceRow) => (hideZero ? row[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)] !== 0 : true))

View File

@ -23,6 +23,9 @@ import Tokens from './Tokens'
import SendModal from './SendModal' import SendModal from './SendModal'
import Receive from './Receive' import Receive from './Receive'
import { styles } from './style' import { styles } from './style'
import DropdownCurrency from '~/routes/safe/components/DropdownCurrency'
import type { BalanceCurrencyType } from '~/logic/currencyValues/store/model/currencyValues'
import { BALANCE_TABLE_BALANCE_ID, BALANCE_TABLE_VALUE_ID } from '~/routes/safe/components/Balances/dataFetcher'
export const MANAGE_TOKENS_BUTTON_TEST_ID = 'manage-tokens-btn' export const MANAGE_TOKENS_BUTTON_TEST_ID = 'manage-tokens-btn'
export const BALANCE_ROW_TEST_ID = 'balance-row' export const BALANCE_ROW_TEST_ID = 'balance-row'
@ -45,6 +48,9 @@ type Props = {
safeName: string, safeName: string,
ethBalance: string, ethBalance: string,
createTransaction: Function, createTransaction: Function,
currencySelected: string,
fetchCurrencyValues: Function,
currencyValues: BalanceCurrencyType[],
} }
type Action = 'Token' | 'Send' | 'Receive' type Action = 'Token' | 'Send' | 'Receive'
@ -63,6 +69,12 @@ class Balances extends React.Component<Props, State> {
props.fetchTokens() props.fetchTokens()
} }
componentDidMount(): void {
const { safeAddress, fetchCurrencyValues, activateTokensByBalance } = this.props
fetchCurrencyValues(safeAddress)
activateTokensByBalance(safeAddress)
}
onShow = (action: Action) => () => { onShow = (action: Action) => () => {
this.setState(() => ({ [`show${action}`]: true })) this.setState(() => ({ [`show${action}`]: true }))
} }
@ -89,17 +101,6 @@ class Balances extends React.Component<Props, State> {
}) })
} }
handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
const { checked } = e.target
this.setState(() => ({ hideZero: checked }))
}
componentDidMount(): void {
const { activateTokensByBalance, safeAddress } = this.props
activateTokensByBalance(safeAddress)
}
render() { render() {
const { const {
showToken, showReceive, sendFunds, showToken, showReceive, sendFunds,
@ -114,17 +115,20 @@ class Balances extends React.Component<Props, State> {
safeName, safeName,
ethBalance, ethBalance,
createTransaction, createTransaction,
currencySelected,
currencyValues,
} = this.props } = this.props
const columns = generateColumns() const columns = generateColumns()
const autoColumns = columns.filter((c) => !c.custom) const autoColumns = columns.filter((c) => !c.custom)
const filteredData = getBalanceData(activeTokens) const filteredData = getBalanceData(activeTokens, currencySelected, currencyValues)
return ( return (
<> <>
<Row align="center" className={classes.message}> <Row align="center" className={classes.message}>
<Col xs={12} end="sm"> <Col xs={12} end="sm">
<DropdownCurrency />
<ButtonLink size="lg" onClick={this.onShow('Token')} testId="manage-tokens-btn"> <ButtonLink size="lg" onClick={this.onShow('Token')} testId="manage-tokens-btn">
Manage List Manage List
</ButtonLink> </ButtonLink>
@ -155,11 +159,42 @@ class Balances extends React.Component<Props, State> {
> >
{(sortedData: Array<BalanceRow>) => sortedData.map((row: any, index: number) => ( {(sortedData: Array<BalanceRow>) => sortedData.map((row: any, index: number) => (
<TableRow tabIndex={-1} key={index} className={classes.hide} data-testid={BALANCE_ROW_TEST_ID}> <TableRow tabIndex={-1} key={index} className={classes.hide} data-testid={BALANCE_ROW_TEST_ID}>
{autoColumns.map((column: Column) => ( {autoColumns.map((column: Column) => {
<TableCell key={column.id} style={cellWidth(column.width)} align={column.align} component="td"> const { id, width, align } = column
{column.id === BALANCE_TABLE_ASSET_ID ? <AssetTableCell asset={row[column.id]} /> : row[column.id]} let cellItem
</TableCell> switch (id) {
))} case BALANCE_TABLE_ASSET_ID: {
cellItem = <AssetTableCell asset={row[id]} />
break
}
case BALANCE_TABLE_BALANCE_ID: {
cellItem = (
<div>
{row[id]}
</div>
)
break
}
case BALANCE_TABLE_VALUE_ID: {
cellItem = <div className={classes.currencyValueRow}>{row[id]}</div>
break
}
default: {
cellItem = null
break
}
}
return (
<TableCell
key={id}
style={cellWidth(width)}
align={align}
component="td"
>
{cellItem}
</TableCell>
)
})}
<TableCell component="td"> <TableCell component="td">
<Row align="end" className={classes.actions}> <Row align="end" className={classes.actions}>
{granted && ( {granted && (

View File

@ -1,5 +1,5 @@
// @flow // @flow
import { sm, md } from '~/theme/variables' import { xs, sm, md } from '~/theme/variables'
export const styles = (theme: Object) => ({ export const styles = (theme: Object) => ({
root: { root: {
@ -41,7 +41,7 @@ export const styles = (theme: Object) => ({
width: '95px', width: '95px',
minWidth: '95px', minWidth: '95px',
marginLeft: sm, marginLeft: sm,
borderRadius: '4px', borderRadius: xs,
'& > span': { '& > span': {
fontSize: '14px', fontSize: '14px',
}, },
@ -49,7 +49,7 @@ export const styles = (theme: Object) => ({
send: { send: {
width: '75px', width: '75px',
minWidth: '75px', minWidth: '75px',
borderRadius: '4px', borderRadius: xs,
'& > span': { '& > span': {
fontSize: '14px', fontSize: '14px',
}, },
@ -63,4 +63,8 @@ export const styles = (theme: Object) => ({
cursor: 'pointer', cursor: 'pointer',
}, },
}, },
currencyValueRow: {
maxWidth: '125px',
textAlign: 'right',
},
}) })

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="14" viewBox="0 0 18 14">
<path fill="#008C73" fill-rule="evenodd" d="M5.6 10.6L1.4 6.4 0 7.8l5.6 5.6 12-12L16.2 0z"/>
</svg>

After

Width:  |  Height:  |  Size: 188 B

View File

@ -0,0 +1,124 @@
// @flow
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import Menu from '@material-ui/core/Menu'
import MenuItem from '@material-ui/core/MenuItem'
import React, { useState } from 'react'
import style from 'currency-flags/dist/currency-flags.min.css'
import { MuiThemeProvider } from '@material-ui/core/styles'
import { useDispatch, useSelector } from 'react-redux'
import SearchIcon from '@material-ui/icons/Search'
import InputBase from '@material-ui/core/InputBase'
import classNames from 'classnames'
import { DropdownListTheme } from '~/theme/mui'
import CheckIcon from './img/check.svg'
import { AVAILABLE_CURRENCIES } from '~/logic/currencyValues/store/model/currencyValues'
import fetchCurrencySelectedValue from '~/logic/currencyValues/store/actions/fetchCurrencySelectedValue'
import { currentCurrencySelector } from '~/logic/currencyValues/store/selectors'
import { useDropdownStyles } from '~/routes/safe/components/DropdownCurrency/style'
import saveCurrencySelected from '~/logic/currencyValues/store/actions/saveCurrencySelected'
const DropdownCurrency = () => {
const currenciesList = Object.values(AVAILABLE_CURRENCIES)
const dispatch = useDispatch()
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
const currencyValueSelected = useSelector(currentCurrencySelector)
const [searchParams, setSearchParams] = useState('')
const classes = useDropdownStyles()
const currenciesListFiltered = currenciesList.filter((currency) => currency.toLowerCase().includes(searchParams.toLowerCase()))
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget)
}
const handleClose = () => {
setAnchorEl(null)
}
const onCurrentCurrencyChangedHandler = (newCurrencySelectedName: AVAILABLE_CURRENCIES) => {
dispatch(fetchCurrencySelectedValue(newCurrencySelectedName))
dispatch(saveCurrencySelected(newCurrencySelectedName))
handleClose()
}
return (
!currencyValueSelected ? null
: (
<MuiThemeProvider theme={DropdownListTheme}>
<>
<button
className={classes.button}
onClick={handleClick}
type="button"
>
<span className={classNames(classes.buttonInner, anchorEl && classes.openMenuButton)}>
{currencyValueSelected}
</span>
</button>
<Menu
anchorEl={anchorEl}
elevation={0}
getContentAnchorEl={null}
id="customizedMenu"
keepMounted
onClose={handleClose}
open={Boolean(anchorEl)}
rounded={0}
anchorOrigin={{
horizontal: 'center',
vertical: 'bottom',
}}
transformOrigin={{
horizontal: 'center',
vertical: 'top',
}}
>
<MenuItem
className={classes.listItemSearch}
key="0"
>
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<InputBase
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
inputProps={{ 'aria-label': 'search' }}
onChange={(event) => setSearchParams(event.target.value)}
value={searchParams}
/>
</div>
</MenuItem>
<div className={classes.dropdownItemsScrollWrapper}>
{currenciesListFiltered.map((currencyName) => (
<MenuItem
className={classes.listItem}
key={currencyName}
value={currencyName}
onClick={() => onCurrentCurrencyChangedHandler(currencyName)}
>
<ListItemIcon className={classes.iconLeft}>
<div
className={classNames(classes.localFlag, style['currency-flag'], style['currency-flag-lg'], style[`currency-flag-${currencyName.toLowerCase()}`])}
/>
</ListItemIcon>
<ListItemText primary={currencyName} />
{currencyName === currencyValueSelected
? <ListItemIcon className={classes.iconRight}><img src={CheckIcon} alt="checked" /></ListItemIcon> : null}
</MenuItem>
))}
</div>
</Menu>
</>
</MuiThemeProvider>
)
)
}
export default DropdownCurrency

View File

@ -0,0 +1,119 @@
// @flow
import { makeStyles } from '@material-ui/core/styles'
const buttonWidth = '140px'
export const useDropdownStyles = makeStyles({
listItem: {
maxWidth: buttonWidth,
boxSizing: 'border-box',
},
listItemSearch: {
maxWidth: buttonWidth,
padding: '0',
boxSizing: 'border-box',
},
localFlag: {
backgroundPosition: '50% 50%',
backgroundRepeat: 'no-repeat',
backgroundSize: 'contain',
height: '20px !important',
width: '26px !important',
},
iconLeft: {
marginRight: '10px',
},
iconRight: {
marginLeft: '18px',
},
button: {
backgroundColor: '#e8e7e6',
border: 'none',
borderRadius: '3px',
boxSizing: 'border-box',
color: '#5d6d74',
cursor: 'pointer',
fontSize: '12px',
fontWeight: 'normal',
height: '24px',
lineHeight: '1.33',
marginRight: '20px',
minWidth: buttonWidth,
outline: 'none',
padding: '0',
textAlign: 'left',
'&:active': {
opacity: '0.8',
},
},
buttonInner: {
boxSizing: 'border-box',
display: 'block',
height: '100%',
lineHeight: '24px',
padding: '0 22px 0 8px',
position: 'relative',
width: '100%',
'&::after': {
borderLeft: '5px solid transparent',
borderRight: '5px solid transparent',
borderTop: '5px solid #5d6d74',
content: '""',
height: '0',
position: 'absolute',
right: '8px',
top: '9px',
width: '0',
},
},
openMenuButton: {
'&::after': {
borderBottom: '5px solid #5d6d74',
borderLeft: '5px solid transparent',
borderRight: '5px solid transparent',
borderTop: 'none',
},
},
dropdownItemsScrollWrapper: {
maxHeight: '280px',
overflow: 'auto',
},
search: {
position: 'relative',
borderRadius: '0',
backgroundColor: '#fff',
'&:hover': {
backgroundColor: '#fff',
},
marginRight: 0,
width: '100%',
},
searchIcon: {
alignItems: 'center',
display: 'flex',
height: '100%',
justifyContent: 'center',
left: '12px',
margin: '0',
pointerEvents: 'none',
position: 'absolute',
top: '50%',
transform: 'translateY(-50%)',
width: '18px',
'& path': {
fill: '#b2b5b2',
},
},
inputRoot: {
color: '#5d6d74',
fontSize: '14px',
fontWeight: 'normal',
lineHeight: '1.43',
width: '100%',
},
inputInput: {
boxSizing: 'border-box',
height: '44px',
padding: '12px 12px 12px 40px',
width: '100%',
},
})

View File

@ -49,6 +49,7 @@ type Props = SelectorProps &
match: Object, match: Object,
location: Object, location: Object,
history: Object, history: Object,
fetchCurrencyValues: Function,
} }
const Layout = (props: Props) => { const Layout = (props: Props) => {
@ -63,7 +64,6 @@ const Layout = (props: Props) => {
blacklistedTokens, blacklistedTokens,
createTransaction, createTransaction,
processTransaction, processTransaction,
fetchTransactions,
activateTokensByBalance, activateTokensByBalance,
fetchTokens, fetchTokens,
updateSafe, updateSafe,
@ -77,6 +77,9 @@ const Layout = (props: Props) => {
hideSendFunds, hideSendFunds,
match, match,
location, location,
currencySelected,
fetchCurrencyValues,
currencyValues,
} = props } = props
const handleCallToRouter = (_, value) => { const handleCallToRouter = (_, value) => {
@ -166,6 +169,9 @@ const Layout = (props: Props) => {
fetchTokens={fetchTokens} fetchTokens={fetchTokens}
safeName={name} safeName={name}
createTransaction={createTransaction} createTransaction={createTransaction}
currencySelected={currencySelected}
fetchCurrencyValues={fetchCurrencyValues}
currencyValues={currencyValues}
/> />
)} )}
/> />
@ -176,8 +182,8 @@ const Layout = (props: Props) => {
<Transactions <Transactions
threshold={safe.threshold} threshold={safe.threshold}
owners={safe.owners} owners={safe.owners}
nonce={safe.nonce}
transactions={transactions} transactions={transactions}
fetchTransactions={fetchTransactions}
safeAddress={address} safeAddress={address}
userAddress={userAddress} userAddress={userAddress}
currentNetwork={network} currentNetwork={network}

View File

@ -1,17 +0,0 @@
// @flow
import { storiesOf } from '@storybook/react'
import * as React from 'react'
import { List } from 'immutable'
import styles from '~/components/layout/PageFrame/index.scss'
import Component from './Layout'
const FrameDecorator = (story) => <div className={styles.frame}>{story()}</div>
storiesOf('Routes /safe:address', module)
.addDecorator(FrameDecorator)
.add('Safe undefined being connected', () => (
<Component userAddress="foo" safe={undefined} provider="METAMASK" activeTokens={List([])} fetchBalance={() => {}} />
))
.add('Safe undefined NOT connected', () => (
<Component userAddress="foo" safe={undefined} provider="" activeTokens={List([])} fetchBalance={() => {}} />
))

View File

@ -1,11 +0,0 @@
// @flow
import { storiesOf } from '@storybook/react'
import * as React from 'react'
import styles from '~/components/layout/PageFrame/index.scss'
import Component from './index.jsx'
const FrameDecorator = (story) => <div className={styles.frame}>{story()}</div>
storiesOf('Components', module)
.addDecorator(FrameDecorator)
.add('NoRights', () => <Component />)

View File

@ -47,15 +47,15 @@ export const sendAddOwner = async (
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress) const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
const txData = gnosisSafe.contract.methods.addOwnerWithThreshold(values.ownerAddress, values.threshold).encodeABI() const txData = gnosisSafe.contract.methods.addOwnerWithThreshold(values.ownerAddress, values.threshold).encodeABI()
const txHash = await createTransaction( const txHash = await createTransaction({
safeAddress, safeAddress,
safeAddress, to: safeAddress,
0, valueInWei: 0,
txData, txData,
TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX, notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
enqueueSnackbar, enqueueSnackbar,
closeSnackbar, closeSnackbar,
) })
if (txHash) { if (txHash) {
addSafeOwner({ safeAddress, ownerName: values.ownerName, ownerAddress: values.ownerAddress }) addSafeOwner({ safeAddress, ownerName: values.ownerName, ownerAddress: values.ownerAddress })

View File

@ -17,7 +17,7 @@ export const styles = () => ({
lineHeight: 'normal', lineHeight: 'normal',
}, },
manage: { manage: {
fontSize: '24px', fontSize: lg,
}, },
closeIcon: { closeIcon: {
height: '35px', height: '35px',

View File

@ -20,7 +20,7 @@ export const styles = () => ({
lineHeight: 'normal', lineHeight: 'normal',
}, },
manage: { manage: {
fontSize: '24px', fontSize: lg,
}, },
closeIcon: { closeIcon: {
height: '35px', height: '35px',

View File

@ -17,14 +17,14 @@ export const styles = () => ({
lineHeight: 'normal', lineHeight: 'normal',
}, },
manage: { manage: {
fontSize: '24px', fontSize: lg,
}, },
closeIcon: { closeIcon: {
height: '35px', height: '35px',
width: '35px', width: '35px',
}, },
headingText: { headingText: {
fontSize: '16px', fontSize: md,
}, },
formContainer: { formContainer: {
padding: `${md} ${lg}`, padding: `${md} ${lg}`,

View File

@ -10,7 +10,7 @@ export const styles = () => ({
boxSizing: 'border-box', boxSizing: 'border-box',
}, },
manage: { manage: {
fontSize: '24px', fontSize: lg,
}, },
container: { container: {
padding: `${md} ${lg}`, padding: `${md} ${lg}`,

View File

@ -62,15 +62,15 @@ export const sendRemoveOwner = async (
.removeOwner(prevAddress, ownerAddressToRemove, values.threshold) .removeOwner(prevAddress, ownerAddressToRemove, values.threshold)
.encodeABI() .encodeABI()
const txHash = await createTransaction( const txHash = await createTransaction({
safeAddress, safeAddress,
safeAddress, to: safeAddress,
0, valueInWei: 0,
txData, txData,
TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX, notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
enqueueSnackbar, enqueueSnackbar,
closeSnackbar, closeSnackbar,
) })
if (txHash && safe.threshold === 1) { if (txHash && safe.threshold === 1) {
removeSafeOwner({ safeAddress, ownerAddress: ownerAddressToRemove }) removeSafeOwner({ safeAddress, ownerAddress: ownerAddressToRemove })

View File

@ -17,7 +17,7 @@ export const styles = () => ({
marginLeft: '20px', marginLeft: '20px',
}, },
manage: { manage: {
fontSize: '24px', fontSize: lg,
}, },
address: { address: {
marginRight: sm, marginRight: sm,

View File

@ -20,7 +20,7 @@ export const styles = () => ({
marginLeft: '20px', marginLeft: '20px',
}, },
manage: { manage: {
fontSize: '24px', fontSize: lg,
}, },
closeIcon: { closeIcon: {
height: '35px', height: '35px',

View File

@ -17,14 +17,14 @@ export const styles = () => ({
marginLeft: '20px', marginLeft: '20px',
}, },
manage: { manage: {
fontSize: '24px', fontSize: lg,
}, },
closeIcon: { closeIcon: {
height: '35px', height: '35px',
width: '35px', width: '35px',
}, },
headingText: { headingText: {
fontSize: '16px', fontSize: md,
}, },
formContainer: { formContainer: {
padding: `${md} ${lg}`, padding: `${md} ${lg}`,

View File

@ -58,15 +58,15 @@ export const sendReplaceOwner = async (
.swapOwner(prevAddress, ownerAddressToRemove, values.ownerAddress) .swapOwner(prevAddress, ownerAddressToRemove, values.ownerAddress)
.encodeABI() .encodeABI()
const txHash = await createTransaction( const txHash = await createTransaction({
safeAddress, safeAddress,
safeAddress, to: safeAddress,
0, valueInWei: 0,
txData, txData,
TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX, notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
enqueueSnackbar, enqueueSnackbar,
closeSnackbar, closeSnackbar,
) })
if (txHash && safe.threshold === 1) { if (txHash && safe.threshold === 1) {
replaceSafeOwner({ replaceSafeOwner({

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