From 8ba0e18940967e1db58adf8d08eb58cb9a5991f8 Mon Sep 17 00:00:00 2001 From: nicolas Date: Tue, 21 Apr 2020 17:26:41 -0300 Subject: [PATCH 1/6] Manage Apps (#765) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * getting apps info from its manifest * Consume app info from manifest in Apps list, Transactions and Toast * fixes * navigate to TX Tab with an app makes a TX * ManageApps partial implementation * update safe-react-components * changes * apps validator * fix providedBy * TextField in addApp * fix checkbox error message * validation * adding app * Manage Apps * fix modal width * update package.json * update netlify url * fix modal widh * Set layout to Non-owners allowed error * some fixes * Apply suggestions from code review Co-Authored-By: lukasschor * review changes * fix condition * Legal Disclaimer * Better error message * update yarn.lock * Error message * review fixes * upgrade safe-react-components * show for all environments * Disclaimer changes * review changes * fix re-render * visual changes Co-authored-by: Agustín Longoni Co-authored-by: lukasschor --- .gitignore | 2 + package.json | 1 + .../layouts/ListContentLayout/Layout.jsx | 29 +- src/components/Root/index.js | 26 +- .../forms/Field/DebounceValidationField.js | 36 + src/components/forms/validator.js | 8 +- src/routes/safe/components/Apps/ManageApps.js | 197 +++++ src/routes/safe/components/Apps/index.jsx | 327 ++++++-- src/routes/safe/components/Apps/utils.js | 40 +- src/routes/safe/components/Layout.jsx | 5 +- .../safe/store/actions/createTransaction.js | 5 - .../safe/store/actions/processTransaction.js | 5 - yarn.lock | 743 +----------------- 13 files changed, 583 insertions(+), 841 deletions(-) create mode 100644 src/components/forms/Field/DebounceValidationField.js create mode 100644 src/routes/safe/components/Apps/ManageApps.js diff --git a/.gitignore b/.gitignore index d0d3934a..7e552830 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ build/ yarn-error.log .env* .idea/ +.yalc/ +yalc.lock \ No newline at end of file diff --git a/package.json b/package.json index f2b8ab5b..962cb13b 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "dependencies": { "@gnosis.pm/safe-contracts": "1.1.1-dev.1", "@gnosis.pm/util-contracts": "2.0.6", + "@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#71e6fed", "@material-ui/core": "4.9.10", "@material-ui/icons": "4.9.1", "@material-ui/lab": "4.0.0-alpha.39", diff --git a/src/components-v2/layouts/ListContentLayout/Layout.jsx b/src/components-v2/layouts/ListContentLayout/Layout.jsx index 905a5a8d..25de4452 100644 --- a/src/components-v2/layouts/ListContentLayout/Layout.jsx +++ b/src/components-v2/layouts/ListContentLayout/Layout.jsx @@ -4,43 +4,44 @@ import styled from 'styled-components' export const Wrapper = styled.div` display: grid; grid-template-columns: 245px auto; - grid-template-rows: 62px 500px 25px; - min-height: 500px; + grid-template-rows: 500px; + min-height: 525px; .background { box-shadow: 1px 2px 10px 0 rgba(212, 212, 211, 0.59); background-color: white; } ` -export const Nav = styled.div` - grid-column: 1/3; - grid-row: 1; - margin: 8px 0; - padding: 16px 0; - box-sizing: border-box; - display: flex; - justify-content: flex-end; -` +// export const Nav = styled.div` +// grid-column: 1/3; +// grid-row: 1; +// margin: 8px 0; +// padding: 16px 0; +// box-sizing: border-box; +// display: flex; +// justify-content: flex-end; +// ` export const Menu = styled.div.attrs(() => ({ className: 'background' }))` grid-column: 1; - grid-row: 2/4; border-right: solid 2px #e8e7e6; border-top-left-radius: 8px; border-bottom-left-radius: 8px; + background-color: white; ` export const Content = styled.div.attrs(() => ({ className: 'background' }))` grid-column: 2; - grid-row: 2; border-top-right-radius: 8px; + background-color: white; ` export const Footer = styled.div.attrs(() => ({ className: 'background' }))` grid-column: 2; - grid-row: 3; + grid-row: 2; border-bottom-right-radius: 8px; display: flex; justify-content: center; align-items: center; + background-color: white; ` diff --git a/src/components/Root/index.js b/src/components/Root/index.js index 7c5dfdd4..8dac4d82 100644 --- a/src/components/Root/index.js +++ b/src/components/Root/index.js @@ -1,11 +1,13 @@ // @flow import 'babel-polyfill' +import { theme as styledTheme } from '@gnosis.pm/safe-react-components' import { MuiThemeProvider } from '@material-ui/core/styles' import { ConnectedRouter } from 'connected-react-router' import React, { Suspense } from 'react' import { hot } from 'react-hot-loader/root' import { Provider } from 'react-redux' +import { ThemeProvider } from 'styled-components' import Loader from '../Loader' import PageFrame from '../layout/PageFrame' @@ -18,17 +20,19 @@ import './index.scss' import './OnboardCustom.scss' const Root = () => ( - - - - - }> - - - - - - + + + + + + }> + + + + + + + ) export default hot(Root) diff --git a/src/components/forms/Field/DebounceValidationField.js b/src/components/forms/Field/DebounceValidationField.js new file mode 100644 index 00000000..325946f5 --- /dev/null +++ b/src/components/forms/Field/DebounceValidationField.js @@ -0,0 +1,36 @@ +// @flow + +// source: https://github.com/final-form/react-final-form/issues/369#issuecomment-439823584 + +import React from 'react' +import { Field } from 'react-final-form' + +type Props = { + validate: () => void, + debounce: number, +} + +const DebounceValidationField = ({ debounce = 1000, validate, ...rest }: Props) => { + let clearTimeout + + const localValidation = (value, values, fieldState) => { + if (fieldState.active) { + return new Promise((resolve) => { + if (clearTimeout) clearTimeout() + const timerId = setTimeout(() => { + resolve(validate(value, values, fieldState)) + }, debounce) + clearTimeout = () => { + clearTimeout(timerId) + resolve() + } + }) + } else { + return validate(value, values, fieldState) + } + } + + return +} + +export default DebounceValidationField diff --git a/src/components/forms/validator.js b/src/components/forms/validator.js index 0a065a4e..9a8c78fa 100644 --- a/src/components/forms/validator.js +++ b/src/components/forms/validator.js @@ -88,8 +88,12 @@ export const uniqueAddress = (addresses: string[] | List) => return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined }) -export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) => - validators.reduce((error, validator) => error || validator(value), undefined) +export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field, values, meta) => { + if (!meta.modified) { + return + } + return validators.reduce((error, validator) => error || validator(value), undefined) +} export const inLimit = (limit: number, base: number, baseText: string, symbol: string = 'ETH') => (value: string) => { const amount = Number(value) diff --git a/src/routes/safe/components/Apps/ManageApps.js b/src/routes/safe/components/Apps/ManageApps.js new file mode 100644 index 00000000..fbacace9 --- /dev/null +++ b/src/routes/safe/components/Apps/ManageApps.js @@ -0,0 +1,197 @@ +// @flow +import { ButtonLink, Checkbox, ManageListModal, Text, TextField } from '@gnosis.pm/safe-react-components' +import React, { useState } from 'react' +import { FormSpy } from 'react-final-form' +import styled from 'styled-components' + +import { getAppInfoFromUrl } from './utils' + +import Field from '~/components/forms/Field' +import DebounceValidationField from '~/components/forms/Field/DebounceValidationField' +import GnoForm from '~/components/forms/GnoForm' +import { composeValidators, required } from '~/components/forms/validator' +import Img from '~/components/layout/Img' +import appsIconSvg from '~/routes/safe/components/Transactions/TxsTable/TxType/assets/appsIcon.svg' + +const FORM_ID = 'add-apps-form' + +const StyledText = styled(Text)` + margin-bottom: 19px; +` + +const StyledTextFileAppName = styled(TextField)` + && { + width: 335px; + } +` + +const AppInfo = styled.div` + margin: 36px 0 24px 0; + + img { + margin-right: 10px; + } +` + +const StyledCheckbox = styled(Checkbox)` + margin: 0; +` +const APP_INFO = { iconUrl: appsIconSvg, name: '', error: false } + +type Props = { + appList: Array<{ + id: string, + iconUrl: string, + name: string, + disabled: boolean, + }>, + onAppAdded: (app: any) => void, + onAppToggle: (appId: string, enabled: boolean) => void, +} + +const urlValidator = (value: string) => { + return /(?:^|[ \t])((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+)(:\d+)?(\/\S*)?)/gm.test(value) + ? undefined + : 'Please, provide a valid url' +} + +const ManageApps = ({ appList, onAppAdded, onAppToggle }: Props) => { + const [isOpen, setIsOpen] = useState(false) + + const [appInfo, setAppInfo] = useState(APP_INFO) + const [isSubmitDisabled, setIsSubmitDisabled] = useState(true) + + const onItemToggle = (itemId: string, checked: boolean) => { + onAppToggle(itemId, checked) + } + + const handleSubmit = () => { + setIsOpen(false) + onAppAdded(appInfo) + } + + const cleanAppInfo = () => setAppInfo(APP_INFO) + + const safeAppValidator = async (value) => { + const appInfo = await getAppInfoFromUrl(value) + + if (appInfo.error) { + setAppInfo(APP_INFO) + return 'This is not a valid Safe app.' + } + + setAppInfo({ ...appInfo }) + } + + const uniqueAppValidator = (value) => { + const exists = appList.find((a) => a.url === value.trim()) + return exists ? 'This app is already registered.' : undefined + } + + const onFormStatusChange = ({ pristine, valid, validating }) => { + if (!pristine) { + setIsSubmitDisabled(validating || !valid || appInfo.error) + } + } + + const customRequiredValidator = (value) => { + if (!value || !value.length) { + setAppInfo(APP_INFO) + return 'Required' + } + } + + const getAddAppForm = () => { + return ( + + {() => ( + <> + Add custom app + + + + Token image + + + + + + + This app is not a Gnosis product and I agree to use this app
at my own risk. +

+ } + name="agreed" + type="checkbox" + validate={required} + /> + + )} +
+ ) + } + + const onSubmitForm = () => { + // This sucks, but it's the way the docs suggest + // https://github.com/final-form/react-final-form/blob/master/docs/faq.md#via-documentgetelementbyid + document.querySelectorAll(`[data-testId=${FORM_ID}]`)[0].dispatchEvent(new Event('submit', { cancelable: true })) + } + + const toggleOpen = () => setIsOpen(!isOpen) + + const closeModal = () => { + setIsOpen(false) + cleanAppInfo() + } + + const getItemList = () => + appList.map((a) => { + return { ...a, checked: !a.disabled } + }) + + return ( + <> + + Manage Apps + + {isOpen && ( + + )} + + ) +} + +export default ManageApps diff --git a/src/routes/safe/components/Apps/index.jsx b/src/routes/safe/components/Apps/index.jsx index c822d183..37d0eec1 100644 --- a/src/routes/safe/components/Apps/index.jsx +++ b/src/routes/safe/components/Apps/index.jsx @@ -1,25 +1,37 @@ // @flow +import { Card, FixedDialog, FixedIcon, IconText, Menu, Text, Title } from '@gnosis.pm/safe-react-components' import { withSnackbar } from 'notistack' import React, { useCallback, useEffect, useState } from 'react' +import { withRouter } from 'react-router-dom' import styled from 'styled-components' +import ManageApps from './ManageApps' import confirmTransactions from './confirmTransactions' import sendTransactions from './sendTransactions' -import { GNOSIS_APPS_URL, getAppInfoFromUrl } from './utils' +import { getAppInfoFromUrl, staticAppsList } from './utils' import { ListContentLayout as LCL, Loader } from '~/components-v2' -import ButtonLink from '~/components/layout/ButtonLink' +import { SAFELIST_ADDRESS } from '~/routes/routes' +import { loadFromStorage, saveToStorage } from '~/utils/storage' + +const APPS_STORAGE_KEY = 'APPS_STORAGE_KEY' +const APPS_LEGAL_DISCLAIMER_STORAGE_KEY = 'APPS_LEGAL_DISCLAIMER_STORAGE_KEY' const StyledIframe = styled.iframe` width: 100%; height: 100%; display: ${(props) => (props.shouldDisplay ? 'block' : 'none')}; ` +const Centered = styled.div` + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +` + const operations = { - SEND_TRANSACTIONS: 'sendTransactions', - GET_TRANSACTIONS: 'getTransactions', - ON_SAFE_INFO: 'onSafeInfo', - ON_TX_UPDATE: 'onTransactionUpdate', + SEND_TRANSACTIONS: 'SEND_TRANSACTIONS', + ON_SAFE_INFO: 'ON_SAFE_INFO', } type Props = { @@ -27,7 +39,9 @@ type Props = { safeAddress: String, safeName: String, ethBalance: String, + history: Object, network: String, + granted: Boolean, createTransaction: any, enqueueSnackbar: Function, closeSnackbar: Function, @@ -41,19 +55,22 @@ function Apps({ createTransaction, enqueueSnackbar, ethBalance, + granted, + history, network, openModal, safeAddress, safeName, web3, }: Props) { - const [appsList, setAppsList] = useState([]) + const [appList, setAppList] = useState([]) + const [legalDisclaimerAccepted, setLegalDisclaimerAccepted] = useState(false) const [selectedApp, setSelectedApp] = useState() const [loading, setLoading] = useState(true) const [appIsLoading, setAppIsLoading] = useState(true) - const [iframeEl, setframeEl] = useState(null) + const [iframeEl, setIframeEl] = useState(null) - const getSelectedApp = () => appsList.find((e) => e.id === selectedApp) + const getSelectedApp = () => appList.find((e) => e.id === selectedApp) const sendMessageToIframe = (messageId, data) => { iframeEl.contentWindow.postMessage({ messageId, data }, getSelectedApp().url) @@ -61,7 +78,7 @@ function Apps({ const handleIframeMessage = async (data) => { if (!data || !data.messageId) { - console.warn('iframe: message without messageId') + console.error('ThirdPartyApp: A message was received without message id.') return } @@ -70,7 +87,7 @@ function Apps({ const onConfirm = async () => { closeModal() - const txHash = await sendTransactions( + await sendTransactions( web3, createTransaction, safeAddress, @@ -79,13 +96,6 @@ function Apps({ closeSnackbar, getSelectedApp().id, ) - - if (txHash) { - sendMessageToIframe(operations.ON_TX_UPDATE, { - txHash, - status: 'pending', - }) - } } confirmTransactions( @@ -101,11 +111,9 @@ function Apps({ break } - case operations.GET_TRANSACTIONS: - break default: { - console.warn(`Iframe:${data.messageId} unkown`) + console.error(`ThirdPartyApp: A message was received with an unknown message id ${data.messageId}.`) break } } @@ -113,10 +121,141 @@ function Apps({ const iframeRef = useCallback((node) => { if (node !== null) { - setframeEl(node) + setIframeEl(node) } }, []) + const onSelectApp = (appId) => { + const selectedApp = getSelectedApp() + + if (selectedApp && selectedApp.id === appId) { + return + } + + setAppIsLoading(true) + setSelectedApp(appId) + } + + const redirectToBalance = () => history.push(`${SAFELIST_ADDRESS}/${safeAddress}/balances`) + + const onAcceptLegalDisclaimer = () => { + setLegalDisclaimerAccepted(true) + saveToStorage(APPS_LEGAL_DISCLAIMER_STORAGE_KEY, true) + } + + const getContent = () => { + if (!selectedApp) { + return null + } + + if (!legalDisclaimerAccepted) { + return ( + + + You are now accessing third-party apps, which we do not own, control, maintain or audit. We are not + liable for any loss you may suffer in connection with interacting with the apps, which is at your own + risk. You must read our Terms, which contain more detailed provisions binding on you relating to the + apps. + +
+ + I have read and understood the{' '} + + Terms + {' '} + and this Disclaimer, and agree to be bound by . + + + } + onCancel={redirectToBalance} + onConfirm={onAcceptLegalDisclaimer} + title="Disclaimer" + /> + ) + } + + if (network === 'UNKNOWN' || !granted) { + return ( + + + To use apps, you must be an owner of this Safe + + ) + } + + return ( + <> + {appIsLoading && } + + + ) + } + + const onAppAdded = (app) => { + const newAppList = [ + { url: app.url, disabled: false }, + ...appList.map((a) => ({ + url: a.url, + disabled: a.disabled, + })), + ] + saveToStorage(APPS_STORAGE_KEY, newAppList) + + setAppList([...appList, { ...app, disabled: false }]) + } + + const selectFirstApp = (apps) => { + const firstEnabledApp = apps.find((a) => !a.disabled) + if (firstEnabledApp) { + onSelectApp(firstEnabledApp.id) + } + } + + const onAppToggle = async (appId: string, enabled: boolean) => { + // update in-memory list + const copyAppList = [...appList] + + const app = copyAppList.find((a) => a.id === appId) + if (!app) { + return + } + + app.disabled = !enabled + setAppList(copyAppList) + + // update storage list + const persistedAppList = (await loadFromStorage(APPS_STORAGE_KEY)) || [] + let storageApp = persistedAppList.find((a) => a.url === app.url) + + if (!storageApp) { + storageApp = { url: app.url } + storageApp.disabled = !enabled + persistedAppList.push(storageApp) + } else { + storageApp.disabled = !enabled + } + + saveToStorage(APPS_STORAGE_KEY, persistedAppList) + + // select app + const selectedApp = getSelectedApp() + if (!selectedApp || (selectedApp && selectedApp.id === appId)) { + setSelectedApp(undefined) + selectFirstApp(copyAppList) + } + } + + const getEnabledApps = () => appList.filter((a) => !a.disabled) + // handle messages from iframe useEffect(() => { const onIframeMessage = async ({ data, origin }) => { @@ -125,7 +264,7 @@ function Apps({ } if (!getSelectedApp().url.includes(origin)) { - console.error(`Message from ${origin} is different to the App URL ${getSelectedApp().url}`) + console.error(`ThirdPartyApp: A message from was received from an unknown origin ${origin}`) return } @@ -139,35 +278,63 @@ function Apps({ } }) + // load legalDisclaimer + useEffect(() => { + const checkLegalDisclaimer = async () => { + const legalDisclaimer = await loadFromStorage(APPS_LEGAL_DISCLAIMER_STORAGE_KEY) + + if (legalDisclaimer) { + setLegalDisclaimerAccepted(true) + } + } + + checkLegalDisclaimer() + }) + // Load apps list useEffect(() => { const loadApps = async () => { - const appsUrl = process.env.REACT_APP_GNOSIS_APPS_URL ? process.env.REACT_APP_GNOSIS_APPS_URL : GNOSIS_APPS_URL - const staticAppsList = [`${appsUrl}/compound`, `${appsUrl}/uniswap`] + // recover apps from storage: + // * third-party apps added by the user + // * disabled status for both static and third-party apps + const persistedAppList = (await loadFromStorage(APPS_STORAGE_KEY)) || [] + const list = [...persistedAppList] + + staticAppsList.forEach((staticApp) => { + if (!list.some((persistedApp) => persistedApp.url === staticApp.url)) { + list.push(staticApp) + } + }) - const list = [...staticAppsList] const apps = [] + // using the appURL to recover app info for (let index = 0; index < list.length; index++) { try { - const appUrl = list[index] - const appInfo = await getAppInfoFromUrl(appUrl) - const app = { url: appUrl, ...appInfo } + const currentApp = list[index] - app.id = JSON.stringify({ url: app.url, name: app.name }) - apps.push(app) + const appInfo = await getAppInfoFromUrl(currentApp.url) + + if (appInfo.error) { + throw Error() + } + + appInfo.disabled = currentApp.disabled === undefined ? false : currentApp.disabled + + apps.push(appInfo) } catch (error) { console.error(error) } } - setAppsList([...apps]) + setAppList(apps) setLoading(false) + selectFirstApp(apps) } - if (!appsList.length) { + if (!appList.length) { loadApps() } - }, [appsList]) + }, []) // on iframe change useEffect(() => { @@ -191,58 +358,54 @@ function Apps({ } }, [iframeEl]) - const onSelectApp = (appId) => { - setAppIsLoading(true) - setSelectedApp(appId) - } - - const getContent = () => { - if (!selectedApp) { - return null - } - - return ( - <> - {appIsLoading && } - - - ) - } - - if (loading || !appsList.length) { + if (loading) { return } return ( - - - {}} size="lg" testId="manage-tokens-btn"> - Manage Apps - - - - - - {getContent()} - - This App is provided by{' '} - window.open(getSelectedApp().providedBy.url, '_blank')} - size="lg" - testId="manage-tokens-btn" - > - {selectedApp && getSelectedApp().providedBy.name} - - - + <> + + + + {getEnabledApps().length ? ( + + + + + {getContent()} + {/* + {getSelectedApp() && getSelectedApp().providedBy && ( + <> +

This App is provided by

+ window.open(getSelectedApp().providedBy.url, '_blank')} + size="lg" + testId="manage-tokens-btn" + > + {selectedApp && getSelectedApp().providedBy.name} + + + )} +
*/} +
+ ) : ( + + + No Apps Enabled + + + )} + + + + ) } -export default withSnackbar(Apps) +export default withSnackbar(withRouter(Apps)) diff --git a/src/routes/safe/components/Apps/utils.js b/src/routes/safe/components/Apps/utils.js index 2731ac9f..91cad05d 100644 --- a/src/routes/safe/components/Apps/utils.js +++ b/src/routes/safe/components/Apps/utils.js @@ -3,34 +3,56 @@ import axios from 'axios' import appsIconSvg from '~/routes/safe/components/Transactions/TxsTable/TxType/assets/appsIcon.svg' -export const GNOSIS_APPS_URL = 'https://gnosis-apps.netlify.com' +export const GNOSIS_APPS_URL = 'https://gnosis-apps.netlify.app' + +const appsUrl = process.env.REACT_APP_GNOSIS_APPS_URL ? process.env.REACT_APP_GNOSIS_APPS_URL : GNOSIS_APPS_URL +export const staticAppsList = [{ url: `${appsUrl}/compound`, disabled: false }] export const getAppInfoFromOrigin = (origin: string) => { try { return JSON.parse(origin) } catch (error) { - console.error(`Impossible to parse TX origin: ${origin}`) + console.error(`Impossible to parse TX from origin: ${origin}`) return null } } export const getAppInfoFromUrl = async (appUrl: string) => { + let cleanedUpAppUrl = appUrl.trim() + if (cleanedUpAppUrl.substr(-1) === '/') { + cleanedUpAppUrl = cleanedUpAppUrl.substr(0, cleanedUpAppUrl.length - 1) + } + + let res = { id: undefined, url: cleanedUpAppUrl, name: 'unknown', iconUrl: appsIconSvg, error: true } + try { - const appInfo = await axios.get(`${appUrl}/manifest.json`) - const res = { url: appUrl, ...appInfo.data, iconUrl: appsIconSvg } + const appInfo = await axios.get(`${cleanedUpAppUrl}/manifest.json`) + + // verify imported app fulfil safe requirements + if (!appInfo || !appInfo.data || !appInfo.data.name || !appInfo.data.description) { + throw Error('The app does not fulfil the structure required.') + } + + res = { + ...res, + ...appInfo.data, + id: JSON.stringify({ url: cleanedUpAppUrl, name: appInfo.data.name }), + error: false, + } if (appInfo.data.iconPath) { try { - const iconInfo = await axios.get(`${appUrl}/${appInfo.data.iconPath}`) + const iconInfo = await axios.get(`${cleanedUpAppUrl}/${appInfo.data.iconPath}`) if (/image\/\w/gm.test(iconInfo.headers['content-type'])) { - res.iconUrl = `${appUrl}/${appInfo.data.iconPath}` + res.iconUrl = `${cleanedUpAppUrl}/${appInfo.data.iconPath}` } } catch (error) { - console.error(`It was not possible to fetch icon from app ${res.name}`) + console.error(`It was not possible to fetch icon from app ${cleanedUpAppUrl}`) } } + return res } catch (error) { - console.error(`It was not possible to fetch app from ${appUrl}`) - return null + console.error(`It was not possible to fetch app from ${cleanedUpAppUrl}: ${error.message}`) + return res } } diff --git a/src/routes/safe/components/Layout.jsx b/src/routes/safe/components/Layout.jsx index 66f5fd94..3a34bacd 100644 --- a/src/routes/safe/components/Layout.jsx +++ b/src/routes/safe/components/Layout.jsx @@ -186,6 +186,7 @@ const Layout = (props: Props) => { closeModal={closeGenericModal} createTransaction={createTransaction} ethBalance={ethBalance} + granted={granted} network={network} openModal={openGenericModal} safeAddress={address} @@ -349,9 +350,7 @@ const Layout = (props: Props) => { /> )} /> - {process.env.REACT_APP_ENV !== 'production' && ( - - )} + Date: Wed, 22 Apr 2020 07:44:05 -0300 Subject: [PATCH 2/6] fix scroll and fix margin layout --- src/components-v2/layouts/ListContentLayout/Layout.jsx | 2 +- src/components/layout/Page/index.scss | 4 ++-- src/routes/safe/components/AddressBook/style.js | 3 +-- src/routes/safe/components/Settings/style.js | 1 - src/routes/safe/components/Transactions/TxsTable/style.js | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components-v2/layouts/ListContentLayout/Layout.jsx b/src/components-v2/layouts/ListContentLayout/Layout.jsx index 905a5a8d..22d50973 100644 --- a/src/components-v2/layouts/ListContentLayout/Layout.jsx +++ b/src/components-v2/layouts/ListContentLayout/Layout.jsx @@ -15,7 +15,7 @@ export const Wrapper = styled.div` export const Nav = styled.div` grid-column: 1/3; grid-row: 1; - margin: 8px 0; + margin: 0; padding: 16px 0; box-sizing: border-box; display: flex; diff --git a/src/components/layout/Page/index.scss b/src/components/layout/Page/index.scss index d33b145f..7a7dfdf4 100644 --- a/src/components/layout/Page/index.scss +++ b/src/components/layout/Page/index.scss @@ -2,12 +2,12 @@ display: flex; flex: 1 0 auto; flex-direction: column; - padding: 135px 200px 0px 200px; + padding: 96px 200px 0px 200px; } @media only screen and (max-width: $(screenLg)px) { .page { - padding: 135px $lg 0px $lg; + padding: 72px $lg 0px $lg; } } diff --git a/src/routes/safe/components/AddressBook/style.js b/src/routes/safe/components/AddressBook/style.js index ab6cafc2..8321abc5 100644 --- a/src/routes/safe/components/AddressBook/style.js +++ b/src/routes/safe/components/AddressBook/style.js @@ -3,7 +3,7 @@ import { lg, marginButtonImg, md, sm } from '~/theme/variables' export const styles = () => ({ formContainer: { - minHeight: '420px', + minHeight: '250px', }, title: { padding: lg, @@ -52,7 +52,6 @@ export const styles = () => ({ cursor: 'default', }, message: { - margin: `${sm} 0`, padding: `${md} 0`, maxHeight: '54px', boxSizing: 'border-box', diff --git a/src/routes/safe/components/Settings/style.js b/src/routes/safe/components/Settings/style.js index f9017b74..7a246c80 100644 --- a/src/routes/safe/components/Settings/style.js +++ b/src/routes/safe/components/Settings/style.js @@ -119,7 +119,6 @@ export const styles = () => ({ position: 'relative', }, message: { - margin: `${sm} 0`, padding: `${md} 0`, maxHeight: '54px', // to make it the same as row in Balances component boxSizing: 'border-box', diff --git a/src/routes/safe/components/Transactions/TxsTable/style.js b/src/routes/safe/components/Transactions/TxsTable/style.js index 730fc7b6..9b0550d3 100644 --- a/src/routes/safe/components/Transactions/TxsTable/style.js +++ b/src/routes/safe/components/Transactions/TxsTable/style.js @@ -1,7 +1,7 @@ // @flow export const styles = () => ({ container: { - marginTop: '70px', + marginTop: '56px', }, row: { cursor: 'pointer', From 66dc95411de32e97578ee6b30f245d6429f8a43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Albela=20P=C3=A9rez?= <3659067+davidalbela@users.noreply.github.com> Date: Wed, 22 Apr 2020 13:17:22 +0200 Subject: [PATCH 3/6] Add region to travis CI develop (#787) Enable region for s3 provider to allow upload development environment to different S3 regions --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index af578168..8325f700 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,6 +44,7 @@ deploy: skip_cleanup: true local_dir: build_webpack upload-dir: app + region: $AWS_DEFAULT_REGION on: branch: development From aee2a1fefb679e73dc814004f16401911c3cfe5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Albela=20P=C3=A9rez?= <3659067+davidalbela@users.noreply.github.com> Date: Wed, 22 Apr 2020 14:42:08 +0200 Subject: [PATCH 4/6] Feature/travis ci s3 region (#789) * Add region to travis CI develop Enable region for s3 provider to allow upload development environment to different S3 regions * Enable region for staging s3 providers --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8325f700..8ecf3dc7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,6 +56,7 @@ deploy: skip_cleanup: true local_dir: build_webpack upload-dir: current/app + region: $AWS_DEFAULT_REGION on: branch: master @@ -67,6 +68,7 @@ deploy: skip_cleanup: true local_dir: build_webpack upload-dir: releases/$TRAVIS_TAG + region: $AWS_DEFAULT_REGION on: tags: true - provider: script From 124a91783e508cc67e594f69dab53636a8830e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Longoni?= Date: Wed, 22 Apr 2020 14:03:34 -0300 Subject: [PATCH 5/6] fix error on Wrapper --- src/components-v2/layouts/ListContentLayout/Layout.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components-v2/layouts/ListContentLayout/Layout.jsx b/src/components-v2/layouts/ListContentLayout/Layout.jsx index d13942fa..28ad2275 100644 --- a/src/components-v2/layouts/ListContentLayout/Layout.jsx +++ b/src/components-v2/layouts/ListContentLayout/Layout.jsx @@ -11,7 +11,7 @@ export const Wrapper = styled.div` box-shadow: 1px 2px 10px 0 rgba(212, 212, 211, 0.59); background-color: white; } - +` export const Nav = styled.div` grid-column: 1/3; grid-row: 1; From 38eb8ab5f0d259bbecfccbdc702f2eabf4303458 Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Wed, 22 Apr 2020 23:03:15 +0400 Subject: [PATCH 6/6] Add gnosis wc bridge to onboard.js wallets options array (#790) --- package.json | 2 +- src/logic/wallets/utils/walletList.js | 1 + yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 962cb13b..326f60a2 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "async-sema": "^3.1.0", "axios": "0.19.2", "bignumber.js": "9.0.0", - "bnc-onboard": "1.7.5", + "bnc-onboard": "1.7.6", "connected-react-router": "6.8.0", "currency-flags": "^2.1.1", "date-fns": "2.12.0", diff --git a/src/logic/wallets/utils/walletList.js b/src/logic/wallets/utils/walletList.js index c4cdc314..e336efd2 100644 --- a/src/logic/wallets/utils/walletList.js +++ b/src/logic/wallets/utils/walletList.js @@ -16,6 +16,7 @@ const wallets = [ preferred: true, infuraKey: process.env.REACT_APP_INFURA_TOKEN, desktop: true, + bridge: 'https://safe-walletconnect.gnosis.io/', }, { walletName: 'trezor', diff --git a/yarn.lock b/yarn.lock index 7b2ba4a4..28cd81b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1716,7 +1716,7 @@ web3-eth-contract "^1.2.6" web3-utils "^1.2.6" -"@toruslabs/torus-embed@^1.2.4": +"@toruslabs/torus-embed@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@toruslabs/torus-embed/-/torus-embed-1.3.0.tgz#46c7b4b0198a4acad1aa924cfb4c00cbd3b59244" integrity sha512-oZO7pFYjQCAXD/MvnhBEYPFf3rKt6eabT3l6f3OOlvOMW3LUxYOGnILgRx4sNwL9RR2YfU2qN5NbtQ3nUJcNFA== @@ -3893,15 +3893,15 @@ bn.js@^5.1.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.1.tgz#48efc4031a9c4041b9c99c6941d903463ab62eb5" integrity sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA== -bnc-onboard@1.7.5: - version "1.7.5" - resolved "https://registry.yarnpkg.com/bnc-onboard/-/bnc-onboard-1.7.5.tgz#e45ae51101e66b29935c16696e1d23478f5a5c4a" - integrity sha512-cxxsMPN2yy4EzaWXJk5TS9A7YYtolE4cuoxjB40Rm9nuP6k03zp1RST3pMMHn7zTXKGfofv99Q+YDJtYrOyCKg== +bnc-onboard@1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/bnc-onboard/-/bnc-onboard-1.7.6.tgz#2eb87a824342fe1a3da08798e30f5ce08b7a1b6d" + integrity sha512-8YMaMBzCYWrkNwZEt1zIZ1Yco9kYCXvn2tQlRRoz+m0LSXvXYmf6FlTB9KF30VtbXWTa+agebcoJzjYkwTrREA== dependencies: "@ledgerhq/hw-app-eth" "^5.7.0" "@ledgerhq/hw-transport-u2f" "^5.7.0" "@portis/web3" "^2.0.0-beta.42" - "@toruslabs/torus-embed" "^1.2.4" + "@toruslabs/torus-embed" "^1.3.0" "@unilogin/provider" "^0.5.21" "@walletconnect/web3-provider" "^1.0.0-beta.75" authereum "^0.0.4-beta.131"