* 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 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 {
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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 },
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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(
|
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),
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 { 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]
|
||||||
|
|
|
@ -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 (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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',
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)} />
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue