diff --git a/.github/workflows/deploy-ewc.yml b/.github/workflows/deploy-ewc.yml index 39aaf20d..8286a554 100644 --- a/.github/workflows/deploy-ewc.yml +++ b/.github/workflows/deploy-ewc.yml @@ -1,10 +1,13 @@ name: Deploy to EWC network -# Run on pushes to master +# Run on pushes to master or PRs to master on: push: branches: - master + pull_request: + branches: + - master # Launches build when release is published release: types: [published] diff --git a/.github/workflows/deploy-mainnet.yml b/.github/workflows/deploy-mainnet.yml index 5e12509b..4314804e 100644 --- a/.github/workflows/deploy-mainnet.yml +++ b/.github/workflows/deploy-mainnet.yml @@ -1,7 +1,9 @@ name: Deploy to Mainnet network -# Run on pushes to master +# Run on pushes to master or PRs on: + # Pull request hook without any config. Launches for every pull request + pull_request: push: branches: - master @@ -84,7 +86,27 @@ jobs: aws-region: ${{ secrets.AWS_DEFAULT_REGION }} # Script to deploy Pull Requests - # Mainnet build is never created in Pull Requests + - run: bash ./scripts/github/deploy_pull_request.sh + if: success() && github.event.number + env: + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + PR_NUMBER: ${{ github.event.number }} + REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }} + REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }} + TRAVIS_TAG: ${{ github.event.release.tag_name }} + + - name: 'PRaul: Comment PR with app URLs' + uses: mshick/add-pr-comment@v1 + with: + message: | + * [Safe Multisig app ${{ env.REACT_APP_NETWORK }}](${{ env.REVIEW_FEATURE_URL }}/${{ env.REACT_APP_NETWORK }}/app/) + repo-token: ${{ secrets.GITHUB_TOKEN }} + repo-token-user-login: 'github-actions[bot]' + allow-repeats: true + if: success() && github.event.number + env: + REVIEW_FEATURE_URL: https://pr${{ github.event.number }}--${{ env.REPO_NAME_ALPHANUMERIC }}.review.gnosisdev.com # Script to deploy to development environment # Mainnet build is never created in development branch diff --git a/public/index.html b/public/index.html index a2f08f03..933b0c24 100644 --- a/public/index.html +++ b/public/index.html @@ -7,7 +7,31 @@ Gnosis Safe Multisig + -
+
diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index fa9b73f5..189000d3 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -20,13 +20,17 @@ import { getNetworkId } from 'src/config' import { ETHEREUM_NETWORK } from 'src/config/networks/network.d' import { networkSelector } from 'src/logic/wallets/store/selectors' import { SAFELIST_ADDRESS, WELCOME_ADDRESS } from 'src/routes/routes' -import { safeNameSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' +import { + safeFiatBalancesTotalSelector, + safeNameSelector, + safeParamAddressFromStateSelector, +} from 'src/logic/safe/store/selectors' +import { currentCurrencySelector } from 'src/logic/currencyValues/store/selectors' import Modal from 'src/components/Modal' import SendModal from 'src/routes/safe/components/Balances/SendModal' import { useLoadSafe } from 'src/logic/safe/hooks/useLoadSafe' import { useSafeScheduledUpdates } from 'src/logic/safe/hooks/useSafeScheduledUpdates' import useSafeActions from 'src/logic/safe/hooks/useSafeActions' -import { currentCurrencySelector, safeFiatBalancesTotalSelector } from 'src/logic/currencyValues/store/selectors' import { formatAmountInUsFormat } from 'src/logic/tokens/utils/formatAmount' import { grantedSelector } from 'src/routes/safe/container/selector' diff --git a/src/components/Stepper/Controls/index.tsx b/src/components/Stepper/Controls/index.tsx index 711d200b..02aa6d07 100644 --- a/src/components/Stepper/Controls/index.tsx +++ b/src/components/Stepper/Controls/index.tsx @@ -3,17 +3,18 @@ import * as React from 'react' import Button from 'src/components/layout/Button' import Col from 'src/components/layout/Col' import Row from 'src/components/layout/Row' -import { boldFont, sm } from 'src/theme/variables' +import { boldFont, sm, lg, secondary } from 'src/theme/variables' const controlStyle = { backgroundColor: 'white', - padding: sm, + padding: lg, borderRadius: sm, } const firstButtonStyle = { marginRight: sm, fontWeight: boldFont, + color: secondary, } const secondButtonStyle = { @@ -50,8 +51,8 @@ const Controls = ({ } return ( - - + + diff --git a/src/components/Stepper/OpenPaper/index.tsx b/src/components/Stepper/OpenPaper/index.tsx index 4e19dfbc..d013ed93 100644 --- a/src/components/Stepper/OpenPaper/index.tsx +++ b/src/components/Stepper/OpenPaper/index.tsx @@ -7,7 +7,7 @@ import { lg } from 'src/theme/variables' const useStyles = makeStyles({ root: { - margin: '10px', + margin: '10px 0 10px 10px', maxWidth: '770px', boxShadow: '0 0 10px 0 rgba(33,48,77,0.10)', }, diff --git a/src/components/forms/validator.test.ts b/src/components/forms/validator.test.ts index 38264deb..cd1918ba 100644 --- a/src/components/forms/validator.test.ts +++ b/src/components/forms/validator.test.ts @@ -10,6 +10,8 @@ import { uniqueAddress, differentFrom, ADDRESS_REPEATED_ERROR, + addressIsNotCurrentSafe, + OWNER_ADDRESS_IS_SAFE_ADDRESS_ERROR, } from 'src/components/forms/validator' describe('Forms > Validators', () => { @@ -179,6 +181,22 @@ describe('Forms > Validators', () => { }) }) + describe('addressIsNotSafe validator', () => { + it('Returns undefined if the given `address` it not the given `safeAddress`', async () => { + const address = '0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe' + const safeAddress = '0x2D6F2B448b0F711Eb81f2929566504117d67E44F' + + expect(addressIsNotCurrentSafe(safeAddress)(address)).toBeUndefined() + }) + + it('Returns an error message if the given `address` is the same as the `safeAddress`', async () => { + const address = '0x2D6F2B448b0F711Eb81f2929566504117d67E44F' + const safeAddress = '0x2D6F2B448b0F711Eb81f2929566504117d67E44F' + + expect(addressIsNotCurrentSafe(safeAddress)(address)).toEqual(OWNER_ADDRESS_IS_SAFE_ADDRESS_ERROR) + }) + }) + describe('differentFrom validator', () => { const getDifferentFromErrMsg = (diffValue: string): string => `Value should be different than ${diffValue}` diff --git a/src/components/forms/validator.ts b/src/components/forms/validator.ts index 09db0431..271c27ba 100644 --- a/src/components/forms/validator.ts +++ b/src/components/forms/validator.ts @@ -92,12 +92,16 @@ export const minMaxLength = (minLen: number, maxLen: number) => (value: string): value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols` export const ADDRESS_REPEATED_ERROR = 'Address already introduced' +export const OWNER_ADDRESS_IS_SAFE_ADDRESS_ERROR = 'Cannot use Safe itself as owner.' export const uniqueAddress = (addresses: string[] | List = []) => (address?: string): string | undefined => { const addressExists = addresses.some((addressFromList) => sameAddress(addressFromList, address)) return addressExists ? ADDRESS_REPEATED_ERROR : undefined } +export const addressIsNotCurrentSafe = (safeAddress: string) => (address?: string): string | undefined => + sameAddress(safeAddress, address) ? OWNER_ADDRESS_IS_SAFE_ADDRESS_ERROR : undefined + export const composeValidators = (...validators: Validator[]) => (value: unknown): ValidatorReturnType => validators.reduce( (error: string | undefined, validator: GenericValidatorType): ValidatorReturnType => error || validator(value), diff --git a/src/index.tsx b/src/index.tsx index 64a80101..7a3aeb82 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,7 +6,6 @@ import { Integrations } from '@sentry/tracing' import Root from 'src/components/Root' import loadCurrentSessionFromStorage from 'src/logic/currentSession/store/actions/loadCurrentSessionFromStorage' -import loadActiveTokens from 'src/logic/tokens/store/actions/loadActiveTokens' import loadDefaultSafe from 'src/logic/safe/store/actions/loadDefaultSafe' import loadSafesFromStorage from 'src/logic/safe/store/actions/loadSafesFromStorage' import { store } from 'src/store' @@ -17,7 +16,6 @@ disableMMAutoRefreshWarning() BigNumber.set({ EXPONENTIAL_AT: [-7, 255] }) -store.dispatch(loadActiveTokens()) store.dispatch(loadSafesFromStorage()) store.dispatch(loadDefaultSafe()) store.dispatch(loadCurrentSessionFromStorage()) diff --git a/src/logic/collectibles/store/selectors/index.ts b/src/logic/collectibles/store/selectors/index.ts index 1c5e6412..85efdb98 100644 --- a/src/logic/collectibles/store/selectors/index.ts +++ b/src/logic/collectibles/store/selectors/index.ts @@ -3,8 +3,6 @@ import { NFTAsset, NFTAssets, NFTToken, NFTTokens } from 'src/logic/collectibles import { AppReduxState } from 'src/store' import { NFT_ASSETS_REDUCER_ID, NFT_TOKENS_REDUCER_ID } from 'src/logic/collectibles/store/reducer/collectibles' -import { safeActiveAssetsSelector } from 'src/logic/safe/store/selectors' - export const nftAssets = (state: AppReduxState): NFTAssets => state[NFT_ASSETS_REDUCER_ID] export const nftTokens = (state: AppReduxState): NFTTokens => state[NFT_TOKENS_REDUCER_ID] @@ -26,21 +24,8 @@ export const orderedNFTAssets = createSelector(nftTokensSelector, (userNftTokens export const activeNftAssetsListSelector = createSelector( nftAssetsListSelector, - safeActiveAssetsSelector, availableNftAssetsAddresses, - (assets, activeAssetsList, availableNftAssetsAddresses): NFTAsset[] => { - return assets - .filter(({ address }) => activeAssetsList.has(address)) - .filter(({ address }) => availableNftAssetsAddresses.includes(address)) - }, -) - -export const safeActiveSelectorMap = createSelector( - activeNftAssetsListSelector, - (activeAssets): NFTAssets => { - return activeAssets.reduce((acc, asset) => { - acc[asset.address] = asset - return acc - }, {}) + (assets, availableNftAssetsAddresses): NFTAsset[] => { + return assets.filter(({ address }) => availableNftAssetsAddresses.includes(address)) }, ) diff --git a/src/logic/currencyValues/api/fetchAvailableCurrencies.ts b/src/logic/currencyValues/api/fetchAvailableCurrencies.ts new file mode 100644 index 00000000..4be6a079 --- /dev/null +++ b/src/logic/currencyValues/api/fetchAvailableCurrencies.ts @@ -0,0 +1,8 @@ +import { getClientGatewayUrl } from 'src/config' +import axios from 'axios' + +export const fetchAvailableCurrencies = async (): Promise => { + const url = `${getClientGatewayUrl()}/balances/supported-fiat-codes` + + return axios.get(url).then(({ data }) => data) +} diff --git a/src/logic/currencyValues/api/fetchCurrenciesRates.ts b/src/logic/currencyValues/api/fetchCurrenciesRates.ts deleted file mode 100644 index 20d1d6b2..00000000 --- a/src/logic/currencyValues/api/fetchCurrenciesRates.ts +++ /dev/null @@ -1,41 +0,0 @@ -import axios from 'axios' -import BigNumber from 'bignumber.js' - -import { EXCHANGE_RATE_URL } from 'src/utils/constants' -import { fetchTokenCurrenciesBalances } from './fetchTokenCurrenciesBalances' -import { sameString } from 'src/utils/strings' -import { AVAILABLE_CURRENCIES } from 'src/logic/currencyValues/store/model/currencyValues' - -const fetchCurrenciesRates = async ( - baseCurrency: string, - targetCurrencyValue: string, - safeAddress: string, -): Promise => { - let rate = 0 - if (sameString(targetCurrencyValue, AVAILABLE_CURRENCIES.NETWORK)) { - try { - const tokenCurrenciesBalances = await fetchTokenCurrenciesBalances(safeAddress) - if (tokenCurrenciesBalances.items.length) { - rate = new BigNumber(1).div(tokenCurrenciesBalances.items[0].fiatConversion).toNumber() - } - } catch (error) { - console.error(`Fetching ${AVAILABLE_CURRENCIES.NETWORK} data from the relayer errored`, error) - } - return rate - } - - // National currencies - try { - const url = `${EXCHANGE_RATE_URL}?base=${baseCurrency}&symbols=${targetCurrencyValue}` - const result = await axios.get(url) - if (result?.data) { - const { rates } = result.data - rate = rates[targetCurrencyValue] ? rates[targetCurrencyValue] : 0 - } - } catch (error) { - console.error('Fetching data from getExchangeRatesUrl errored', error) - } - return rate -} - -export default fetchCurrenciesRates diff --git a/src/logic/currencyValues/store/actions/fetchCurrencyRate.ts b/src/logic/currencyValues/store/actions/fetchCurrencyRate.ts deleted file mode 100644 index 580b90e4..00000000 --- a/src/logic/currencyValues/store/actions/fetchCurrencyRate.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Action } from 'redux-actions' -import { ThunkDispatch } from 'redux-thunk' - -import fetchCurrenciesRates from 'src/logic/currencyValues/api/fetchCurrenciesRates' -import { setCurrencyRate } from 'src/logic/currencyValues/store/actions/setCurrencyRate' -import { AVAILABLE_CURRENCIES } from 'src/logic/currencyValues/store/model/currencyValues' -import { CurrencyRatePayload } from 'src/logic/currencyValues/store/reducer/currencyValues' -import { AppReduxState } from 'src/store' - -const fetchCurrencyRate = (safeAddress: string, selectedCurrency: string) => async ( - dispatch: ThunkDispatch>, -): Promise => { - if (AVAILABLE_CURRENCIES.USD === selectedCurrency) { - dispatch(setCurrencyRate(safeAddress, 1)) - return - } - - const selectedCurrencyRateInBaseCurrency: number = await fetchCurrenciesRates( - AVAILABLE_CURRENCIES.USD, - selectedCurrency, - safeAddress, - ) - - dispatch(setCurrencyRate(safeAddress, selectedCurrencyRateInBaseCurrency)) -} - -export default fetchCurrencyRate diff --git a/src/logic/currencyValues/store/actions/fetchSelectedCurrency.ts b/src/logic/currencyValues/store/actions/fetchSelectedCurrency.ts index 2297b676..b4f279a0 100644 --- a/src/logic/currencyValues/store/actions/fetchSelectedCurrency.ts +++ b/src/logic/currencyValues/store/actions/fetchSelectedCurrency.ts @@ -2,18 +2,17 @@ import { Action } from 'redux-actions' import { ThunkDispatch } from 'redux-thunk' import { setSelectedCurrency } from 'src/logic/currencyValues/store/actions/setSelectedCurrency' -import { AVAILABLE_CURRENCIES } from 'src/logic/currencyValues/store/model/currencyValues' -import { CurrentCurrencyPayload } from 'src/logic/currencyValues/store/reducer/currencyValues' -import { loadSelectedCurrency } from 'src/logic/currencyValues/store/utils/currencyValuesStorage' -import { AppReduxState } from 'src/store' -export const fetchSelectedCurrency = (safeAddress: string) => async ( - dispatch: ThunkDispatch>, +import { loadSelectedCurrency } from 'src/logic/safe/utils/currencyValuesStorage' +import { AppReduxState } from 'src/store' +import { SelectedCurrencyPayload } from 'src/logic/currencyValues/store/reducer/currencyValues' + +export const fetchSelectedCurrency = () => async ( + dispatch: ThunkDispatch>, ): Promise => { try { const storedSelectedCurrency = await loadSelectedCurrency() - - dispatch(setSelectedCurrency(safeAddress, storedSelectedCurrency || AVAILABLE_CURRENCIES.USD)) + dispatch(setSelectedCurrency({ selectedCurrency: storedSelectedCurrency || 'USD' })) } catch (err) { console.error('Error fetching currency values', err) } diff --git a/src/logic/currencyValues/store/actions/setAvailableCurrencies.ts b/src/logic/currencyValues/store/actions/setAvailableCurrencies.ts new file mode 100644 index 00000000..e510b494 --- /dev/null +++ b/src/logic/currencyValues/store/actions/setAvailableCurrencies.ts @@ -0,0 +1,6 @@ +import { createAction } from 'redux-actions' +import { AvailableCurrenciesPayload } from 'src/logic/currencyValues/store/reducer/currencyValues' + +export const SET_AVAILABLE_CURRENCIES = 'SET_AVAILABLE_CURRENCIES' + +export const setAvailableCurrencies = createAction(SET_AVAILABLE_CURRENCIES) diff --git a/src/logic/currencyValues/store/actions/setCurrencyBalances.ts b/src/logic/currencyValues/store/actions/setCurrencyBalances.ts deleted file mode 100644 index b7390166..00000000 --- a/src/logic/currencyValues/store/actions/setCurrencyBalances.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createAction } from 'redux-actions' -import { BalanceCurrencyList } from 'src/logic/currencyValues/store/model/currencyValues' - -export const SET_CURRENCY_BALANCES = 'SET_CURRENCY_BALANCES' - -export const setCurrencyBalances = createAction( - SET_CURRENCY_BALANCES, - (safeAddress: string, currencyBalances: BalanceCurrencyList) => ({ - safeAddress, - currencyBalances, - }), -) diff --git a/src/logic/currencyValues/store/actions/setCurrencyRate.ts b/src/logic/currencyValues/store/actions/setCurrencyRate.ts deleted file mode 100644 index 786a2db7..00000000 --- a/src/logic/currencyValues/store/actions/setCurrencyRate.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createAction } from 'redux-actions' - -export const SET_CURRENCY_RATE = 'SET_CURRENCY_RATE' - -// eslint-disable-next-line max-len -export const setCurrencyRate = createAction(SET_CURRENCY_RATE, (safeAddress: string, currencyRate: number) => ({ - safeAddress, - currencyRate, -})) diff --git a/src/logic/currencyValues/store/actions/setSelectedCurrency.ts b/src/logic/currencyValues/store/actions/setSelectedCurrency.ts index 8a87126d..b1b7c577 100644 --- a/src/logic/currencyValues/store/actions/setSelectedCurrency.ts +++ b/src/logic/currencyValues/store/actions/setSelectedCurrency.ts @@ -1,20 +1,6 @@ -import { Action, createAction } from 'redux-actions' -import { ThunkDispatch } from 'redux-thunk' - -import { CurrencyPayloads } from 'src/logic/currencyValues/store/reducer/currencyValues' -import { AppReduxState } from 'src/store' -import fetchCurrencyRate from 'src/logic/currencyValues/store/actions/fetchCurrencyRate' +import { createAction } from 'redux-actions' +import { SelectedCurrencyPayload } from 'src/logic/currencyValues/store/reducer/currencyValues' export const SET_CURRENT_CURRENCY = 'SET_CURRENT_CURRENCY' -const setCurrentCurrency = createAction(SET_CURRENT_CURRENCY, (safeAddress: string, selectedCurrency: string) => ({ - safeAddress, - selectedCurrency, -})) - -export const setSelectedCurrency = (safeAddress: string, selectedCurrency: string) => ( - dispatch: ThunkDispatch>, -): void => { - dispatch(setCurrentCurrency(safeAddress, selectedCurrency)) - dispatch(fetchCurrencyRate(safeAddress, selectedCurrency)) -} +export const setSelectedCurrency = createAction(SET_CURRENT_CURRENCY) diff --git a/src/logic/currencyValues/store/actions/updateAvailableCurrencies.ts b/src/logic/currencyValues/store/actions/updateAvailableCurrencies.ts new file mode 100644 index 00000000..77e24961 --- /dev/null +++ b/src/logic/currencyValues/store/actions/updateAvailableCurrencies.ts @@ -0,0 +1,18 @@ +import { Action } from 'redux-actions' +import { ThunkDispatch } from 'redux-thunk' +import { AppReduxState } from 'src/store' +import { AvailableCurrenciesPayload } from 'src/logic/currencyValues/store/reducer/currencyValues' +import { setAvailableCurrencies } from 'src/logic/currencyValues/store/actions/setAvailableCurrencies' +import { fetchAvailableCurrencies } from 'src/logic/currencyValues/api/fetchAvailableCurrencies' + +export const updateAvailableCurrencies = () => async ( + dispatch: ThunkDispatch>, +): Promise => { + try { + const availableCurrencies = await fetchAvailableCurrencies() + dispatch(setAvailableCurrencies({ availableCurrencies })) + } catch (err) { + console.error('Error fetching available currencies', err) + } + return Promise.resolve() +} diff --git a/src/logic/currencyValues/store/middleware/index.ts b/src/logic/currencyValues/store/middleware/currencyValuesStorageMiddleware.ts similarity index 61% rename from src/logic/currencyValues/store/middleware/index.ts rename to src/logic/currencyValues/store/middleware/currencyValuesStorageMiddleware.ts index a361f593..e03420b7 100644 --- a/src/logic/currencyValues/store/middleware/index.ts +++ b/src/logic/currencyValues/store/middleware/currencyValuesStorageMiddleware.ts @@ -1,16 +1,15 @@ import { SET_CURRENT_CURRENCY } from 'src/logic/currencyValues/store/actions/setSelectedCurrency' -import { saveSelectedCurrency } from 'src/logic/currencyValues/store/utils/currencyValuesStorage' +import { saveSelectedCurrency } from 'src/logic/safe/utils/currencyValuesStorage' const watchedActions = [SET_CURRENT_CURRENCY] -const currencyValuesStorageMiddleware = () => (next) => async (action) => { +export const currencyValuesStorageMiddleware = () => (next) => async (action) => { const handledAction = next(action) if (watchedActions.includes(action.type)) { switch (action.type) { case SET_CURRENT_CURRENCY: { const { selectedCurrency } = action.payload - - saveSelectedCurrency(selectedCurrency) + await saveSelectedCurrency(selectedCurrency) break } @@ -21,5 +20,3 @@ const currencyValuesStorageMiddleware = () => (next) => async (action) => { return handledAction } - -export default currencyValuesStorageMiddleware diff --git a/src/logic/currencyValues/store/model/currencyValues.ts b/src/logic/currencyValues/store/model/currencyValues.ts deleted file mode 100644 index 26618349..00000000 --- a/src/logic/currencyValues/store/model/currencyValues.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { List, Record, RecordOf } from 'immutable' - -import { getNetworkInfo } from 'src/config' - -const { nativeCoin } = getNetworkInfo() - -export const AVAILABLE_CURRENCIES = { - NETWORK: nativeCoin.symbol.toLocaleUpperCase(), - USD: 'USD', - EUR: 'EUR', - AUD: 'AUD', - BGN: 'BGN', - BRL: 'BRL', - CAD: 'CAD', - CHF: 'CHF', - CNY: 'CNY', - CZK: 'CZK', - DKK: 'DKK', - GBP: 'GBP', - HKD: 'HKD', - HRK: 'HRK', - HUF: 'HUF', - IDR: 'IDR', - ILS: 'ILS', - INR: 'INR', - ISK: 'ISK', - JPY: 'JPY', - KRW: 'KRW', - MXN: 'MXN', - MYR: 'MYR', - NOK: 'NOK', - NZD: 'NZD', - PHP: 'PHP', - PLN: 'PLN', - RON: 'RON', - RUB: 'RUB', - SEK: 'SEK', - SGD: 'SGD', - THB: 'THB', - TRY: 'TRY', - ZAR: 'ZAR', -} as const - -export type BalanceCurrencyRecord = { - currencyName?: string - tokenAddress?: string - balanceInBaseCurrency: string - balanceInSelectedCurrency: string -} - -export const makeBalanceCurrency = Record({ - currencyName: '', - tokenAddress: '', - balanceInBaseCurrency: '', - balanceInSelectedCurrency: '', -}) - -export type CurrencyRateValueRecord = RecordOf - -export type BalanceCurrencyList = List - -export interface CurrencyRateValue { - currencyRate?: number - selectedCurrency?: string - currencyBalances?: BalanceCurrencyList -} diff --git a/src/logic/currencyValues/store/reducer/currencyValues.ts b/src/logic/currencyValues/store/reducer/currencyValues.ts index ee626001..4855aedf 100644 --- a/src/logic/currencyValues/store/reducer/currencyValues.ts +++ b/src/logic/currencyValues/store/reducer/currencyValues.ts @@ -1,44 +1,35 @@ -import { Map } from 'immutable' import { Action, handleActions } from 'redux-actions' - -import { SET_CURRENCY_BALANCES } from 'src/logic/currencyValues/store/actions/setCurrencyBalances' -import { SET_CURRENCY_RATE } from 'src/logic/currencyValues/store/actions/setCurrencyRate' import { SET_CURRENT_CURRENCY } from 'src/logic/currencyValues/store/actions/setSelectedCurrency' -import { BalanceCurrencyList, CurrencyRateValue } from 'src/logic/currencyValues/store/model/currencyValues' +import { AppReduxState } from 'src/store' +import { SET_AVAILABLE_CURRENCIES } from 'src/logic/currencyValues/store/actions/setAvailableCurrencies' export const CURRENCY_VALUES_KEY = 'currencyValues' -export interface CurrencyReducerMap extends Map { - get(key: K, notSetValue?: unknown): CurrencyRateValue[K] - setIn(keys: [string, K], value: CurrencyRateValue[K]): this +export type CurrencyValuesState = { + selectedCurrency: string + availableCurrencies: string[] } -export type CurrencyValuesState = Map +export const initialState = { + selectedCurrency: 'USD', + availableCurrencies: ['USD'], +} -type CurrencyBasePayload = { safeAddress: string } -export type CurrencyRatePayload = CurrencyBasePayload & { currencyRate: number } -export type CurrencyBalancesPayload = CurrencyBasePayload & { currencyBalances: BalanceCurrencyList } -export type CurrentCurrencyPayload = CurrencyBasePayload & { selectedCurrency: string } +export type SelectedCurrencyPayload = { selectedCurrency: string } +export type AvailableCurrenciesPayload = { availableCurrencies: string[] } -export type CurrencyPayloads = CurrencyRatePayload | CurrencyBalancesPayload | CurrentCurrencyPayload - -export default handleActions( +export default handleActions( { - [SET_CURRENCY_RATE]: (state, action: Action) => { - const { currencyRate, safeAddress } = action.payload - - return state.setIn([safeAddress, 'currencyRate'], currencyRate) + [SET_CURRENT_CURRENCY]: (state, action: Action) => { + const { selectedCurrency } = action.payload + state.selectedCurrency = selectedCurrency + return state }, - [SET_CURRENCY_BALANCES]: (state, action: Action) => { - const { safeAddress, currencyBalances } = action.payload - - return state.setIn([safeAddress, 'currencyBalances'], currencyBalances) - }, - [SET_CURRENT_CURRENCY]: (state, action: Action) => { - const { safeAddress, selectedCurrency } = action.payload - - return state.setIn([safeAddress, 'selectedCurrency'], selectedCurrency) + [SET_AVAILABLE_CURRENCIES]: (state, action: Action) => { + const { availableCurrencies } = action.payload + state.availableCurrencies = availableCurrencies + return state }, }, - Map(), + initialState, ) diff --git a/src/logic/currencyValues/store/selectors/index.ts b/src/logic/currencyValues/store/selectors/index.ts index b1789174..e70adf05 100644 --- a/src/logic/currencyValues/store/selectors/index.ts +++ b/src/logic/currencyValues/store/selectors/index.ts @@ -1,53 +1,12 @@ -import { createSelector } from 'reselect' - -import { - CURRENCY_VALUES_KEY, - CurrencyReducerMap, - CurrencyValuesState, -} from 'src/logic/currencyValues/store/reducer/currencyValues' -import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { AppReduxState } from 'src/store' -import { CurrencyRateValue } from 'src/logic/currencyValues/store/model/currencyValues' -import { BigNumber } from 'bignumber.js' +import { CURRENCY_VALUES_KEY, CurrencyValuesState } from 'src/logic/currencyValues/store/reducer/currencyValues' export const currencyValuesSelector = (state: AppReduxState): CurrencyValuesState => state[CURRENCY_VALUES_KEY] -export const safeFiatBalancesSelector = createSelector( - currencyValuesSelector, - safeParamAddressFromStateSelector, - (currencyValues, safeAddress): CurrencyReducerMap | undefined => { - if (!currencyValues || !safeAddress) return - return currencyValues.get(safeAddress) - }, -) +export const currentCurrencySelector = (state: AppReduxState): string => { + return state[CURRENCY_VALUES_KEY].selectedCurrency +} -const currencyValueSelector = (key: K) => ( - currencyValuesMap?: CurrencyReducerMap, -): CurrencyRateValue[K] => currencyValuesMap?.get(key) - -export const safeFiatBalancesListSelector = createSelector( - safeFiatBalancesSelector, - currencyValueSelector('currencyBalances'), -) - -export const currentCurrencySelector = createSelector( - safeFiatBalancesSelector, - currencyValueSelector('selectedCurrency'), -) - -export const currencyRateSelector = createSelector(safeFiatBalancesSelector, currencyValueSelector('currencyRate')) - -export const safeFiatBalancesTotalSelector = createSelector( - safeFiatBalancesListSelector, - currencyRateSelector, - (currencyBalances, currencyRate): string | null => { - if (!currencyBalances) return '0' - if (!currencyRate) return null - - const totalInBaseCurrency = currencyBalances.reduce((total, balanceCurrencyRecord) => { - return total.plus(balanceCurrencyRecord.balanceInBaseCurrency) - }, new BigNumber(0)) - - return totalInBaseCurrency.times(currencyRate).toFixed(2) - }, -) +export const availableCurrenciesSelector = (state: AppReduxState): string[] => { + return state[CURRENCY_VALUES_KEY].availableCurrencies +} diff --git a/src/logic/currencyValues/__tests__/fetchSafeTokens.test.ts b/src/logic/safe/__tests__/fetchSafeTokens.test.ts similarity index 84% rename from src/logic/currencyValues/__tests__/fetchSafeTokens.test.ts rename to src/logic/safe/__tests__/fetchSafeTokens.test.ts index adc85654..4b9a4901 100644 --- a/src/logic/currencyValues/__tests__/fetchSafeTokens.test.ts +++ b/src/logic/safe/__tests__/fetchSafeTokens.test.ts @@ -1,10 +1,7 @@ import axios from 'axios' import { getSafeClientGatewayBaseUrl } from 'src/config' -import { - fetchTokenCurrenciesBalances, - BalanceEndpoint, -} from 'src/logic/currencyValues/api/fetchTokenCurrenciesBalances' +import { fetchTokenCurrenciesBalances } from 'src/logic/safe/api/fetchTokenCurrenciesBalances' import { aNewStore } from 'src/store' jest.mock('axios') @@ -52,11 +49,15 @@ describe('fetchTokenCurrenciesBalances', () => { axios.get.mockImplementationOnce(() => Promise.resolve({ data: expectedResult })) // when - const result = await fetchTokenCurrenciesBalances(safeAddress, excludeSpamTokens) + const result = await fetchTokenCurrenciesBalances({ + safeAddress, + excludeSpamTokens, + selectedCurrency: 'USD', + }) // then expect(result).toStrictEqual(expectedResult) expect(axios.get).toHaveBeenCalled() - expect(axios.get).toBeCalledWith(`${apiUrl}/balances/usd/?trusted=false&exclude_spam=${excludeSpamTokens}`) + expect(axios.get).toBeCalledWith(`${apiUrl}/balances/USD/?trusted=false&exclude_spam=${excludeSpamTokens}`) }) }) diff --git a/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts b/src/logic/safe/api/fetchTokenCurrenciesBalances.ts similarity index 59% rename from src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts rename to src/logic/safe/api/fetchTokenCurrenciesBalances.ts index fd21f689..d1185a3d 100644 --- a/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts +++ b/src/logic/safe/api/fetchTokenCurrenciesBalances.ts @@ -16,14 +16,22 @@ export type BalanceEndpoint = { items: TokenBalance[] } -export const fetchTokenCurrenciesBalances = ( - safeAddress: string, +type FetchTokenCurrenciesBalancesProps = { + safeAddress: string + selectedCurrency: string + excludeSpamTokens?: boolean + trustedTokens?: boolean +} + +export const fetchTokenCurrenciesBalances = async ({ + safeAddress, + selectedCurrency, excludeSpamTokens = true, trustedTokens = false, -): Promise => { +}: FetchTokenCurrenciesBalancesProps): Promise => { const url = `${getSafeClientGatewayBaseUrl( checksumAddress(safeAddress), - )}/balances/usd/?trusted=${trustedTokens}&exclude_spam=${excludeSpamTokens}` + )}/balances/${selectedCurrency}/?trusted=${trustedTokens}&exclude_spam=${excludeSpamTokens}` return axios.get(url).then(({ data }) => data) } diff --git a/src/logic/safe/hooks/useFetchTokens.tsx b/src/logic/safe/hooks/useFetchTokens.tsx index 7d19e373..829fefaf 100644 --- a/src/logic/safe/hooks/useFetchTokens.tsx +++ b/src/logic/safe/hooks/useFetchTokens.tsx @@ -1,35 +1,32 @@ import { useMemo } from 'react' -import { batch, useDispatch } from 'react-redux' +import { batch, useDispatch, useSelector } from 'react-redux' import { useLocation } from 'react-router-dom' import { fetchCollectibles } from 'src/logic/collectibles/store/actions/fetchCollectibles' import { fetchSelectedCurrency } from 'src/logic/currencyValues/store/actions/fetchSelectedCurrency' -import activateAssetsByBalance from 'src/logic/tokens/store/actions/activateAssetsByBalance' -import fetchSafeTokens from 'src/logic/tokens/store/actions/fetchSafeTokens' +import { fetchSafeTokens } from 'src/logic/tokens/store/actions/fetchSafeTokens' import { fetchTokens } from 'src/logic/tokens/store/actions/fetchTokens' import { COINS_LOCATION_REGEX, COLLECTIBLES_LOCATION_REGEX } from 'src/routes/safe/components/Balances' import { Dispatch } from 'src/logic/safe/store/actions/types.d' +import { currentCurrencySelector } from 'src/logic/currencyValues/store/selectors' export const useFetchTokens = (safeAddress: string): void => { const dispatch = useDispatch() const location = useLocation() + const currentCurrency = useSelector(currentCurrencySelector) useMemo(() => { if (COINS_LOCATION_REGEX.test(location.pathname)) { batch(() => { // fetch tokens there to get symbols for tokens in TXs list dispatch(fetchTokens()) - dispatch(fetchSelectedCurrency(safeAddress)) - dispatch(fetchSafeTokens(safeAddress)) + dispatch(fetchSelectedCurrency()) + dispatch(fetchSafeTokens(safeAddress, currentCurrency)) }) } if (COLLECTIBLES_LOCATION_REGEX.test(location.pathname)) { - batch(() => { - dispatch(fetchCollectibles(safeAddress)).then(() => { - dispatch(activateAssetsByBalance(safeAddress)) - }) - }) + dispatch(fetchCollectibles(safeAddress)) } - }, [dispatch, location.pathname, safeAddress]) + }, [dispatch, location.pathname, safeAddress, currentCurrency]) } diff --git a/src/logic/safe/hooks/useLoadSafe.tsx b/src/logic/safe/hooks/useLoadSafe.tsx index 116bf24c..bf07f60b 100644 --- a/src/logic/safe/hooks/useLoadSafe.tsx +++ b/src/logic/safe/hooks/useLoadSafe.tsx @@ -3,11 +3,12 @@ import { useDispatch } from 'react-redux' import loadAddressBookFromStorage from 'src/logic/addressBook/store/actions/loadAddressBookFromStorage' import addViewedSafe from 'src/logic/currentSession/store/actions/addViewedSafe' -import fetchSafeTokens from 'src/logic/tokens/store/actions/fetchSafeTokens' +import { fetchSafeTokens } from 'src/logic/tokens/store/actions/fetchSafeTokens' import fetchLatestMasterContractVersion from 'src/logic/safe/store/actions/fetchLatestMasterContractVersion' import fetchSafe from 'src/logic/safe/store/actions/fetchSafe' import fetchTransactions from 'src/logic/safe/store/actions/transactions/fetchTransactions' import { Dispatch } from 'src/logic/safe/store/actions/types.d' +import { updateAvailableCurrencies } from 'src/logic/currencyValues/store/actions/updateAvailableCurrencies' export const useLoadSafe = (safeAddress?: string): boolean => { const dispatch = useDispatch() @@ -20,6 +21,7 @@ export const useLoadSafe = (safeAddress?: string): boolean => { await dispatch(fetchSafe(safeAddress)) setIsSafeLoaded(true) await dispatch(fetchSafeTokens(safeAddress)) + dispatch(updateAvailableCurrencies()) dispatch(fetchTransactions(safeAddress)) dispatch(addViewedSafe(safeAddress)) } diff --git a/src/logic/safe/hooks/useSafeScheduledUpdates.tsx b/src/logic/safe/hooks/useSafeScheduledUpdates.tsx index a18f7349..aba374b4 100644 --- a/src/logic/safe/hooks/useSafeScheduledUpdates.tsx +++ b/src/logic/safe/hooks/useSafeScheduledUpdates.tsx @@ -2,8 +2,8 @@ import { useEffect, useRef } from 'react' import { batch, useDispatch } from 'react-redux' import { fetchCollectibles } from 'src/logic/collectibles/store/actions/fetchCollectibles' -import fetchSafeTokens from 'src/logic/tokens/store/actions/fetchSafeTokens' -import fetchEtherBalance from 'src/logic/safe/store/actions/fetchEtherBalance' +import { fetchSafeTokens } from 'src/logic/tokens/store/actions/fetchSafeTokens' +import { fetchEtherBalance } from 'src/logic/safe/store/actions/fetchEtherBalance' import { checkAndUpdateSafe } from 'src/logic/safe/store/actions/fetchSafe' import fetchTransactions from 'src/logic/safe/store/actions/transactions/fetchTransactions' import { TIMEOUT } from 'src/utils/constants' diff --git a/src/logic/safe/store/actions/activateTokenForAllSafes.ts b/src/logic/safe/store/actions/activateTokenForAllSafes.ts deleted file mode 100644 index 64b9b13c..00000000 --- a/src/logic/safe/store/actions/activateTokenForAllSafes.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createAction } from 'redux-actions' - -export const ACTIVATE_TOKEN_FOR_ALL_SAFES = 'ACTIVATE_TOKEN_FOR_ALL_SAFES' - -const activateTokenForAllSafes = createAction(ACTIVATE_TOKEN_FOR_ALL_SAFES) - -export default activateTokenForAllSafes diff --git a/src/logic/safe/store/actions/addSafeOwner.ts b/src/logic/safe/store/actions/addSafeOwner.ts index 69e01898..2a341b4e 100644 --- a/src/logic/safe/store/actions/addSafeOwner.ts +++ b/src/logic/safe/store/actions/addSafeOwner.ts @@ -2,6 +2,4 @@ import { createAction } from 'redux-actions' export const ADD_SAFE_OWNER = 'ADD_SAFE_OWNER' -const addSafeOwner = createAction(ADD_SAFE_OWNER) - -export default addSafeOwner +export const addSafeOwner = createAction(ADD_SAFE_OWNER) diff --git a/src/logic/safe/store/actions/createTransaction.ts b/src/logic/safe/store/actions/createTransaction.ts index 4ce9c211..b2e6b347 100644 --- a/src/logic/safe/store/actions/createTransaction.ts +++ b/src/logic/safe/store/actions/createTransaction.ts @@ -12,6 +12,7 @@ import { tryOffchainSigning, } from 'src/logic/safe/transactions' import { estimateGasForTransactionCreation } from 'src/logic/safe/transactions/gas' +import * as aboutToExecuteTx from 'src/logic/safe/utils/aboutToExecuteTx' import { getCurrentSafeVersion } from 'src/logic/safe/utils/safeVersion' import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' @@ -148,6 +149,9 @@ export const createTransaction = ( await saveTxToHistory({ ...txArgs, txHash, origin }) + // store the pending transaction's nonce + isExecution && aboutToExecuteTx.setNonce(txArgs.nonce) + dispatch(fetchTransactions(safeAddress)) }) .on('error', (error) => { @@ -156,10 +160,6 @@ export const createTransaction = ( onError?.() }) .then(async (receipt) => { - if (isExecution) { - dispatch(enqueueSnackbar(notificationsQueue.afterExecution.noMoreConfirmationsNeeded)) - } - dispatch(fetchTransactions(safeAddress)) return receipt.transactionHash diff --git a/src/logic/safe/store/actions/fetchEtherBalance.ts b/src/logic/safe/store/actions/fetchEtherBalance.ts index 146a2033..8025f639 100644 --- a/src/logic/safe/store/actions/fetchEtherBalance.ts +++ b/src/logic/safe/store/actions/fetchEtherBalance.ts @@ -5,7 +5,7 @@ import { Dispatch } from 'redux' import { backOff } from 'exponential-backoff' import { AppReduxState } from 'src/store' -const fetchEtherBalance = (safeAddress: string) => async ( +export const fetchEtherBalance = (safeAddress: string) => async ( dispatch: Dispatch, getState: () => AppReduxState, ): Promise => { @@ -21,5 +21,3 @@ const fetchEtherBalance = (safeAddress: string) => async ( console.error('Error when fetching Ether balance:', err) } } - -export default fetchEtherBalance diff --git a/src/logic/safe/store/actions/fetchSafe.ts b/src/logic/safe/store/actions/fetchSafe.ts index 014e6e26..d7dcba4f 100644 --- a/src/logic/safe/store/actions/fetchSafe.ts +++ b/src/logic/safe/store/actions/fetchSafe.ts @@ -8,8 +8,8 @@ import { getLocalSafe, getSafeName } from 'src/logic/safe/utils' import { enabledFeatures, safeNeedsUpdate } from 'src/logic/safe/utils/safeVersion' import { sameAddress } from 'src/logic/wallets/ethAddresses' import { getBalanceInEtherOf } from 'src/logic/wallets/getWeb3' -import addSafeOwner from 'src/logic/safe/store/actions/addSafeOwner' -import removeSafeOwner from 'src/logic/safe/store/actions/removeSafeOwner' +import { addSafeOwner } from 'src/logic/safe/store/actions/addSafeOwner' +import { removeSafeOwner } from 'src/logic/safe/store/actions/removeSafeOwner' import updateSafe from 'src/logic/safe/store/actions/updateSafe' import { makeOwner } from 'src/logic/safe/store/models/owner' import { checksumAddress } from 'src/utils/checksumAddress' @@ -80,6 +80,7 @@ export const buildSafe = async ( threshold, owners, ethBalance, + totalFiatBalance: 0, nonce, currentVersion: currentVersion ?? '', needsUpdate, @@ -88,8 +89,6 @@ export const buildSafe = async ( latestIncomingTxBlock: 0, activeAssets: Set(), activeTokens: Set(), - blacklistedAssets: Set(), - blacklistedTokens: Set(), modules, spendingLimits, } diff --git a/src/logic/safe/store/actions/processTransaction.ts b/src/logic/safe/store/actions/processTransaction.ts index f54d2c12..28934b23 100644 --- a/src/logic/safe/store/actions/processTransaction.ts +++ b/src/logic/safe/store/actions/processTransaction.ts @@ -11,6 +11,7 @@ import { } from 'src/logic/safe/safeTxSigner' import { getApprovalTransaction, getExecutionTransaction, saveTxToHistory } from 'src/logic/safe/transactions' import { tryOffchainSigning } from 'src/logic/safe/transactions/offchainSigner' +import * as aboutToExecuteTx from 'src/logic/safe/utils/aboutToExecuteTx' import { getCurrentSafeVersion } from 'src/logic/safe/utils/safeVersion' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' import { providerSelector } from 'src/logic/wallets/store/selectors' @@ -117,8 +118,6 @@ export const processTransaction = ({ dispatch(updateTransactionStatus({ txStatus: 'PENDING', safeAddress, nonce: tx.nonce, id: tx.id })) await saveTxToHistory({ ...txArgs, signature }) - // TODO: while we wait for the tx to be stored in the service and later update the tx info - // we should update the tx status in the store to disable owners' action buttons dispatch(fetchTransactions(safeAddress)) return @@ -154,6 +153,10 @@ export const processTransaction = ({ try { await saveTxToHistory({ ...txArgs, txHash }) + + // store the pending transaction's nonce + isExecution && aboutToExecuteTx.setNonce(txArgs.nonce) + dispatch(fetchTransactions(safeAddress)) } catch (e) { console.error(e) @@ -172,10 +175,6 @@ export const processTransaction = ({ console.error('Processing transaction error: ', error) }) .then(async (receipt) => { - if (isExecution) { - dispatch(enqueueSnackbar(notificationsQueue.afterExecution.noMoreConfirmationsNeeded)) - } - dispatch(fetchTransactions(safeAddress)) if (isExecution) { diff --git a/src/logic/safe/store/actions/removeSafeOwner.ts b/src/logic/safe/store/actions/removeSafeOwner.ts index fd18a3bd..5f06a6e6 100644 --- a/src/logic/safe/store/actions/removeSafeOwner.ts +++ b/src/logic/safe/store/actions/removeSafeOwner.ts @@ -2,6 +2,4 @@ import { createAction } from 'redux-actions' export const REMOVE_SAFE_OWNER = 'REMOVE_SAFE_OWNER' -const removeSafeOwner = createAction(REMOVE_SAFE_OWNER) - -export default removeSafeOwner +export const removeSafeOwner = createAction(REMOVE_SAFE_OWNER) diff --git a/src/logic/safe/store/actions/replaceSafeOwner.ts b/src/logic/safe/store/actions/replaceSafeOwner.ts index 6b4498a3..8850023c 100644 --- a/src/logic/safe/store/actions/replaceSafeOwner.ts +++ b/src/logic/safe/store/actions/replaceSafeOwner.ts @@ -2,6 +2,4 @@ import { createAction } from 'redux-actions' export const REPLACE_SAFE_OWNER = 'REPLACE_SAFE_OWNER' -const replaceSafeOwner = createAction(REPLACE_SAFE_OWNER) - -export default replaceSafeOwner +export const replaceSafeOwner = createAction(REPLACE_SAFE_OWNER) diff --git a/src/logic/safe/store/actions/updateActiveAssets.ts b/src/logic/safe/store/actions/updateActiveAssets.ts deleted file mode 100644 index 80be2139..00000000 --- a/src/logic/safe/store/actions/updateActiveAssets.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Set } from 'immutable' -import updateAssetsList from './updateAssetsList' -import { Dispatch } from 'src/logic/safe/store/actions/types.d' - -const updateActiveAssets = (safeAddress: string, activeAssets: Set) => (dispatch: Dispatch): void => { - dispatch(updateAssetsList({ safeAddress, activeAssets })) -} - -export default updateActiveAssets diff --git a/src/logic/safe/store/actions/updateActiveTokens.ts b/src/logic/safe/store/actions/updateActiveTokens.ts deleted file mode 100644 index 012836c9..00000000 --- a/src/logic/safe/store/actions/updateActiveTokens.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Set } from 'immutable' -import updateTokensList from './updateTokensList' -import { Dispatch } from 'src/logic/safe/store/actions/types.d' - -// the selector uses ownProps argument/router props to get the address of the safe -// so in order to use it I had to recreate the same structure -// const generateMatchProps = (safeAddress: string) => ({ -// match: { -// params: { -// [SAFE_PARAM_ADDRESS]: safeAddress, -// }, -// }, -// }) - -const updateActiveTokens = (safeAddress: string, activeTokens: Set) => (dispatch: Dispatch): void => { - dispatch(updateTokensList({ safeAddress, activeTokens })) -} - -export default updateActiveTokens diff --git a/src/logic/safe/store/actions/updateAssetsList.ts b/src/logic/safe/store/actions/updateAssetsList.ts deleted file mode 100644 index 0f098e28..00000000 --- a/src/logic/safe/store/actions/updateAssetsList.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createAction } from 'redux-actions' - -export const UPDATE_ASSETS_LIST = 'UPDATE_ASSETS_LIST' - -const updateAssetsList = createAction(UPDATE_ASSETS_LIST) - -export default updateAssetsList diff --git a/src/logic/safe/store/actions/updateBlacklistedAssets.ts b/src/logic/safe/store/actions/updateBlacklistedAssets.ts deleted file mode 100644 index 8b52bbfd..00000000 --- a/src/logic/safe/store/actions/updateBlacklistedAssets.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Set } from 'immutable' -import updateAssetsList from './updateAssetsList' -import { Dispatch } from 'src/logic/safe/store/actions/types.d' - -const updateBlacklistedAssets = (safeAddress: string, blacklistedAssets: Set) => (dispatch: Dispatch): void => { - dispatch(updateAssetsList({ safeAddress, blacklistedAssets })) -} - -export default updateBlacklistedAssets diff --git a/src/logic/safe/store/actions/updateBlacklistedTokens.ts b/src/logic/safe/store/actions/updateBlacklistedTokens.ts deleted file mode 100644 index e81293b9..00000000 --- a/src/logic/safe/store/actions/updateBlacklistedTokens.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Set } from 'immutable' -import updateTokensList from './updateTokensList' -import { Dispatch } from 'src/logic/safe/store/actions/types.d' - -const updateBlacklistedTokens = (safeAddress: string, blacklistedTokens: Set) => (dispatch: Dispatch): void => { - dispatch(updateTokensList({ safeAddress, blacklistedTokens })) -} - -export default updateBlacklistedTokens diff --git a/src/logic/safe/store/actions/updateTokensList.ts b/src/logic/safe/store/actions/updateTokensList.ts deleted file mode 100644 index 91123bd6..00000000 --- a/src/logic/safe/store/actions/updateTokensList.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createAction } from 'redux-actions' - -export const UPDATE_TOKENS_LIST = 'UPDATE_TOKENS_LIST' - -const updateTokenList = createAction(UPDATE_TOKENS_LIST) - -export default updateTokenList diff --git a/src/logic/safe/store/middleware/notificationsMiddleware.ts b/src/logic/safe/store/middleware/notificationsMiddleware.ts index f516a141..1ad8cb52 100644 --- a/src/logic/safe/store/middleware/notificationsMiddleware.ts +++ b/src/logic/safe/store/middleware/notificationsMiddleware.ts @@ -1,4 +1,5 @@ import { push } from 'connected-react-router' +import { Action } from 'redux-actions' import { NOTIFICATIONS, enhanceSnackbarForAction } from 'src/logic/notifications' import closeSnackbarAction from 'src/logic/notifications/store/actions/closeSnackbar' @@ -8,14 +9,19 @@ import { getSafeVersionInfo } from 'src/logic/safe/utils/safeVersion' import { isUserAnOwner } from 'src/logic/wallets/ethAddresses' import { userAccountSelector } from 'src/logic/wallets/store/selectors' import { grantedSelector } from 'src/routes/safe/container/selector' -import { ADD_QUEUED_TRANSACTIONS } from 'src/logic/safe/store/actions/transactions/gatewayTransactions' +import { + ADD_QUEUED_TRANSACTIONS, + ADD_HISTORY_TRANSACTIONS, +} from 'src/logic/safe/store/actions/transactions/gatewayTransactions' +import * as aboutToExecuteTx from 'src/logic/safe/utils/aboutToExecuteTx' +import { QueuedPayload } from 'src/logic/safe/store/reducer/gatewayTransactions' import { safeParamAddressFromStateSelector, safesMapSelector } from 'src/logic/safe/store/selectors' -import { isTransactionSummary } from 'src/logic/safe/store/models/types/gateway.d' +import { isTransactionSummary, TransactionGatewayResult } from 'src/logic/safe/store/models/types/gateway.d' import { loadFromStorage, saveToStorage } from 'src/utils/storage' import { ADD_OR_UPDATE_SAFE } from '../actions/addOrUpdateSafe' -const watchedActions = [ADD_OR_UPDATE_SAFE, ADD_QUEUED_TRANSACTIONS] +const watchedActions = [ADD_OR_UPDATE_SAFE, ADD_QUEUED_TRANSACTIONS, ADD_HISTORY_TRANSACTIONS] const LAST_TIME_USED_LOGGED_IN_ID = 'LAST_TIME_USED_LOGGED_IN_ID' @@ -70,9 +76,21 @@ const notificationsMiddleware = (store) => (next) => async (action) => { const state = store.getState() switch (action.type) { + case ADD_HISTORY_TRANSACTIONS: { + const userAddress: string = userAccountSelector(state) + const safes = safesMapSelector(state) + + const executedTxNotification = aboutToExecuteTx.getNotification(action.payload, userAddress, safes) + // if we have a notification, dispatch it depending on transaction's status + executedTxNotification && dispatch(enqueueSnackbar(executedTxNotification)) + + break + } case ADD_QUEUED_TRANSACTIONS: { - const { safeAddress, values } = action.payload - const transactions = values.filter((tx) => isTransactionSummary(tx)).map((item) => item.transaction) + const { safeAddress, values } = (action as Action).payload + const transactions = values + .filter((tx) => isTransactionSummary(tx)) + .map((item: TransactionGatewayResult) => item.transaction) const userAddress: string = userAccountSelector(state) const awaitingTransactions = getAwaitingGatewayTransactions(transactions, userAddress) diff --git a/src/logic/safe/store/middleware/safeStorage.ts b/src/logic/safe/store/middleware/safeStorage.ts index 90b26346..d51be225 100644 --- a/src/logic/safe/store/middleware/safeStorage.ts +++ b/src/logic/safe/store/middleware/safeStorage.ts @@ -1,7 +1,4 @@ import { saveDefaultSafe, saveSafes } from 'src/logic/safe/utils' -import { tokensSelector } from 'src/logic/tokens/store/selectors' -import { saveActiveTokens } from 'src/logic/tokens/utils/tokensStorage' -import { ACTIVATE_TOKEN_FOR_ALL_SAFES } from 'src/logic/safe/store/actions/activateTokenForAllSafes' import { ADD_SAFE_OWNER } from 'src/logic/safe/store/actions/addSafeOwner' import { EDIT_SAFE_OWNER } from 'src/logic/safe/store/actions/editSafeOwner' import { REMOVE_SAFE } from 'src/logic/safe/store/actions/removeSafe' @@ -9,9 +6,7 @@ import { REMOVE_SAFE_OWNER } from 'src/logic/safe/store/actions/removeSafeOwner' import { REPLACE_SAFE_OWNER } from 'src/logic/safe/store/actions/replaceSafeOwner' import { SET_DEFAULT_SAFE } from 'src/logic/safe/store/actions/setDefaultSafe' import { UPDATE_SAFE } from 'src/logic/safe/store/actions/updateSafe' -import { UPDATE_TOKENS_LIST } from 'src/logic/safe/store/actions/updateTokensList' -import { UPDATE_ASSETS_LIST } from 'src/logic/safe/store/actions/updateAssetsList' -import { getActiveTokensAddressesForAllSafes, safesMapSelector } from 'src/logic/safe/store/selectors' +import { safesMapSelector } from 'src/logic/safe/store/selectors' import { ADD_OR_UPDATE_SAFE } from 'src/logic/safe/store/actions/addOrUpdateSafe' import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook' import { checksumAddress } from 'src/utils/checksumAddress' @@ -26,28 +21,10 @@ const watchedActions = [ REMOVE_SAFE_OWNER, REPLACE_SAFE_OWNER, EDIT_SAFE_OWNER, - ACTIVATE_TOKEN_FOR_ALL_SAFES, - UPDATE_TOKENS_LIST, - UPDATE_ASSETS_LIST, SET_DEFAULT_SAFE, ] -const recalculateActiveTokens = (state) => { - const tokens = tokensSelector(state) - const activeTokenAddresses = getActiveTokensAddressesForAllSafes(state) - - const activeTokens = tokens.withMutations((map) => { - map.forEach((token) => { - if (!activeTokenAddresses.has(token.address)) { - map.remove(token.address) - } - }) - }) - - saveActiveTokens(activeTokens) -} - -const safeStorageMware = (store) => (next) => async (action) => { +export const safeStorageMiddleware = (store) => (next) => async (action) => { const handledAction = next(action) if (watchedActions.includes(action.type)) { @@ -57,10 +34,6 @@ const safeStorageMware = (store) => (next) => async (action) => { await saveSafes(safes.toJSON()) switch (action.type) { - case ACTIVATE_TOKEN_FOR_ALL_SAFES: { - recalculateActiveTokens(state) - break - } case ADD_OR_UPDATE_SAFE: { const { safe } = action.payload safe.owners.forEach((owner) => { @@ -72,10 +45,7 @@ const safeStorageMware = (store) => (next) => async (action) => { break } case UPDATE_SAFE: { - const { activeTokens, name, address } = action.payload - if (activeTokens) { - recalculateActiveTokens(state) - } + const { name, address } = action.payload if (name) { dispatch(addOrUpdateAddressBookEntry(makeAddressBookEntry({ name, address }))) } @@ -94,5 +64,3 @@ const safeStorageMware = (store) => (next) => async (action) => { return handledAction } - -export default safeStorageMware diff --git a/src/logic/safe/store/models/safe.ts b/src/logic/safe/store/models/safe.ts index 7d0547e0..38616f17 100644 --- a/src/logic/safe/store/models/safe.ts +++ b/src/logic/safe/store/models/safe.ts @@ -1,5 +1,6 @@ import { List, Map, Record, RecordOf, Set } from 'immutable' import { FEATURES } from 'src/config/networks/network.d' +import { BalanceRecord } from 'src/logic/tokens/store/actions/fetchSafeTokens' export type SafeOwner = { name: string @@ -28,14 +29,13 @@ export type SafeRecordProps = { address: string threshold: number ethBalance: string + totalFiatBalance: number owners: List modules?: ModulePair[] | null spendingLimits?: SpendingLimit[] | null activeTokens: Set activeAssets: Set - blacklistedTokens: Set - blacklistedAssets: Set - balances: Map + balances: Map nonce: number latestIncomingTxBlock: number recurringUser?: boolean @@ -49,13 +49,12 @@ const makeSafe = Record({ address: '', threshold: 0, ethBalance: '0', + totalFiatBalance: 0, owners: List([]), modules: [], spendingLimits: [], activeTokens: Set(), activeAssets: Set(), - blacklistedTokens: Set(), - blacklistedAssets: Set(), balances: Map(), nonce: 0, latestIncomingTxBlock: 0, diff --git a/src/logic/safe/store/reducer/gatewayTransactions.ts b/src/logic/safe/store/reducer/gatewayTransactions.ts index a3eada88..fbd7e314 100644 --- a/src/logic/safe/store/reducer/gatewayTransactions.ts +++ b/src/logic/safe/store/reducer/gatewayTransactions.ts @@ -344,6 +344,7 @@ export const gatewayTransactions = handleActions { + // TODO: review if is this `PENDING` status required under `queued.queued` list // prevent setting `PENDING_FAILED` status, if previous status wasn't `PENDING` if (txStatus === 'PENDING_FAILED' && txToUpdate.txStatus !== 'PENDING') { return txToUpdate diff --git a/src/logic/safe/store/reducer/safe.ts b/src/logic/safe/store/reducer/safe.ts index d327e560..44112c4b 100644 --- a/src/logic/safe/store/reducer/safe.ts +++ b/src/logic/safe/store/reducer/safe.ts @@ -1,7 +1,6 @@ import { Map, Set, List } from 'immutable' import { Action, handleActions } from 'redux-actions' -import { ACTIVATE_TOKEN_FOR_ALL_SAFES } from 'src/logic/safe/store/actions/activateTokenForAllSafes' import { ADD_SAFE_OWNER } from 'src/logic/safe/store/actions/addSafeOwner' import { EDIT_SAFE_OWNER } from 'src/logic/safe/store/actions/editSafeOwner' import { REMOVE_SAFE } from 'src/logic/safe/store/actions/removeSafe' @@ -10,8 +9,6 @@ import { REPLACE_SAFE_OWNER } from 'src/logic/safe/store/actions/replaceSafeOwne import { SET_DEFAULT_SAFE } from 'src/logic/safe/store/actions/setDefaultSafe' import { SET_LATEST_MASTER_CONTRACT_VERSION } from 'src/logic/safe/store/actions/setLatestMasterContractVersion' import { UPDATE_SAFE } from 'src/logic/safe/store/actions/updateSafe' -import { UPDATE_TOKENS_LIST } from 'src/logic/safe/store/actions/updateTokensList' -import { UPDATE_ASSETS_LIST } from 'src/logic/safe/store/actions/updateAssetsList' import { makeOwner } from 'src/logic/safe/store/models/owner' import makeSafe, { SafeRecord, SafeRecordProps } from 'src/logic/safe/store/models/safe' import { AppReduxState } from 'src/store' @@ -29,8 +26,6 @@ export const buildSafe = (storedSafe: SafeRecordProps): SafeRecordProps => { const owners = buildOwnersFrom(Array.from(names), Array.from(addresses)) const activeTokens = Set(storedSafe.activeTokens) const activeAssets = Set(storedSafe.activeAssets) - const blacklistedTokens = Set(storedSafe.blacklistedTokens) - const blacklistedAssets = Set(storedSafe.blacklistedAssets) const balances = Map(storedSafe.balances) return { @@ -38,9 +33,7 @@ export const buildSafe = (storedSafe: SafeRecordProps): SafeRecordProps => { owners, balances, activeTokens, - blacklistedTokens, activeAssets, - blacklistedAssets, latestIncomingTxBlock: 0, modules: null, } @@ -102,21 +95,6 @@ export default handleActions( ) : state }, - [ACTIVATE_TOKEN_FOR_ALL_SAFES]: (state, action: Action) => { - const tokenAddress = action.payload - - return state.withMutations((map) => { - map - .get('safes') - .keySeq() - .forEach((safeAddress) => { - const safeActiveTokens = map.getIn(['safes', safeAddress, 'activeTokens']) - const activeTokens = safeActiveTokens.add(tokenAddress) - - map.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.mergeDeep({ activeTokens })) - }) - }) - }, [ADD_OR_UPDATE_SAFE]: (state, action: Action) => { const { safe } = action.payload const safeAddress = safe.address @@ -195,24 +173,6 @@ export default handleActions( return prevSafe.merge({ owners: updatedOwners }) }) }, - [UPDATE_TOKENS_LIST]: (state, action: Action) => { - // Only activeTokens or blackListedTokens is required - const { safeAddress, activeTokens, blacklistedTokens } = action.payload - - const key = activeTokens ? 'activeTokens' : 'blacklistedTokens' - const list = activeTokens ?? blacklistedTokens - - return state.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.set(key, list)) - }, - [UPDATE_ASSETS_LIST]: (state, action: Action) => { - // Only activeAssets or blackListedAssets is required - const { safeAddress, activeAssets, blacklistedAssets } = action.payload - - const key = activeAssets ? 'activeAssets' : 'blacklistedAssets' - const list = activeAssets ?? blacklistedAssets - - return state.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.set(key, list)) - }, [SET_DEFAULT_SAFE]: (state, action: Action) => state.set('defaultSafe', action.payload), [SET_LATEST_MASTER_CONTRACT_VERSION]: (state, action: Action) => state.set('latestMasterContractVersion', action.payload), diff --git a/src/logic/safe/store/selectors/index.ts b/src/logic/safe/store/selectors/index.ts index 7675d0a9..2c9ebd7d 100644 --- a/src/logic/safe/store/selectors/index.ts +++ b/src/logic/safe/store/selectors/index.ts @@ -76,51 +76,6 @@ export const safeActiveTokensSelector = createSelector( }, ) -export const safeActiveAssetsSelector = createSelector( - safeSelector, - (safe): Set => { - if (!safe) { - return Set() - } - return safe.activeAssets - }, -) - -export const safeActiveAssetsListSelector = createSelector(safeActiveAssetsSelector, (safeList) => { - if (!safeList) { - return Set([]) - } - return Set(safeList) -}) - -export const safeBlacklistedTokensSelector = createSelector( - safeSelector, - (safe): Set => { - if (!safe) { - return Set() - } - - return safe.blacklistedTokens - }, -) - -export const safeBlacklistedAssetsSelector = createSelector( - safeSelector, - (safe): Set => { - if (!safe) { - return Set() - } - - return safe.blacklistedAssets - }, -) - -export const safeActiveAssetsSelectorBySafe = (safeAddress: string, safes: SafesMap): Set => - safes.get(safeAddress)?.get('activeAssets') || Set() - -export const safeBlacklistedAssetsSelectorBySafe = (safeAddress: string, safes: SafesMap): Set => - safes.get(safeAddress)?.get('blacklistedAssets') || Set() - const baseSafe = makeSafe() export const safeFieldSelector = (field: K) => ( @@ -172,14 +127,6 @@ export const getActiveTokensAddressesForAllSafes = createSelector(safesListSelec return addresses }) -export const getBlacklistedTokensAddressesForAllSafes = createSelector(safesListSelector, (safes) => { - const addresses = Set().withMutations((set) => { - safes.forEach((safe) => { - safe.blacklistedTokens.forEach((tokenAddress) => { - set.add(tokenAddress) - }) - }) - }) - - return addresses +export const safeFiatBalancesTotalSelector = createSelector(safeSelector, (currentSafe) => { + return currentSafe?.totalFiatBalance.toString() }) diff --git a/src/logic/safe/store/tests/safe.balances.test.ts b/src/logic/safe/store/tests/safe.balances.test.ts deleted file mode 100644 index 4238d4c3..00000000 --- a/src/logic/safe/store/tests/safe.balances.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Set, Map } from 'immutable' -import { aNewStore } from 'src/store' -import updateActiveTokens from 'src/logic/safe/store/actions/updateActiveTokens' -import '@testing-library/jest-dom/extend-expect' -import updateSafe from 'src/logic/safe/store/actions/updateSafe' -import { makeToken } from 'src/logic/tokens/store/model/token' -import { safesMapSelector } from 'src/logic/safe/store/selectors' - -describe('Feature > Balances', () => { - let store - const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf' - beforeEach(async () => { - store = aNewStore() - }) - - it('It should return an updated balance when updates active tokens', async () => { - // given - const tokensAmount = '100' - const token = makeToken({ - address: '0x00Df91984582e6e96288307E9c2f20b38C8FeCE9', - name: 'OmiseGo', - symbol: 'OMG', - decimals: 18, - logoUri: - 'https://github.com/TrustWallet/tokens/blob/master/images/0x6810e776880c02933d47db1b9fc05908e5386b96.png?raw=true', - }) - const balances = Map({ - [token.address]: tokensAmount, - }) - const expectedResult = '100' - - // when - store.dispatch(updateSafe({ address: safeAddress, balances })) - store.dispatch(updateActiveTokens(safeAddress, Set([token.address]))) - - const safe = safesMapSelector(store.getState()).get(safeAddress) - const balanceResult = safe?.get('balances').get(token.address) - const activeTokens = safe?.get('activeTokens') - const tokenIsActive = activeTokens?.has(token.address) - - // then - expect(balanceResult).toBe(expectedResult) - expect(tokenIsActive).toBe(true) - }) - - it('The store should have an updated ether balance after updating the value', async () => { - // given - const etherAmount = '1' - const expectedResult = '1' - - // when - store.dispatch(updateSafe({ address: safeAddress, ethBalance: etherAmount })) - const safe = safesMapSelector(store.getState()).get(safeAddress) - const balanceResult = safe?.get('ethBalance') - - // then - expect(balanceResult).toBe(expectedResult) - }) -}) diff --git a/src/logic/safe/utils/__tests__/shouldSafeStoreBeUpdated.test.ts b/src/logic/safe/utils/__tests__/shouldSafeStoreBeUpdated.test.ts index 5d029744..51ca8521 100644 --- a/src/logic/safe/utils/__tests__/shouldSafeStoreBeUpdated.test.ts +++ b/src/logic/safe/utils/__tests__/shouldSafeStoreBeUpdated.test.ts @@ -7,9 +7,7 @@ const getMockedOldSafe = ({ needsUpdate, balances, recurringUser, - blacklistedAssets, - blacklistedTokens, - activeAssets, + assets, activeTokens, owners, featuresEnabled, @@ -34,8 +32,6 @@ const getMockedOldSafe = ({ const mockedActiveTokenAddress2 = '0x92aF97cbF10742dD2527ffaBA70e34C03CFFC2c1' const mockedActiveAssetsAddress1 = '0x503ab2a6A70c6C6ec8b25a4C87C784e1c8f8e8CD' const mockedActiveAssetsAddress2 = '0xfdd4E685361CB7E89a4D27e03DCd0001448d731F' - const mockedBlacklistedTokenAddress1 = '0xc7d892dca37a244Fb1A7461e6141e58Ead460282' - const mockedBlacklistedAssetAddress1 = '0x0ac539137c4c99001f16Dd132E282F99A02Ddc3F' return { name: name || 'MockedSafe', @@ -46,14 +42,12 @@ const getMockedOldSafe = ({ modules: modules || [], spendingLimits: spendingLimits || [], activeTokens: activeTokens || Set([mockedActiveTokenAddress1, mockedActiveTokenAddress2]), - activeAssets: activeAssets || Set([mockedActiveAssetsAddress1, mockedActiveAssetsAddress2]), - blacklistedTokens: blacklistedTokens || Set([mockedBlacklistedTokenAddress1]), - blacklistedAssets: blacklistedAssets || Set([mockedBlacklistedAssetAddress1]), + assets: assets || Set([mockedActiveAssetsAddress1, mockedActiveAssetsAddress2]), balances: balances || Map({ - [mockedActiveTokenAddress1]: '100', - [mockedActiveTokenAddress2]: '10', + [mockedActiveTokenAddress1]: { tokenBalance: '100' }, + [mockedActiveTokenAddress2]: { tokenBalance: '10' }, }), nonce: nonce || 2, latestIncomingTxBlock: latestIncomingTxBlock || 1, @@ -61,6 +55,7 @@ const getMockedOldSafe = ({ currentVersion: currentVersion || 'v1.1.1', needsUpdate: needsUpdate || false, featuresEnabled: featuresEnabled || [], + totalFiatBalance: 110, } } @@ -209,43 +204,9 @@ describe('shouldSafeStoreBeUpdated', () => { const mockedActiveTokenAddress2 = '0x92aF97cbF10742dD2527ffaBA70e34C03CFFC2c1' const oldActiveAssets = Set([mockedActiveTokenAddress1, mockedActiveTokenAddress2]) const newActiveAssets = Set([mockedActiveTokenAddress1]) - const oldSafe = getMockedOldSafe({ activeAssets: oldActiveAssets }) + const oldSafe = getMockedOldSafe({ assets: oldActiveAssets }) const newSafeProps: Partial = { - activeAssets: newActiveAssets, - } - - // When - const expectedResult = shouldSafeStoreBeUpdated(newSafeProps, oldSafe) - - // Then - expect(expectedResult).toEqual(true) - }) - it(`Given an old blacklistedTokens list and a new blacklistedTokens list for the safe, should return true`, () => { - // given - const mockedActiveTokenAddress1 = '0x36591cd3DA96b21Ac9ca54cFaf80fe45107294F1' - const mockedActiveTokenAddress2 = '0x92aF97cbF10742dD2527ffaBA70e34C03CFFC2c1' - const oldBlacklistedTokens = Set([mockedActiveTokenAddress1, mockedActiveTokenAddress2]) - const newBlacklistedTokens = Set([mockedActiveTokenAddress1]) - const oldSafe = getMockedOldSafe({ blacklistedTokens: oldBlacklistedTokens }) - const newSafeProps: Partial = { - blacklistedTokens: newBlacklistedTokens, - } - - // When - const expectedResult = shouldSafeStoreBeUpdated(newSafeProps, oldSafe) - - // Then - expect(expectedResult).toEqual(true) - }) - it(`Given an old blacklistedAssets list and a new blacklistedAssets list for the safe, should return true`, () => { - // given - const mockedActiveTokenAddress1 = '0x36591cd3DA96b21Ac9ca54cFaf80fe45107294F1' - const mockedActiveTokenAddress2 = '0x92aF97cbF10742dD2527ffaBA70e34C03CFFC2c1' - const oldBlacklistedAssets = Set([mockedActiveTokenAddress1, mockedActiveTokenAddress2]) - const newBlacklistedAssets = Set([mockedActiveTokenAddress1]) - const oldSafe = getMockedOldSafe({ blacklistedAssets: oldBlacklistedAssets }) - const newSafeProps: Partial = { - blacklistedAssets: newBlacklistedAssets, + assets: newActiveAssets, } // When @@ -259,11 +220,11 @@ describe('shouldSafeStoreBeUpdated', () => { const mockedActiveTokenAddress1 = '0x36591cd3DA96b21Ac9ca54cFaf80fe45107294F1' const mockedActiveTokenAddress2 = '0x92aF97cbF10742dD2527ffaBA70e34C03CFFC2c1' const oldBalances = Map({ - [mockedActiveTokenAddress1]: '100', - [mockedActiveTokenAddress2]: '10', + [mockedActiveTokenAddress1]: { tokenBalance: '100' }, + [mockedActiveTokenAddress2]: { tokenBalance: '100' }, }) const newBalances = Map({ - [mockedActiveTokenAddress1]: '100', + [mockedActiveTokenAddress1]: { tokenBalance: '100' }, }) const oldSafe = getMockedOldSafe({ balances: oldBalances }) const newSafeProps: Partial = { diff --git a/src/logic/safe/utils/aboutToExecuteTx.ts b/src/logic/safe/utils/aboutToExecuteTx.ts new file mode 100644 index 00000000..7db235e3 --- /dev/null +++ b/src/logic/safe/utils/aboutToExecuteTx.ts @@ -0,0 +1,51 @@ +import { getNotificationsFromTxType } from 'src/logic/notifications' +import { + isStatusFailed, + isTransactionSummary, + TransactionGatewayResult, +} from 'src/logic/safe/store/models/types/gateway.d' +import { HistoryPayload } from 'src/logic/safe/store/reducer/gatewayTransactions' +import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' +import { isUserAnOwner } from 'src/logic/wallets/ethAddresses' +import { SafesMap } from 'src/routes/safe/store/reducer/types/safe' + +let nonce: number | undefined + +export const setNonce = (newNonce: typeof nonce): void => { + nonce = newNonce +} + +export const getNotification = ( + { safeAddress, values }: HistoryPayload, + userAddress: string, + safes: SafesMap, +): undefined => { + const currentSafe = safes.get(safeAddress) + + // no notification if not in the current safe or if its not an owner + if (!currentSafe || !isUserAnOwner(currentSafe, userAddress)) { + return + } + + // if we have a nonce, then we have a tx that is about to be executed + if (nonce !== undefined) { + const executedTx = values + .filter(isTransactionSummary) + .map((item: TransactionGatewayResult) => item.transaction) + .find((transaction) => transaction.executionInfo?.nonce === nonce) + + // transaction that was pending, was moved into history + // that is: it was executed + if (executedTx !== undefined) { + const notificationsQueue = getNotificationsFromTxType(TX_NOTIFICATION_TYPES.STANDARD_TX) + const notification = isStatusFailed(executedTx.txStatus) + ? notificationsQueue.afterExecutionError + : notificationsQueue.afterExecution.noMoreConfirmationsNeeded + + // reset nonce value + setNonce(undefined) + + return notification + } + } +} diff --git a/src/logic/currencyValues/store/utils/currencyValuesStorage.ts b/src/logic/safe/utils/currencyValuesStorage.ts similarity index 100% rename from src/logic/currencyValues/store/utils/currencyValuesStorage.ts rename to src/logic/safe/utils/currencyValuesStorage.ts diff --git a/src/logic/tokens/store/actions/activateAssetsByBalance.ts b/src/logic/tokens/store/actions/activateAssetsByBalance.ts deleted file mode 100644 index eae19074..00000000 --- a/src/logic/tokens/store/actions/activateAssetsByBalance.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { nftAssetsSelector } from 'src/logic/collectibles/store/selectors' -import updateActiveAssets from 'src/logic/safe/store/actions/updateActiveAssets' -import { - safeActiveAssetsSelectorBySafe, - safeBlacklistedAssetsSelectorBySafe, - safesMapSelector, -} from 'src/logic/safe/store/selectors' - -const activateAssetsByBalance = (safeAddress) => async (dispatch, getState) => { - try { - const state = getState() - const safes = safesMapSelector(state) - - if (safes.size === 0) { - return - } - - const availableAssets = nftAssetsSelector(state) - const alreadyActiveAssets = safeActiveAssetsSelectorBySafe(safeAddress, safes) - const blacklistedAssets = safeBlacklistedAssetsSelectorBySafe(safeAddress, safes) - - // active tokens by balance, excluding those already blacklisted and the `null` address - const activeByBalance = Object.entries(availableAssets) - .filter((asset) => { - const { address, numberOfTokens }: any = asset[1] - return address !== null && !blacklistedAssets.has(address) && numberOfTokens > 0 - }) - .map((asset) => { - return asset[0] - }) - - // need to persist those already active assets, despite its balances - const activeAssets = alreadyActiveAssets.union(activeByBalance) - - // update list of active tokens - dispatch(updateActiveAssets(safeAddress, activeAssets)) - } catch (err) { - console.error('Error fetching active assets list', err) - } - - return null -} - -export default activateAssetsByBalance diff --git a/src/logic/tokens/store/actions/saveTokens.ts b/src/logic/tokens/store/actions/addTokens.ts similarity index 54% rename from src/logic/tokens/store/actions/saveTokens.ts rename to src/logic/tokens/store/actions/addTokens.ts index 0aa58838..f5367cce 100644 --- a/src/logic/tokens/store/actions/saveTokens.ts +++ b/src/logic/tokens/store/actions/addTokens.ts @@ -2,8 +2,6 @@ import { createAction } from 'redux-actions' export const ADD_TOKENS = 'ADD_TOKENS' -const addTokens = createAction(ADD_TOKENS, (tokens) => ({ +export const addTokens = createAction(ADD_TOKENS, (tokens) => ({ tokens, })) - -export default addTokens diff --git a/src/logic/tokens/store/actions/fetchSafeTokens.ts b/src/logic/tokens/store/actions/fetchSafeTokens.ts index fb585e94..227c49b8 100644 --- a/src/logic/tokens/store/actions/fetchSafeTokens.ts +++ b/src/logic/tokens/store/actions/fetchSafeTokens.ts @@ -2,63 +2,56 @@ import { backOff } from 'exponential-backoff' import { List, Map } from 'immutable' import { Dispatch } from 'redux' -import { fetchTokenCurrenciesBalances, TokenBalance } from 'src/logic/currencyValues/api/fetchTokenCurrenciesBalances' - -import { - AVAILABLE_CURRENCIES, - CurrencyRateValueRecord, - makeBalanceCurrency, -} from 'src/logic/currencyValues/store/model/currencyValues' -import addTokens from 'src/logic/tokens/store/actions/saveTokens' +import { fetchTokenCurrenciesBalances, TokenBalance } from 'src/logic/safe/api/fetchTokenCurrenciesBalances' +import { addTokens } from 'src/logic/tokens/store/actions/addTokens' import { makeToken, Token } from 'src/logic/tokens/store/model/token' import { TokenState } from 'src/logic/tokens/store/reducer/tokens' import updateSafe from 'src/logic/safe/store/actions/updateSafe' import { AppReduxState } from 'src/store' import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue' -import { safeActiveTokensSelector, safeBlacklistedTokensSelector, safeSelector } from 'src/logic/safe/store/selectors' +import { safeActiveTokensSelector, safeSelector } from 'src/logic/safe/store/selectors' import { tokensSelector } from 'src/logic/tokens/store/selectors' -import { currentCurrencySelector } from 'src/logic/currencyValues/store/selectors' import { sameAddress, ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' -import { setCurrencyBalances } from 'src/logic/currencyValues/store/actions/setCurrencyBalances' import { getNetworkInfo } from 'src/config' +import BigNumber from 'bignumber.js' +import { currentCurrencySelector } from 'src/logic/currencyValues/store/selectors' + +export type BalanceRecord = { + tokenBalance: string + fiatBalance?: string +} interface ExtractedData { - balances: Map - currencyList: List + balances: Map ethBalance: string tokens: List } const { nativeCoin } = getNetworkInfo() -const extractDataFromResult = (currentTokens: TokenState, fiatCode: string) => ( +const extractDataFromResult = (currentTokens: TokenState) => ( acc: ExtractedData, { balance, fiatBalance, tokenInfo }: TokenBalance, ): ExtractedData => { const { address: tokenAddress, decimals } = tokenInfo if (sameAddress(tokenAddress, ZERO_ADDRESS) || sameAddress(tokenAddress, nativeCoin.address)) { acc.ethBalance = humanReadableValue(balance, 18) - } else { - acc.balances = acc.balances.merge({ [tokenAddress]: humanReadableValue(balance, Number(decimals)) }) - - if (currentTokens && !currentTokens.get(tokenAddress)) { - acc.tokens = acc.tokens.push(makeToken({ ...tokenInfo })) - } } + acc.balances = acc.balances.merge({ + [tokenAddress]: { + fiatBalance, + tokenBalance: humanReadableValue(balance, Number(decimals)), + }, + }) - acc.currencyList = acc.currencyList.push( - makeBalanceCurrency({ - currencyName: fiatCode, - tokenAddress, - balanceInBaseCurrency: fiatBalance, - balanceInSelectedCurrency: fiatBalance, - }), - ) + if (currentTokens && !currentTokens.get(tokenAddress)) { + acc.tokens = acc.tokens.push(makeToken({ ...tokenInfo })) + } return acc } -const fetchSafeTokens = (safeAddress: string) => async ( +export const fetchSafeTokens = (safeAddress: string, currencySelected?: string) => async ( dispatch: Dispatch, getState: () => AppReduxState, ): Promise => { @@ -66,38 +59,40 @@ const fetchSafeTokens = (safeAddress: string) => async ( const state = getState() const safe = safeSelector(state) const currentTokens = tokensSelector(state) - const currencySelected = currentCurrencySelector(state) if (!safe) { return } + const selectedCurrency = currentCurrencySelector(state) - const tokenCurrenciesBalances = await backOff(() => fetchTokenCurrenciesBalances(safeAddress)) + const tokenCurrenciesBalances = await backOff(() => + fetchTokenCurrenciesBalances({ safeAddress, selectedCurrency: currencySelected ?? selectedCurrency }), + ) const alreadyActiveTokens = safeActiveTokensSelector(state) - const blacklistedTokens = safeBlacklistedTokensSelector(state) - const { balances, currencyList, ethBalance, tokens } = tokenCurrenciesBalances.items.reduce( - extractDataFromResult(currentTokens, currencySelected || AVAILABLE_CURRENCIES.USD), + const { balances, ethBalance, tokens } = tokenCurrenciesBalances.items.reduce( + extractDataFromResult(currentTokens), { balances: Map(), - currencyList: List(), ethBalance: '0', tokens: List(), }, ) // need to persist those already active tokens, despite its balances - const activeTokens = alreadyActiveTokens.union( - // active tokens by balance, excluding those already blacklisted and the `null` address - balances.keySeq().toSet().subtract(blacklistedTokens), - ) + const activeTokens = alreadyActiveTokens.union(balances.keySeq().toSet()) - dispatch(updateSafe({ address: safeAddress, activeTokens, balances, ethBalance })) - dispatch(setCurrencyBalances(safeAddress, currencyList)) + dispatch( + updateSafe({ + address: safeAddress, + activeTokens, + balances, + ethBalance, + totalFiatBalance: new BigNumber(tokenCurrenciesBalances.fiatTotal).toFixed(2), + }), + ) dispatch(addTokens(tokens)) } catch (err) { console.error('Error fetching active token list', err) } } - -export default fetchSafeTokens diff --git a/src/logic/tokens/store/actions/fetchTokens.ts b/src/logic/tokens/store/actions/fetchTokens.ts index 20e170af..d00dde76 100644 --- a/src/logic/tokens/store/actions/fetchTokens.ts +++ b/src/logic/tokens/store/actions/fetchTokens.ts @@ -5,9 +5,7 @@ import ERC721 from '@openzeppelin/contracts/build/contracts/ERC721.json' import { List } from 'immutable' import contract from '@truffle/contract/index.js' import { AbiItem } from 'web3-utils' - -import saveTokens from './saveTokens' - +import { addTokens } from 'src/logic/tokens/store/actions/addTokens' import generateBatchRequests from 'src/logic/contracts/generateBatchRequests' import { fetchErc20AndErc721AssetsList } from 'src/logic/tokens/api' import { makeToken, Token } from 'src/logic/tokens/store/model/token' @@ -85,7 +83,7 @@ export const getTokenInfos = async (tokenAddress: string): Promise async ( const tokens = List(erc20Tokens.map((token) => makeToken(token))) - dispatch(saveTokens(tokens)) + dispatch(addTokens(tokens)) } catch (err) { console.error('Error fetching token list', err) } } - -export default fetchTokens diff --git a/src/logic/tokens/store/actions/loadActiveTokens.ts b/src/logic/tokens/store/actions/loadActiveTokens.ts deleted file mode 100644 index 7b4ae61b..00000000 --- a/src/logic/tokens/store/actions/loadActiveTokens.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { List } from 'immutable' - -import saveTokens from './saveTokens' - -import { makeToken } from 'src/logic/tokens/store/model/token' -import { getActiveTokens } from 'src/logic/tokens/utils/tokensStorage' - -const loadActiveTokens = () => async (dispatch) => { - try { - const tokens = (await getActiveTokens()) || {} - // The filter of strings was made because of the issue #751. Please see: https://github.com/gnosis/safe-react/pull/755#issuecomment-612969340 - const tokenRecordsList = List( - Object.values(tokens) - .filter((t: any) => typeof t.decimals !== 'string') - .map((token) => makeToken(token)), - ) - - dispatch(saveTokens(tokenRecordsList)) - } catch (err) { - // eslint-disable-next-line - console.error('Error while loading active tokens from storage:', err) - } -} - -export default loadActiveTokens diff --git a/src/logic/tokens/store/model/token.ts b/src/logic/tokens/store/model/token.ts index e0c612bb..3e9b5bcd 100644 --- a/src/logic/tokens/store/model/token.ts +++ b/src/logic/tokens/store/model/token.ts @@ -1,5 +1,6 @@ import { Record, RecordOf } from 'immutable' import { TokenType } from 'src/logic/safe/store/models/types/gateway' +import { BalanceRecord } from 'src/logic/tokens/store/actions/fetchSafeTokens' export type TokenProps = { address: string @@ -7,7 +8,7 @@ export type TokenProps = { symbol: string decimals: number | string logoUri: string - balance: number | string + balance: BalanceRecord type?: TokenType } @@ -17,7 +18,10 @@ export const makeToken = Record({ symbol: '', decimals: 0, logoUri: '', - balance: 0, + balance: { + fiatBalance: '0', + tokenBalance: '0', + }, }) // balance is only set in extendedSafeTokensSelector when we display user's token balances diff --git a/src/logic/tokens/store/reducer/tokens.ts b/src/logic/tokens/store/reducer/tokens.ts index c567e630..bd506670 100644 --- a/src/logic/tokens/store/reducer/tokens.ts +++ b/src/logic/tokens/store/reducer/tokens.ts @@ -2,7 +2,7 @@ import { List, Map } from 'immutable' import { Action, handleActions } from 'redux-actions' import { ADD_TOKEN } from 'src/logic/tokens/store/actions/addToken' -import { ADD_TOKENS } from 'src/logic/tokens/store/actions/saveTokens' +import { ADD_TOKENS } from 'src/logic/tokens/store/actions/addTokens' import { makeToken, Token } from 'src/logic/tokens/store/model/token' import { AppReduxState } from 'src/store' diff --git a/src/logic/tokens/utils/tokenHelpers.ts b/src/logic/tokens/utils/tokenHelpers.ts index 9dc7d305..43135dbd 100644 --- a/src/logic/tokens/utils/tokenHelpers.ts +++ b/src/logic/tokens/utils/tokenHelpers.ts @@ -15,7 +15,9 @@ export const getEthAsToken = (balance: string | number): Token => { const { nativeCoin } = getNetworkInfo() return makeToken({ ...nativeCoin, - balance, + balance: { + tokenBalance: balance.toString(), + }, }) } @@ -73,7 +75,7 @@ export type GetTokenByAddress = { tokens: List } -export type TokenFound = { +type TokenFound = { balance: string | number decimals: string | number } @@ -92,7 +94,7 @@ export const getBalanceAndDecimalsFromToken = ({ tokenAddress, tokens }: GetToke } return { - balance: token.balance ?? 0, + balance: token.balance.tokenBalance ?? 0, decimals: token.decimals ?? 0, } } diff --git a/src/logic/tokens/utils/tokensStorage.ts b/src/logic/tokens/utils/tokensStorage.ts deleted file mode 100644 index 27364f24..00000000 --- a/src/logic/tokens/utils/tokensStorage.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Map } from 'immutable' - -import { loadFromStorage, saveToStorage } from 'src/utils/storage' -import { TokenProps, Token } from './../store/model/token' - -export const ACTIVE_TOKENS_KEY = 'ACTIVE_TOKENS' -export const CUSTOM_TOKENS_KEY = 'CUSTOM_TOKENS' - -// Tokens which are active at least in one of used Safes in the app should be saved to localstorage -// to avoid iterating a large amount of data of tokens from the backend -// Custom tokens should be saved too unless they're deleted (marking them as inactive doesn't count) - -export const saveActiveTokens = async (tokens: Map): Promise => { - try { - await saveToStorage(ACTIVE_TOKENS_KEY, tokens.toJS() as Record) - } catch (err) { - console.error('Error storing tokens in localstorage', err) - } -} - -export const getActiveTokens = async (): Promise | undefined> => { - const data = await loadFromStorage>(ACTIVE_TOKENS_KEY) - - return data -} diff --git a/src/routes/open/components/ReviewInformation/index.tsx b/src/routes/open/components/ReviewInformation/index.tsx index 8e899bee..0a400112 100644 --- a/src/routes/open/components/ReviewInformation/index.tsx +++ b/src/routes/open/components/ReviewInformation/index.tsx @@ -128,7 +128,7 @@ const ReviewComponent = ({ values, form }: ReviewComponentProps): ReactElement = - + You're about to create a new Safe and will have to confirm a transaction with your currently connected wallet. The creation will cost approximately {gasCostFormatted} {nativeCoin.name}. The exact amount will be determined by your wallet. diff --git a/src/routes/open/components/SafeNameForm/index.tsx b/src/routes/open/components/SafeNameForm/index.tsx index 82ebd944..5d7e1837 100644 --- a/src/routes/open/components/SafeNameForm/index.tsx +++ b/src/routes/open/components/SafeNameForm/index.tsx @@ -1,5 +1,6 @@ import { createStyles, makeStyles } from '@material-ui/core/styles' import * as React from 'react' +import styled from 'styled-components' import OpenPaper from 'src/components/Stepper/OpenPaper' import Field from 'src/components/forms/Field' @@ -28,6 +29,12 @@ const styles = createStyles({ }, }) +const StyledField = styled(Field)` + &.MuiTextField-root { + width: 460px; + } +` + const useSafeNameStyles = makeStyles(styles) const SafeNameForm = ({ safeName }: { safeName: string }): React.ReactElement => { @@ -36,13 +43,13 @@ const SafeNameForm = ({ safeName }: { safeName: string }): React.ReactElement => return ( <> - + You are about to create a new Gnosis Safe wallet with one or more owners. First, let's give your new wallet a name. This name is only stored locally and will never be shared with Gnosis or any third parties. - /> - + By continuing you consent to the{' '} terms of use diff --git a/src/routes/open/components/SafeOwnersConfirmationsForm/index.tsx b/src/routes/open/components/SafeOwnersConfirmationsForm/index.tsx index a23bfb4b..0e87697e 100644 --- a/src/routes/open/components/SafeOwnersConfirmationsForm/index.tsx +++ b/src/routes/open/components/SafeOwnersConfirmationsForm/index.tsx @@ -5,6 +5,7 @@ import { makeStyles } from '@material-ui/core/styles' import CheckCircle from '@material-ui/icons/CheckCircle' import * as React from 'react' import { styles } from './style' +import styled from 'styled-components' import QRIcon from 'src/assets/icons/qrcode.svg' import trash from 'src/assets/icons/trash.svg' @@ -45,6 +46,10 @@ const { useState } = React export const ADD_OWNER_BUTTON = '+ Add another owner' +const StyledAddressInput = styled(AddressInput)` + width: 460px; +` + /** * Validates the whole OwnersForm, specially checks for non-repeated addresses * @@ -152,7 +157,7 @@ const SafeOwnersForm = (props): React.ReactElement => { return ( <> - + Your Safe will have one or more owners. We have prefilled the first owner with your connected wallet details, but you are free to change this to a different owner.
@@ -167,7 +172,7 @@ const SafeOwnersForm = (props): React.ReactElement => { rel="noreferrer" title="Learn about which Safe setup to use" > - + Learn about which Safe setup to use @@ -176,8 +181,8 @@ const SafeOwnersForm = (props): React.ReactElement => {
- NAME - ADDRESS + NAME + ADDRESS @@ -187,7 +192,7 @@ const SafeOwnersForm = (props): React.ReactElement => { return ( - + { testId={`create-safe-owner-name-field-${index}`} /> - - + { const newOwnerName = getNameFromAddressBook(addressBook, newOwnerAddress, { filterOnlyValidName: true, @@ -246,7 +251,7 @@ const SafeOwnersForm = (props): React.ReactElement => { @@ -256,7 +261,7 @@ const SafeOwnersForm = (props): React.ReactElement => { Any transaction requires the confirmation of:
- + `owner${index}Name` -export const getOwnerAddressBy = (index: number): string => `owner${index}Address` +export const getOwnerNameBy = (index: number): string => `owner${index.toString().padStart(4, '0')}Name` +export const getOwnerAddressBy = (index: number): string => `owner${index.toString().padStart(4, '0')}Address` -export const getNumOwnersFrom = (values) => { +export const getNumOwnersFrom = (values: CreateSafeValues | LoadFormValues): number => { const accounts = Object.keys(values) .sort() .filter((key) => { diff --git a/src/routes/opening/assets/creation-process.gif b/src/routes/opening/assets/creation-process.gif new file mode 100644 index 00000000..573740b0 Binary files /dev/null and b/src/routes/opening/assets/creation-process.gif differ diff --git a/src/routes/opening/assets/loader-dots.svg b/src/routes/opening/assets/loader-dots.svg deleted file mode 100644 index bd96324c..00000000 --- a/src/routes/opening/assets/loader-dots.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/routes/opening/assets/safe-created.svg b/src/routes/opening/assets/safe-created.svg new file mode 100644 index 00000000..2cfaaaa7 --- /dev/null +++ b/src/routes/opening/assets/safe-created.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/routes/opening/assets/success.svg b/src/routes/opening/assets/success.svg deleted file mode 100644 index d635672d..00000000 --- a/src/routes/opening/assets/success.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/routes/opening/components/Footer.tsx b/src/routes/opening/components/Footer.tsx index 8f213484..c4b30ac5 100644 --- a/src/routes/opening/components/Footer.tsx +++ b/src/routes/opening/components/Footer.tsx @@ -1,19 +1,36 @@ -import React, { SyntheticEvent } from 'react' +import React, { ReactElement, SyntheticEvent } from 'react' import styled from 'styled-components' +import { Icon, Link, Text } from '@gnosis.pm/safe-react-components' + import Button from 'src/components/layout/Button' -import { connected } from 'src/theme/variables' import { getExplorerInfo } from 'src/config' +import Hairline from 'src/components/layout/Hairline' -const ExplorerLink = styled.a` - color: ${connected}; +const StyledText = styled(Text)` + display: inline-flex; + a { + margin-left: 4px; + } + svg { + position: relative; + top: 4px; + left: 4px; + } ` - const ButtonWithMargin = styled(Button)` margin-right: 16px; ` +const FooterContainer = styled.div` + width: 100%; + height: 76px; -export const GenericFooter = ({ safeCreationTxHash }: { safeCreationTxHash: string }) => { + button { + margin-top: 24px; + } +` + +export const GenericFooter = ({ safeCreationTxHash }: { safeCreationTxHash: string }): ReactElement => { const explorerInfo = getExplorerInfo(safeCreationTxHash) const { url, alt } = explorerInfo() const match = /(http|https):\/\/(\w+\.\w+)\/.*/i.exec(url) @@ -21,20 +38,23 @@ export const GenericFooter = ({ safeCreationTxHash }: { safeCreationTxHash: stri return ( -

This process should take a couple of minutes.

-

+ This process should take a couple of minutes. + Follow the progress on{' '} - - {explorerDomain} - - . -

+ + {explorerDomain} + + + +
) } @@ -45,16 +65,19 @@ export const ContinueFooter = ({ }: { continueButtonDisabled: boolean onContinue: (event: SyntheticEvent) => void -}) => ( - +}): ReactElement => ( + + + + ) export const ErrorFooter = ({ @@ -63,13 +86,14 @@ export const ErrorFooter = ({ }: { onCancel: (event: SyntheticEvent) => void onRetry: (event: SyntheticEvent) => void -}) => ( - <> +}): ReactElement => ( + + Cancel - + ) diff --git a/src/routes/opening/index.tsx b/src/routes/opening/index.tsx index 22c961fc..7f3f79a3 100644 --- a/src/routes/opening/index.tsx +++ b/src/routes/opening/index.tsx @@ -12,20 +12,19 @@ import Paragraph from 'src/components/layout/Paragraph' import { instantiateSafeContracts } from 'src/logic/contracts/safeContracts' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' import { getWeb3 } from 'src/logic/wallets/getWeb3' -import { background, connected } from 'src/theme/variables' +import { background, connected, fontColor } from 'src/theme/variables' import { providerNameSelector } from 'src/logic/wallets/store/selectors' import { useSelector } from 'react-redux' -import LoaderDotsSvg from './assets/loader-dots.svg' -import SuccessSvg from './assets/success.svg' +import SuccessSvg from './assets/safe-created.svg' import VaultErrorSvg from './assets/vault-error.svg' -import VaultSvg from './assets/vault.svg' +import VaultLoading from './assets/creation-process.gif' import { PromiEvent, TransactionReceipt } from 'web3-core' const Wrapper = styled.div` display: grid; grid-template-columns: 250px auto; - grid-template-rows: 62px auto; + grid-template-rows: 43px auto; margin-bottom: 30px; ` @@ -44,29 +43,31 @@ const Body = styled.div` grid-column: 2; grid-row: 2; text-align: center; - background-color: #ffffff; + background-color: ${({ theme }) => theme.colors.white}; border-radius: 5px; min-width: 700px; - padding-top: 50px; + padding-top: 70px; box-shadow: 0 0 10px 0 rgba(33, 48, 77, 0.1); display: grid; - grid-template-rows: 100px 50px 70px 60px 100px; + grid-template-rows: 100px 50px 110px 1fr; ` const CardTitle = styled.div` font-size: 20px; + padding-top: 10px; ` interface FullParagraphProps { inversecolors: string + stepIndex: number } const FullParagraph = styled(Paragraph)` - background-color: ${(p) => (p.inversecolors ? connected : background)}; - color: ${(p) => (p.inversecolors ? background : connected)}; - padding: 24px; - font-size: 16px; + background-color: ${({ stepIndex }) => (stepIndex === 0 ? connected : background)}; + color: ${({ theme, stepIndex }) => (stepIndex === 0 ? theme.colors.white : fontColor)}; + padding: 28px; + font-size: 20px; margin-bottom: 16px; transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out; ` @@ -77,17 +78,12 @@ const BodyImage = styled.div` const BodyDescription = styled.div` grid-row: 2; ` -const BodyLoader = styled.div` - grid-row: 3; - display: flex; - justify-content: center; - align-items: center; -` const BodyInstruction = styled.div` - grid-row: 4; + grid-row: 3; + margin: 27px 0; ` const BodyFooter = styled.div` - grid-row: 5; + grid-row: 4; padding: 10px 0; display: flex; @@ -154,7 +150,7 @@ export const SafeDeployment = ({ } if (stepIndex <= 4) { - return VaultSvg + return VaultLoading } return SuccessSvg @@ -326,20 +322,26 @@ export const SafeDeployment = ({ - Vault + Vault {steps[stepIndex].description || steps[stepIndex].label} - {!error && stepIndex <= 4 && Loader dots} - - - - {error ? 'You can Cancel or Retry the Safe creation process.' : steps[stepIndex].instruction} - - + {steps[stepIndex].instruction && ( + + + {error ? 'You can Cancel or Retry the Safe creation process.' : steps[stepIndex].instruction} + + + )} {FooterComponent ? ( @@ -354,9 +356,12 @@ export const SafeDeployment = ({ ) : null} - - Back - + + {stepIndex !== 0 && ( + + Back + + )} ) } diff --git a/src/routes/opening/steps.ts b/src/routes/opening/steps.ts index 8863d7a4..a2f6e2d7 100644 --- a/src/routes/opening/steps.ts +++ b/src/routes/opening/steps.ts @@ -1,6 +1,6 @@ import { ContinueFooter, GenericFooter } from './components/Footer' -export const isConfirmationStep = (stepIndex?: number) => stepIndex === 0 +export const isConfirmationStep = (stepIndex?: number): boolean => stepIndex === 0 export const steps = [ { @@ -42,7 +42,7 @@ export const steps = [ id: '6', label: 'Success', description: 'Your Safe was created successfully', - instruction: 'Click below to get started', + instruction: undefined, footerComponent: ContinueFooter, }, ] diff --git a/src/routes/safe/components/AddressBook/index.tsx b/src/routes/safe/components/AddressBook/index.tsx index e6cbaafa..7460f663 100644 --- a/src/routes/safe/components/AddressBook/index.tsx +++ b/src/routes/safe/components/AddressBook/index.tsx @@ -33,7 +33,7 @@ import { generateColumns, } from 'src/routes/safe/components/AddressBook/columns' import SendModal from 'src/routes/safe/components/Balances/SendModal' -import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' +import { OwnerAddressTableCell } from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' import RenameOwnerIcon from 'src/routes/safe/components/Settings/ManageOwners/assets/icons/rename-owner.svg' import RemoveOwnerIcon from 'src/routes/safe/components/Settings/assets/icons/bin.svg' import { addressBookQueryParamsSelector, safesListSelector } from 'src/logic/safe/store/selectors' diff --git a/src/routes/safe/components/Apps/utils.ts b/src/routes/safe/components/Apps/utils.ts index 3fa9d1f3..6d8a8c7d 100644 --- a/src/routes/safe/components/Apps/utils.ts +++ b/src/routes/safe/components/Apps/utils.ts @@ -77,7 +77,7 @@ export const staticAppsList: Array = [ }, // Mushrooms finance { - url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQs6CUbMUyKe3Sa3tU3HcnWWzsuCk8oJEk8CZKhRcJfEh`, + url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmT96aES2YA9BssByc6DVizQDkofmKRErs8gJyqWipjyS8`, disabled: false, networks: [ETHEREUM_NETWORK.MAINNET], }, diff --git a/src/routes/safe/components/Balances/Coins/index.tsx b/src/routes/safe/components/Balances/Coins/index.tsx index 7b72922f..20335252 100644 --- a/src/routes/safe/components/Balances/Coins/index.tsx +++ b/src/routes/safe/components/Balances/Coins/index.tsx @@ -14,11 +14,6 @@ import Table from 'src/components/Table' import { cellWidth } from 'src/components/Table/TableHead' import Button from 'src/components/layout/Button' import Row from 'src/components/layout/Row' -import { - currencyRateSelector, - currentCurrencySelector, - safeFiatBalancesListSelector, -} from 'src/logic/currencyValues/store/selectors' import { BALANCE_ROW_TEST_ID } from 'src/routes/safe/components/Balances' import AssetTableCell from 'src/routes/safe/components/Balances/AssetTableCell' import { @@ -33,6 +28,7 @@ import { extendedSafeTokensSelector, grantedSelector } from 'src/routes/safe/con import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics' import { makeStyles } from '@material-ui/core/styles' import { styles } from './styles' +import { currentCurrencySelector } from 'src/logic/currencyValues/store/selectors' const useStyles = makeStyles(styles) @@ -69,9 +65,7 @@ const Coins = (props: Props): React.ReactElement => { const columns = generateColumns() const autoColumns = columns.filter((c) => !c.custom) const selectedCurrency = useSelector(currentCurrencySelector) - const currencyRate = useSelector(currencyRateSelector) const activeTokens = useSelector(extendedSafeTokensSelector) - const currencyValues = useSelector(safeFiatBalancesListSelector) const granted = useSelector(grantedSelector) const { trackEvent } = useAnalytics() @@ -79,10 +73,10 @@ const Coins = (props: Props): React.ReactElement => { trackEvent({ category: SAFE_NAVIGATION_EVENT, action: 'Coins' }) }, [trackEvent]) - const filteredData: List = useMemo( - () => getBalanceData(activeTokens, selectedCurrency, currencyValues, currencyRate), - [activeTokens, selectedCurrency, currencyValues, currencyRate], - ) + const filteredData: List = useMemo(() => getBalanceData(activeTokens, selectedCurrency), [ + activeTokens, + selectedCurrency, + ]) return ( diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx index 391e4463..049be1e5 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.tsx @@ -18,7 +18,7 @@ import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper' import WhenFieldChanges from 'src/components/WhenFieldChanges' import { addressBookSelector } from 'src/logic/addressBook/store/selectors' import { getNameFromAddressBook } from 'src/logic/addressBook/utils' -import { nftTokensSelector, safeActiveSelectorMap } from 'src/logic/collectibles/store/selectors' +import { nftAssetsSelector, nftTokensSelector } from 'src/logic/collectibles/store/selectors' import { Erc721Transfer } from 'src/logic/safe/store/models/types/gateway' import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo' import { AddressBookInput } from 'src/routes/safe/components/Balances/SendModal/screens/AddressBookInput' @@ -71,7 +71,7 @@ const SendCollectible = ({ selectedToken, }: SendCollectibleProps): React.ReactElement => { const classes = useStyles() - const nftAssets = useSelector(safeActiveSelectorMap) + const nftAssets = useSelector(nftAssetsSelector) const nftTokens = useSelector(nftTokensSelector) const addressBook = useSelector(addressBookSelector) const [selectedEntry, setSelectedEntry] = useState<{ address: string; name: string } | null>(() => { diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx index 85aeb91d..f7f2b540 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx @@ -208,7 +208,7 @@ const SendFunds = ({ const setMaxAllowedAmount = () => { const isSpendingLimit = tokenSpendingLimit && txType === 'spendingLimit' - let maxAmount = selectedToken?.balance ?? 0 + let maxAmount = selectedToken?.balance.tokenBalance ?? 0 if (isSpendingLimit) { const spendingLimitBalance = fromTokenUnit( diff --git a/src/routes/safe/components/Balances/Tokens/index.tsx b/src/routes/safe/components/Balances/Tokens/index.tsx deleted file mode 100644 index b7c01a4a..00000000 --- a/src/routes/safe/components/Balances/Tokens/index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import IconButton from '@material-ui/core/IconButton' -import { makeStyles } from '@material-ui/core/styles' -import Close from '@material-ui/icons/Close' - -import React from 'react' -import { useSelector } from 'react-redux' - -import { styles } from './style' - -import Hairline from 'src/components/layout/Hairline' -import Paragraph from 'src/components/layout/Paragraph' -import Row from 'src/components/layout/Row' - -import { orderedTokenListSelector } from 'src/logic/tokens/store/selectors' -import { AssetsList } from 'src/routes/safe/components/Balances/Tokens/screens/AssetsList' - -import { extendedSafeTokensSelector } from 'src/routes/safe/container/selector' -import { safeBlacklistedTokensSelector } from 'src/logic/safe/store/selectors' -import { TokenList } from 'src/routes/safe/components/Balances/Tokens/screens/TokenList' - -export const MANAGE_TOKENS_MODAL_CLOSE_BUTTON_TEST_ID = 'manage-tokens-close-modal-btn' - -const useStyles = makeStyles(styles) - -type Props = { - safeAddress: string - modalScreen: string - onClose: () => void -} - -export const Tokens = (props: Props): React.ReactElement => { - const { modalScreen, onClose, safeAddress } = props - const tokens = useSelector(orderedTokenListSelector) - const activeTokens = useSelector(extendedSafeTokensSelector) - const blacklistedTokens = useSelector(safeBlacklistedTokensSelector) - const classes = useStyles() - - return ( - <> - - - Manage List - - - - - - - {modalScreen === 'tokenList' && ( - - )} - {modalScreen === 'assetsList' && } - - ) -} diff --git a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/AssetRow.tsx b/src/routes/safe/components/Balances/Tokens/screens/AssetsList/AssetRow.tsx deleted file mode 100644 index 9a927ad2..00000000 --- a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/AssetRow.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import ListItem from '@material-ui/core/ListItem' -import ListItemIcon from '@material-ui/core/ListItemIcon' -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction' -import ListItemText from '@material-ui/core/ListItemText' -import Switch from '@material-ui/core/Switch' -import React, { memo } from 'react' - -import { useStyles } from 'src/routes/safe/components/Balances/Tokens/screens/TokenList/style' -import Img from 'src/components/layout/Img' -import { getNetworkInfo } from 'src/config' -import { setCollectibleImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' - -export const TOGGLE_ASSET_TEST_ID = 'toggle-asset-btn' - -const { nativeCoin } = getNetworkInfo() - -const AssetRow = memo(({ data, index, style }: any) => { - const classes = useStyles() - const { activeAssetsAddresses, assets, onSwitch } = data - const asset = assets[index] - const { address, image, name, symbol } = asset - const isActive = activeAssetsAddresses.includes(asset.address) - - return ( -
- - - {name} - - - {address !== nativeCoin.address && ( - - - - )} - -
- ) -}) - -AssetRow.displayName = 'AssetRow' - -export default AssetRow diff --git a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx b/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx deleted file mode 100644 index 5671a662..00000000 --- a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/index.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import MuiList from '@material-ui/core/List' -import CircularProgress from '@material-ui/core/CircularProgress' -import Search from '@material-ui/icons/Search' -import cn from 'classnames' -import SearchBar from 'material-ui-search-bar' -import React, { useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { FixedSizeList } from 'react-window' -import Paragraph from 'src/components/layout/Paragraph' - -import { useStyles } from './style' - -import Block from 'src/components/layout/Block' -import Hairline from 'src/components/layout/Hairline' -import Row from 'src/components/layout/Row' -import { nftAssetsListSelector } from 'src/logic/collectibles/store/selectors' -import AssetRow from 'src/routes/safe/components/Balances/Tokens/screens/AssetsList/AssetRow' -import updateActiveAssets from 'src/logic/safe/store/actions/updateActiveAssets' -import updateBlacklistedAssets from 'src/logic/safe/store/actions/updateBlacklistedAssets' -import { - safeActiveAssetsListSelector, - safeBlacklistedAssetsSelector, - safeParamAddressFromStateSelector, -} from 'src/logic/safe/store/selectors' - -const filterBy = (filter, nfts) => - nfts.filter( - (asset) => - !filter || - asset.description.toLowerCase().includes(filter.toLowerCase()) || - asset.name.toLowerCase().includes(filter.toLowerCase()) || - asset.symbol.toLowerCase().includes(filter.toLowerCase()), - ) - -export const AssetsList = (): React.ReactElement => { - const classes = useStyles() - const searchClasses = { - input: classes.searchInput, - root: classes.searchRoot, - iconButton: classes.searchIcon, - searchContainer: classes.searchContainer, - } - const dispatch = useDispatch() - const activeAssetsList = useSelector(safeActiveAssetsListSelector) - const blacklistedAssets = useSelector(safeBlacklistedAssetsSelector) - const safeAddress = useSelector(safeParamAddressFromStateSelector) - const [filterValue, setFilterValue] = useState('') - const [activeAssetsAddresses, setActiveAssetsAddresses] = useState(activeAssetsList) - const [blacklistedAssetsAddresses, setBlacklistedAssetsAddresses] = useState(blacklistedAssets) - const nftAssetsList = useSelector(nftAssetsListSelector) - - const onCancelSearch = () => { - setFilterValue('') - } - - const onChangeSearchBar = (value) => { - setFilterValue(value) - } - - const getItemKey = (index) => { - return index - } - - const onSwitch = (asset) => () => { - let newActiveAssetsAddresses - let newBlacklistedAssetsAddresses - if (activeAssetsAddresses.has(asset.address)) { - newActiveAssetsAddresses = activeAssetsAddresses.delete(asset.address) - newBlacklistedAssetsAddresses = blacklistedAssetsAddresses.add(asset.address) - } else { - newActiveAssetsAddresses = activeAssetsAddresses.add(asset.address) - newBlacklistedAssetsAddresses = blacklistedAssetsAddresses.delete(asset.address) - } - - // Set local state - setActiveAssetsAddresses(newActiveAssetsAddresses) - setBlacklistedAssetsAddresses(newBlacklistedAssetsAddresses) - // Dispatch to global state - dispatch(updateActiveAssets(safeAddress, newActiveAssetsAddresses)) - dispatch(updateBlacklistedAssets(safeAddress, newBlacklistedAssetsAddresses)) - } - - const createItemData = (assetsList) => { - return { - assets: assetsList, - activeAssetsAddresses, - onSwitch, - } - } - - const nftAssetsFilteredList = filterBy(filterValue, nftAssetsList) - const itemData = createItemData(nftAssetsFilteredList) - - return ( - <> - - - - } - value={filterValue} - /> - - - - {!nftAssetsList?.length && ( - - {!nftAssetsList ? : No collectibles available} - - )} - {nftAssetsFilteredList.length > 0 && ( - - - {AssetRow} - - - )} - - ) -} diff --git a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/style.ts b/src/routes/safe/components/Balances/Tokens/screens/AssetsList/style.ts deleted file mode 100644 index 65c0ed1b..00000000 --- a/src/routes/safe/components/Balances/Tokens/screens/AssetsList/style.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { createStyles, makeStyles } from '@material-ui/core' - -import { md, mediumFontSize, secondaryText, sm, xs } from 'src/theme/variables' - -export const useStyles = makeStyles( - createStyles({ - root: { - minHeight: '52px', - }, - search: { - color: secondaryText, - paddingLeft: sm, - }, - padding: { - padding: `0 ${md}`, - }, - add: { - fontSize: '11px', - fontWeight: 'normal', - paddingRight: md, - paddingLeft: md, - }, - addBtnLabel: { - fontSize: mediumFontSize, - }, - actions: { - height: '50px', - }, - list: { - overflow: 'hidden', - overflowY: 'scroll', - padding: 0, - height: '100%', - }, - tokenIcon: { - marginRight: sm, - height: '28px', - width: '28px', - }, - searchInput: { - backgroundColor: 'transparent', - lineHeight: 'initial', - fontSize: '13px', - padding: 0, - '& > input::placeholder': { - letterSpacing: '-0.5px', - fontSize: mediumFontSize, - color: 'black', - }, - '& > input': { - letterSpacing: '-0.5px', - }, - }, - progressContainer: { - width: '100%', - height: '100%', - alignItems: 'center', - }, - searchContainer: { - marginLeft: xs, - marginRight: xs, - }, - searchRoot: { - letterSpacing: '-0.5px', - fontSize: '13px', - border: 'none', - boxShadow: 'none', - '& > button': { - display: 'none', - }, - flex: 1, - }, - searchIcon: { - '&:hover': { - backgroundColor: 'transparent !important', - }, - }, - }), -) diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow.tsx b/src/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow.tsx deleted file mode 100644 index 9b73f079..00000000 --- a/src/routes/safe/components/Balances/Tokens/screens/TokenList/TokenRow.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import ListItem from '@material-ui/core/ListItem' -import ListItemIcon from '@material-ui/core/ListItemIcon' -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction' -import ListItemText from '@material-ui/core/ListItemText' -import Switch from '@material-ui/core/Switch' -import React, { CSSProperties, memo, ReactElement } from 'react' - -import { useStyles } from './style' -import Img from 'src/components/layout/Img' -import { getNetworkInfo } from 'src/config' -import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' -import { ItemData } from 'src/routes/safe/components/Balances/Tokens/screens/TokenList/index' - -export const TOGGLE_TOKEN_TEST_ID = 'toggle-token-btn' - -interface TokenRowProps { - data: ItemData - index: number - style: CSSProperties -} - -const { nativeCoin } = getNetworkInfo() - -const TokenRow = memo(({ data, index, style }: TokenRowProps): ReactElement | null => { - const classes = useStyles() - const { activeTokensAddresses, onSwitch, tokens } = data - const token = tokens.get(index) - - if (!token) { - return null - } - - const isActive = activeTokensAddresses.has(token.address) - - return ( -
- - - {token.name} - - - {token.address !== nativeCoin.address && ( - - - - )} - -
- ) -}) - -TokenRow.displayName = 'TokenRow' - -export default TokenRow diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx b/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx deleted file mode 100644 index aa7e12cc..00000000 --- a/src/routes/safe/components/Balances/Tokens/screens/TokenList/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import CircularProgress from '@material-ui/core/CircularProgress' -import MuiList from '@material-ui/core/List' -import Search from '@material-ui/icons/Search' -import cn from 'classnames' -import { List, Set } from 'immutable' -import SearchBar from 'material-ui-search-bar' -import React, { useState } from 'react' -import { FixedSizeList } from 'react-window' - -import TokenRow from './TokenRow' -import { useStyles } from './style' -import Block from 'src/components/layout/Block' -import Hairline from 'src/components/layout/Hairline' -import Row from 'src/components/layout/Row' -import { Token } from 'src/logic/tokens/store/model/token' -import { useDispatch } from 'react-redux' -import updateBlacklistedTokens from 'src/logic/safe/store/actions/updateBlacklistedTokens' -import updateActiveTokens from 'src/logic/safe/store/actions/updateActiveTokens' - -export const ADD_CUSTOM_TOKEN_BUTTON_TEST_ID = 'add-custom-token-btn' - -const filterBy = (filter: string, tokens: List): List => - tokens.filter( - (token) => - !filter || - token.symbol.toLowerCase().includes(filter.toLowerCase()) || - token.name.toLowerCase().includes(filter.toLowerCase()), - ) - -type Props = { - tokens: List - activeTokens: List - blacklistedTokens: Set - safeAddress: string -} - -export type ItemData = { - tokens: List - activeTokensAddresses: Set - onSwitch: (token: Token) => () => void -} - -export const TokenList = (props: Props): React.ReactElement => { - const classes = useStyles() - const { tokens, activeTokens, blacklistedTokens, safeAddress } = props - const [activeTokensAddresses, setActiveTokensAddresses] = useState(Set(activeTokens.map(({ address }) => address))) - const [blacklistedTokensAddresses, setBlacklistedTokensAddresses] = useState>(blacklistedTokens) - const [filter, setFilter] = useState('') - const dispatch = useDispatch() - - const searchClasses = { - input: classes.searchInput, - root: classes.searchRoot, - iconButton: classes.searchIcon, - searchContainer: classes.searchContainer, - } - - const onCancelSearch = () => { - setFilter('') - } - - const onChangeSearchBar = (value: string) => { - setFilter(value) - } - - const onSwitch = (token: Token) => () => { - let newActiveTokensAddresses - let newBlacklistedTokensAddresses - if (activeTokensAddresses.has(token.address)) { - newActiveTokensAddresses = activeTokensAddresses.delete(token.address) - newBlacklistedTokensAddresses = blacklistedTokensAddresses.add(token.address) - } else { - newActiveTokensAddresses = activeTokensAddresses.add(token.address) - newBlacklistedTokensAddresses = blacklistedTokensAddresses.delete(token.address) - } - - // Set local state - setActiveTokensAddresses(newActiveTokensAddresses) - setBlacklistedTokensAddresses(newBlacklistedTokensAddresses) - // Dispatch to global state - dispatch(updateActiveTokens(safeAddress, newActiveTokensAddresses)) - dispatch(updateBlacklistedTokens(safeAddress, newBlacklistedTokensAddresses)) - } - - const createItemData = (tokens: List, activeTokensAddresses: Set): ItemData => ({ - tokens, - activeTokensAddresses, - onSwitch, - }) - - const getItemKey = (index: number, { tokens }): string => { - return tokens.get(index).address - } - - const filteredTokens = filterBy(filter, tokens) - const itemData = createItemData(filteredTokens, activeTokensAddresses) - - return ( - <> - - - - } - value={filter} - /> - - - - {!tokens.size && ( - - - - )} - {tokens.size > 0 && ( - - - {TokenRow} - - - )} - - ) -} diff --git a/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.ts b/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.ts deleted file mode 100644 index dbf3500b..00000000 --- a/src/routes/safe/components/Balances/Tokens/screens/TokenList/style.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { createStyles, makeStyles } from '@material-ui/core' - -import { border, md, mediumFontSize, secondaryText, sm, xs } from 'src/theme/variables' - -export const useStyles = makeStyles( - createStyles({ - root: { - minHeight: '52px', - }, - search: { - color: secondaryText, - paddingLeft: sm, - }, - padding: { - padding: `0 ${md}`, - }, - add: { - fontSize: '11px', - fontWeight: 'normal', - paddingRight: md, - paddingLeft: md, - }, - addBtnLabel: { - fontSize: mediumFontSize, - }, - actions: { - height: '50px', - }, - list: { - overflow: 'hidden', - overflowY: 'scroll', - padding: 0, - height: '100%', - }, - token: { - minHeight: '50px', - borderBottom: `1px solid ${border}`, - }, - tokenRoot: { - paddingTop: 0, - paddingBottom: 0, - }, - searchInput: { - backgroundColor: 'transparent', - lineHeight: 'initial', - fontSize: '13px', - padding: 0, - '& > input::placeholder': { - letterSpacing: '-0.5px', - fontSize: mediumFontSize, - color: 'black', - }, - '& > input': { - letterSpacing: '-0.5px', - }, - }, - tokenIcon: { - marginRight: md, - height: '28px', - width: '28px', - }, - progressContainer: { - width: '100%', - height: '100%', - alignItems: 'center', - }, - searchContainer: { - marginLeft: xs, - marginRight: xs, - }, - searchRoot: { - letterSpacing: '-0.5px', - fontSize: '13px', - border: 'none', - boxShadow: 'none', - '& > button': { - display: 'none', - }, - flex: 1, - }, - searchIcon: { - '&:hover': { - backgroundColor: 'transparent !important', - }, - }, - }), -) diff --git a/src/routes/safe/components/Balances/Tokens/style.ts b/src/routes/safe/components/Balances/Tokens/style.ts deleted file mode 100644 index b37e7b7f..00000000 --- a/src/routes/safe/components/Balances/Tokens/style.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { lg, md } from 'src/theme/variables' -import { createStyles } from '@material-ui/core' - -export const styles = createStyles({ - heading: { - padding: `${md} ${lg}`, - justifyContent: 'space-between', - maxHeight: '75px', - boxSizing: 'border-box', - }, - close: { - height: '35px', - width: '35px', - }, -}) diff --git a/src/routes/safe/components/Balances/dataFetcher.ts b/src/routes/safe/components/Balances/dataFetcher.ts index 7d48d54a..f9868540 100644 --- a/src/routes/safe/components/Balances/dataFetcher.ts +++ b/src/routes/safe/components/Balances/dataFetcher.ts @@ -1,32 +1,13 @@ -import { BigNumber } from 'bignumber.js' import { List } from 'immutable' import { getNetworkInfo } from 'src/config' import { FIXED } from 'src/components/Table/sorting' import { formatAmountInUsFormat } from 'src/logic/tokens/utils/formatAmount' import { TableColumn } from 'src/components/Table/types.d' -import { BalanceCurrencyList } from 'src/logic/currencyValues/store/model/currencyValues' import { Token } from 'src/logic/tokens/store/model/token' -import { sameAddress } from 'src/logic/wallets/ethAddresses' - export const BALANCE_TABLE_ASSET_ID = 'asset' export const BALANCE_TABLE_BALANCE_ID = 'balance' export const BALANCE_TABLE_VALUE_ID = 'value' -const { nativeCoin } = getNetworkInfo() - -const getTokenValue = (token: Token, currencyValues: BalanceCurrencyList, currencyRate: number): string => { - const currencyValue = currencyValues.find( - ({ tokenAddress }) => sameAddress(token.address, tokenAddress) || sameAddress(token.address, nativeCoin.address), - ) - - if (!currencyValue) { - return '' - } - - const { balanceInBaseCurrency } = currencyValue - return new BigNumber(balanceInBaseCurrency).times(currencyRate).toString() -} - const getTokenPriceInCurrency = (balance: string, currencySelected?: string): string => { if (!currencySelected) { return Number('').toFixed(2) @@ -44,15 +25,10 @@ export interface BalanceData { valueOrder: number } -export const getBalanceData = ( - activeTokens: List, - currencySelected?: string, - currencyValues?: BalanceCurrencyList, - currencyRate?: number, -): List => { +export const getBalanceData = (activeTokens: List, currencySelected?: string): List => { const { nativeCoin } = getNetworkInfo() return activeTokens.map((token) => { - const balance = currencyRate && currencyValues ? getTokenValue(token, currencyValues, currencyRate) : '0' + const { tokenBalance, fiatBalance } = token.balance return { [BALANCE_TABLE_ASSET_ID]: { @@ -62,11 +38,11 @@ export const getBalanceData = ( symbol: token.symbol, }, assetOrder: token.name, - [BALANCE_TABLE_BALANCE_ID]: `${formatAmountInUsFormat(token.balance?.toString() || '0')} ${token.symbol}`, - balanceOrder: Number(token.balance), + [BALANCE_TABLE_BALANCE_ID]: `${formatAmountInUsFormat(tokenBalance?.toString() || '0')} ${token.symbol}`, + balanceOrder: Number(tokenBalance), [FIXED]: token.symbol === nativeCoin.symbol, - [BALANCE_TABLE_VALUE_ID]: getTokenPriceInCurrency(balance, currencySelected), - valueOrder: Number(balance), + [BALANCE_TABLE_VALUE_ID]: getTokenPriceInCurrency(fiatBalance || '0', currencySelected), + valueOrder: Number(tokenBalance), } }) } @@ -78,6 +54,7 @@ export const generateColumns = (): List => { disablePadding: false, label: 'Asset', custom: false, + static: true, width: 250, } @@ -88,6 +65,7 @@ export const generateColumns = (): List => { disablePadding: false, label: 'Balance', custom: false, + static: true, } const actions: TableColumn = { @@ -105,6 +83,7 @@ export const generateColumns = (): List => { order: true, label: 'Value', custom: false, + static: true, disablePadding: false, } diff --git a/src/routes/safe/components/Balances/index.tsx b/src/routes/safe/components/Balances/index.tsx index be80a8ba..d9dd2bdc 100644 --- a/src/routes/safe/components/Balances/index.tsx +++ b/src/routes/safe/components/Balances/index.tsx @@ -3,18 +3,16 @@ import React, { useEffect, useState } from 'react' import { useSelector } from 'react-redux' import ReceiveModal from 'src/components/App/ReceiveModal' -import { Tokens } from './Tokens' import { styles } from './style' import Modal from 'src/components/Modal' -import ButtonLink from 'src/components/layout/ButtonLink' import Col from 'src/components/layout/Col' import Divider from 'src/components/layout/Divider' import Row from 'src/components/layout/Row' import { SAFELIST_ADDRESS } from 'src/routes/routes' import SendModal from 'src/routes/safe/components/Balances/SendModal' -import CurrencyDropdown from 'src/routes/safe/components/CurrencyDropdown' +import { CurrencyDropdown } from 'src/routes/safe/components/CurrencyDropdown' import { safeFeaturesEnabledSelector, safeNameSelector, @@ -35,7 +33,6 @@ export const BALANCE_ROW_TEST_ID = 'balance-row' const INITIAL_STATE = { erc721Enabled: false, showToken: false, - showManageCollectibleModal: false, sendFunds: { isOpen: false, selectedToken: '', @@ -95,17 +92,8 @@ const Balances = (): React.ReactElement => { })) } - const { - assetDivider, - assetTab, - assetTabActive, - assetTabs, - controls, - manageTokensButton, - receiveModal, - tokenControls, - } = classes - const { erc721Enabled, sendFunds, showManageCollectibleModal, showReceive, showToken } = state + const { assetDivider, assetTab, assetTabActive, assetTabs, controls, receiveModal, tokenControls } = classes + const { erc721Enabled, sendFunds, showReceive } = state return ( <> @@ -140,32 +128,7 @@ const Balances = (): React.ReactElement => { path={`${SAFELIST_ADDRESS}/${address}/balances/collectibles`} exact render={() => { - return !erc721Enabled ? ( - - ) : ( - - onShow('ManageCollectibleModal')} - size="lg" - testId="manage-tokens-btn" - > - Manage List - - onHide('ManageCollectibleModal')} - open={showManageCollectibleModal} - title="Manage List" - > - onHide('ManageCollectibleModal')} - safeAddress={address} - /> - - - ) + return !erc721Enabled ? : null }} /> { <> - onShow('Token')} - size="lg" - testId="manage-tokens-btn" - > - Manage List - - onHide('Token')} - open={showToken} - title="Manage List" - > - onHide('Token')} safeAddress={address} /> - ) diff --git a/src/routes/safe/components/CurrencyDropdown/index.tsx b/src/routes/safe/components/CurrencyDropdown/index.tsx index 5aea152a..012ba0e8 100644 --- a/src/routes/safe/components/CurrencyDropdown/index.tsx +++ b/src/routes/safe/components/CurrencyDropdown/index.tsx @@ -13,26 +13,22 @@ import { useDispatch, useSelector } from 'react-redux' import CheckIcon from './img/check.svg' import { setSelectedCurrency } from 'src/logic/currencyValues/store/actions/setSelectedCurrency' -import { AVAILABLE_CURRENCIES } from 'src/logic/currencyValues/store/model/currencyValues' -import { currentCurrencySelector } from 'src/logic/currencyValues/store/selectors' import { useDropdownStyles } from 'src/routes/safe/components/CurrencyDropdown/style' -import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' +import { availableCurrenciesSelector, currentCurrencySelector } from 'src/logic/currencyValues/store/selectors' import { DropdownListTheme } from 'src/theme/mui' -import { setImageToPlaceholder } from '../Balances/utils' +import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils' import Img from 'src/components/layout/Img/index' import { getNetworkInfo } from 'src/config' import { sameString } from 'src/utils/strings' const { nativeCoin } = getNetworkInfo() -const CurrencyDropdown = (): React.ReactElement | null => { - const safeAddress = useSelector(safeParamAddressFromStateSelector) as string +export const CurrencyDropdown = (): React.ReactElement | null => { const dispatch = useDispatch() const [anchorEl, setAnchorEl] = useState(null) const selectedCurrency = useSelector(currentCurrencySelector) const [searchParams, setSearchParams] = useState('') - - const currenciesList = Object.values(AVAILABLE_CURRENCIES) + const currenciesList = useSelector(availableCurrenciesSelector) const tokenImage = nativeCoin.logoUri const classes = useDropdownStyles({}) const currenciesListFiltered = currenciesList.filter((currency) => @@ -47,8 +43,8 @@ const CurrencyDropdown = (): React.ReactElement | null => { setAnchorEl(null) } - const onCurrentCurrencyChangedHandler = (newCurrencySelectedName) => { - dispatch(setSelectedCurrency(safeAddress, newCurrencySelectedName)) + const onCurrentCurrencyChangedHandler = (newCurrencySelectedName: string) => { + dispatch(setSelectedCurrency({ selectedCurrency: newCurrencySelectedName })) handleClose() } @@ -80,6 +76,7 @@ const CurrencyDropdown = (): React.ReactElement | null => { horizontal: 'center', vertical: 'top', }} + TransitionProps={{ mountOnEnter: true, unmountOnExit: true }} >
@@ -139,5 +136,3 @@ const CurrencyDropdown = (): React.ReactElement | null => { ) } - -export default CurrencyDropdown diff --git a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/index.tsx b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/index.tsx index f88db617..864e1918 100644 --- a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/index.tsx @@ -6,7 +6,7 @@ import Modal from 'src/components/Modal' import { addOrUpdateAddressBookEntry } from 'src/logic/addressBook/store/actions/addOrUpdateAddressBookEntry' import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' -import addSafeOwner from 'src/logic/safe/store/actions/addSafeOwner' +import { addSafeOwner } from 'src/logic/safe/store/actions/addSafeOwner' import { createTransaction } from 'src/logic/safe/store/actions/createTransaction' import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { checksumAddress } from 'src/utils/checksumAddress' @@ -16,7 +16,7 @@ import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionPara import { OwnerForm } from './screens/OwnerForm' import { ReviewAddOwner } from './screens/Review' -import ThresholdForm from './screens/ThresholdForm' +import { ThresholdForm } from './screens/ThresholdForm' const styles = createStyles({ biggerModalWindow: { @@ -65,7 +65,7 @@ type Props = { onClose: () => void } -const AddOwner = ({ isOpen, onClose }: Props): React.ReactElement => { +export const AddOwnerModal = ({ isOpen, onClose }: Props): React.ReactElement => { const classes = useStyles() const [activeScreen, setActiveScreen] = useState('selectOwner') const [values, setValues] = useState({ ownerName: '', ownerAddress: '', threshold: '' }) @@ -138,5 +138,3 @@ const AddOwner = ({ isOpen, onClose }: Props): React.ReactElement => { ) } - -export default AddOwner diff --git a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/OwnerForm/index.tsx b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/OwnerForm/index.tsx index 73083d62..dabe3b40 100644 --- a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/OwnerForm/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/OwnerForm/index.tsx @@ -11,14 +11,20 @@ import AddressInput from 'src/components/forms/AddressInput' import Field from 'src/components/forms/Field' import GnoForm from 'src/components/forms/GnoForm' import TextField from 'src/components/forms/TextField' -import { composeValidators, minMaxLength, required, uniqueAddress } from 'src/components/forms/validator' +import { + addressIsNotCurrentSafe, + composeValidators, + minMaxLength, + required, + uniqueAddress, +} from 'src/components/forms/validator' import Block from 'src/components/layout/Block' import Button from 'src/components/layout/Button' import Col from 'src/components/layout/Col' import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' -import { safeOwnersAddressesListSelector } from 'src/logic/safe/store/selectors' +import { safeOwnersAddressesListSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' export const ADD_OWNER_NAME_INPUT_TEST_ID = 'add-owner-name-input' export const ADD_OWNER_ADDRESS_INPUT_TEST_ID = 'add-owner-address-testid' @@ -43,7 +49,9 @@ export const OwnerForm = ({ onClose, onSubmit }: OwnerFormProps): React.ReactEle onSubmit(values) } const owners = useSelector(safeOwnersAddressesListSelector) + const safeAddress = useSelector(safeParamAddressFromStateSelector) const ownerDoesntExist = uniqueAddress(owners) + const ownerAddressIsNotSafeAddress = addressIsNotCurrentSafe(safeAddress) return ( <> @@ -98,7 +106,7 @@ export const OwnerForm = ({ onClose, onSubmit }: OwnerFormProps): React.ReactEle placeholder="Owner address*" testId={ADD_OWNER_ADDRESS_INPUT_TEST_ID} text="Owner address*" - validators={[ownerDoesntExist]} + validators={[ownerDoesntExist, ownerAddressIsNotSafeAddress]} /> diff --git a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/index.tsx b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/index.tsx index 651c9078..ba38c9d6 100644 --- a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/index.tsx @@ -1,8 +1,8 @@ import IconButton from '@material-ui/core/IconButton' import MenuItem from '@material-ui/core/MenuItem' -import { withStyles } from '@material-ui/core/styles' +import { makeStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' -import React from 'react' +import React, { ReactElement } from 'react' import { useSelector } from 'react-redux' import { styles } from './style' @@ -21,10 +21,24 @@ import { safeOwnersSelector, safeThresholdSelector } from 'src/logic/safe/store/ export const ADD_OWNER_THRESHOLD_NEXT_BTN_TEST_ID = 'add-owner-threshold-next-btn' -const ThresholdForm = ({ classes, onClickBack, onClose, onSubmit }) => { +const useStyles = makeStyles(styles) + +type SubmitProps = { + threshold: number +} + +type Props = { + onClickBack: () => void + onClose: () => void + onSubmit: (values: SubmitProps) => void +} + +export const ThresholdForm = ({ onClickBack, onClose, onSubmit }: Props): ReactElement => { + const classes = useStyles() const threshold = useSelector(safeThresholdSelector) as number const owners = useSelector(safeOwnersSelector) - const handleSubmit = (values) => { + + const handleSubmit = (values: SubmitProps) => { onSubmit(values) } @@ -110,5 +124,3 @@ const ThresholdForm = ({ classes, onClickBack, onClose, onSubmit }) => { ) } - -export default withStyles(styles as any)(ThresholdForm) diff --git a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/style.ts b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/style.ts index 331290a0..c9b06d0d 100644 --- a/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/style.ts +++ b/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/style.ts @@ -1,6 +1,7 @@ import { lg, md, secondaryText, sm } from 'src/theme/variables' +import { createStyles } from '@material-ui/core' -export const styles = () => ({ +export const styles = createStyles({ heading: { padding: `${sm} ${lg}`, justifyContent: 'flex-start', diff --git a/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx b/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx index f626aabe..3ef0769f 100644 --- a/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { useEffect, useState } from 'react' +import { ReactElement, useEffect, useState } from 'react' import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' @@ -16,7 +16,7 @@ type OwnerAddressTableCellProps = { sendModalOpenHandler?: () => void } -const OwnerAddressTableCell = (props: OwnerAddressTableCellProps): React.ReactElement => { +export const OwnerAddressTableCell = (props: OwnerAddressTableCellProps): ReactElement => { const { address, knownAddress, showLinks, userName, sendModalOpenHandler } = props const [cut, setCut] = useState(0) const { width } = useWindowDimensions() @@ -50,5 +50,3 @@ const OwnerAddressTableCell = (props: OwnerAddressTableCellProps): React.ReactEl ) } - -export default OwnerAddressTableCell diff --git a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/index.tsx b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/index.tsx index 436e233a..0875cac1 100644 --- a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/index.tsx @@ -4,13 +4,13 @@ import { useDispatch, useSelector } from 'react-redux' import CheckOwner from './screens/CheckOwner' import { ReviewRemoveOwnerModal } from './screens/Review' -import ThresholdForm from './screens/ThresholdForm' +import { ThresholdForm } from './screens/ThresholdForm' import Modal from 'src/components/Modal' import { SENTINEL_ADDRESS, getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { createTransaction } from 'src/logic/safe/store/actions/createTransaction' -import removeSafeOwner from 'src/logic/safe/store/actions/removeSafeOwner' +import { removeSafeOwner } from 'src/logic/safe/store/actions/removeSafeOwner' import { safeParamAddressFromStateSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors' import { Dispatch } from 'src/logic/safe/store/actions/types.d' import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters' diff --git a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/index.tsx b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/index.tsx index a7d66ca3..480fbdef 100644 --- a/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/index.tsx @@ -30,7 +30,7 @@ type Props = { onSubmit: (txParameters: TxParameters) => void } -const ThresholdForm = ({ onClickBack, onClose, onSubmit }: Props): ReactElement => { +export const ThresholdForm = ({ onClickBack, onClose, onSubmit }: Props): ReactElement => { const classes = useStyles() const owners = useSelector(safeOwnersSelector) const threshold = useSelector(safeThresholdSelector) as number @@ -120,5 +120,3 @@ const ThresholdForm = ({ onClickBack, onClose, onSubmit }: Props): ReactElement ) } - -export default ThresholdForm diff --git a/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/index.tsx b/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/index.tsx index 7c812f90..74cd9f81 100644 --- a/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/index.tsx @@ -7,15 +7,15 @@ import { addOrUpdateAddressBookEntry } from 'src/logic/addressBook/store/actions import { SENTINEL_ADDRESS, getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions' import { createTransaction } from 'src/logic/safe/store/actions/createTransaction' -import replaceSafeOwner from 'src/logic/safe/store/actions/replaceSafeOwner' +import { replaceSafeOwner } from 'src/logic/safe/store/actions/replaceSafeOwner' import { safeParamAddressFromStateSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors' import { checksumAddress } from 'src/utils/checksumAddress' import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook' import { sameAddress } from 'src/logic/wallets/ethAddresses' import { Dispatch } from 'src/logic/safe/store/actions/types.d' -import OwnerForm from './screens/OwnerForm' -import { ReviewReplaceOwnerModal } from './screens/Review' +import { OwnerForm } from 'src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm' +import { ReviewReplaceOwnerModal } from 'src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/Review' import { TxParameters } from 'src/routes/safe/container/hooks/useTransactionParameters' const styles = createStyles({ diff --git a/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm/index.tsx b/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm/index.tsx index 035a5e8f..e1ae1472 100644 --- a/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm/index.tsx @@ -1,8 +1,7 @@ import IconButton from '@material-ui/core/IconButton' -import { withStyles } from '@material-ui/core/styles' import Close from '@material-ui/icons/Close' import classNames from 'classnames/bind' -import React from 'react' +import React, { ReactElement } from 'react' import { useSelector } from 'react-redux' import CopyBtn from 'src/components/CopyBtn' @@ -10,7 +9,13 @@ import AddressInput from 'src/components/forms/AddressInput' import Field from 'src/components/forms/Field' import GnoForm from 'src/components/forms/GnoForm' import TextField from 'src/components/forms/TextField' -import { composeValidators, minMaxLength, required, uniqueAddress } from 'src/components/forms/validator' +import { + addressIsNotCurrentSafe, + composeValidators, + minMaxLength, + required, + uniqueAddress, +} from 'src/components/forms/validator' import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' import Button from 'src/components/layout/Button' @@ -19,11 +24,12 @@ import Hairline from 'src/components/layout/Hairline' import Paragraph from 'src/components/layout/Paragraph' import Row from 'src/components/layout/Row' import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper' -import { safeOwnersAddressesListSelector } from 'src/logic/safe/store/selectors' +import { safeOwnersAddressesListSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { styles } from './style' import { getExplorerInfo } from 'src/config' import { ExplorerButton } from '@gnosis.pm/safe-react-components' +import { makeStyles } from '@material-ui/core' export const REPLACE_OWNER_NAME_INPUT_TEST_ID = 'replace-owner-name-input' export const REPLACE_OWNER_ADDRESS_INPUT_TEST_ID = 'replace-owner-address-testid' @@ -35,12 +41,30 @@ const formMutators = { }, } -const OwnerForm = ({ classes, onClose, onSubmit, ownerAddress, ownerName }) => { - const handleSubmit = (values) => { +const useStyles = makeStyles(styles) + +type NewOwnerProps = { + ownerAddress: string + ownerName: string +} + +type OwnerFormProps = { + onClose: () => void + onSubmit: (values: NewOwnerProps) => void + ownerAddress: string + ownerName: string +} + +export const OwnerForm = ({ onClose, onSubmit, ownerAddress, ownerName }: OwnerFormProps): ReactElement => { + const classes = useStyles() + + const handleSubmit = (values: NewOwnerProps) => { onSubmit(values) } const owners = useSelector(safeOwnersAddressesListSelector) + const safeAddress = useSelector(safeParamAddressFromStateSelector) const ownerDoesntExist = uniqueAddress(owners) + const ownerAddressIsNotSafeAddress = addressIsNotCurrentSafe(safeAddress) return ( <> @@ -106,7 +130,6 @@ const OwnerForm = ({ classes, onClose, onSubmit, ownerAddress, ownerName }) => { { @@ -136,11 +158,10 @@ const OwnerForm = ({ classes, onClose, onSubmit, ownerAddress, ownerName }) => { - diff --git a/src/routes/safe/components/Settings/ThresholdSettings/index.tsx b/src/routes/safe/components/Settings/ThresholdSettings/index.tsx index bdac5ec3..2e9246f2 100644 --- a/src/routes/safe/components/Settings/ThresholdSettings/index.tsx +++ b/src/routes/safe/components/Settings/ThresholdSettings/index.tsx @@ -57,7 +57,7 @@ const ThresholdSettings = (): React.ReactElement => { onClick={toggleModal} variant="contained" > - Modify + Change )} diff --git a/src/routes/safe/components/Transactions/TxList/HexEncodedData.tsx b/src/routes/safe/components/Transactions/TxList/HexEncodedData.tsx index 81955e08..fc48d716 100644 --- a/src/routes/safe/components/Transactions/TxList/HexEncodedData.tsx +++ b/src/routes/safe/components/Transactions/TxList/HexEncodedData.tsx @@ -4,7 +4,7 @@ import React, { ReactElement, useState } from 'react' import Paragraph from 'src/components/layout/Paragraph' import LinkWithRef from 'src/components/layout/Link' -import { shortVersionOf } from 'src/logic/wallets/ethAddresses' +import { textShortener } from 'src/utils/strings' export const styles = createStyles({ txDataParagraph: { @@ -18,16 +18,27 @@ export const styles = createStyles({ const useStyles = makeStyles(styles) -export const HexEncodedData = ({ hexData }: { hexData: string }): ReactElement => { +export const HexEncodedData = ({ + hexData, + title, + limit = 20, +}: { + hexData: string + title?: string + limit?: number +}): ReactElement => { const classes = useStyles() const [showTxData, setShowTxData] = useState(false) - const showExpandBtn = hexData.length > 20 + const showExpandBtn = hexData.length > limit + const shortener = textShortener({ charsStart: 40, charsEnd: 0 }) return (
- - Data (hex encoded): - + {title && ( + + {title}: + + )} {showExpandBtn ? ( <> @@ -46,7 +57,7 @@ export const HexEncodedData = ({ hexData }: { hexData: string }): ReactElement = ) : ( <> - {shortVersionOf(hexData, 20)}{' '} + {shortener(hexData)}{' '} { - const getTextValue = (value: string) => {value} + const getTextValue = (value: string) => const getArrayValue = (parentId: string, value: string[] | string) => (
@@ -31,10 +28,12 @@ const GenericValue = ({ method, type, value }: RenderValueProps): React.ReactEle {(value as string[]).map((currentValue, index) => { const key = `${parentId}-value-${index}` - return ( + return Array.isArray(currentValue) ? ( - {Array.isArray(currentValue) ? getArrayValue(key, currentValue) : getTextValue(currentValue)} + {getArrayValue(key, currentValue)} + ) : ( + getTextValue(currentValue) ) })} diff --git a/src/routes/safe/components/Transactions/TxList/MultiSendDetails.tsx b/src/routes/safe/components/Transactions/TxList/MultiSendDetails.tsx index 956d3f66..f3a87201 100644 --- a/src/routes/safe/components/Transactions/TxList/MultiSendDetails.tsx +++ b/src/routes/safe/components/Transactions/TxList/MultiSendDetails.tsx @@ -43,7 +43,7 @@ export const MultiSendDetails = ({ txData }: { txData: TransactionData }): React if (!txData.dataDecoded?.parameters) { // we render the hex encoded data if (txData.hexData) { - return + return } return null @@ -64,7 +64,7 @@ export const MultiSendDetails = ({ txData }: { txData: TransactionData }): React details = } else { // We couldn't decode it but we have data - details = data && + details = data && } return ( diff --git a/src/routes/safe/components/Transactions/TxList/TxData.tsx b/src/routes/safe/components/Transactions/TxList/TxData.tsx index 060cb6a9..d6c8dfa5 100644 --- a/src/routes/safe/components/Transactions/TxList/TxData.tsx +++ b/src/routes/safe/components/Transactions/TxList/TxData.tsx @@ -52,7 +52,7 @@ export const TxData = ({ txData }: TxDataProps): ReactElement | null => { // we render the hex encoded data return ( - + ) } diff --git a/src/routes/safe/components/Transactions/TxList/modals/ApproveTxModal.tsx b/src/routes/safe/components/Transactions/TxList/modals/ApproveTxModal.tsx index eca47c1d..687dea96 100644 --- a/src/routes/safe/components/Transactions/TxList/modals/ApproveTxModal.tsx +++ b/src/routes/safe/components/Transactions/TxList/modals/ApproveTxModal.tsx @@ -396,8 +396,8 @@ export const ApproveTxModal = ({ {/* Footer */} -