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

View File

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

View File

@ -6,12 +6,11 @@ import * as React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { SafeRecord } from 'src/logic/safe/store/models/safe' import { SafeRecord } from 'src/logic/safe/store/models/safe'
import { DefaultSafe } from 'src/routes/safe/store/reducer/types/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 Hairline from 'src/components/layout/Hairline'
import Link from 'src/components/layout/Link' import Link from 'src/components/layout/Link'
import { sameAddress } from 'src/logic/wallets/ethAddresses' import { sameAddress } from 'src/logic/wallets/ethAddresses'
import { SAFELIST_ADDRESS } from 'src/routes/routes' 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' export const SIDEBAR_SAFELIST_ROW_TESTID = 'SIDEBAR_SAFELIST_ROW_TESTID'
const StyledIcon = styled(Icon)` const StyledIcon = styled(Icon)`
@ -46,10 +45,9 @@ type Props = {
defaultSafe: DefaultSafe defaultSafe: DefaultSafe
safes: SafeRecord[] safes: SafeRecord[]
onSafeClick: () => void 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() const classes = useStyles()
return ( return (
@ -67,7 +65,7 @@ const SafeList = ({ currentSafe, defaultSafe, onSafeClick, safes, setDefaultSafe
) : ( ) : (
<div className={classes.noIcon}>placeholder</div> <div className={classes.noIcon}>placeholder</div>
)} )}
<AddressWrapper safe={safe} defaultSafe={defaultSafe} setDefaultSafe={setDefaultSafe} /> <AddressWrapper safe={safe} defaultSafe={defaultSafe} />
</ListItem> </ListItem>
</Link> </Link>
<Hairline /> <Hairline />
@ -76,5 +74,3 @@ const SafeList = ({ currentSafe, defaultSafe, onSafeClick, safes, setDefaultSafe
</MuiList> </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 Drawer from '@material-ui/core/Drawer'
import SearchIcon from '@material-ui/icons/Search' import SearchIcon from '@material-ui/icons/Search'
import SearchBar from 'material-ui-search-bar' 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 { sortedSafeListSelector } from './selectors'
import useSidebarStyles from './style' import useSidebarStyles from './style'
@ -15,11 +15,9 @@ import Hairline from 'src/components/layout/Hairline'
import Link from 'src/components/layout/Link' import Link from 'src/components/layout/Link'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
import { WELCOME_ADDRESS } from 'src/routes/routes' 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 { useAnalytics, SAFE_NAVIGATION_EVENT } from 'src/utils/googleAnalytics'
import { defaultSafeSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' import { defaultSafeSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import { AppReduxState } from 'src/store'
export const SafeListSidebarContext = React.createContext({ export const SafeListSidebarContext = React.createContext({
isOpen: false, isOpen: false,
@ -34,9 +32,17 @@ const filterBy = (filter, safes) =>
safe.name.toLowerCase().includes(filter.toLowerCase()), 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 [isOpen, setIsOpen] = useState(false)
const [filter, setFilter] = useState('') const [filter, setFilter] = useState('')
const safes = useSelector(sortedSafeListSelector)
const defaultSafe = useSelector(defaultSafeSelector)
const currentSafe = useSelector(safeParamAddressFromStateSelector)
const classes = useSidebarStyles() const classes = useSidebarStyles()
const { trackEvent } = useAnalytics() const { trackEvent } = useAnalytics()
@ -118,19 +124,9 @@ const SafeListSidebar = ({ children, currentSafe, defaultSafe, safes, setDefault
defaultSafe={defaultSafe} defaultSafe={defaultSafe}
onSafeClick={toggleSidebar} onSafeClick={toggleSidebar}
safes={filteredSafes} safes={filteredSafes}
setDefaultSafe={setDefaultSafeAction}
/> />
</Drawer> </Drawer>
{children} {children}
</SafeListSidebarContext.Provider> </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) => { [REMOVE_SAFE]: (state: SafeReducerMap, action) => {
const safeAddress = action.payload 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) => { [ADD_SAFE_OWNER]: (state: SafeReducerMap, action) => {
const { ownerAddress, ownerName, safeAddress } = action.payload const { ownerAddress, ownerName, safeAddress } = action.payload

View File

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

View File

@ -40,17 +40,18 @@ type OwnProps = {
selectedOwnerName: string 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 classes = useStyles()
const dispatch = useDispatch() const dispatch = useDispatch()
const safeAddress = useSelector(safeParamAddressFromStateSelector) as string const safeAddress = useSelector(safeParamAddressFromStateSelector)
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 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() onClose()
} }
@ -71,54 +72,56 @@ const EditOwnerComponent = ({ isOpen, onClose, ownerAddress, selectedOwnerName }
</IconButton> </IconButton>
</Row> </Row>
<Hairline /> <Hairline />
<GnoForm onSubmit={handleSubmit}> <GnoForm onSubmit={handleSubmit} subscription={{ pristine: true }}>
{() => ( {(...args) => {
<> const pristine = args[2].pristine
<Block className={classes.container}> return (
<Row margin="md"> <>
<Field <Block className={classes.container}>
component={TextField} <Row margin="md">
initialValue={selectedOwnerName} <Field
name="ownerName" component={TextField}
placeholder="Owner name*" initialValue={selectedOwnerName}
testId={RENAME_OWNER_INPUT_TEST_ID} name="ownerName"
text="Owner name*" placeholder="Owner name*"
type="text" testId={RENAME_OWNER_INPUT_TEST_ID}
validate={composeValidators(required, minMaxLength(1, 50))} 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>
<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> </GnoForm>
</Modal> </Modal>
) )
} }
export default EditOwnerComponent

View File

@ -9,7 +9,7 @@ import { List } from 'immutable'
import RemoveOwnerIcon from '../assets/icons/bin.svg' import RemoveOwnerIcon from '../assets/icons/bin.svg'
import AddOwnerModal from './AddOwnerModal' import AddOwnerModal from './AddOwnerModal'
import EditOwnerModal from './EditOwnerModal' import { EditOwnerModal } from './EditOwnerModal'
import OwnerAddressTableCell from './OwnerAddressTableCell' import OwnerAddressTableCell from './OwnerAddressTableCell'
import RemoveOwnerModal from './RemoveOwnerModal' import RemoveOwnerModal from './RemoveOwnerModal'
import ReplaceOwnerModal from './ReplaceOwnerModal' 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 Link from 'src/components/layout/Link'
import Paragraph from 'src/components/layout/Paragraph' import Paragraph from 'src/components/layout/Paragraph'
import Row from 'src/components/layout/Row' import Row from 'src/components/layout/Row'
import removeSafe from 'src/logic/safe/store/actions/removeSafe' import {
import { safeNameSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors' defaultSafeSelector,
safeNameSelector,
safeParamAddressFromStateSelector,
} from 'src/logic/safe/store/selectors'
import { md, secondary } from 'src/theme/variables' 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 = { const openIconStyle = {
height: md, height: md,
@ -29,14 +36,33 @@ const openIconStyle = {
const useStyles = makeStyles(styles) 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 classes = useStyles()
const safeAddress = useSelector(safeParamAddressFromStateSelector) as string const safeAddress = useSelector(safeParamAddressFromStateSelector)
const safeName = useSelector(safeNameSelector) const safeName = useSelector(safeNameSelector)
const defaultSafe = useSelector(defaultSafeSelector)
const dispatch = useDispatch() const dispatch = useDispatch()
const explorerInfo = getExplorerInfo(safeAddress) const explorerInfo = getExplorerInfo(safeAddress)
const { url } = explorerInfo() 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 ( return (
<Modal <Modal
description="Remove the selected Safe" description="Remove the selected Safe"
@ -91,13 +117,7 @@ const RemoveSafeComponent = ({ isOpen, onClose }) => {
<Button <Button
className={classes.buttonRemove} className={classes.buttonRemove}
minWidth={140} minWidth={140}
onClick={() => { onClick={onRemoveSafeHandler}
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/'
}}
type="submit" type="submit"
variant="contained" variant="contained"
> >
@ -107,5 +127,3 @@ const RemoveSafeComponent = ({ isOpen, onClose }) => {
</Modal> </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 thresholdReached = !isIncomingTx && threshold <= tx.confirmations.size
const canExecute = !isIncomingTx && nonce === tx.nonce 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 canExecuteCancel = nonce === tx.nonce
const openRejectModal = () => { const openRejectModal = () => {

View File

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

View File

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