* 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:
parent
d1348713ad
commit
6f707a632b
|
@ -11,8 +11,8 @@ import { getValidAddressBookName } from 'src/logic/addressBook/utils'
|
|||
|
||||
export const ADDRESS_BOOK_REDUCER_ID = 'addressBook'
|
||||
|
||||
export const buildAddressBook = (storedAdbk: AddressBookState): AddressBookState => {
|
||||
return storedAdbk.map((addressBookEntry) => {
|
||||
export const buildAddressBook = (storedAddressBook: AddressBookState): AddressBookState => {
|
||||
return storedAddressBook.map((addressBookEntry) => {
|
||||
const { address, name } = addressBookEntry
|
||||
return makeAddressBookEntry({ address: checksumAddress(address), name })
|
||||
})
|
||||
|
@ -51,14 +51,16 @@ export default handleActions(
|
|||
return state
|
||||
},
|
||||
[ADD_OR_UPDATE_ENTRY]: (state, action) => {
|
||||
const { entry, entryAddress } = action.payload
|
||||
const { entry } = action.payload
|
||||
|
||||
// Only updates entries with valid names
|
||||
const validName = getValidAddressBookName(entry.name)
|
||||
if (!validName) {
|
||||
return state
|
||||
}
|
||||
const entryIndex = state.findIndex((oldEntry) => oldEntry.address === entryAddress)
|
||||
|
||||
const entryIndex = state.findIndex((oldEntry) => oldEntry.address === entry.address)
|
||||
|
||||
if (entryIndex >= 0) {
|
||||
state[entryIndex] = entry
|
||||
} else {
|
||||
|
|
|
@ -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 getNameFromAddressBook = createSelector(
|
||||
export const getNameFromAddressBookSelector = createSelector(
|
||||
addressBookSelector,
|
||||
(_, address) => address,
|
||||
(addressBook, address) => {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { List } from 'immutable'
|
||||
import {
|
||||
checkIfEntryWasDeletedFromAddressBook,
|
||||
getAddressBookFromStorage,
|
||||
getAddressesListFromAddressBook,
|
||||
getNameFromAddressBook,
|
||||
getOwnersWithNameFromAddressBook,
|
||||
isValidAddressBookName,
|
||||
migrateOldAddressBook,
|
||||
OldAddressBookEntry,
|
||||
OldAddressBookType,
|
||||
|
@ -85,6 +87,7 @@ describe('getOwnersWithNameFromAddressBook', () => {
|
|||
})
|
||||
})
|
||||
|
||||
jest.mock('src/utils/storage/index')
|
||||
describe('saveAddressBook', () => {
|
||||
const mockAdd1 = '0x696fd93D725d84acfFf6c62a1fe8C94E1c9E934A'
|
||||
const mockAdd2 = '0x2C7aC78b01Be0FC66AD29b684ffAb0C93B381D00'
|
||||
|
@ -92,19 +95,27 @@ describe('saveAddressBook', () => {
|
|||
const entry1 = getMockAddressBookEntry(mockAdd1, 'test1')
|
||||
const entry2 = getMockAddressBookEntry(mockAdd2, 'test2')
|
||||
const entry3 = getMockAddressBookEntry(mockAdd3, 'test3')
|
||||
afterAll(() => {
|
||||
jest.unmock('src/utils/storage/index')
|
||||
})
|
||||
it('It should save a given addressBook to the localStorage', async () => {
|
||||
// given
|
||||
const addressBook: AddressBookState = [entry1, entry2, entry3]
|
||||
|
||||
// when
|
||||
// @ts-ignore
|
||||
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
|
||||
let result = buildAddressBook(storedAdBk)
|
||||
let result = buildAddressBook(storedAddressBook)
|
||||
|
||||
// then
|
||||
expect(result).toStrictEqual(addressBook)
|
||||
expect(spy).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -136,18 +147,19 @@ describe('migrateOldAddressBook', () => {
|
|||
})
|
||||
})
|
||||
|
||||
jest.mock('src/utils/storage/index')
|
||||
describe('getAddressBookFromStorage', () => {
|
||||
const safeAddress1 = '0x696fd93D725d84acfFf6c62a1fe8C94E1c9E934A'
|
||||
const safeAddress2 = '0x2C7aC78b01Be0FC66AD29b684ffAb0C93B381D00'
|
||||
const mockAdd1 = '0x9163c2F4452E3399CB60AAf737231Af87548DA91'
|
||||
const mockAdd2 = '0xC4e446Da9C3D37385C86488294C6758c4e25dbD8'
|
||||
beforeAll(() => {
|
||||
jest.mock('src/utils/storage/index')
|
||||
})
|
||||
afterAll(() => {
|
||||
jest.unmock('src/utils/storage/index')
|
||||
})
|
||||
it('It should return null if no addressBook in storage', async () => {
|
||||
// given
|
||||
|
||||
const expectedResult = null
|
||||
const storageUtils = require('src/utils/storage/index')
|
||||
const spy = storageUtils.loadFromStorage.mockImplementationOnce(() => null)
|
||||
|
@ -199,3 +211,102 @@ describe('getAddressBookFromStorage', () => {
|
|||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { List } from 'immutable'
|
||||
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 { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
|
||||
|
@ -16,6 +16,8 @@ export type OldAddressBookType = {
|
|||
[safeAddress: string]: [OldAddressBookEntry]
|
||||
}
|
||||
|
||||
const ADDRESSBOOK_INVALID_NAMES = ['UNKNOWN', 'OWNER #', 'MY WALLET']
|
||||
|
||||
export const migrateOldAddressBook = (oldAddressBook: OldAddressBookType): AddressBookState => {
|
||||
const values: AddressBookState = []
|
||||
const adbkValues = Object.values(oldAddressBook)
|
||||
|
@ -56,19 +58,31 @@ export const saveAddressBook = async (addressBook: AddressBookState): Promise<vo
|
|||
export const getAddressesListFromAddressBook = (addressBook: AddressBookState): string[] =>
|
||||
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)
|
||||
if (entry) {
|
||||
return entry.name
|
||||
return options?.filterOnlyValidName ? getValidAddressBookName(entry.name) : entry.name
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export const getValidAddressBookName = (addressbookName: string): string | null => {
|
||||
const INVALID_NAMES = ['UNKNOWN', 'OWNER #', 'MY WALLET']
|
||||
const isInvalid = INVALID_NAMES.find((invalidName) => addressbookName.toUpperCase().includes(invalidName))
|
||||
if (isInvalid) return null
|
||||
return addressbookName
|
||||
export const isValidAddressBookName = (addressBookName: string): boolean => {
|
||||
const hasInvalidName = ADDRESSBOOK_INVALID_NAMES.find((invalidName) =>
|
||||
addressBookName.toUpperCase().includes(invalidName),
|
||||
)
|
||||
return !hasInvalidName
|
||||
}
|
||||
|
||||
export const getValidAddressBookName = (addressBookName: string): string | null => {
|
||||
return isValidAddressBookName(addressBookName) ? addressBookName : null
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@ describe('fetchTokenCurrenciesBalances', () => {
|
|||
// then
|
||||
expect(result).toStrictEqual(expectedResult)
|
||||
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 },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -22,13 +22,13 @@ export const useLoadSafe = (safeAddress?: string): void => {
|
|||
return dispatch(fetchSafeTokens(safeAddress))
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(loadAddressBookFromStorage())
|
||||
dispatch(fetchSafeCreationTx(safeAddress))
|
||||
dispatch(fetchTransactions(safeAddress))
|
||||
return dispatch(addViewedSafe(safeAddress))
|
||||
})
|
||||
}
|
||||
}
|
||||
dispatch(loadAddressBookFromStorage())
|
||||
|
||||
fetchData()
|
||||
}, [dispatch, safeAddress])
|
||||
|
|
|
@ -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,
|
||||
}))
|
|
@ -119,6 +119,7 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: Dispatch
|
|||
dispatch(
|
||||
updateSafe({
|
||||
address: safeAddress,
|
||||
name: localSafe?.name,
|
||||
modules: buildModulesLinkedList(modules?.array, modules?.next),
|
||||
nonce: Number(remoteNonce),
|
||||
threshold: Number(remoteThreshold),
|
||||
|
|
|
@ -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 { getActiveTokensAddressesForAllSafes, safesMapSelector } from 'src/logic/safe/store/selectors'
|
||||
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 { getValidAddressBookName } from 'src/logic/addressBook/utils'
|
||||
import { checkIfEntryWasDeletedFromAddressBook, isValidAddressBookName } from 'src/logic/addressBook/utils'
|
||||
import { addressBookSelector } from 'src/logic/addressBook/store/selectors'
|
||||
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 = [
|
||||
ADD_SAFE,
|
||||
UPDATE_SAFE,
|
||||
REMOVE_SAFE,
|
||||
ADD_OR_UPDATE_SAFE,
|
||||
ADD_SAFE_OWNER,
|
||||
REMOVE_SAFE_OWNER,
|
||||
REPLACE_SAFE_OWNER,
|
||||
|
@ -46,30 +49,6 @@ const recalculateActiveTokens = (state) => {
|
|||
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 handledAction = next(action)
|
||||
|
||||
|
@ -87,22 +66,30 @@ const safeStorageMware = (store) => (next) => async (action) => {
|
|||
}
|
||||
case ADD_SAFE: {
|
||||
const { safe, loadedFromStorage } = action.payload
|
||||
const safeAlreadyLoaded =
|
||||
loadedFromStorage || safes.find((safeIterator) => sameAddress(safeIterator.address, safe.address))
|
||||
|
||||
safe.owners.forEach((owner) => {
|
||||
const checksumEntry = makeAddressBookEntry({ address: checksumAddress(owner.address), name: owner.name })
|
||||
const ownerWasAlreadyInAddressBook = checkIfOwnerWasDeletedFromAddressBook(
|
||||
|
||||
const ownerWasAlreadyInAddressBook = checkIfEntryWasDeletedFromAddressBook(
|
||||
checksumEntry,
|
||||
addressBook,
|
||||
loadedFromStorage,
|
||||
safeAlreadyLoaded,
|
||||
)
|
||||
|
||||
if (!ownerWasAlreadyInAddressBook) {
|
||||
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 },
|
||||
addressBook,
|
||||
loadedFromStorage,
|
||||
safeAlreadyLoaded,
|
||||
)
|
||||
|
||||
if (!safeWasAlreadyInAddressBook) {
|
||||
|
@ -114,13 +101,23 @@ const safeStorageMware = (store) => (next) => async (action) => {
|
|||
}
|
||||
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: {
|
||||
const { activeTokens, name, address } = action.payload
|
||||
if (activeTokens) {
|
||||
recalculateActiveTokens(state)
|
||||
}
|
||||
if (name) {
|
||||
dispatch(addOrUpdateAddressBookEntry(makeAddressBookEntry({ name, address })), { notifyEntryUpdate: false })
|
||||
dispatch(addOrUpdateAddressBookEntry(makeAddressBookEntry({ name, address })))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import { makeOwner } from 'src/logic/safe/store/models/owner'
|
|||
import makeSafe, { SafeRecordProps } from 'src/logic/safe/store/models/safe'
|
||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||
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 DEFAULT_SAFE_INITIAL_STATE = 'NOT_ASKED'
|
||||
|
@ -42,6 +43,32 @@ export const buildSafe = (storedSafe: SafeRecordProps): SafeRecordProps => {
|
|||
}
|
||||
}
|
||||
|
||||
const updateSafeProps = (prevSafe, safe) => {
|
||||
return prevSafe.withMutations((record) => {
|
||||
// Every property is updated individually to overcome the issue with nested data being overwritten
|
||||
const safeProperties = Object.keys(safe)
|
||||
|
||||
// We check each safe property sent in action.payload
|
||||
safeProperties.forEach((key) => {
|
||||
if (safe[key] && typeof safe[key] === 'object') {
|
||||
if (safe[key].length) {
|
||||
// If type is array we update the array
|
||||
record.update(key, () => safe[key])
|
||||
} else if (safe[key].size) {
|
||||
// If type is Immutable List we replace current List
|
||||
// If type is Object we do a merge
|
||||
List.isList(safe[key])
|
||||
? record.update(key, (current) => current.set(safe[key]))
|
||||
: record.update(key, (current) => current.merge(safe[key]))
|
||||
}
|
||||
} else {
|
||||
// By default we overwrite the value. This is for strings, numbers and unset values
|
||||
record.set(key, safe[key])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[UPDATE_SAFE]: (state: SafeReducerMap, action) => {
|
||||
|
@ -50,32 +77,8 @@ export default handleActions(
|
|||
|
||||
return state.updateIn(
|
||||
['safes', safeAddress],
|
||||
makeSafe({ name: 'LOADED SAFE', address: safeAddress }),
|
||||
(prevSafe) => {
|
||||
return prevSafe.withMutations((record) => {
|
||||
// Every property is updated individually to overcome the issue with nested data being overwritten
|
||||
const safeProperties = Object.keys(safe)
|
||||
|
||||
// We check each safe property sent in action.payload
|
||||
safeProperties.forEach((key) => {
|
||||
if (safe[key] && typeof safe[key] === 'object') {
|
||||
if (safe[key].length) {
|
||||
// If type is array we update the array
|
||||
record.update(key, () => safe[key])
|
||||
} else if (safe[key].size) {
|
||||
// If type is Immutable List we replace current List
|
||||
// If type is Object we do a merge
|
||||
List.isList(safe[key])
|
||||
? record.update(key, (current) => current.set(safe[key]))
|
||||
: record.update(key, (current) => current.merge(safe[key]))
|
||||
}
|
||||
} else {
|
||||
// By default we overwrite the value. This is for strings, numbers and unset values
|
||||
record.set(key, safe[key])
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
makeSafe({ name: safe?.name || 'LOADED SAFE', address: safeAddress }),
|
||||
(prevSafe) => updateSafeProps(prevSafe, safe),
|
||||
)
|
||||
},
|
||||
[ACTIVATE_TOKEN_FOR_ALL_SAFES]: (state: SafeReducerMap, action) => {
|
||||
|
@ -106,6 +109,19 @@ export default handleActions(
|
|||
|
||||
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) => {
|
||||
const safeAddress = action.payload
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 CopyBtn from 'src/components/CopyBtn'
|
||||
|
@ -17,57 +17,13 @@ import Row from 'src/components/layout/Row'
|
|||
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
||||
import { FIELD_LOAD_ADDRESS, THRESHOLD } from 'src/routes/load/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 = () => ({
|
||||
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}`,
|
||||
},
|
||||
})
|
||||
import { useSelector } from 'react-redux'
|
||||
import { addressBookSelector } from 'src/logic/addressBook/store/selectors'
|
||||
|
||||
import { formatAddressListToAddressBookNames } from 'src/logic/addressBook/utils'
|
||||
import { AddressBookEntry } from 'src/logic/addressBook/model/addressBook'
|
||||
import { styles } from './styles'
|
||||
|
||||
const calculateSafeValues = (owners, threshold, values) => {
|
||||
const initialValues = { ...values }
|
||||
|
@ -78,9 +34,20 @@ const calculateSafeValues = (owners, threshold, values) => {
|
|||
return initialValues
|
||||
}
|
||||
|
||||
const useAddressBookForOwnersNames = (ownersList: string[]): AddressBookEntry[] => {
|
||||
const addressBook = useSelector(addressBookSelector)
|
||||
|
||||
return formatAddressListToAddressBookNames(addressBook, ownersList)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const OwnerListComponent = (props) => {
|
||||
const [owners, setOwners] = useState<string[]>([])
|
||||
const { classes, updateInitialProps, values } = props
|
||||
const classes = useStyles()
|
||||
const { updateInitialProps, values } = props
|
||||
|
||||
const ownersWithNames = useAddressBookForOwnersNames(owners)
|
||||
|
||||
useEffect(() => {
|
||||
let isCurrent = true
|
||||
|
@ -121,47 +88,48 @@ const OwnerListComponent = (props) => {
|
|||
</Row>
|
||||
<Hairline />
|
||||
<Block margin="md" padding="md">
|
||||
{owners.map((address, index) => (
|
||||
<Row className={classes.owner} key={address} data-testid="owner-row">
|
||||
<Col className={classes.ownerName} xs={4}>
|
||||
<Field
|
||||
className={classes.name}
|
||||
component={TextField}
|
||||
initialValue={`Owner #${index + 1}`}
|
||||
name={getOwnerNameBy(index)}
|
||||
placeholder="Owner Name*"
|
||||
text="Owner Name"
|
||||
type="text"
|
||||
validate={composeValidators(required, minMaxLength(1, 50))}
|
||||
testId={`load-safe-owner-name-${index}`}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={8}>
|
||||
<Row className={classes.ownerAddresses}>
|
||||
<Identicon address={address} diameter={32} />
|
||||
<Paragraph className={classes.address} color="disabled" noMargin size="md">
|
||||
{address}
|
||||
</Paragraph>
|
||||
<CopyBtn content={address} />
|
||||
<EtherscanBtn type="address" value={address} />
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
{ownersWithNames.map(({ address, name }, index) => {
|
||||
const ownerName = name || `Owner #${index + 1}`
|
||||
return (
|
||||
<Row className={classes.owner} key={address} data-testid="owner-row">
|
||||
<Col className={classes.ownerName} xs={4}>
|
||||
<Field
|
||||
className={classes.name}
|
||||
component={TextField}
|
||||
initialValue={ownerName}
|
||||
name={getOwnerNameBy(index)}
|
||||
placeholder="Owner Name*"
|
||||
text="Owner Name"
|
||||
type="text"
|
||||
validate={composeValidators(required, minMaxLength(1, 50))}
|
||||
testId={`load-safe-owner-name-${index}`}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={8}>
|
||||
<Row className={classes.ownerAddresses}>
|
||||
<Identicon address={address} diameter={32} />
|
||||
<Paragraph className={classes.address} color="disabled" noMargin size="md">
|
||||
{address}
|
||||
</Paragraph>
|
||||
<CopyBtn content={address} />
|
||||
<EtherscanBtn type="address" value={address} />
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
})}
|
||||
</Block>
|
||||
</TableContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const OwnerListPage = withStyles(styles as any)(OwnerListComponent)
|
||||
|
||||
const OwnerList = ({ updateInitialProps }, network) =>
|
||||
function LoadSafeOwnerList(controls, { values }): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<OpenPaper controls={controls} padding={false}>
|
||||
<OwnerListPage network={network} updateInitialProps={updateInitialProps} values={values} />
|
||||
<OwnerListComponent network={network} updateInitialProps={updateInitialProps} values={values} />
|
||||
</OpenPaper>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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}`,
|
||||
},
|
||||
})
|
|
@ -14,8 +14,8 @@ import { history } from 'src/store'
|
|||
import { SafeOwner, SafeRecordProps } from 'src/logic/safe/store/models/safe'
|
||||
import { List } from 'immutable'
|
||||
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 { addOrUpdateSafe } from 'src/logic/safe/store/actions/addOrUpdateSafe'
|
||||
|
||||
export const loadSafe = async (
|
||||
safeName: string,
|
||||
|
@ -46,8 +46,8 @@ const Load = (): React.ReactElement => {
|
|||
const network = useSelector(networkSelector)
|
||||
const userAddress = useSelector(userAccountSelector)
|
||||
|
||||
const addSafeHandler = (safe: SafeRecordProps) => {
|
||||
dispatch(addSafe(safe))
|
||||
const addSafeHandler = async (safe: SafeRecordProps) => {
|
||||
await dispatch(addOrUpdateSafe(safe))
|
||||
}
|
||||
const onLoadSafeSubmit = async (values: LoadFormValues) => {
|
||||
let safeAddress = values[FIELD_LOAD_ADDRESS]
|
||||
|
|
|
@ -19,22 +19,43 @@ import {
|
|||
import Welcome from 'src/routes/welcome/components/Layout'
|
||||
import { history } from 'src/store'
|
||||
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 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) {
|
||||
return {
|
||||
[getOwnerNameBy(0)]: 'My Wallet',
|
||||
[getOwnerNameBy(0)]: ownerName || 'My Wallet',
|
||||
[getOwnerAddressBy(0)]: userAccount,
|
||||
[FIELD_CONFIRMATIONS]: '1',
|
||||
}
|
||||
}
|
||||
let obj = {}
|
||||
const { name, ownerAddresses, ownerNames, threshold } = safeProps
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
|
||||
for (const [index, value] of ownerAddresses.entries()) {
|
||||
const safeName = ownerNames[index] ? ownerNames[index] : 'My Wallet'
|
||||
obj = {
|
||||
|
@ -66,8 +87,17 @@ const formMutators = {
|
|||
},
|
||||
}
|
||||
|
||||
const Layout = (props) => {
|
||||
const { network, onCallSafeContractSubmit, provider, safeProps, userAccount } = props
|
||||
type LayoutProps = {
|
||||
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(() => {
|
||||
if (provider) {
|
||||
|
@ -77,7 +107,7 @@ const Layout = (props) => {
|
|||
|
||||
const steps = getSteps()
|
||||
|
||||
const initialValues = initialValuesFrom(userAccount, safeProps)
|
||||
const initialValues = useInitialValuesFrom(userAccount, safeProps)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 * as React from 'react'
|
||||
|
||||
|
@ -22,7 +22,7 @@ import { background, border, lg, screenSm, sm } from 'src/theme/variables'
|
|||
|
||||
const { useEffect, useState } = React
|
||||
|
||||
const styles = () => ({
|
||||
const styles = createStyles({
|
||||
root: {
|
||||
minHeight: '300px',
|
||||
[`@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 names = getNamesFrom(values)
|
||||
const addresses = getAccountsFrom(values)
|
||||
|
@ -198,12 +206,11 @@ const ReviewComponent = ({ classes, userAccount, values }: any) => {
|
|||
)
|
||||
}
|
||||
|
||||
const ReviewPage = withStyles(styles as any)(ReviewComponent)
|
||||
|
||||
const Review = () => (controls, { values }) => (
|
||||
// eslint-disable-next-line react/display-name
|
||||
const Review = () => (controls, props): React.ReactElement => (
|
||||
<>
|
||||
<OpenPaper controls={controls} padding={false}>
|
||||
<ReviewPage values={values} />
|
||||
<ReviewComponent {...props} />
|
||||
</OpenPaper>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import InputAdornment from '@material-ui/core/InputAdornment'
|
||||
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 * as React from 'react'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
|
||||
import { styles } from './style'
|
||||
import { getAddressValidator } from './validators'
|
||||
|
||||
|
@ -38,6 +36,9 @@ import {
|
|||
getOwnerNameBy,
|
||||
} from 'src/routes/open/components/fields'
|
||||
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
|
||||
|
||||
|
@ -63,10 +64,14 @@ export const calculateValuesAfterRemoving = (index, notRemovedOwners, values) =>
|
|||
return initialValues
|
||||
}
|
||||
|
||||
const SafeOwners = (props) => {
|
||||
const { classes, errors, form, otherAccounts, values } = props
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const SafeOwnersForm = (props): React.ReactElement => {
|
||||
const { errors, form, otherAccounts, values } = props
|
||||
const classes = useStyles()
|
||||
|
||||
const validOwners = getNumOwnersFrom(values)
|
||||
const addressBook = useSelector(addressBookSelector)
|
||||
|
||||
const [numOwners, setNumOwners] = useState(validOwners)
|
||||
const [qrModalOpen, setQrModalOpen] = useState(false)
|
||||
|
@ -125,6 +130,7 @@ const SafeOwners = (props) => {
|
|||
<Block margin="md" padding="md">
|
||||
{[...Array(Number(numOwners))].map((x, index) => {
|
||||
const addressName = getOwnerAddressBy(index)
|
||||
const ownerName = getOwnerNameBy(index)
|
||||
|
||||
return (
|
||||
<Row className={classes.owner} key={`owner${index}`} data-testid={`create-safe-owner-row`}>
|
||||
|
@ -132,7 +138,7 @@ const SafeOwners = (props) => {
|
|||
<Field
|
||||
className={classes.name}
|
||||
component={TextField}
|
||||
name={getOwnerNameBy(index)}
|
||||
name={ownerName}
|
||||
placeholder="Owner Name*"
|
||||
text="Owner Name"
|
||||
type="text"
|
||||
|
@ -142,8 +148,14 @@ const SafeOwners = (props) => {
|
|||
</Col>
|
||||
<Col className={classes.ownerAddress} xs={6}>
|
||||
<AddressInput
|
||||
fieldMutator={(val) => {
|
||||
form.mutators.setValue(addressName, val)
|
||||
fieldMutator={(newOwnerAddress) => {
|
||||
const newOwnerName = getNameFromAddressBook(addressBook, newOwnerAddress, {
|
||||
filterOnlyValidName: true,
|
||||
})
|
||||
form.mutators.setValue(addressName, newOwnerAddress)
|
||||
if (newOwnerName) {
|
||||
form.mutators.setValue(ownerName, newOwnerName)
|
||||
}
|
||||
}}
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
|
@ -224,8 +236,6 @@ const SafeOwners = (props) => {
|
|||
)
|
||||
}
|
||||
|
||||
const SafeOwnersForm = withStyles(styles as any)(withRouter(SafeOwners))
|
||||
|
||||
const SafeOwnersPage = ({ updateInitialProps }) =>
|
||||
function OpenSafeOwnersPage(controls, { errors, form, values }) {
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
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: {
|
||||
display: 'flex',
|
||||
},
|
||||
|
|
|
@ -2,15 +2,9 @@ import { Loader } from '@gnosis.pm/safe-react-components'
|
|||
import queryString from 'query-string'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom'
|
||||
|
||||
import Opening from '../../opening'
|
||||
import Layout from '../components/Layout'
|
||||
|
||||
import actions from './actions'
|
||||
import selector from './selector'
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import Opening from 'src/routes/opening'
|
||||
import Layout from 'src/routes/open/components/Layout'
|
||||
import Page from 'src/components/layout/Page'
|
||||
import { getSafeDeploymentTransaction } from 'src/logic/contracts/safeContracts'
|
||||
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 { history } from 'src/store'
|
||||
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'
|
||||
|
||||
|
@ -39,13 +36,15 @@ const validateQueryParams = (ownerAddresses, ownerNames, threshold, safeName) =>
|
|||
if (Number.isNaN(Number(threshold))) {
|
||||
return false
|
||||
}
|
||||
if (threshold > ownerAddresses.length) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return threshold <= ownerAddresses.length
|
||||
}
|
||||
|
||||
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 owners = getOwnersFrom(ownersNames, ownerAddresses)
|
||||
safeProps.owners = owners
|
||||
|
@ -81,19 +80,14 @@ export const createSafe = (values, userAccount) => {
|
|||
return promiEvent
|
||||
}
|
||||
|
||||
interface OwnProps extends RouteComponentProps {
|
||||
userAccount: string
|
||||
network: string
|
||||
provider: string
|
||||
addSafe: any
|
||||
}
|
||||
|
||||
const Open = ({ addSafe, network, provider, userAccount }: OwnProps): React.ReactElement => {
|
||||
const Open = (): React.ReactElement => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [showProgress, setShowProgress] = useState(false)
|
||||
const [creationTxPromise, setCreationTxPromise] = useState()
|
||||
const [safeCreationPendingInfo, setSafeCreationPendingInfo] = useState<any>()
|
||||
const [safePropsFromUrl, setSafePropsFromUrl] = useState()
|
||||
const userAccount = useSelector(userAccountSelector)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
// #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)
|
||||
}
|
||||
|
||||
const onSafeCreated = async (safeAddress) => {
|
||||
const onSafeCreated = async (safeAddress): Promise<void> => {
|
||||
const pendingCreation = await loadFromStorage<{ txHash: string }>(SAFE_PENDING_CREATION_STORAGE_KEY)
|
||||
|
||||
const name = getSafeNameFrom(pendingCreation)
|
||||
const ownersNames = getNamesFrom(pendingCreation)
|
||||
const ownerAddresses = getAccountsFrom(pendingCreation)
|
||||
const safeProps = await getSafeProps(safeAddress, name, ownersNames, ownerAddresses)
|
||||
addSafe(safeProps)
|
||||
|
||||
await dispatch(addOrUpdateSafe(safeProps))
|
||||
|
||||
ReactGA.event({
|
||||
category: 'User',
|
||||
|
@ -194,21 +189,14 @@ const Open = ({ addSafe, network, provider, userAccount }: OwnProps): React.Reac
|
|||
creationTxHash={safeCreationPendingInfo?.txHash}
|
||||
onCancel={onCancel}
|
||||
onRetry={onRetry}
|
||||
onSuccess={onSafeCreated as any}
|
||||
provider={provider}
|
||||
onSuccess={onSafeCreated}
|
||||
submittedPromise={creationTxPromise}
|
||||
/>
|
||||
) : (
|
||||
<Layout
|
||||
network={network}
|
||||
onCallSafeContractSubmit={createSafeProxy}
|
||||
provider={provider}
|
||||
safeProps={safePropsFromUrl}
|
||||
userAccount={userAccount}
|
||||
/>
|
||||
<Layout onCallSafeContractSubmit={createSafeProxy} safeProps={safePropsFromUrl} />
|
||||
)}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(withRouter(Open))
|
||||
export default Open
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import addSafe from 'src/logic/safe/store/actions/addSafe'
|
||||
|
||||
export default {
|
||||
addSafe,
|
||||
}
|
|
@ -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,
|
||||
})
|
|
@ -2,7 +2,7 @@ import { Loader, Stepper } from '@gnosis.pm/safe-react-components'
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { ErrorFooter } from './components/Footer'
|
||||
import { ErrorFooter } from 'src/routes/opening/components/Footer'
|
||||
import { isConfirmationStep, steps } from './steps'
|
||||
|
||||
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 { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
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 successSvg = require('./assets/success.svg')
|
||||
|
@ -102,16 +104,17 @@ const BackButton = styled(Button)`
|
|||
// 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 [stepIndex, setStepIndex] = useState(0)
|
||||
const [safeCreationTxHash, setSafeCreationTxHash] = useState('')
|
||||
const [createdSafeAddress, setCreatedSafeAddress] = useState()
|
||||
const [createdSafeAddress, setCreatedSafeAddress] = useState('')
|
||||
|
||||
const [error, setError] = useState(false)
|
||||
const [intervalStarted, setIntervalStarted] = useState(false)
|
||||
const [waitingSafeDeployed, setWaitingSafeDeployed] = useState(false)
|
||||
const [continueButtonDisabled, setContinueButtonDisabled] = useState(false)
|
||||
const provider = useSelector(providerNameSelector)
|
||||
|
||||
const confirmationStep = isConfirmationStep(stepIndex)
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import createTransaction from 'src/logic/safe/store/actions/createTransaction'
|
|||
|
||||
import { safeOwnersSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||
import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
|
||||
|
||||
const styles = () => ({
|
||||
biggerModalWindow: {
|
||||
|
@ -92,7 +93,7 @@ const AddOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose })
|
|||
try {
|
||||
await sendAddOwner(values, safeAddress, owners, enqueueSnackbar, closeSnackbar, dispatch)
|
||||
dispatch(
|
||||
addOrUpdateAddressBookEntry(values.ownerAddress, { name: values.ownerName, address: values.ownerAddress }),
|
||||
addOrUpdateAddressBookEntry(makeAddressBookEntry({ name: values.ownerName, address: values.ownerAddress })),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Error while removing an owner', error)
|
||||
|
|
|
@ -14,6 +14,7 @@ import createTransaction from 'src/logic/safe/store/actions/createTransaction'
|
|||
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'
|
||||
|
||||
const styles = () => ({
|
||||
biggerModalWindow: {
|
||||
|
@ -96,10 +97,7 @@ const ReplaceOwner = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose
|
|||
await sendReplaceOwner(values, safeAddress, ownerAddress, enqueueSnackbar, closeSnackbar, threshold, dispatch)
|
||||
|
||||
dispatch(
|
||||
// Needs the `address` field because we need to provide the minimum required values to ADD a new entry
|
||||
// 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 }),
|
||||
addOrUpdateAddressBookEntry(makeAddressBookEntry({ address: values.ownerAddress, name: values.ownerName })),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Error while removing an owner', error)
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useSelector } from 'react-redux'
|
|||
import EtherscanLink from 'src/components/EtherscanLink'
|
||||
import Block from 'src/components/layout/Block'
|
||||
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 { getIncomingTxAmount } from 'src/routes/safe/components/Transactions/TxsTable/columns'
|
||||
import { lg, md } from 'src/theme/variables'
|
||||
|
@ -35,7 +35,7 @@ const TransferDescription = ({ from, txFromName, value = '' }) => (
|
|||
|
||||
const IncomingTxDescription = ({ tx }) => {
|
||||
const classes = useStyles()
|
||||
const txFromName = useSelector((state) => getNameFromAddressBook(state, tx.from))
|
||||
const txFromName = useSelector((state) => getNameFromAddressBookSelector(state, tx.from))
|
||||
return (
|
||||
<Block className={classes.txDataContainer}>
|
||||
<TransferDescription from={tx.from} txFromName={txFromName} value={getIncomingTxAmount(tx, false)} />
|
||||
|
|
|
@ -16,7 +16,7 @@ import { styles } from './style'
|
|||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
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'
|
||||
|
||||
export const CONFIRM_TX_BTN_TEST_ID = 'confirm-btn'
|
||||
|
@ -64,7 +64,7 @@ const OwnerComponent = (props: OwnerComponentProps): React.ReactElement => {
|
|||
showExecuteRejectBtn,
|
||||
confirmed,
|
||||
} = props
|
||||
const nameInAdbk = useSelector((state) => getNameFromAddressBook(state, owner))
|
||||
const nameInAdbk = useSelector((state) => getNameFromAddressBookSelector(state, owner))
|
||||
const classes = useStyles()
|
||||
const [imgCircle, setImgCircle] = React.useState(ConfirmSmallGreyCircle)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import Bold from 'src/components/layout/Bold'
|
|||
import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import Collapse from 'src/components/Collapse'
|
||||
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 LinkWithRef from 'src/components/layout/Link'
|
||||
import { shortVersionOf } from 'src/logic/wallets/ethAddresses'
|
||||
|
@ -176,7 +176,7 @@ interface GenericCustomDataProps {
|
|||
|
||||
const GenericCustomData = ({ amount = '0', data, recipient, storedTx }: GenericCustomDataProps): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
const recipientName = useSelector((state) => getNameFromAddressBook(state, recipient))
|
||||
const recipientName = useSelector((state) => getNameFromAddressBookSelector(state, recipient))
|
||||
|
||||
return (
|
||||
<Block>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useSelector } from 'react-redux'
|
||||
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 Bold from 'src/components/layout/Bold'
|
||||
import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell'
|
||||
|
@ -21,7 +21,7 @@ interface RemovedOwnerProps {
|
|||
}
|
||||
|
||||
const RemovedOwner = ({ removedOwner }: RemovedOwnerProps): React.ReactElement => {
|
||||
const ownerChangedName = useSelector((state) => getNameFromAddressBook(state, removedOwner))
|
||||
const ownerChangedName = useSelector((state) => getNameFromAddressBookSelector(state, removedOwner))
|
||||
|
||||
return (
|
||||
<Block data-testid={TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID}>
|
||||
|
@ -40,7 +40,7 @@ interface AddedOwnerProps {
|
|||
}
|
||||
|
||||
const AddedOwner = ({ addedOwner }: AddedOwnerProps): React.ReactElement => {
|
||||
const ownerChangedName = useSelector((state) => getNameFromAddressBook(state, addedOwner))
|
||||
const ownerChangedName = useSelector((state) => getNameFromAddressBookSelector(state, addedOwner))
|
||||
|
||||
return (
|
||||
<Block data-testid={TRANSACTIONS_DESC_ADD_OWNER_TEST_ID}>
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import { useSelector } from 'react-redux'
|
||||
|
||||
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 Bold from 'src/components/layout/Bold'
|
||||
import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell'
|
||||
|
@ -14,7 +14,7 @@ interface TransferDescriptionProps {
|
|||
}
|
||||
|
||||
const TransferDescription = ({ amount = '', recipient }: TransferDescriptionProps): React.ReactElement => {
|
||||
const recipientName = useSelector((state) => getNameFromAddressBook(state, recipient))
|
||||
const recipientName = useSelector((state) => getNameFromAddressBookSelector(state, recipient))
|
||||
return (
|
||||
<Block data-testid={TRANSACTIONS_DESC_SEND_TEST_ID}>
|
||||
<Bold>Send {amount} to:</Bold>
|
||||
|
|
Loading…
Reference in New Issue