Merge pull request #296 from gnosis/189-cookie-banner

Feature #189: Cookie banner
This commit is contained in:
Mikhail Mikheev 2019-12-04 16:40:09 +04:00 committed by GitHub
commit 2c42eb56af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 562 additions and 893 deletions

View File

@ -4,4 +4,4 @@ flow-typed
flow-typed/npm
config
scripts
migrations
migrations

30
flow-typed/npm/js-cookie_v2.x.x.js vendored Normal file
View File

@ -0,0 +1,30 @@
// flow-typed signature: a23fa96dc9c75f8931650efff45badee
// flow-typed version: c6154227d1/js-cookie_v2.x.x/flow_>=v0.104.x
declare module 'js-cookie' {
declare type CookieOptions = {
expires?: number | Date,
path?: string,
domain?: string,
secure?: boolean,
...
}
declare type ConverterFunc = (value: string, name: string) => string;
declare type ConverterObj = {
read: ConverterFunc,
write: ConverterFunc,
...
};
declare class Cookie {
defaults: CookieOptions;
set(name: string, value: mixed, options?: CookieOptions): void;
get(...args: Array<void>): { [key: string]: string, ... };
get(name: string, ...args: Array<void>): string | void;
remove(name: string, options?: CookieOptions): void;
getJSON(name: string): Object;
withConverter(converter: ConverterFunc | ConverterObj): this;
noConflict(): this;
}
declare module.exports: Cookie;
}

View File

@ -49,9 +49,11 @@
"history": "4.10.1",
"immortal-db": "^1.0.2",
"immutable": "^4.0.0-rc.9",
"js-cookie": "^2.2.1",
"material-ui-search-bar": "^1.0.0-beta.13",
"notistack": "https://github.com/gnosis/notistack.git#v0.9.4",
"optimize-css-assets-webpack-plugin": "5.0.3",
"polished": "^3.4.2",
"qrcode.react": "1.0.0",
"react": "16.12.0",
"react-dom": "16.12.0",

View File

@ -0,0 +1,169 @@
// @flow
import Checkbox from '@material-ui/core/Checkbox'
import Close from '@material-ui/icons/Close'
import FormControlLabel from '@material-ui/core/FormControlLabel'
import IconButton from '@material-ui/core/IconButton'
import React, { useEffect, useState } from 'react'
import { makeStyles } from '@material-ui/core/styles'
import { useDispatch, useSelector } from 'react-redux'
import Link from '~/components/layout/Link'
import Button from '~/components/layout/Button'
import { primary, mainFontFamily } from '~/theme/variables'
import type { CookiesProps } from '~/logic/cookies/model/cookie'
import { COOKIES_KEY } from '~/logic/cookies/model/cookie'
import { loadFromCookie, saveCookie } from '~/logic/cookies/utils'
import { cookieBannerOpen } from '~/logic/cookies/store/selectors'
import { openCookieBanner } from '~/logic/cookies/store/actions/openCookieBanner'
const useStyles = makeStyles({
container: {
backgroundColor: '#fff',
bottom: '0',
boxShadow: '0 2px 4px 0 rgba(212, 212, 211, 0.59)',
boxSizing: 'border-box',
display: 'flex',
justifyContent: 'center',
left: '0',
minHeight: '200px',
padding: '27px 15px',
position: 'fixed',
width: '100%',
},
content: {
maxWidth: '100%',
width: '830px',
},
text: {
color: primary,
fontFamily: mainFontFamily,
fontSize: '16px',
fontWeight: 'normal',
lineHeight: '1.38',
margin: '0 0 25px',
textAlign: 'center',
},
form: {
columnGap: '10px',
display: 'grid',
gridTemplateColumns: '1fr',
rowGap: '10px',
'@media (min-width: 960px)': {
gridTemplateColumns: '1fr 1fr 1fr',
},
},
formItem: {
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
},
link: {
textDecoration: 'underline',
'&:hover': {
textDecoration: 'none',
},
},
close: {
position: 'absolute',
right: '12px',
top: '12px',
},
})
const CookiesBanner = () => {
const classes = useStyles()
const dispatch = useDispatch()
const [localNecessary, setLocalNecessary] = useState(true)
const [localAnalytics, setLocalAnalytics] = useState(false)
const showBanner = useSelector(cookieBannerOpen)
useEffect(() => {
async function fetchCookiesFromStorage() {
const cookiesState: ?CookiesProps = await loadFromCookie(COOKIES_KEY)
if (cookiesState) {
const { acceptedNecessary, acceptedAnalytics } = cookiesState
setLocalAnalytics(acceptedAnalytics)
setLocalNecessary(acceptedNecessary)
const openBanner = acceptedNecessary === false || showBanner
dispatch(openCookieBanner(openBanner))
} else {
dispatch(openCookieBanner(true))
}
}
fetchCookiesFromStorage()
}, [showBanner])
const acceptCookiesHandler = async () => {
const newState = {
acceptedNecessary: true,
acceptedAnalytics: true,
}
await saveCookie(COOKIES_KEY, newState, 365)
dispatch(openCookieBanner(false))
}
const closeCookiesBannerHandler = async () => {
const newState = {
acceptedNecessary: true,
acceptedAnalytics: localAnalytics,
}
const expDays = localAnalytics ? 365 : 7
await saveCookie(COOKIES_KEY, newState, expDays)
dispatch(openCookieBanner(false))
}
return showBanner ? (
<div className={classes.container}>
<IconButton onClick={() => closeCookiesBannerHandler()} className={classes.close}><Close /></IconButton>
<div className={classes.content}>
<p className={classes.text}>
We use cookies to give you the best experience and to help improve our website. Please read our
{' '}
<Link className={classes.link} to="https://safe.gnosis.io/cookie">Cookie Policy</Link>
{' '}
for more information. By clicking &quot;Accept all&quot;, you agree to the storing of cookies on your device
to enhance site navigation, analyze site usage and provide customer support.
</p>
<div className={classes.form}>
<div className={classes.formItem}>
<FormControlLabel
checked={localNecessary}
disabled
label="Necessary"
name="Necessary"
onChange={() => setLocalNecessary((prev) => !prev)}
value={localNecessary}
control={(
<Checkbox disabled />
)}
/>
</div>
<div className={classes.formItem}>
<FormControlLabel
label="Analytics"
name="Analytics"
onChange={() => setLocalAnalytics((prev) => !prev)}
value={localAnalytics}
control={(
<Checkbox checked={localAnalytics} />
)}
/>
</div>
<div className={classes.formItem}>
<Button
color="primary"
component={Link}
minWidth={180}
variant="outlined"
onClick={() => acceptCookiesHandler()}
>
Accept All
</Button>
</div>
</div>
</div>
</div>
) : null
}
export default CookiesBanner

View File

@ -75,9 +75,4 @@ class Notifier extends Component<Props> {
}
}
export default withSnackbar(
connect(
selector,
actions,
)(Notifier),
)
export default withSnackbar(connect(selector, actions)(Notifier))

View File

@ -9,32 +9,34 @@ body {
font-style: normal;
font-weight: 400;
font-display: swap;
src: local("Averta-Regular"), url(../../assets/fonts/Averta-normal.woff2) format('woff2');
src: local("Averta-Regular"),
url(../../assets/fonts/Averta-normal.woff2) format("woff2");
}
@font-face {
font-family: 'Averta';
font-family: "Averta";
font-style: normal;
font-weight: 800;
font-display: swap;
src: local("Averta-Extrabold"), url(../../assets/fonts/Averta-ExtraBold.woff2) format('woff2');
src: local("Averta-Extrabold"),
url(../../assets/fonts/Averta-ExtraBold.woff2) format("woff2");
}
body {
position: absolute;
bottom: 0;
top: 0;
left: 0;
right: 0;
overflow-x: hidden;
color: $fontColor;
font-family: 'Averta', monospace;
font-size: $mediumFontSize;
margin: 0;
background-color: $background;
text-rendering: geometricPrecision;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
background-color: $background;
bottom: 0;
color: $fontColor;
font-family: "Averta", monospace;
font-size: $mediumFontSize;
left: 0;
margin: 0;
overflow-x: hidden;
position: absolute;
right: 0;
text-rendering: geometricPrecision;
top: 0;
}
body > div:first-child {

View File

@ -1,9 +1,12 @@
// @flow
import React from 'react'
import { makeStyles } from '@material-ui/core/styles'
import { useDispatch } from 'react-redux'
import Block from '~/components/layout/Block'
import Link from '~/components/layout/Link'
import { sm, primary } from '~/theme/variables'
import { openCookieBanner } from '~/logic/cookies/store/actions/openCookieBanner'
import GnoButtonLink from '~/components/layout/ButtonLink'
const useStyles = makeStyles({
container: {
@ -12,24 +15,42 @@ const useStyles = makeStyles({
link: {
color: primary,
},
buttonLink: {
textDecoration: 'none',
color: primary,
},
})
const LegalLinks = () => {
type Props = {
toggleSidebar: Function,
}
const LegalLinks = (props: Props) => {
const classes = useStyles()
const dispatch = useDispatch()
const openCookiesHandler = () => {
dispatch(openCookieBanner(true))
props.toggleSidebar()
}
return (
<Block className={classes.container} justify="space-around">
<Link className={classes.link} to="https://safe.gnosis.io/terms-of-use-072018.html" target="_blank">
<Link className={classes.link} to="https://safe.gnosis.io/terms" target="_blank">
Terms
</Link>
<Link className={classes.link} to="https://safe.gnosis.io/privacy-policy-052019.html" target="_blank">
<Link className={classes.link} to="https://safe.gnosis.io/privacy" target="_blank">
Privacy
</Link>
<Link className={classes.link} to="https://safe.gnosis.io/licenses-092019.html" target="_blank">
<Link className={classes.link} to="https://safe.gnosis.io/licenses" target="_blank">
Licenses
</Link>
<Link className={classes.link} to="https://safe.gnosis.io/imprint.html" target="_blank">
<Link className={classes.link} to="https://safe.gnosis.io/imprint" target="_blank">
Imprint
</Link>
<GnoButtonLink className={classes.buttonLink} onClick={openCookiesHandler}>
Cookies
</GnoButtonLink>
</Block>
)
}

View File

@ -131,7 +131,7 @@ const Sidebar = ({
setDefaultSafe={setDefaultSafeAction}
defaultSafe={defaultSafe}
/>
<LegalLinks />
<LegalLinks toggleSidebar={toggleSidebar} />
</Drawer>
</ClickAwayListener>
{children}

View File

@ -15,6 +15,7 @@ import AlertIcon from './assets/alert.svg'
import CheckIcon from './assets/check.svg'
import ErrorIcon from './assets/error.svg'
import InfoIcon from './assets/info.svg'
import CookiesBanner from '~/components/CookiesBanner'
import styles from './index.scss'
const notificationStyles = {
@ -92,6 +93,7 @@ const PageFrame = ({ children, classes, currentNetwork }: Props) => {
{children}
</SidebarProvider>
</SnackbarProvider>
<CookiesBanner />
</div>
)
}

View File

@ -1,14 +1,13 @@
// @flow
import 'babel-polyfill'
import React from 'react'
import ReactDOM from 'react-dom'
import { BigNumber } from 'bignumber.js'
import Root from '~/components/Root'
import { store } from '~/store'
import loadSafesFromStorage from '~/routes/safe/store/actions/loadSafesFromStorage'
import loadActiveTokens from '~/logic/tokens/store/actions/loadActiveTokens'
import loadDefaultSafe from '~/routes/safe/store/actions/loadDefaultSafe'
import loadSafesFromStorage from '~/routes/safe/store/actions/loadSafesFromStorage'
import { store } from '~/store'
BigNumber.set({ EXPONENTIAL_AT: [-7, 255] })
@ -18,8 +17,13 @@ if (process.env.NODE_ENV !== 'production') {
whyDidYouRender(React)
}
// $FlowFixMe
store.dispatch(loadActiveTokens())
store.dispatch(loadSafesFromStorage())
store.dispatch(loadDefaultSafe())
ReactDOM.render(<Root />, document.getElementById('root'))
const root = document.getElementById('root')
if (root !== null) {
ReactDOM.render(<Root />, root)
}

View File

@ -75,7 +75,10 @@ export const deploySafeContract = async (safeAccounts: string[], numConfirmation
const gasPrice = await calculateGasPrice()
return proxyFactoryMaster.createProxy(safeMaster.address, gnosisSafeData, {
from: userAccount, gas, gasPrice, value: 0,
from: userAccount,
gas,
gasPrice,
value: 0,
})
}

View File

@ -0,0 +1,12 @@
// @flow
import type { RecordOf } from 'immutable'
export const COOKIES_KEY = 'COOKIES'
export type CookiesProps = {
acceptedNecessary: boolean,
acceptedAnalytics: boolean,
cookieBannerOpen: boolean,
}
export type Cookie = RecordOf<CookiesProps>

View File

@ -0,0 +1,6 @@
// @flow
import { createAction } from 'redux-actions'
export const OPEN_COOKIE_BANNER = 'OPEN_COOKIE_BANNER'
export const openCookieBanner = createAction<string, *, *>(OPEN_COOKIE_BANNER, (cookieBannerOpen: boolean) => ({ cookieBannerOpen }))

View File

@ -0,0 +1,20 @@
// @flow
import { Map } from 'immutable'
import { handleActions, type ActionType } from 'redux-actions'
import type { Cookie } from '~/logic/cookies/model/cookie'
import { OPEN_COOKIE_BANNER } from '~/logic/cookies/store/actions/openCookieBanner'
export const COOKIES_REDUCER_ID = 'cookies'
export type State = Map<string, Map<string, Cookie>>
export default handleActions<State, *>(
{
[OPEN_COOKIE_BANNER]: (state: State, action: ActionType<Function>): State => {
const { cookieBannerOpen } = action.payload
return state.set('cookieBannerOpen', cookieBannerOpen)
},
},
Map(),
)

View File

@ -0,0 +1,5 @@
// @flow
import type { Provider } from '~/logic/wallets/store/model/provider'
import { COOKIES_REDUCER_ID } from '~/logic/cookies/store/reducer/cookies'
export const cookieBannerOpen = (state: any): Provider => state[COOKIES_REDUCER_ID].get('cookieBannerOpen')

View File

@ -0,0 +1,29 @@
// @flow
import Cookies from 'js-cookie'
import { getNetwork } from '~/config'
const PREFIX = `v1_${getNetwork()}`
export const loadFromCookie = async (key: string): Promise<*> => {
try {
const stringifiedValue = await Cookies.get(`${PREFIX}__${key}`)
if (stringifiedValue === null || stringifiedValue === undefined) {
return undefined
}
return JSON.parse(stringifiedValue)
} catch (err) {
console.error(`Failed to load ${key} from cookies:`, err)
return undefined
}
}
export const saveCookie = async (key: string, value: *, expirationDays: number): Promise<*> => {
try {
const stringifiedValue = JSON.stringify(value)
const expiration = expirationDays ? { expires: expirationDays } : undefined
await Cookies.set(`${PREFIX}__${key}`, stringifiedValue, expiration)
} catch (err) {
console.error(`Failed to save ${key} in cookies:`, err)
}
}

View File

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

View File

@ -5,11 +5,10 @@ import { type GlobalState } from '~/store'
import { NOTIFICATIONS_REDUCER_ID } from '~/logic/notifications/store/reducer/notifications'
import { type Notification } from '~/logic/notifications/store/models/notification'
const notificationsMapSelector = (
state: GlobalState,
): Map<string, Notification> => state[NOTIFICATIONS_REDUCER_ID]
const notificationsMapSelector = (state: GlobalState): Map<string, Notification> => state[NOTIFICATIONS_REDUCER_ID]
export const notificationsListSelector: Selector<GlobalState, {}, List<Notification>> = createSelector(
notificationsMapSelector,
(notifications: Map<string, Notification>): List<Notification> => notifications.toList(),
)
export const notificationsListSelector: Selector<
GlobalState,
{},
List<Notification>,
> = createSelector(notificationsMapSelector, (notifications: Map<string, Notification>): List<Notification> => notifications.toList())

View File

@ -68,7 +68,18 @@ export const getExecutionTransaction = async (
const web3 = getWeb3()
const contract = new web3.eth.Contract(GnosisSafeSol.abi, safeInstance.address)
return contract.methods.execTransaction(to, valueInWei, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, sigs)
return contract.methods.execTransaction(
to,
valueInWei,
data,
operation,
safeTxGas,
baseGas,
gasPrice,
gasToken,
refundReceiver,
sigs,
)
} catch (err) {
console.error(`Error while creating transaction: ${err}`)

View File

@ -13,7 +13,8 @@ export const tokenListSelector: Selector<GlobalState, Map<string, Token>, List<T
(tokens: Map<string, Token>) => tokens.toList(),
)
export const orderedTokenListSelector: Selector<GlobalState, RouterProps, List<Token>> = createSelector(
tokenListSelector,
(tokens: List<Token>) => tokens.sortBy((token: Token) => token.get('symbol')),
)
export const orderedTokenListSelector: Selector<
GlobalState,
RouterProps,
List<Token>,
> = createSelector(tokenListSelector, (tokens: List<Token>) => tokens.sortBy((token: Token) => token.get('symbol')))

View File

@ -6,40 +6,25 @@ import { ETHEREUM_NETWORK_IDS, ETHEREUM_NETWORK } from '~/logic/wallets/getWeb3'
const providerSelector = (state: any): Provider => state[PROVIDER_REDUCER_ID]
export const userAccountSelector = createSelector(
providerSelector,
(provider: Provider) => {
const account = provider.get('account')
export const userAccountSelector = createSelector(providerSelector, (provider: Provider) => {
const account = provider.get('account')
return account || ''
},
)
return account || ''
})
export const providerNameSelector = createSelector(
providerSelector,
(provider: Provider) => {
const name = provider.get('name')
export const providerNameSelector = createSelector(providerSelector, (provider: Provider) => {
const name = provider.get('name')
return name ? name.toLowerCase() : undefined
},
)
return name ? name.toLowerCase() : undefined
})
export const networkSelector = createSelector(
providerSelector,
(provider: Provider) => {
const networkId = provider.get('network')
const network = ETHEREUM_NETWORK_IDS[networkId] || ETHEREUM_NETWORK.UNKNOWN
export const networkSelector = createSelector(providerSelector, (provider: Provider) => {
const networkId = provider.get('network')
const network = ETHEREUM_NETWORK_IDS[networkId] || ETHEREUM_NETWORK.UNKNOWN
return network
},
)
return network
})
export const loadedSelector = createSelector(
providerSelector,
(provider: Provider) => provider.get('loaded'),
)
export const loadedSelector = createSelector(providerSelector, (provider: Provider) => provider.get('loaded'))
export const availableSelector = createSelector(
providerSelector,
(provider: Provider) => provider.get('available'),
)
export const availableSelector = createSelector(providerSelector, (provider: Provider) => provider.get('available'))

View File

@ -72,8 +72,8 @@ const Routes = ({ defaultSafe, location }: RoutesProps) => {
)
}
// $FlowFixMe
export default connect<Object, Object, ?Function, ?Object>(
// $FlowFixMe
(state) => ({ defaultSafe: defaultSafeSelector(state) }),
null,
)(withRouter(Routes))

View File

@ -55,13 +55,35 @@ const createTransaction = (
try {
if (isExecution) {
tx = await getExecutionTransaction(
safeInstance, to, valueInWei, txData, CALL, nonce,
0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, from, sigs
safeInstance,
to,
valueInWei,
txData,
CALL,
nonce,
0,
0,
0,
ZERO_ADDRESS,
ZERO_ADDRESS,
from,
sigs,
)
} else {
tx = await getApprovalTransaction(
safeInstance, to, valueInWei, txData, CALL, nonce,
0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, from, sigs
safeInstance,
to,
valueInWei,
txData,
CALL,
nonce,
0,
0,
0,
ZERO_ADDRESS,
ZERO_ADDRESS,
from,
sigs,
)
}

View File

@ -43,9 +43,7 @@ const getLocalSafe = async (safeAddress: string) => {
return storedSafes[safeAddress]
}
export const checkAndUpdateSafeOwners = (safeAddress: string) => async (
dispatch: ReduxDispatch<GlobalState>,
) => {
export const checkAndUpdateSafeOwners = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
// Check if the owner's safe did change and update them
const [gnosisSafe, localSafe] = await Promise.all([getGnosisSafeInstanceAt(safeAddress), getLocalSafe(safeAddress)])
const remoteOwners = await gnosisSafe.getOwners()

View File

@ -15,11 +15,7 @@ import {
TX_TYPE_EXECUTION,
TX_TYPE_CONFIRMATION,
} from '~/logic/safe/transactions'
import {
type NotificationsQueue,
getNotificationsFromTxType,
showSnackbar,
} from '~/logic/notifications'
import { type NotificationsQueue, getNotificationsFromTxType, showSnackbar } from '~/logic/notifications'
import { getErrorMessage } from '~/test/utils/ethereumErrors'
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
@ -40,18 +36,20 @@ export const generateSignaturesFromTxConfirmations = (
}
let sigs = '0x'
Object.keys(confirmationsMap).sort().forEach((addr) => {
const conf = confirmationsMap[addr]
if (conf.signature) {
sigs += conf.signature.slice(2)
} else {
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
sigs += `000000000000000000000000${addr.replace(
'0x',
'',
)}000000000000000000000000000000000000000000000000000000000000000001`
}
})
Object.keys(confirmationsMap)
.sort()
.forEach((addr) => {
const conf = confirmationsMap[addr]
if (conf.signature) {
sigs += conf.signature.slice(2)
} else {
// https://gnosis-safe.readthedocs.io/en/latest/contracts/signatures.html#pre-validated-signatures
sigs += `000000000000000000000000${addr.replace(
'0x',
'',
)}000000000000000000000000000000000000000000000000000000000000000001`
}
})
return sigs
}

View File

@ -90,7 +90,10 @@ const safeStorageMware = (store: Store<GlobalState>) => (next: Function) => asyn
case REMOVE_SAFE_OWNER: {
const { safeAddress, ownerAddress } = action.payload
const { owners } = safes.get(safeAddress)
setOwners(safeAddress, owners.filter((o) => o.address.toLowerCase() !== ownerAddress.toLowerCase()))
setOwners(
safeAddress,
owners.filter((o) => o.address.toLowerCase() !== ownerAddress.toLowerCase()),
)
break
}
case REPLACE_SAFE_OWNER: {
@ -110,7 +113,10 @@ const safeStorageMware = (store: Store<GlobalState>) => (next: Function) => asyn
const { safeAddress, ownerAddress, ownerName } = action.payload
const { owners } = safes.get(safeAddress)
const ownerToUpdateIndex = owners.findIndex((o) => o.address.toLowerCase() === ownerAddress.toLowerCase())
setOwners(safeAddress, owners.update(ownerToUpdateIndex, (owner) => owner.set('name', ownerName)))
setOwners(
safeAddress,
owners.update(ownerToUpdateIndex, (owner) => owner.set('name', ownerName)),
)
break
}
case SET_DEFAULT_SAFE: {

View File

@ -46,12 +46,15 @@ export default handleActions<SafeReducerState, *>(
const tokenAddress = action.payload
const newState = state.withMutations((map) => {
map.get('safes').keySeq().forEach((safeAddress) => {
const safeActiveTokens = map.getIn(['safes', safeAddress, 'activeTokens'])
const activeTokens = safeActiveTokens.add(tokenAddress)
map
.get('safes')
.keySeq()
.forEach((safeAddress) => {
const safeActiveTokens = map.getIn(['safes', safeAddress, 'activeTokens'])
const activeTokens = safeActiveTokens.add(tokenAddress)
map.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.merge({ activeTokens }))
})
map.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.merge({ activeTokens }))
})
})
return newState

View File

@ -18,6 +18,8 @@ import notifications, {
NOTIFICATIONS_REDUCER_ID,
type NotificationReducerState as NotificationsState,
} from '~/logic/notifications/store/reducer/notifications'
import cookies, { COOKIES_REDUCER_ID } from '~/logic/cookies/store/reducer/cookies'
export const history = createBrowserHistory()
@ -44,8 +46,10 @@ const reducers: Reducer<GlobalState> = combineReducers({
[TOKEN_REDUCER_ID]: tokens,
[TRANSACTIONS_REDUCER_ID]: transactions,
[NOTIFICATIONS_REDUCER_ID]: notifications,
[COOKIES_REDUCER_ID]: cookies,
})
export const store: Store<GlobalState> = createStore(reducers, finalCreateStore)
// eslint-disable-next-line max-len
export const aNewStore = (localState?: Object): Store<GlobalState> => createStore(reducers, localState, finalCreateStore)

View File

@ -115,7 +115,7 @@ export const whenSafeDeployed = (): Promise<string> => new Promise((resolve, rej
const interval = setInterval(() => {
if (times >= MAX_TIMES_EXECUTED) {
clearInterval(interval)
reject(new Error('Didn\'t load the safe'))
reject(new Error("Didn't load the safe"))
}
const url = `${window.location}`
console.log(url)

View File

@ -12,9 +12,7 @@ import { fillAndSubmitSendFundsForm } from './utils/transactions'
import { TRANSACTIONS_TAB_BTN_TEST_ID } from '~/routes/safe/components/Layout'
import { TRANSACTION_ROW_TEST_ID } from '~/routes/safe/components/Transactions/TxsTable'
import { useTestAccountAt, resetTestAccount } from './utils/accounts'
import {
CONFIRM_TX_BTN_TEST_ID,
} from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/ButtonRow'
import { CONFIRM_TX_BTN_TEST_ID } from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/OwnersColumn/ButtonRow'
import { APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID } from '~/routes/safe/components/Transactions/TxsTable/ExpandedTx/ApproveTxModal'
afterEach(resetTestAccount)

View File

@ -1,23 +1,26 @@
// @flow
import { createMuiTheme } from '@material-ui/core/styles'
import { rgba } from 'polished'
import {
extraSmallFontSize,
mediumFontSize,
smallFontSize,
disabled,
primary,
secondary,
error,
sm,
md,
lg,
bolderFont,
regularFont,
boldFont,
bolderFont,
buttonLargeFontSize,
disabled,
error,
extraSmallFontSize,
largeFontSize,
xs,
lg,
mainFontFamily,
md,
mediumFontSize,
primary,
regularFont,
secondary,
secondaryFontFamily,
secondaryText,
sm,
smallFontSize,
xs,
} from './variables'
export type WithStyles = {
@ -42,7 +45,7 @@ const palette = {
// see https://github.com/mui-org/material-ui/blob/v1-beta/src/styles/createMuiTheme.js
export default createMuiTheme({
typography: {
fontFamily: 'Averta,sans-serif',
fontFamily: mainFontFamily,
useNextVariants: true,
},
overrides: {
@ -53,7 +56,7 @@ export default createMuiTheme({
fontWeight: regularFont,
},
root: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
letterSpacing: '0.9px',
'&$disabled': {
color: disabled,
@ -109,7 +112,7 @@ export default createMuiTheme({
},
MuiChip: {
root: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
},
},
MuiStepIcon: {
@ -132,30 +135,30 @@ export default createMuiTheme({
},
MuiTypography: {
body1: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
letterSpacing: '-0.5px',
fontSize: mediumFontSize,
},
body2: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
},
},
MuiFormHelperText: {
root: {
fontFamily: 'Averta, monospace',
color: secondary,
fontFamily: secondaryFontFamily,
fontSize: '12px',
marginTop: '0px',
order: 0,
padding: `0 0 0 ${md}`,
position: 'absolute',
top: '5px',
color: secondary,
order: 0,
marginTop: '0px',
zIndex: 1, // for firefox
},
},
MuiInput: {
root: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
color: primary,
fontSize: mediumFontSize,
lineHeight: '56px',
@ -222,7 +225,7 @@ export default createMuiTheme({
},
MuiTab: {
root: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
fontWeight: 'normal',
fontSize: extraSmallFontSize,
'&$selected': {
@ -244,7 +247,7 @@ export default createMuiTheme({
top: '0px',
},
caption: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
fontSize: mediumFontSize,
order: 2,
color: disabled,
@ -270,7 +273,7 @@ export default createMuiTheme({
},
MuiTableCell: {
root: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
fontSize: mediumFontSize,
borderBottomWidth: '2px',
},
@ -298,7 +301,7 @@ export default createMuiTheme({
},
MuiMenuItem: {
root: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
},
},
MuiListItemIcon: {
@ -308,17 +311,31 @@ export default createMuiTheme({
},
MuiListItemText: {
primary: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
fontSize: mediumFontSize,
fontWeight: bolderFont,
color: primary,
},
secondary: {
fontFamily: 'Averta, monospace',
fontFamily: secondaryFontFamily,
fontSize: smallFontSize,
color: disabled,
},
},
MuiCheckbox: {
colorSecondary: {
'&$disabled': {
color: rgba(secondary, 0.5),
},
},
},
MuiFormControlLabel: {
label: {
'&$disabled': {
color: primary,
},
},
},
},
palette,
})

View File

@ -1,65 +1,67 @@
// @flow
const border = '#e8e7e6'
const background = '#f7f5f5'
const primary = '#001428'
const secondary = '#008C73'
const fontColor = '#001428'
const fancyColor = '#f02525'
const warningColor = '#ffc05f'
const errorColor = '#f02525'
const secondaryTextOrSvg = '#B2B5B2'
const border = '#e8e7e6'
const connectedColor = '#008C73'
const disabled = '#5D6D74'
const xs = '4px'
const sm = '8px'
const md = '16px'
const lg = '24px'
const xl = '32px'
const xxl = '40px'
const marginButtonImg = '12px'
const errorColor = '#f02525'
const fancyColor = '#f02525'
const fontColor = '#001428'
const headerHeight = '53px'
const lg = '24px'
const marginButtonImg = '12px'
const md = '16px'
const primary = '#001428'
const secondary = '#008C73'
const secondaryTextOrSvg = '#B2B5B2'
const sm = '8px'
const warningColor = '#ffc05f'
const xl = '32px'
const xs = '4px'
const xxl = '40px'
module.exports = {
primary,
secondary,
disabled,
background,
fontColor,
secondaryText: secondaryTextOrSvg,
fancy: fancyColor,
warning: warningColor,
error: errorColor,
connected: connectedColor,
headerHeight,
xs,
sm,
md,
lg,
xl,
xxl,
border,
marginButtonImg,
fontSizeHeadingXs: 13,
fontSizeHeadingSm: 16,
fontSizeHeadingMd: 20,
fontSizeHeadingLg: 32,
buttonLargeFontSize: '16px',
lightFont: 300,
regularFont: 400,
bolderFont: 500,
boldFont: 700,
bolderFont: 500,
border,
buttonLargeFontSize: '16px',
connected: connectedColor,
disabled,
error: errorColor,
extraBoldFont: 800,
extraSmallFontSize: '11px',
smallFontSize: '12px',
mediumFontSize: '14px',
largeFontSize: '16px',
extraLargeFontSize: '20px',
xxlFontSize: '32px',
screenXs: 480,
screenXsMax: 767,
screenSm: 768,
screenSmMax: 991,
extraSmallFontSize: '11px',
fancy: fancyColor,
fontColor,
fontSizeHeadingLg: 32,
fontSizeHeadingMd: 20,
fontSizeHeadingSm: 16,
fontSizeHeadingXs: 13,
headerHeight,
largeFontSize: '16px',
lg,
lightFont: 300,
mainFontFamily: 'Averta, sans-serif',
marginButtonImg,
md,
mediumFontSize: '14px',
primary,
regularFont: 400,
screenLg: 1200,
screenMd: 992,
screenMdMax: 1199,
screenLg: 1200,
screenSm: 768,
screenSmMax: 991,
screenXs: 480,
screenXsMax: 767,
secondary,
secondaryFontFamily: 'Averta, monospace',
secondaryText: secondaryTextOrSvg,
sm,
smallFontSize: '12px',
warning: warningColor,
xl,
xs,
xxl,
xxlFontSize: '32px',
}

723
yarn.lock

File diff suppressed because it is too large Load Diff