(Feature) #588 Use addressBook names when creating/loading safe (#1377)

* Fix addressbook types
Restructure addressbook store type

* Add more safe types

* Fix imports

* Removes .toJS() usage

* Fix condition for saving addressBook

* Types & remove send button from addressbook if user not an owner

* Add types for addressBook actions
Remove unused saveAndUpdateAddressBook action

* Refactor addressBook: make it global and removes immutableJS
Removes unused addAddressBook action

* Fix edit and remove entries style when user is not owner

* Adds and updates safe name in addressBook

* Adds checkIfOwnerWasDeletedFromAddressBook
Let the user remove owners users without adding them again each time the safe loads

* Simplify loadAddressBookFromStorage

* Fix compilation errors included in pr #1301

* Uses sameAddress function

* Add migration function for old stored address books

* Replaces shouldAvoidUpdatesNotifications with addAddressBookEntryOptions on addAddressBookEntry

* Unify return on getOwnersWithNameFromAddressBook

* Adds the addressbook names in safe load

* Reword shouldAvoidUpdatesNotifications

* Replaces adbk with addressBook

* Renames adbk to AddressBook

* Types on Open and Layout

* Remove unused actions and selectors

* Replaces initialValuesFrom to a hook and retrieves the ownerName

* Uses addressBook names in safe creation

* Fix owner name on creating safe

* Renames getNameFromAddressBook to getNameFromAddressBookSelector

* Fixs addOrUpdateAddressBookEntry action

* Updates addressbook on safe load

* Revert load update addressbook behaviour

* Renames checkIfOwnerWasDeletedFromAddressBook to checkIfEntryWasDeletedFromAddressBook

* Feedback

* Type review informaiton

* Adds ADD_OR_UPDATE_SAFE action

* Replaces addSafe with addOrUpdateSafe on addSafeHandler

* Exports isValidAddressBookName util function

* Adds isValidAddressBookName test

* Add tests for checkIfEntryWasDeletedFromAddressBook

* Fix saveAddressBook test

* Fix fetchSafeTokens.test.ts

* Add update individually safe props in addOrUpdate

* Fix updating addressbook entries on safe load/create

* Fix always loading safe as LOADED SAFE instead of safe name

* Fix adding owner as UNKNOWN on addressBook when adding new owner

Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>
This commit is contained in:
Agustin Pane 2020-09-23 15:14:49 -03:00 committed by GitHub
parent d1348713ad
commit 6f707a632b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 487 additions and 253 deletions

View File

@ -11,8 +11,8 @@ import { getValidAddressBookName } from 'src/logic/addressBook/utils'
export const ADDRESS_BOOK_REDUCER_ID = 'addressBook' export const ADDRESS_BOOK_REDUCER_ID = 'addressBook'
export const buildAddressBook = (storedAdbk: AddressBookState): AddressBookState => { export const buildAddressBook = (storedAddressBook: AddressBookState): AddressBookState => {
return storedAdbk.map((addressBookEntry) => { return storedAddressBook.map((addressBookEntry) => {
const { address, name } = addressBookEntry const { address, name } = addressBookEntry
return makeAddressBookEntry({ address: checksumAddress(address), name }) return makeAddressBookEntry({ address: checksumAddress(address), name })
}) })
@ -51,14 +51,16 @@ export default handleActions(
return state return state
}, },
[ADD_OR_UPDATE_ENTRY]: (state, action) => { [ADD_OR_UPDATE_ENTRY]: (state, action) => {
const { entry, entryAddress } = action.payload const { entry } = action.payload
// Only updates entries with valid names // Only updates entries with valid names
const validName = getValidAddressBookName(entry.name) const validName = getValidAddressBookName(entry.name)
if (!validName) { if (!validName) {
return state return state
} }
const entryIndex = state.findIndex((oldEntry) => oldEntry.address === entryAddress)
const entryIndex = state.findIndex((oldEntry) => oldEntry.address === entry.address)
if (entryIndex >= 0) { if (entryIndex >= 0) {
state[entryIndex] = entry state[entryIndex] = entry
} else { } else {

View File

@ -8,7 +8,7 @@ import { AddressBookState } from 'src/logic/addressBook/model/addressBook'
export const addressBookSelector = (state: AppReduxState): AddressBookState => state[ADDRESS_BOOK_REDUCER_ID] export const addressBookSelector = (state: AppReduxState): AddressBookState => state[ADDRESS_BOOK_REDUCER_ID]
export const getNameFromAddressBook = createSelector( export const getNameFromAddressBookSelector = createSelector(
addressBookSelector, addressBookSelector,
(_, address) => address, (_, address) => address,
(addressBook, address) => { (addressBook, address) => {

View File

@ -1,9 +1,11 @@
import { List } from 'immutable' import { List } from 'immutable'
import { import {
checkIfEntryWasDeletedFromAddressBook,
getAddressBookFromStorage, getAddressBookFromStorage,
getAddressesListFromAddressBook, getAddressesListFromAddressBook,
getNameFromAddressBook, getNameFromAddressBook,
getOwnersWithNameFromAddressBook, getOwnersWithNameFromAddressBook,
isValidAddressBookName,
migrateOldAddressBook, migrateOldAddressBook,
OldAddressBookEntry, OldAddressBookEntry,
OldAddressBookType, OldAddressBookType,
@ -85,6 +87,7 @@ describe('getOwnersWithNameFromAddressBook', () => {
}) })
}) })
jest.mock('src/utils/storage/index')
describe('saveAddressBook', () => { describe('saveAddressBook', () => {
const mockAdd1 = '0x696fd93D725d84acfFf6c62a1fe8C94E1c9E934A' const mockAdd1 = '0x696fd93D725d84acfFf6c62a1fe8C94E1c9E934A'
const mockAdd2 = '0x2C7aC78b01Be0FC66AD29b684ffAb0C93B381D00' const mockAdd2 = '0x2C7aC78b01Be0FC66AD29b684ffAb0C93B381D00'
@ -92,19 +95,27 @@ describe('saveAddressBook', () => {
const entry1 = getMockAddressBookEntry(mockAdd1, 'test1') const entry1 = getMockAddressBookEntry(mockAdd1, 'test1')
const entry2 = getMockAddressBookEntry(mockAdd2, 'test2') const entry2 = getMockAddressBookEntry(mockAdd2, 'test2')
const entry3 = getMockAddressBookEntry(mockAdd3, 'test3') const entry3 = getMockAddressBookEntry(mockAdd3, 'test3')
afterAll(() => {
jest.unmock('src/utils/storage/index')
})
it('It should save a given addressBook to the localStorage', async () => { it('It should save a given addressBook to the localStorage', async () => {
// given // given
const addressBook: AddressBookState = [entry1, entry2, entry3] const addressBook: AddressBookState = [entry1, entry2, entry3]
// when // when
// @ts-ignore
await saveAddressBook(addressBook) await saveAddressBook(addressBook)
const storedAdBk = await getAddressBookFromStorage()
const storageUtils = require('src/utils/storage/index')
const spy = storageUtils.loadFromStorage.mockImplementationOnce(() => JSON.stringify(addressBook))
const storedAddressBook = await getAddressBookFromStorage()
// @ts-ignore // @ts-ignore
let result = buildAddressBook(storedAdBk) let result = buildAddressBook(storedAddressBook)
// then // then
expect(result).toStrictEqual(addressBook) expect(result).toStrictEqual(addressBook)
expect(spy).toHaveBeenCalled()
}) })
}) })
@ -136,18 +147,19 @@ describe('migrateOldAddressBook', () => {
}) })
}) })
jest.mock('src/utils/storage/index')
describe('getAddressBookFromStorage', () => { describe('getAddressBookFromStorage', () => {
const safeAddress1 = '0x696fd93D725d84acfFf6c62a1fe8C94E1c9E934A' const safeAddress1 = '0x696fd93D725d84acfFf6c62a1fe8C94E1c9E934A'
const safeAddress2 = '0x2C7aC78b01Be0FC66AD29b684ffAb0C93B381D00' const safeAddress2 = '0x2C7aC78b01Be0FC66AD29b684ffAb0C93B381D00'
const mockAdd1 = '0x9163c2F4452E3399CB60AAf737231Af87548DA91' const mockAdd1 = '0x9163c2F4452E3399CB60AAf737231Af87548DA91'
const mockAdd2 = '0xC4e446Da9C3D37385C86488294C6758c4e25dbD8' const mockAdd2 = '0xC4e446Da9C3D37385C86488294C6758c4e25dbD8'
beforeAll(() => {
jest.mock('src/utils/storage/index')
})
afterAll(() => { afterAll(() => {
jest.unmock('src/utils/storage/index') jest.unmock('src/utils/storage/index')
}) })
it('It should return null if no addressBook in storage', async () => { it('It should return null if no addressBook in storage', async () => {
// given // given
const expectedResult = null const expectedResult = null
const storageUtils = require('src/utils/storage/index') const storageUtils = require('src/utils/storage/index')
const spy = storageUtils.loadFromStorage.mockImplementationOnce(() => null) const spy = storageUtils.loadFromStorage.mockImplementationOnce(() => null)
@ -199,3 +211,102 @@ describe('getAddressBookFromStorage', () => {
expect(spy).toHaveBeenCalled() expect(spy).toHaveBeenCalled()
}) })
}) })
describe('isValidAddressBookName', () => {
it('It should return false if given a blacklisted name like UNKNOWN', () => {
// given
const addressNameInput = 'UNKNOWN'
const expectedResult = false
// when
const result = isValidAddressBookName(addressNameInput)
// then
expect(result).toStrictEqual(expectedResult)
})
it('It should return false if given a blacklisted name like MY WALLET', () => {
// given
const addressNameInput = 'MY WALLET'
const expectedResult = false
// when
const result = isValidAddressBookName(addressNameInput)
// then
expect(result).toStrictEqual(expectedResult)
})
it('It should return false if given a blacklisted name like OWNER #', () => {
// given
const addressNameInput = 'OWNER #'
const expectedResult = false
// when
const result = isValidAddressBookName(addressNameInput)
// then
expect(result).toStrictEqual(expectedResult)
})
it('It should return true if the given address name is valid', () => {
// given
const addressNameInput = 'User'
const expectedResult = true
// when
const result = isValidAddressBookName(addressNameInput)
// then
expect(result).toEqual(expectedResult)
})
})
describe('checkIfEntryWasDeletedFromAddressBook', () => {
const mockAdd1 = '0x696fd93D725d84acfFf6c62a1fe8C94E1c9E934A'
const mockAdd2 = '0x2C7aC78b01Be0FC66AD29b684ffAb0C93B381D00'
const mockAdd3 = '0x537BD452c3505FC07bA242E437bD29D4E1DC9126'
const entry1 = getMockAddressBookEntry(mockAdd1, 'test1')
const entry2 = getMockAddressBookEntry(mockAdd2, 'test2')
const entry3 = getMockAddressBookEntry(mockAdd3, 'test3')
it('It should return true if a given entry was deleted from addressBook', () => {
// given
const addressBookEntry = entry1
const addressBook: AddressBookState = [entry2, entry3]
const safeAlreadyLoaded = true
const expectedResult = true
// when
const result = checkIfEntryWasDeletedFromAddressBook(addressBookEntry, addressBook, safeAlreadyLoaded)
// then
expect(result).toEqual(expectedResult)
})
it('It should return false if a given entry was not deleted from addressBook', () => {
// given
const addressBookEntry = entry1
const addressBook: AddressBookState = [entry1, entry2, entry3]
const safeAlreadyLoaded = true
const expectedResult = false
// when
const result = checkIfEntryWasDeletedFromAddressBook(addressBookEntry, addressBook, safeAlreadyLoaded)
// then
expect(result).toEqual(expectedResult)
})
it('It should return false if the safe was not already loaded', () => {
// given
const addressBookEntry = entry1
const addressBook: AddressBookState = [entry1, entry2, entry3]
const safeAlreadyLoaded = false
const expectedResult = false
// when
const result = checkIfEntryWasDeletedFromAddressBook(addressBookEntry, addressBook, safeAlreadyLoaded)
// then
expect(result).toEqual(expectedResult)
})
})

View File

@ -1,6 +1,6 @@
import { List } from 'immutable' import { List } from 'immutable'
import { loadFromStorage, saveToStorage } from 'src/utils/storage' import { loadFromStorage, saveToStorage } from 'src/utils/storage'
import { AddressBookState, makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook' import { AddressBookEntry, AddressBookState, makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
import { SafeOwner } from 'src/logic/safe/store/models/safe' import { SafeOwner } from 'src/logic/safe/store/models/safe'
import { sameAddress } from 'src/logic/wallets/ethAddresses' import { sameAddress } from 'src/logic/wallets/ethAddresses'
@ -16,6 +16,8 @@ export type OldAddressBookType = {
[safeAddress: string]: [OldAddressBookEntry] [safeAddress: string]: [OldAddressBookEntry]
} }
const ADDRESSBOOK_INVALID_NAMES = ['UNKNOWN', 'OWNER #', 'MY WALLET']
export const migrateOldAddressBook = (oldAddressBook: OldAddressBookType): AddressBookState => { export const migrateOldAddressBook = (oldAddressBook: OldAddressBookType): AddressBookState => {
const values: AddressBookState = [] const values: AddressBookState = []
const adbkValues = Object.values(oldAddressBook) const adbkValues = Object.values(oldAddressBook)
@ -56,19 +58,31 @@ export const saveAddressBook = async (addressBook: AddressBookState): Promise<vo
export const getAddressesListFromAddressBook = (addressBook: AddressBookState): string[] => export const getAddressesListFromAddressBook = (addressBook: AddressBookState): string[] =>
addressBook.map((entry) => entry.address) addressBook.map((entry) => entry.address)
export const getNameFromAddressBook = (addressBook: AddressBookState, userAddress: string): string | null => { type GetNameFromAddressBookOptions = {
filterOnlyValidName: boolean
}
export const getNameFromAddressBook = (
addressBook: AddressBookState,
userAddress: string,
options?: GetNameFromAddressBookOptions,
): string | null => {
const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress) const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress)
if (entry) { if (entry) {
return entry.name return options?.filterOnlyValidName ? getValidAddressBookName(entry.name) : entry.name
} }
return null return null
} }
export const getValidAddressBookName = (addressbookName: string): string | null => { export const isValidAddressBookName = (addressBookName: string): boolean => {
const INVALID_NAMES = ['UNKNOWN', 'OWNER #', 'MY WALLET'] const hasInvalidName = ADDRESSBOOK_INVALID_NAMES.find((invalidName) =>
const isInvalid = INVALID_NAMES.find((invalidName) => addressbookName.toUpperCase().includes(invalidName)) addressBookName.toUpperCase().includes(invalidName),
if (isInvalid) return null )
return addressbookName return !hasInvalidName
}
export const getValidAddressBookName = (addressBookName: string): string | null => {
return isValidAddressBookName(addressBookName) ? addressBookName : null
} }
export const getOwnersWithNameFromAddressBook = ( export const getOwnersWithNameFromAddressBook = (
@ -86,3 +100,41 @@ export const getOwnersWithNameFromAddressBook = (
} }
}) })
} }
export const formatAddressListToAddressBookNames = (
addressBook: AddressBookState,
addresses: string[],
): AddressBookEntry[] => {
if (!addresses.length) {
return []
}
return addresses.map((address) => {
const ownerName = getNameFromAddressBook(addressBook, address)
return {
address: address,
name: ownerName || '',
}
})
}
/**
* If the safe is not loaded, the owner wasn't not deleted
* If the safe is already loaded and the owner has a valid name, will return true if the address is not already on the addressBook
* @param name
* @param address
* @param addressBook
* @param safeAlreadyLoaded
*/
export const checkIfEntryWasDeletedFromAddressBook = (
{ name, address }: AddressBookEntry,
addressBook: AddressBookState,
safeAlreadyLoaded: boolean,
): boolean => {
if (!safeAlreadyLoaded) {
return false
}
const addressShouldBeOnTheAddressBook = !!getValidAddressBookName(name)
const isAlreadyInAddressBook = !!addressBook.find((entry) => sameAddress(entry.address, address))
return addressShouldBeOnTheAddressBook && !isAlreadyInAddressBook
}

View File

@ -48,6 +48,8 @@ describe('fetchTokenCurrenciesBalances', () => {
// then // then
expect(result).toStrictEqual(expectedResult) expect(result).toStrictEqual(expectedResult)
expect(axios.get).toHaveBeenCalled() expect(axios.get).toHaveBeenCalled()
expect(axios.get).toBeCalledWith(`${apiUrl}safes/${safeAddress}/balances/usd/`, { params: { limit: 3000 } }) expect(axios.get).toBeCalledWith(`${apiUrl}safes/${safeAddress}/balances/usd/?exclude_spam=true`, {
params: { limit: 3000 },
})
}) })
}) })

View File

@ -22,13 +22,13 @@ export const useLoadSafe = (safeAddress?: string): void => {
return dispatch(fetchSafeTokens(safeAddress)) return dispatch(fetchSafeTokens(safeAddress))
}) })
.then(() => { .then(() => {
dispatch(loadAddressBookFromStorage())
dispatch(fetchSafeCreationTx(safeAddress)) dispatch(fetchSafeCreationTx(safeAddress))
dispatch(fetchTransactions(safeAddress)) dispatch(fetchTransactions(safeAddress))
return dispatch(addViewedSafe(safeAddress)) return dispatch(addViewedSafe(safeAddress))
}) })
} }
} }
dispatch(loadAddressBookFromStorage())
fetchData() fetchData()
}, [dispatch, safeAddress]) }, [dispatch, safeAddress])

View File

@ -0,0 +1,9 @@
import { createAction } from 'redux-actions'
import { SafeRecordProps } from '../models/safe'
export const ADD_OR_UPDATE_SAFE = 'ADD_OR_UPDATE_SAFE'
export const addOrUpdateSafe = createAction(ADD_OR_UPDATE_SAFE, (safe: SafeRecordProps) => ({
safe,
}))

View File

@ -119,6 +119,7 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: Dispatch
dispatch( dispatch(
updateSafe({ updateSafe({
address: safeAddress, address: safeAddress,
name: localSafe?.name,
modules: buildModulesLinkedList(modules?.array, modules?.next), modules: buildModulesLinkedList(modules?.array, modules?.next),
nonce: Number(remoteNonce), nonce: Number(remoteNonce),
threshold: Number(remoteThreshold), threshold: Number(remoteThreshold),

View File

@ -13,16 +13,19 @@ import { SET_DEFAULT_SAFE } from 'src/logic/safe/store/actions/setDefaultSafe'
import { UPDATE_SAFE } from 'src/logic/safe/store/actions/updateSafe' import { UPDATE_SAFE } from 'src/logic/safe/store/actions/updateSafe'
import { getActiveTokensAddressesForAllSafes, safesMapSelector } from 'src/logic/safe/store/selectors' import { getActiveTokensAddressesForAllSafes, safesMapSelector } from 'src/logic/safe/store/selectors'
import { checksumAddress } from 'src/utils/checksumAddress' import { checksumAddress } from 'src/utils/checksumAddress'
import { AddressBookEntry, AddressBookState, makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook' import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
import { addOrUpdateAddressBookEntry } from 'src/logic/addressBook/store/actions/addOrUpdateAddressBookEntry' import { addOrUpdateAddressBookEntry } from 'src/logic/addressBook/store/actions/addOrUpdateAddressBookEntry'
import { getValidAddressBookName } from 'src/logic/addressBook/utils' import { checkIfEntryWasDeletedFromAddressBook, isValidAddressBookName } from 'src/logic/addressBook/utils'
import { addressBookSelector } from 'src/logic/addressBook/store/selectors' import { addressBookSelector } from 'src/logic/addressBook/store/selectors'
import { sameAddress } from 'src/logic/wallets/ethAddresses' import { sameAddress } from 'src/logic/wallets/ethAddresses'
import { updateAddressBookEntry } from 'src/logic/addressBook/store/actions/updateAddressBookEntry'
import { ADD_OR_UPDATE_SAFE } from 'src/logic/safe/store/actions/addOrUpdateSafe'
const watchedActions = [ const watchedActions = [
ADD_SAFE, ADD_SAFE,
UPDATE_SAFE, UPDATE_SAFE,
REMOVE_SAFE, REMOVE_SAFE,
ADD_OR_UPDATE_SAFE,
ADD_SAFE_OWNER, ADD_SAFE_OWNER,
REMOVE_SAFE_OWNER, REMOVE_SAFE_OWNER,
REPLACE_SAFE_OWNER, REPLACE_SAFE_OWNER,
@ -46,30 +49,6 @@ const recalculateActiveTokens = (state) => {
saveActiveTokens(activeTokens) saveActiveTokens(activeTokens)
} }
/**
* If the owner has a valid name that means that should be on the addressBook
* if the owner is not currently on the addressBook, that means the user deleted it
* or that it's a new safe with valid names, so we also check if it's a new safe or an already loaded one
* @param name
* @param address
* @param addressBook
* @param safeAlreadyLoaded -> true if the safe was loaded from the localStorage
*/
// TODO TEST
const checkIfOwnerWasDeletedFromAddressBook = (
{ name, address }: AddressBookEntry,
addressBook: AddressBookState,
safeAlreadyLoaded: boolean,
) => {
if (!safeAlreadyLoaded) {
return false
}
const addressShouldBeOnTheAddressBook = !!getValidAddressBookName(name)
const isAlreadyInAddressBook = !!addressBook.find((entry) => sameAddress(entry.address, address))
return addressShouldBeOnTheAddressBook && !isAlreadyInAddressBook
}
const safeStorageMware = (store) => (next) => async (action) => { const safeStorageMware = (store) => (next) => async (action) => {
const handledAction = next(action) const handledAction = next(action)
@ -87,22 +66,30 @@ const safeStorageMware = (store) => (next) => async (action) => {
} }
case ADD_SAFE: { case ADD_SAFE: {
const { safe, loadedFromStorage } = action.payload const { safe, loadedFromStorage } = action.payload
const safeAlreadyLoaded =
loadedFromStorage || safes.find((safeIterator) => sameAddress(safeIterator.address, safe.address))
safe.owners.forEach((owner) => { safe.owners.forEach((owner) => {
const checksumEntry = makeAddressBookEntry({ address: checksumAddress(owner.address), name: owner.name }) const checksumEntry = makeAddressBookEntry({ address: checksumAddress(owner.address), name: owner.name })
const ownerWasAlreadyInAddressBook = checkIfOwnerWasDeletedFromAddressBook(
const ownerWasAlreadyInAddressBook = checkIfEntryWasDeletedFromAddressBook(
checksumEntry, checksumEntry,
addressBook, addressBook,
loadedFromStorage, safeAlreadyLoaded,
) )
if (!ownerWasAlreadyInAddressBook) { if (!ownerWasAlreadyInAddressBook) {
dispatch(addAddressBookEntry(checksumEntry, { notifyEntryUpdate: false })) dispatch(addAddressBookEntry(checksumEntry, { notifyEntryUpdate: false }))
} }
const addressAlreadyExists = addressBook.find((entry) => sameAddress(entry.address, checksumEntry.address))
if (isValidAddressBookName(checksumEntry.name) && addressAlreadyExists) {
dispatch(updateAddressBookEntry(checksumEntry))
}
}) })
const safeWasAlreadyInAddressBook = checkIfOwnerWasDeletedFromAddressBook( const safeWasAlreadyInAddressBook = checkIfEntryWasDeletedFromAddressBook(
{ address: safe.address, name: safe.name }, { address: safe.address, name: safe.name },
addressBook, addressBook,
loadedFromStorage, safeAlreadyLoaded,
) )
if (!safeWasAlreadyInAddressBook) { if (!safeWasAlreadyInAddressBook) {
@ -114,13 +101,23 @@ const safeStorageMware = (store) => (next) => async (action) => {
} }
break break
} }
case ADD_OR_UPDATE_SAFE: {
const { safe } = action.payload
safe.owners.forEach((owner) => {
const checksumEntry = makeAddressBookEntry({ address: checksumAddress(owner.address), name: owner.name })
if (isValidAddressBookName(checksumEntry.name)) {
dispatch(addOrUpdateAddressBookEntry(checksumEntry))
}
})
break
}
case UPDATE_SAFE: { case UPDATE_SAFE: {
const { activeTokens, name, address } = action.payload const { activeTokens, name, address } = action.payload
if (activeTokens) { if (activeTokens) {
recalculateActiveTokens(state) recalculateActiveTokens(state)
} }
if (name) { if (name) {
dispatch(addOrUpdateAddressBookEntry(makeAddressBookEntry({ name, address })), { notifyEntryUpdate: false }) dispatch(addOrUpdateAddressBookEntry(makeAddressBookEntry({ name, address })))
} }
break break
} }

View File

@ -15,6 +15,7 @@ import { makeOwner } from 'src/logic/safe/store/models/owner'
import makeSafe, { SafeRecordProps } from 'src/logic/safe/store/models/safe' import makeSafe, { SafeRecordProps } from 'src/logic/safe/store/models/safe'
import { checksumAddress } from 'src/utils/checksumAddress' import { checksumAddress } from 'src/utils/checksumAddress'
import { SafeReducerMap } from 'src/routes/safe/store/reducer/types/safe' import { SafeReducerMap } from 'src/routes/safe/store/reducer/types/safe'
import { ADD_OR_UPDATE_SAFE } from 'src/logic/safe/store/actions/addOrUpdateSafe'
export const SAFE_REDUCER_ID = 'safes' export const SAFE_REDUCER_ID = 'safes'
export const DEFAULT_SAFE_INITIAL_STATE = 'NOT_ASKED' export const DEFAULT_SAFE_INITIAL_STATE = 'NOT_ASKED'
@ -42,16 +43,7 @@ export const buildSafe = (storedSafe: SafeRecordProps): SafeRecordProps => {
} }
} }
export default handleActions( const updateSafeProps = (prevSafe, safe) => {
{
[UPDATE_SAFE]: (state: SafeReducerMap, action) => {
const safe = action.payload
const safeAddress = safe.address
return state.updateIn(
['safes', safeAddress],
makeSafe({ name: 'LOADED SAFE', address: safeAddress }),
(prevSafe) => {
return prevSafe.withMutations((record) => { return prevSafe.withMutations((record) => {
// Every property is updated individually to overcome the issue with nested data being overwritten // Every property is updated individually to overcome the issue with nested data being overwritten
const safeProperties = Object.keys(safe) const safeProperties = Object.keys(safe)
@ -75,7 +67,18 @@ export default handleActions(
} }
}) })
}) })
}, }
export default handleActions(
{
[UPDATE_SAFE]: (state: SafeReducerMap, action) => {
const safe = action.payload
const safeAddress = safe.address
return state.updateIn(
['safes', safeAddress],
makeSafe({ name: safe?.name || 'LOADED SAFE', address: safeAddress }),
(prevSafe) => updateSafeProps(prevSafe, safe),
) )
}, },
[ACTIVATE_TOKEN_FOR_ALL_SAFES]: (state: SafeReducerMap, action) => { [ACTIVATE_TOKEN_FOR_ALL_SAFES]: (state: SafeReducerMap, action) => {
@ -106,6 +109,19 @@ export default handleActions(
return state.setIn(['safes', safe.address], makeSafe(safe)) return state.setIn(['safes', safe.address], makeSafe(safe))
}, },
[ADD_OR_UPDATE_SAFE]: (state: SafeReducerMap, action) => {
const { safe } = action.payload
if (!state.hasIn(['safes', safe.address])) {
return state.setIn(['safes', safe.address], makeSafe(safe))
}
return state.updateIn(
['safes', safe.address],
makeSafe({ name: 'LOADED SAFE', address: safe.address }),
(prevSafe) => updateSafeProps(prevSafe, safe),
)
},
[REMOVE_SAFE]: (state: SafeReducerMap, action) => { [REMOVE_SAFE]: (state: SafeReducerMap, action) => {
const safeAddress = action.payload const safeAddress = action.payload

View File

@ -1,5 +1,5 @@
import TableContainer from '@material-ui/core/TableContainer' import TableContainer from '@material-ui/core/TableContainer'
import { withStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import CopyBtn from 'src/components/CopyBtn' import CopyBtn from 'src/components/CopyBtn'
@ -17,57 +17,13 @@ import Row from 'src/components/layout/Row'
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
import { FIELD_LOAD_ADDRESS, THRESHOLD } from 'src/routes/load/components/fields' import { FIELD_LOAD_ADDRESS, THRESHOLD } from 'src/routes/load/components/fields'
import { getOwnerAddressBy, getOwnerNameBy } from 'src/routes/open/components/fields' import { getOwnerAddressBy, getOwnerNameBy } from 'src/routes/open/components/fields'
import { border, disabled, extraSmallFontSize, lg, md, screenSm, sm } from 'src/theme/variables'
const styles = () => ({ import { useSelector } from 'react-redux'
details: { import { addressBookSelector } from 'src/logic/addressBook/store/selectors'
padding: lg,
borderRight: `solid 1px ${border}`, import { formatAddressListToAddressBookNames } from 'src/logic/addressBook/utils'
height: '100%', import { AddressBookEntry } from 'src/logic/addressBook/model/addressBook'
}, import { styles } from './styles'
owners: {
display: 'flex',
justifyContent: 'flex-start',
},
ownerName: {
marginBottom: '15px',
minWidth: '100%',
[`@media (min-width: ${screenSm}px)`]: {
marginBottom: '0',
minWidth: '0',
},
},
ownerAddresses: {
alignItems: 'center',
marginLeft: `${sm}`,
},
address: {
paddingLeft: '6px',
marginRight: sm,
},
open: {
paddingLeft: sm,
width: 'auto',
'&:hover': {
cursor: 'pointer',
},
},
title: {
padding: `${md} ${lg}`,
},
owner: {
padding: `0 ${lg}`,
marginBottom: '12px',
},
header: {
padding: `${sm} ${lg}`,
color: disabled,
fontSize: extraSmallFontSize,
},
name: {
marginRight: `${sm}`,
},
})
const calculateSafeValues = (owners, threshold, values) => { const calculateSafeValues = (owners, threshold, values) => {
const initialValues = { ...values } const initialValues = { ...values }
@ -78,9 +34,20 @@ const calculateSafeValues = (owners, threshold, values) => {
return initialValues return initialValues
} }
const useAddressBookForOwnersNames = (ownersList: string[]): AddressBookEntry[] => {
const addressBook = useSelector(addressBookSelector)
return formatAddressListToAddressBookNames(addressBook, ownersList)
}
const useStyles = makeStyles(styles)
const OwnerListComponent = (props) => { const OwnerListComponent = (props) => {
const [owners, setOwners] = useState<string[]>([]) const [owners, setOwners] = useState<string[]>([])
const { classes, updateInitialProps, values } = props const classes = useStyles()
const { updateInitialProps, values } = props
const ownersWithNames = useAddressBookForOwnersNames(owners)
useEffect(() => { useEffect(() => {
let isCurrent = true let isCurrent = true
@ -121,13 +88,15 @@ const OwnerListComponent = (props) => {
</Row> </Row>
<Hairline /> <Hairline />
<Block margin="md" padding="md"> <Block margin="md" padding="md">
{owners.map((address, index) => ( {ownersWithNames.map(({ address, name }, index) => {
const ownerName = name || `Owner #${index + 1}`
return (
<Row className={classes.owner} key={address} data-testid="owner-row"> <Row className={classes.owner} key={address} data-testid="owner-row">
<Col className={classes.ownerName} xs={4}> <Col className={classes.ownerName} xs={4}>
<Field <Field
className={classes.name} className={classes.name}
component={TextField} component={TextField}
initialValue={`Owner #${index + 1}`} initialValue={ownerName}
name={getOwnerNameBy(index)} name={getOwnerNameBy(index)}
placeholder="Owner Name*" placeholder="Owner Name*"
text="Owner Name" text="Owner Name"
@ -147,21 +116,20 @@ const OwnerListComponent = (props) => {
</Row> </Row>
</Col> </Col>
</Row> </Row>
))} )
})}
</Block> </Block>
</TableContainer> </TableContainer>
</> </>
) )
} }
const OwnerListPage = withStyles(styles as any)(OwnerListComponent)
const OwnerList = ({ updateInitialProps }, network) => const OwnerList = ({ updateInitialProps }, network) =>
function LoadSafeOwnerList(controls, { values }): React.ReactElement { function LoadSafeOwnerList(controls, { values }): React.ReactElement {
return ( return (
<> <>
<OpenPaper controls={controls} padding={false}> <OpenPaper controls={controls} padding={false}>
<OwnerListPage network={network} updateInitialProps={updateInitialProps} values={values} /> <OwnerListComponent network={network} updateInitialProps={updateInitialProps} values={values} />
</OpenPaper> </OpenPaper>
</> </>
) )

View File

@ -0,0 +1,52 @@
import { border, disabled, extraSmallFontSize, lg, md, screenSm, sm } from 'src/theme/variables'
import { createStyles } from '@material-ui/core'
export const styles = createStyles({
details: {
padding: lg,
borderRight: `solid 1px ${border}`,
height: '100%',
},
owners: {
display: 'flex',
justifyContent: 'flex-start',
},
ownerName: {
marginBottom: '15px',
minWidth: '100%',
[`@media (min-width: ${screenSm}px)`]: {
marginBottom: '0',
minWidth: '0',
},
},
ownerAddresses: {
alignItems: 'center',
marginLeft: `${sm}`,
},
address: {
paddingLeft: '6px',
marginRight: sm,
},
open: {
paddingLeft: sm,
width: 'auto',
'&:hover': {
cursor: 'pointer',
},
},
title: {
padding: `${md} ${lg}`,
},
owner: {
padding: `0 ${lg}`,
marginBottom: '12px',
},
header: {
padding: `${sm} ${lg}`,
color: disabled,
fontSize: extraSmallFontSize,
},
name: {
marginRight: `${sm}`,
},
})

View File

@ -14,8 +14,8 @@ import { history } from 'src/store'
import { SafeOwner, SafeRecordProps } from 'src/logic/safe/store/models/safe' import { SafeOwner, SafeRecordProps } from 'src/logic/safe/store/models/safe'
import { List } from 'immutable' import { List } from 'immutable'
import { checksumAddress } from 'src/utils/checksumAddress' import { checksumAddress } from 'src/utils/checksumAddress'
import { addSafe } from 'src/logic/safe/store/actions/addSafe'
import { networkSelector, providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors' import { networkSelector, providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors'
import { addOrUpdateSafe } from 'src/logic/safe/store/actions/addOrUpdateSafe'
export const loadSafe = async ( export const loadSafe = async (
safeName: string, safeName: string,
@ -46,8 +46,8 @@ const Load = (): React.ReactElement => {
const network = useSelector(networkSelector) const network = useSelector(networkSelector)
const userAddress = useSelector(userAccountSelector) const userAddress = useSelector(userAccountSelector)
const addSafeHandler = (safe: SafeRecordProps) => { const addSafeHandler = async (safe: SafeRecordProps) => {
dispatch(addSafe(safe)) await dispatch(addOrUpdateSafe(safe))
} }
const onLoadSafeSubmit = async (values: LoadFormValues) => { const onLoadSafeSubmit = async (values: LoadFormValues) => {
let safeAddress = values[FIELD_LOAD_ADDRESS] let safeAddress = values[FIELD_LOAD_ADDRESS]

View File

@ -19,22 +19,43 @@ import {
import Welcome from 'src/routes/welcome/components/Layout' import Welcome from 'src/routes/welcome/components/Layout'
import { history } from 'src/store' import { history } from 'src/store'
import { secondary, sm } from 'src/theme/variables' import { secondary, sm } from 'src/theme/variables'
import { networkSelector, providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors'
import { useSelector } from 'react-redux'
import { addressBookSelector } from 'src/logic/addressBook/store/selectors'
import { getNameFromAddressBook } from 'src/logic/addressBook/utils'
const { useEffect } = React const { useEffect } = React
const getSteps = () => ['Name', 'Owners and confirmations', 'Review'] const getSteps = () => ['Name', 'Owners and confirmations', 'Review']
const initialValuesFrom = (userAccount, safeProps) => { type SafeProps = {
name: string
ownerAddresses: any
ownerNames: string
threshold: string
}
type InitialValuesForm = {
owner0Address?: string
owner0Name?: string
confirmations: string
safeName?: string
}
const useInitialValuesFrom = (userAccount: string, safeProps?: SafeProps): InitialValuesForm => {
const addressBook = useSelector(addressBookSelector)
const ownerName = getNameFromAddressBook(addressBook, userAccount, { filterOnlyValidName: true })
if (!safeProps) { if (!safeProps) {
return { return {
[getOwnerNameBy(0)]: 'My Wallet', [getOwnerNameBy(0)]: ownerName || 'My Wallet',
[getOwnerAddressBy(0)]: userAccount, [getOwnerAddressBy(0)]: userAccount,
[FIELD_CONFIRMATIONS]: '1', [FIELD_CONFIRMATIONS]: '1',
} }
} }
let obj = {} let obj = {}
const { name, ownerAddresses, ownerNames, threshold } = safeProps const { name, ownerAddresses, ownerNames, threshold } = safeProps
// eslint-disable-next-line no-restricted-syntax
for (const [index, value] of ownerAddresses.entries()) { for (const [index, value] of ownerAddresses.entries()) {
const safeName = ownerNames[index] ? ownerNames[index] : 'My Wallet' const safeName = ownerNames[index] ? ownerNames[index] : 'My Wallet'
obj = { obj = {
@ -66,8 +87,17 @@ const formMutators = {
}, },
} }
const Layout = (props) => { type LayoutProps = {
const { network, onCallSafeContractSubmit, provider, safeProps, userAccount } = props onCallSafeContractSubmit: (formValues: unknown) => void
safeProps?: SafeProps
}
const Layout = (props: LayoutProps): React.ReactElement => {
const { onCallSafeContractSubmit, safeProps } = props
const provider = useSelector(providerNameSelector)
const network = useSelector(networkSelector)
const userAccount = useSelector(userAccountSelector)
useEffect(() => { useEffect(() => {
if (provider) { if (provider) {
@ -77,7 +107,7 @@ const Layout = (props) => {
const steps = getSteps() const steps = getSteps()
const initialValues = initialValuesFrom(userAccount, safeProps) const initialValues = useInitialValuesFrom(userAccount, safeProps)
return ( return (
<> <>

View File

@ -1,5 +1,5 @@
import TableContainer from '@material-ui/core/TableContainer' import TableContainer from '@material-ui/core/TableContainer'
import { withStyles } from '@material-ui/core/styles' import { createStyles, makeStyles } from '@material-ui/core/styles'
import classNames from 'classnames' import classNames from 'classnames'
import * as React from 'react' import * as React from 'react'
@ -22,7 +22,7 @@ import { background, border, lg, screenSm, sm } from 'src/theme/variables'
const { useEffect, useState } = React const { useEffect, useState } = React
const styles = () => ({ const styles = createStyles({
root: { root: {
minHeight: '300px', minHeight: '300px',
[`@media (min-width: ${screenSm}px)`]: { [`@media (min-width: ${screenSm}px)`]: {
@ -84,7 +84,15 @@ const styles = () => ({
}, },
}) })
const ReviewComponent = ({ classes, userAccount, values }: any) => { const useStyles = makeStyles(styles)
type ReviewComponentProps = {
userAccount: string
values: any
}
const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => {
const classes = useStyles()
const [gasCosts, setGasCosts] = useState('< 0.001') const [gasCosts, setGasCosts] = useState('< 0.001')
const names = getNamesFrom(values) const names = getNamesFrom(values)
const addresses = getAccountsFrom(values) const addresses = getAccountsFrom(values)
@ -198,12 +206,11 @@ const ReviewComponent = ({ classes, userAccount, values }: any) => {
) )
} }
const ReviewPage = withStyles(styles as any)(ReviewComponent) // eslint-disable-next-line react/display-name
const Review = () => (controls, props): React.ReactElement => (
const Review = () => (controls, { values }) => (
<> <>
<OpenPaper controls={controls} padding={false}> <OpenPaper controls={controls} padding={false}>
<ReviewPage values={values} /> <ReviewComponent {...props} />
</OpenPaper> </OpenPaper>
</> </>
) )

View File

@ -1,10 +1,8 @@
import InputAdornment from '@material-ui/core/InputAdornment' import InputAdornment from '@material-ui/core/InputAdornment'
import MenuItem from '@material-ui/core/MenuItem' import MenuItem from '@material-ui/core/MenuItem'
import { withStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import CheckCircle from '@material-ui/icons/CheckCircle' import CheckCircle from '@material-ui/icons/CheckCircle'
import * as React from 'react' import * as React from 'react'
import { withRouter } from 'react-router-dom'
import { styles } from './style' import { styles } from './style'
import { getAddressValidator } from './validators' import { getAddressValidator } from './validators'
@ -38,6 +36,9 @@ import {
getOwnerNameBy, getOwnerNameBy,
} from 'src/routes/open/components/fields' } from 'src/routes/open/components/fields'
import { getAccountsFrom } from 'src/routes/open/utils/safeDataExtractor' import { getAccountsFrom } from 'src/routes/open/utils/safeDataExtractor'
import { useSelector } from 'react-redux'
import { addressBookSelector } from 'src/logic/addressBook/store/selectors'
import { getNameFromAddressBook } from 'src/logic/addressBook/utils'
const { useState } = React const { useState } = React
@ -63,10 +64,14 @@ export const calculateValuesAfterRemoving = (index, notRemovedOwners, values) =>
return initialValues return initialValues
} }
const SafeOwners = (props) => { const useStyles = makeStyles(styles)
const { classes, errors, form, otherAccounts, values } = props
const SafeOwnersForm = (props): React.ReactElement => {
const { errors, form, otherAccounts, values } = props
const classes = useStyles()
const validOwners = getNumOwnersFrom(values) const validOwners = getNumOwnersFrom(values)
const addressBook = useSelector(addressBookSelector)
const [numOwners, setNumOwners] = useState(validOwners) const [numOwners, setNumOwners] = useState(validOwners)
const [qrModalOpen, setQrModalOpen] = useState(false) const [qrModalOpen, setQrModalOpen] = useState(false)
@ -125,6 +130,7 @@ const SafeOwners = (props) => {
<Block margin="md" padding="md"> <Block margin="md" padding="md">
{[...Array(Number(numOwners))].map((x, index) => { {[...Array(Number(numOwners))].map((x, index) => {
const addressName = getOwnerAddressBy(index) const addressName = getOwnerAddressBy(index)
const ownerName = getOwnerNameBy(index)
return ( return (
<Row className={classes.owner} key={`owner${index}`} data-testid={`create-safe-owner-row`}> <Row className={classes.owner} key={`owner${index}`} data-testid={`create-safe-owner-row`}>
@ -132,7 +138,7 @@ const SafeOwners = (props) => {
<Field <Field
className={classes.name} className={classes.name}
component={TextField} component={TextField}
name={getOwnerNameBy(index)} name={ownerName}
placeholder="Owner Name*" placeholder="Owner Name*"
text="Owner Name" text="Owner Name"
type="text" type="text"
@ -142,8 +148,14 @@ const SafeOwners = (props) => {
</Col> </Col>
<Col className={classes.ownerAddress} xs={6}> <Col className={classes.ownerAddress} xs={6}>
<AddressInput <AddressInput
fieldMutator={(val) => { fieldMutator={(newOwnerAddress) => {
form.mutators.setValue(addressName, val) const newOwnerName = getNameFromAddressBook(addressBook, newOwnerAddress, {
filterOnlyValidName: true,
})
form.mutators.setValue(addressName, newOwnerAddress)
if (newOwnerName) {
form.mutators.setValue(ownerName, newOwnerName)
}
}} }}
// eslint-disable-next-line // eslint-disable-next-line
// @ts-ignore // @ts-ignore
@ -224,8 +236,6 @@ const SafeOwners = (props) => {
) )
} }
const SafeOwnersForm = withStyles(styles as any)(withRouter(SafeOwners))
const SafeOwnersPage = ({ updateInitialProps }) => const SafeOwnersPage = ({ updateInitialProps }) =>
function OpenSafeOwnersPage(controls, { errors, form, values }) { function OpenSafeOwnersPage(controls, { errors, form, values }) {
return ( return (

View File

@ -1,6 +1,7 @@
import { disabled, extraSmallFontSize, lg, md, screenSm, sm } from 'src/theme/variables' import { disabled, extraSmallFontSize, lg, md, screenSm, sm } from 'src/theme/variables'
import { createStyles } from '@material-ui/core'
export const styles = () => ({ export const styles = createStyles({
root: { root: {
display: 'flex', display: 'flex',
}, },

View File

@ -2,15 +2,9 @@ import { Loader } from '@gnosis.pm/safe-react-components'
import queryString from 'query-string' import queryString from 'query-string'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { connect } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { withRouter, RouteComponentProps } from 'react-router-dom' import Opening from 'src/routes/opening'
import Layout from 'src/routes/open/components/Layout'
import Opening from '../../opening'
import Layout from '../components/Layout'
import actions from './actions'
import selector from './selector'
import Page from 'src/components/layout/Page' import Page from 'src/components/layout/Page'
import { getSafeDeploymentTransaction } from 'src/logic/contracts/safeContracts' import { getSafeDeploymentTransaction } from 'src/logic/contracts/safeContracts'
import { checkReceiptStatus } from 'src/logic/wallets/ethTransactions' import { checkReceiptStatus } from 'src/logic/wallets/ethTransactions'
@ -25,6 +19,9 @@ import { SAFELIST_ADDRESS, WELCOME_ADDRESS } from 'src/routes/routes'
import { buildSafe } from 'src/logic/safe/store/actions/fetchSafe' import { buildSafe } from 'src/logic/safe/store/actions/fetchSafe'
import { history } from 'src/store' import { history } from 'src/store'
import { loadFromStorage, removeFromStorage, saveToStorage } from 'src/utils/storage' import { loadFromStorage, removeFromStorage, saveToStorage } from 'src/utils/storage'
import { userAccountSelector } from 'src/logic/wallets/store/selectors'
import { SafeRecordProps } from 'src/logic/safe/store/models/safe'
import { addOrUpdateSafe } from 'src/logic/safe/store/actions/addOrUpdateSafe'
const SAFE_PENDING_CREATION_STORAGE_KEY = 'SAFE_PENDING_CREATION_STORAGE_KEY' const SAFE_PENDING_CREATION_STORAGE_KEY = 'SAFE_PENDING_CREATION_STORAGE_KEY'
@ -39,13 +36,15 @@ const validateQueryParams = (ownerAddresses, ownerNames, threshold, safeName) =>
if (Number.isNaN(Number(threshold))) { if (Number.isNaN(Number(threshold))) {
return false return false
} }
if (threshold > ownerAddresses.length) { return threshold <= ownerAddresses.length
return false
}
return true
} }
export const getSafeProps = async (safeAddress, safeName, ownersNames, ownerAddresses) => { export const getSafeProps = async (
safeAddress: string,
safeName: string,
ownersNames: string[],
ownerAddresses: string[],
): Promise<SafeRecordProps> => {
const safeProps = await buildSafe(safeAddress, safeName) const safeProps = await buildSafe(safeAddress, safeName)
const owners = getOwnersFrom(ownersNames, ownerAddresses) const owners = getOwnersFrom(ownersNames, ownerAddresses)
safeProps.owners = owners safeProps.owners = owners
@ -81,19 +80,14 @@ export const createSafe = (values, userAccount) => {
return promiEvent return promiEvent
} }
interface OwnProps extends RouteComponentProps { const Open = (): React.ReactElement => {
userAccount: string
network: string
provider: string
addSafe: any
}
const Open = ({ addSafe, network, provider, userAccount }: OwnProps): React.ReactElement => {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [showProgress, setShowProgress] = useState(false) const [showProgress, setShowProgress] = useState(false)
const [creationTxPromise, setCreationTxPromise] = useState() const [creationTxPromise, setCreationTxPromise] = useState()
const [safeCreationPendingInfo, setSafeCreationPendingInfo] = useState<any>() const [safeCreationPendingInfo, setSafeCreationPendingInfo] = useState<any>()
const [safePropsFromUrl, setSafePropsFromUrl] = useState() const [safePropsFromUrl, setSafePropsFromUrl] = useState()
const userAccount = useSelector(userAccountSelector)
const dispatch = useDispatch()
useEffect(() => { useEffect(() => {
// #122: Allow to migrate an old Multisig by passing the parameters to the URL. // #122: Allow to migrate an old Multisig by passing the parameters to the URL.
@ -141,14 +135,15 @@ const Open = ({ addSafe, network, provider, userAccount }: OwnProps): React.Reac
setShowProgress(true) setShowProgress(true)
} }
const onSafeCreated = async (safeAddress) => { const onSafeCreated = async (safeAddress): Promise<void> => {
const pendingCreation = await loadFromStorage<{ txHash: string }>(SAFE_PENDING_CREATION_STORAGE_KEY) const pendingCreation = await loadFromStorage<{ txHash: string }>(SAFE_PENDING_CREATION_STORAGE_KEY)
const name = getSafeNameFrom(pendingCreation) const name = getSafeNameFrom(pendingCreation)
const ownersNames = getNamesFrom(pendingCreation) const ownersNames = getNamesFrom(pendingCreation)
const ownerAddresses = getAccountsFrom(pendingCreation) const ownerAddresses = getAccountsFrom(pendingCreation)
const safeProps = await getSafeProps(safeAddress, name, ownersNames, ownerAddresses) const safeProps = await getSafeProps(safeAddress, name, ownersNames, ownerAddresses)
addSafe(safeProps)
await dispatch(addOrUpdateSafe(safeProps))
ReactGA.event({ ReactGA.event({
category: 'User', category: 'User',
@ -194,21 +189,14 @@ const Open = ({ addSafe, network, provider, userAccount }: OwnProps): React.Reac
creationTxHash={safeCreationPendingInfo?.txHash} creationTxHash={safeCreationPendingInfo?.txHash}
onCancel={onCancel} onCancel={onCancel}
onRetry={onRetry} onRetry={onRetry}
onSuccess={onSafeCreated as any} onSuccess={onSafeCreated}
provider={provider}
submittedPromise={creationTxPromise} submittedPromise={creationTxPromise}
/> />
) : ( ) : (
<Layout <Layout onCallSafeContractSubmit={createSafeProxy} safeProps={safePropsFromUrl} />
network={network}
onCallSafeContractSubmit={createSafeProxy}
provider={provider}
safeProps={safePropsFromUrl}
userAccount={userAccount}
/>
)} )}
</Page> </Page>
) )
} }
export default connect(selector, actions)(withRouter(Open)) export default Open

View File

@ -1,5 +0,0 @@
import addSafe from 'src/logic/safe/store/actions/addSafe'
export default {
addSafe,
}

View File

@ -1,9 +0,0 @@
import { createStructuredSelector } from 'reselect'
import { networkSelector, providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors'
export default createStructuredSelector({
provider: providerNameSelector,
network: networkSelector,
userAccount: userAccountSelector,
})

View File

@ -2,7 +2,7 @@ import { Loader, Stepper } from '@gnosis.pm/safe-react-components'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { ErrorFooter } from './components/Footer' import { ErrorFooter } from 'src/routes/opening/components/Footer'
import { isConfirmationStep, steps } from './steps' import { isConfirmationStep, steps } from './steps'
import Button from 'src/components/layout/Button' import Button from 'src/components/layout/Button'
@ -13,6 +13,8 @@ import { initContracts } from 'src/logic/contracts/safeContracts'
import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
import { getWeb3 } from 'src/logic/wallets/getWeb3' import { getWeb3 } from 'src/logic/wallets/getWeb3'
import { background, connected } from 'src/theme/variables' import { background, connected } from 'src/theme/variables'
import { providerNameSelector } from 'src/logic/wallets/store/selectors'
import { useSelector } from 'react-redux'
const loaderDotsSvg = require('./assets/loader-dots.svg') const loaderDotsSvg = require('./assets/loader-dots.svg')
const successSvg = require('./assets/success.svg') const successSvg = require('./assets/success.svg')
@ -102,16 +104,17 @@ const BackButton = styled(Button)`
// onCancel: () => void // onCancel: () => void
// } // }
const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, provider, submittedPromise }: any) => { const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, submittedPromise }): React.ReactElement => {
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [stepIndex, setStepIndex] = useState(0) const [stepIndex, setStepIndex] = useState(0)
const [safeCreationTxHash, setSafeCreationTxHash] = useState('') const [safeCreationTxHash, setSafeCreationTxHash] = useState('')
const [createdSafeAddress, setCreatedSafeAddress] = useState() const [createdSafeAddress, setCreatedSafeAddress] = useState('')
const [error, setError] = useState(false) const [error, setError] = useState(false)
const [intervalStarted, setIntervalStarted] = useState(false) const [intervalStarted, setIntervalStarted] = useState(false)
const [waitingSafeDeployed, setWaitingSafeDeployed] = useState(false) const [waitingSafeDeployed, setWaitingSafeDeployed] = useState(false)
const [continueButtonDisabled, setContinueButtonDisabled] = useState(false) const [continueButtonDisabled, setContinueButtonDisabled] = useState(false)
const provider = useSelector(providerNameSelector)
const confirmationStep = isConfirmationStep(stepIndex) const confirmationStep = isConfirmationStep(stepIndex)

View File

@ -16,6 +16,7 @@ import createTransaction from 'src/logic/safe/store/actions/createTransaction'
import { safeOwnersSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { safeOwnersSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import { checksumAddress } from 'src/utils/checksumAddress' import { checksumAddress } from 'src/utils/checksumAddress'
import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
const styles = () => ({ const styles = () => ({
biggerModalWindow: { biggerModalWindow: {
@ -92,7 +93,7 @@ const AddOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose })
try { try {
await sendAddOwner(values, safeAddress, owners, enqueueSnackbar, closeSnackbar, dispatch) await sendAddOwner(values, safeAddress, owners, enqueueSnackbar, closeSnackbar, dispatch)
dispatch( dispatch(
addOrUpdateAddressBookEntry(values.ownerAddress, { name: values.ownerName, address: values.ownerAddress }), addOrUpdateAddressBookEntry(makeAddressBookEntry({ name: values.ownerName, address: values.ownerAddress })),
) )
} catch (error) { } catch (error) {
console.error('Error while removing an owner', error) console.error('Error while removing an owner', error)

View File

@ -14,6 +14,7 @@ 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 { safeParamAddressFromStateSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors'
import { checksumAddress } from 'src/utils/checksumAddress' import { checksumAddress } from 'src/utils/checksumAddress'
import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
const styles = () => ({ const styles = () => ({
biggerModalWindow: { biggerModalWindow: {
@ -96,10 +97,7 @@ const ReplaceOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose
await sendReplaceOwner(values, safeAddress, ownerAddress, enqueueSnackbar, closeSnackbar, threshold, dispatch) await sendReplaceOwner(values, safeAddress, ownerAddress, enqueueSnackbar, closeSnackbar, threshold, dispatch)
dispatch( dispatch(
// Needs the `address` field because we need to provide the minimum required values to ADD a new entry addOrUpdateAddressBookEntry(makeAddressBookEntry({ address: values.ownerAddress, name: values.ownerName })),
// The reducer will update all the addressBooks stored, so we cannot decide what to do beforehand,
// thus, we pass the minimum required fields (name and address)
addOrUpdateAddressBookEntry(values.ownerAddress, { name: values.ownerName, address: values.ownerAddress }),
) )
} catch (error) { } catch (error) {
console.error('Error while removing an owner', error) console.error('Error while removing an owner', error)

View File

@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'
import EtherscanLink from 'src/components/EtherscanLink' import EtherscanLink from 'src/components/EtherscanLink'
import Block from 'src/components/layout/Block' import Block from 'src/components/layout/Block'
import Bold from 'src/components/layout/Bold' import Bold from 'src/components/layout/Bold'
import { getNameFromAddressBook } from 'src/logic/addressBook/store/selectors' import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors'
import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell'
import { getIncomingTxAmount } from 'src/routes/safe/components/Transactions/TxsTable/columns' import { getIncomingTxAmount } from 'src/routes/safe/components/Transactions/TxsTable/columns'
import { lg, md } from 'src/theme/variables' import { lg, md } from 'src/theme/variables'
@ -35,7 +35,7 @@ const TransferDescription = ({ from, txFromName, value = '' }) => (
const IncomingTxDescription = ({ tx }) => { const IncomingTxDescription = ({ tx }) => {
const classes = useStyles() const classes = useStyles()
const txFromName = useSelector((state) => getNameFromAddressBook(state, tx.from)) const txFromName = useSelector((state) => getNameFromAddressBookSelector(state, tx.from))
return ( return (
<Block className={classes.txDataContainer}> <Block className={classes.txDataContainer}>
<TransferDescription from={tx.from} txFromName={txFromName} value={getIncomingTxAmount(tx, false)} /> <TransferDescription from={tx.from} txFromName={txFromName} value={getIncomingTxAmount(tx, false)} />

View File

@ -16,7 +16,7 @@ import { styles } from './style'
import Block from 'src/components/layout/Block' import Block from 'src/components/layout/Block'
import Button from 'src/components/layout/Button' import Button from 'src/components/layout/Button'
import Img from 'src/components/layout/Img' import Img from 'src/components/layout/Img'
import { getNameFromAddressBook } from 'src/logic/addressBook/store/selectors' import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors'
import { OwnersWithoutConfirmations } from './index' import { OwnersWithoutConfirmations } from './index'
export const CONFIRM_TX_BTN_TEST_ID = 'confirm-btn' export const CONFIRM_TX_BTN_TEST_ID = 'confirm-btn'
@ -64,7 +64,7 @@ const OwnerComponent = (props: OwnerComponentProps): React.ReactElement => {
showExecuteRejectBtn, showExecuteRejectBtn,
confirmed, confirmed,
} = props } = props
const nameInAdbk = useSelector((state) => getNameFromAddressBook(state, owner)) const nameInAdbk = useSelector((state) => getNameFromAddressBookSelector(state, owner))
const classes = useStyles() const classes = useStyles()
const [imgCircle, setImgCircle] = React.useState(ConfirmSmallGreyCircle) const [imgCircle, setImgCircle] = React.useState(ConfirmSmallGreyCircle)

View File

@ -15,7 +15,7 @@ import Bold from 'src/components/layout/Bold'
import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue' import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue'
import Collapse from 'src/components/Collapse' import Collapse from 'src/components/Collapse'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { getNameFromAddressBook } from 'src/logic/addressBook/store/selectors' import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors'
import Paragraph from 'src/components/layout/Paragraph' import Paragraph from 'src/components/layout/Paragraph'
import LinkWithRef from 'src/components/layout/Link' import LinkWithRef from 'src/components/layout/Link'
import { shortVersionOf } from 'src/logic/wallets/ethAddresses' import { shortVersionOf } from 'src/logic/wallets/ethAddresses'
@ -176,7 +176,7 @@ interface GenericCustomDataProps {
const GenericCustomData = ({ amount = '0', data, recipient, storedTx }: GenericCustomDataProps): React.ReactElement => { const GenericCustomData = ({ amount = '0', data, recipient, storedTx }: GenericCustomDataProps): React.ReactElement => {
const classes = useStyles() const classes = useStyles()
const recipientName = useSelector((state) => getNameFromAddressBook(state, recipient)) const recipientName = useSelector((state) => getNameFromAddressBookSelector(state, recipient))
return ( return (
<Block> <Block>

View File

@ -1,7 +1,7 @@
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import React from 'react' import React from 'react'
import { getNameFromAddressBook } from 'src/logic/addressBook/store/selectors' import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors'
import Block from 'src/components/layout/Block' import Block from 'src/components/layout/Block'
import Bold from 'src/components/layout/Bold' import Bold from 'src/components/layout/Bold'
import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell'
@ -21,7 +21,7 @@ interface RemovedOwnerProps {
} }
const RemovedOwner = ({ removedOwner }: RemovedOwnerProps): React.ReactElement => { const RemovedOwner = ({ removedOwner }: RemovedOwnerProps): React.ReactElement => {
const ownerChangedName = useSelector((state) => getNameFromAddressBook(state, removedOwner)) const ownerChangedName = useSelector((state) => getNameFromAddressBookSelector(state, removedOwner))
return ( return (
<Block data-testid={TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID}> <Block data-testid={TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID}>
@ -40,7 +40,7 @@ interface AddedOwnerProps {
} }
const AddedOwner = ({ addedOwner }: AddedOwnerProps): React.ReactElement => { const AddedOwner = ({ addedOwner }: AddedOwnerProps): React.ReactElement => {
const ownerChangedName = useSelector((state) => getNameFromAddressBook(state, addedOwner)) const ownerChangedName = useSelector((state) => getNameFromAddressBookSelector(state, addedOwner))
return ( return (
<Block data-testid={TRANSACTIONS_DESC_ADD_OWNER_TEST_ID}> <Block data-testid={TRANSACTIONS_DESC_ADD_OWNER_TEST_ID}>

View File

@ -2,7 +2,7 @@ import React from 'react'
import { useSelector } from 'react-redux' import { useSelector } from 'react-redux'
import { TRANSACTIONS_DESC_SEND_TEST_ID } from './index' import { TRANSACTIONS_DESC_SEND_TEST_ID } from './index'
import { getNameFromAddressBook } from 'src/logic/addressBook/store/selectors' import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors'
import Block from 'src/components/layout/Block' import Block from 'src/components/layout/Block'
import Bold from 'src/components/layout/Bold' import Bold from 'src/components/layout/Bold'
import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell'
@ -14,7 +14,7 @@ interface TransferDescriptionProps {
} }
const TransferDescription = ({ amount = '', recipient }: TransferDescriptionProps): React.ReactElement => { const TransferDescription = ({ amount = '', recipient }: TransferDescriptionProps): React.ReactElement => {
const recipientName = useSelector((state) => getNameFromAddressBook(state, recipient)) const recipientName = useSelector((state) => getNameFromAddressBookSelector(state, recipient))
return ( return (
<Block data-testid={TRANSACTIONS_DESC_SEND_TEST_ID}> <Block data-testid={TRANSACTIONS_DESC_SEND_TEST_ID}>
<Bold>Send {amount} to:</Bold> <Bold>Send {amount} to:</Bold>