(Fix) - Remove safe fix (#1744)

* Avoid displaying notification for non updated owner

* Remove old selector usage on welcome page

* Remove provider props on WelcomeLayout
Fix removing safe behaviour

* Fix missing undefined property check on cancelThresholdReached

* Removes the default safe when removing the safe marked as default

* Remove index

* Change react import

* Fix relative path for relocate url

* Adds removeLocalSafe action
Uses removeLocalSafe on onRemoveSafeHandler also removes default safe

* Refactor SafeListSidebar uses redux-hooks approach

* Disabled save button when the owner name input is pristine
This commit is contained in:
Agustin Pane 2021-01-12 11:39:57 -03:00 committed by GitHub
parent 09b470187f
commit f1ffb5d147
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 148 additions and 123 deletions

View File

@ -11,7 +11,7 @@ import ErrorIcon from 'src/assets/icons/error.svg'
import InfoIcon from 'src/assets/icons/info.svg'
import AppLayout from 'src/components/AppLayout'
import SafeListSidebarProvider, { SafeListSidebarContext } from 'src/components/SafeListSidebar'
import { SafeListSidebar, SafeListSidebarContext } from 'src/components/SafeListSidebar'
import CookiesBanner from 'src/components/CookiesBanner'
import Notifier from 'src/components/Notifier'
import Backdrop from 'src/components/layout/Backdrop'
@ -159,9 +159,9 @@ const App: React.FC = ({ children }) => {
}
const WrapperAppWithSidebar: React.FC = ({ children }) => (
<SafeListSidebarProvider>
<SafeListSidebar>
<App>{children}</App>
</SafeListSidebarProvider>
</SafeListSidebar>
)
export default WrapperAppWithSidebar

View File

@ -6,9 +6,10 @@ import { sameAddress } from 'src/logic/wallets/ethAddresses'
import DefaultBadge from './DefaultBadge'
import { SafeRecordProps } from 'src/logic/safe/store/models/safe'
import { DefaultSafe } from 'src/routes/safe/store/reducer/types/safe'
import { SetDefaultSafe } from 'src/logic/safe/store/actions/setDefaultSafe'
import setDefaultSafe from 'src/logic/safe/store/actions/setDefaultSafe'
import { makeStyles } from '@material-ui/core/styles'
import { getNetworkInfo } from 'src/config'
import { useDispatch } from 'react-redux'
const StyledButtonLink = styled(ButtonLink)`
visibility: hidden;
@ -46,14 +47,18 @@ const useStyles = makeStyles({
type Props = {
safe: SafeRecordProps
defaultSafe: DefaultSafe
setDefaultSafe: SetDefaultSafe
}
const { nativeCoin } = getNetworkInfo()
export const AddressWrapper = (props: Props): React.ReactElement => {
const classes = useStyles()
const { safe, defaultSafe, setDefaultSafe } = props
const { safe, defaultSafe } = props
const dispatch = useDispatch()
const setDefaultSafeAction = (safeAddress: string) => {
dispatch(setDefaultSafe(safeAddress))
}
return (
<div className={classes.wrapper}>
@ -68,7 +73,7 @@ export const AddressWrapper = (props: Props): React.ReactElement => {
className="safeListMakeDefaultButton"
textSize="sm"
onClick={() => {
setDefaultSafe(safe.address)
setDefaultSafeAction(safe.address)
}}
color="primary"
>

View File

@ -6,12 +6,11 @@ import * as React from 'react'
import styled from 'styled-components'
import { SafeRecord } from 'src/logic/safe/store/models/safe'
import { DefaultSafe } from 'src/routes/safe/store/reducer/types/safe'
import { SetDefaultSafe } from 'src/logic/safe/store/actions/setDefaultSafe'
import Hairline from 'src/components/layout/Hairline'
import Link from 'src/components/layout/Link'
import { sameAddress } from 'src/logic/wallets/ethAddresses'
import { SAFELIST_ADDRESS } from 'src/routes/routes'
import { AddressWrapper } from './AddresWrapper'
import { AddressWrapper } from 'src/components/SafeListSidebar/SafeList/AddressWrapper'
export const SIDEBAR_SAFELIST_ROW_TESTID = 'SIDEBAR_SAFELIST_ROW_TESTID'
const StyledIcon = styled(Icon)`
@ -46,10 +45,9 @@ type Props = {
defaultSafe: DefaultSafe
safes: SafeRecord[]
onSafeClick: () => void
setDefaultSafe: SetDefaultSafe
}
const SafeList = ({ currentSafe, defaultSafe, onSafeClick, safes, setDefaultSafe }: Props): React.ReactElement => {
export const SafeList = ({ currentSafe, defaultSafe, onSafeClick, safes }: Props): React.ReactElement => {
const classes = useStyles()
return (
@ -67,7 +65,7 @@ const SafeList = ({ currentSafe, defaultSafe, onSafeClick, safes, setDefaultSafe
) : (
<div className={classes.noIcon}>placeholder</div>
)}
<AddressWrapper safe={safe} defaultSafe={defaultSafe} setDefaultSafe={setDefaultSafe} />
<AddressWrapper safe={safe} defaultSafe={defaultSafe} />
</ListItem>
</Link>
<Hairline />
@ -76,5 +74,3 @@ const SafeList = ({ currentSafe, defaultSafe, onSafeClick, safes, setDefaultSafe
</MuiList>
)
}
export default SafeList

View File

@ -1,10 +1,10 @@
import React, { useEffect, useMemo, useState } from 'react'
import React, { useEffect, useMemo, useState, ReactElement } from 'react'
import Drawer from '@material-ui/core/Drawer'
import SearchIcon from '@material-ui/icons/Search'
import SearchBar from 'material-ui-search-bar'
import { connect } from 'react-redux'
import { useSelector } from 'react-redux'
import SafeList from './SafeList'
import { SafeList } from './SafeList'
import { sortedSafeListSelector } from './selectors'
import useSidebarStyles from './style'
@ -15,11 +15,9 @@ import Hairline from 'src/components/layout/Hairline'
import Link from 'src/components/layout/Link'
import Row from 'src/components/layout/Row'
import { WELCOME_ADDRESS } from 'src/routes/routes'
import setDefaultSafe from 'src/logic/safe/store/actions/setDefaultSafe'
import { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
import { defaultSafeSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import { AppReduxState } from 'src/store'
export const SafeListSidebarContext = React.createContext({
isOpen: false,
@ -34,9 +32,17 @@ const filterBy = (filter, safes) =>
safe.name.toLowerCase().includes(filter.toLowerCase()),
)
const SafeListSidebar = ({ children, currentSafe, defaultSafe, safes, setDefaultSafeAction }) => {
type Props = {
children: ReactElement
}
export const SafeListSidebar = ({ children }: Props): ReactElement => {
const [isOpen, setIsOpen] = useState(false)
const [filter, setFilter] = useState('')
const safes = useSelector(sortedSafeListSelector)
const defaultSafe = useSelector(defaultSafeSelector)
const currentSafe = useSelector(safeParamAddressFromStateSelector)
const classes = useSidebarStyles()
const { trackEvent } = useAnalytics()
@ -118,19 +124,9 @@ const SafeListSidebar = ({ children, currentSafe, defaultSafe, safes, setDefault
defaultSafe={defaultSafe}
onSafeClick={toggleSidebar}
safes={filteredSafes}
setDefaultSafe={setDefaultSafeAction}
/>
</Drawer>
{children}
</SafeListSidebarContext.Provider>
)
}
export default connect(
(state: AppReduxState) => ({
safes: sortedSafeListSelector(state),
defaultSafe: defaultSafeSelector(state),
currentSafe: safeParamAddressFromStateSelector(state),
}),
{ setDefaultSafeAction: setDefaultSafe },
)(SafeListSidebar)

View File

@ -0,0 +1,11 @@
import { Action, Dispatch } from 'redux'
import { loadStoredSafes } from 'src/logic/safe/utils'
import removeSafe from 'src/logic/safe/store/actions/removeSafe'
export const removeLocalSafe = (safeAddress: string) => async (dispatch: Dispatch): Promise<Action | void> => {
const storedSafes = await loadStoredSafes()
if (storedSafes) {
delete storedSafes[safeAddress]
}
dispatch(removeSafe(safeAddress))
}

View File

@ -126,7 +126,14 @@ export default handleActions(
[REMOVE_SAFE]: (state: SafeReducerMap, action) => {
const safeAddress = action.payload
return state.deleteIn(['safes', safeAddress])
const currentDefaultSafe = state.get('defaultSafe')
let newState = state.deleteIn(['safes', safeAddress])
if (sameAddress(safeAddress, currentDefaultSafe)) {
newState = newState.set('defaultSafe', DEFAULT_SAFE_INITIAL_STATE)
}
return newState
},
[ADD_SAFE_OWNER]: (state: SafeReducerMap, action) => {
const { ownerAddress, ownerName, safeAddress } = action.payload

View File

@ -17,7 +17,7 @@ import {
getOwnerAddressBy,
getOwnerNameBy,
} from 'src/routes/open/components/fields'
import Welcome from 'src/routes/welcome/components/Layout'
import { WelcomeLayout } from 'src/routes/welcome/components/index'
import { history } from 'src/store'
import { secondary, sm } from 'src/theme/variables'
import { networkSelector, providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors'
@ -138,7 +138,7 @@ export const Layout = (props: LayoutProps): React.ReactElement => {
</Stepper>
</Block>
) : (
<Welcome isOldMultisigMigration provider={provider} />
<WelcomeLayout isOldMultisigMigration />
)}
</>
)

View File

@ -40,17 +40,18 @@ type OwnProps = {
selectedOwnerName: string
}
const EditOwnerComponent = ({ isOpen, onClose, ownerAddress, selectedOwnerName }: OwnProps): React.ReactElement => {
export const EditOwnerModal = ({ isOpen, onClose, ownerAddress, selectedOwnerName }: OwnProps): React.ReactElement => {
const classes = useStyles()
const dispatch = useDispatch()
const safeAddress = useSelector(safeParamAddressFromStateSelector) as string
const handleSubmit = (values) => {
const { ownerName } = values
dispatch(editSafeOwner({ safeAddress, ownerAddress, ownerName }))
dispatch(addOrUpdateAddressBookEntry(makeAddressBookEntry({ address: ownerAddress, name: ownerName })))
dispatch(enqueueSnackbar(NOTIFICATIONS.OWNER_NAME_CHANGE_EXECUTED_MSG))
const safeAddress = useSelector(safeParamAddressFromStateSelector)
const handleSubmit = ({ ownerName }: { ownerName: string }): void => {
// Update the value only if the ownerName really changed
if (ownerName !== selectedOwnerName) {
dispatch(editSafeOwner({ safeAddress, ownerAddress, ownerName }))
dispatch(addOrUpdateAddressBookEntry(makeAddressBookEntry({ address: ownerAddress, name: ownerName })))
dispatch(enqueueSnackbar(NOTIFICATIONS.OWNER_NAME_CHANGE_EXECUTED_MSG))
}
onClose()
}
@ -71,54 +72,56 @@ const EditOwnerComponent = ({ isOpen, onClose, ownerAddress, selectedOwnerName }
</IconButton>
</Row>
<Hairline />
<GnoForm onSubmit={handleSubmit}>
{() => (
<>
<Block className={classes.container}>
<Row margin="md">
<Field
component={TextField}
initialValue={selectedOwnerName}
name="ownerName"
placeholder="Owner name*"
testId={RENAME_OWNER_INPUT_TEST_ID}
text="Owner name*"
type="text"
validate={composeValidators(required, minMaxLength(1, 50))}
/>
<GnoForm onSubmit={handleSubmit} subscription={{ pristine: true }}>
{(...args) => {
const pristine = args[2].pristine
return (
<>
<Block className={classes.container}>
<Row margin="md">
<Field
component={TextField}
initialValue={selectedOwnerName}
name="ownerName"
placeholder="Owner name*"
testId={RENAME_OWNER_INPUT_TEST_ID}
text="Owner name*"
type="text"
validate={composeValidators(required, minMaxLength(1, 50))}
/>
</Row>
<Row>
<Block justify="center">
<Identicon address={ownerAddress} diameter={32} />
<Paragraph color="disabled" noMargin size="md" style={{ marginLeft: sm, marginRight: sm }}>
{ownerAddress}
</Paragraph>
<CopyBtn content={ownerAddress} />
<ExplorerButton explorerUrl={getExplorerInfo(ownerAddress)} />
</Block>
</Row>
</Block>
<Hairline />
<Row align="center" className={classes.buttonRow}>
<Button minHeight={42} minWidth={140} onClick={onClose}>
Cancel
</Button>
<Button
color="primary"
minHeight={42}
minWidth={140}
testId={SAVE_OWNER_CHANGES_BTN_TEST_ID}
type="submit"
variant="contained"
disabled={pristine}
>
Save
</Button>
</Row>
<Row>
<Block justify="center">
<Identicon address={ownerAddress} diameter={32} />
<Paragraph color="disabled" noMargin size="md" style={{ marginLeft: sm, marginRight: sm }}>
{ownerAddress}
</Paragraph>
<CopyBtn content={ownerAddress} />
<ExplorerButton explorerUrl={getExplorerInfo(ownerAddress)} />
</Block>
</Row>
</Block>
<Hairline />
<Row align="center" className={classes.buttonRow}>
<Button minHeight={42} minWidth={140} onClick={onClose}>
Cancel
</Button>
<Button
color="primary"
minHeight={42}
minWidth={140}
testId={SAVE_OWNER_CHANGES_BTN_TEST_ID}
type="submit"
variant="contained"
>
Save
</Button>
</Row>
</>
)}
</>
)
}}
</GnoForm>
</Modal>
)
}
export default EditOwnerComponent

View File

@ -9,7 +9,7 @@ import { List } from 'immutable'
import RemoveOwnerIcon from '../assets/icons/bin.svg'
import AddOwnerModal from './AddOwnerModal'
import EditOwnerModal from './EditOwnerModal'
import { EditOwnerModal } from './EditOwnerModal'
import OwnerAddressTableCell from './OwnerAddressTableCell'
import RemoveOwnerModal from './RemoveOwnerModal'
import ReplaceOwnerModal from './ReplaceOwnerModal'

View File

@ -18,9 +18,16 @@ import Hairline from 'src/components/layout/Hairline'
import Link from 'src/components/layout/Link'
import Paragraph from 'src/components/layout/Paragraph'
import Row from 'src/components/layout/Row'
import removeSafe from 'src/logic/safe/store/actions/removeSafe'
import { safeNameSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import {
defaultSafeSelector,
safeNameSelector,
safeParamAddressFromStateSelector,
} from 'src/logic/safe/store/selectors'
import { md, secondary } from 'src/theme/variables'
import { WELCOME_ADDRESS } from 'src/routes/routes'
import { removeLocalSafe } from 'src/logic/safe/store/actions/removeLocalSafe'
import { sameAddress } from 'src/logic/wallets/ethAddresses'
import { saveDefaultSafe } from 'src/logic/safe/utils'
const openIconStyle = {
height: md,
@ -29,14 +36,33 @@ const openIconStyle = {
const useStyles = makeStyles(styles)
const RemoveSafeComponent = ({ isOpen, onClose }) => {
type RemoveSafeModalProps = {
isOpen: boolean
onClose: () => void
}
export const RemoveSafeModal = ({ isOpen, onClose }: RemoveSafeModalProps): React.ReactElement => {
const classes = useStyles()
const safeAddress = useSelector(safeParamAddressFromStateSelector) as string
const safeAddress = useSelector(safeParamAddressFromStateSelector)
const safeName = useSelector(safeNameSelector)
const defaultSafe = useSelector(defaultSafeSelector)
const dispatch = useDispatch()
const explorerInfo = getExplorerInfo(safeAddress)
const { url } = explorerInfo()
const onRemoveSafeHandler = async () => {
await dispatch(removeLocalSafe(safeAddress))
if (sameAddress(safeAddress, defaultSafe)) {
await saveDefaultSafe('')
}
onClose()
// using native redirect in order to avoid problems in several components
// trying to access references of the removed safe.
const relativePath = window.location.href.split('/#/')[0]
window.location.href = `${relativePath}/#/${WELCOME_ADDRESS}`
}
return (
<Modal
description="Remove the selected Safe"
@ -91,13 +117,7 @@ const RemoveSafeComponent = ({ isOpen, onClose }) => {
<Button
className={classes.buttonRemove}
minWidth={140}
onClick={() => {
dispatch(removeSafe(safeAddress))
onClose()
// using native redirect in order to avoid problems in several components
// trying to access references of the removed safe.
window.location.href = '/app/'
}}
onClick={onRemoveSafeHandler}
type="submit"
variant="contained"
>
@ -107,5 +127,3 @@ const RemoveSafeComponent = ({ isOpen, onClose }) => {
</Modal>
)
}
export const RemoveSafeModal = RemoveSafeComponent

View File

@ -105,7 +105,7 @@ const ExpandedSafeTx = ({ cancelTx, tx }: ExpandedSafeTxProps): ReactElement =>
const thresholdReached = !isIncomingTx && threshold <= tx.confirmations.size
const canExecute = !isIncomingTx && nonce === tx.nonce
const cancelThresholdReached = !!cancelTx && threshold <= cancelTx.confirmations.size
const cancelThresholdReached = !!cancelTx && threshold <= cancelTx.confirmations?.size
const canExecuteCancel = nonce === tx.nonce
const openRejectModal = () => {

View File

@ -16,6 +16,8 @@ import Link from 'src/components/layout/Link'
import Block from 'src/components/layout/Block'
import { LOAD_ADDRESS, OPEN_ADDRESS } from 'src/routes/routes'
import { onConnectButtonClick } from 'src/components/ConnectButton'
import { useSelector } from 'react-redux'
import { providerNameSelector } from 'src/logic/wallets/store/selectors'
const Wrapper = styled.div`
display: flex;
@ -67,10 +69,10 @@ const StyledButtonLink = styled(ButtonLink)`
type Props = {
isOldMultisigMigration?: boolean
provider: any
}
const Welcome = ({ isOldMultisigMigration, provider }: Props): React.ReactElement => {
export const WelcomeLayout = ({ isOldMultisigMigration }: Props): React.ReactElement => {
const provider = useSelector(providerNameSelector)
return (
<Block>
{/* Title */}
@ -125,7 +127,7 @@ const Welcome = ({ isOldMultisigMigration, provider }: Props): React.ReactElemen
color="primary"
variant="contained"
onClick={onConnectButtonClick}
disabled={provider}
disabled={!!provider}
data-testid="connect-btn"
>
<Text size="xl" color="white">
@ -187,5 +189,3 @@ const Welcome = ({ isOldMultisigMigration, provider }: Props): React.ReactElemen
</Block>
)
}
export default Welcome

View File

@ -1,16 +1,12 @@
import * as React from 'react'
import { connect } from 'react-redux'
import Layout from '../components/Layout'
import selector from './selector'
import React, { ReactElement } from 'react'
import { WelcomeLayout } from 'src/routes/welcome/components'
import Page from 'src/components/layout/Page'
const Welcome = ({ provider }) => (
const Welcome = (): ReactElement => (
<Page align="center">
<Layout provider={provider} />
<WelcomeLayout />
</Page>
)
export default connect(selector)(Welcome)
export default Welcome

View File

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