Skip to content
This repository has been archived by the owner on Nov 10, 2023. It is now read-only.

[WIP] Delegates management PoC #3917

Draft
wants to merge 12 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"@gnosis.pm/safe-deployments": "^1.15.0",
"@gnosis.pm/safe-modules-deployments": "^1.0.0",
"@gnosis.pm/safe-react-components": "^1.1.2",
"@gnosis.pm/safe-react-gateway-sdk": "^3.0.1",
"@gnosis.pm/safe-react-gateway-sdk": "git+https://github.com/safe-global/safe-react-gateway-sdk#delegates",
"@gnosis.pm/safe-web3-lib": "^1.0.0",
"@material-ui/core": "^4.12.3",
"@material-ui/icons": "^4.11.0",
Expand Down
5 changes: 5 additions & 0 deletions src/components/AppLayout/Sidebar/useSidebarItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ const useSidebarItems = (): ListItemType[] => {
iconType: 'settingsTool',
href: currentSafeRoutes.SETTINGS_ADVANCED,
}),
makeEntryItem({
label: 'Delegates',
iconType: 'settingsTool',
href: currentSafeRoutes.SETTINGS_DELEGATES,
}),
].filter(Boolean)

return [
Expand Down
3 changes: 1 addition & 2 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ export const _getChainId = (): ChainId => {
return _chainId
}

export const isValidChainId = (chainId: unknown): chainId is ChainId =>
getChains().some((chain) => chain.chainId === chainId)
export const isValidChainId = (chainId: unknown): boolean => getChains().some((chain) => chain.chainId === chainId)

export const getChainById = (chainId: ChainId): ChainInfo => {
return getChains().find((chain) => chain.chainId === chainId) || emptyChainInfo
Expand Down
10 changes: 10 additions & 0 deletions src/logic/delegates/api/delegates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getDelegates } from '@gnosis.pm/safe-react-gateway-sdk'
import { DelegateResponse, DelegatesRequest } from '@gnosis.pm/safe-react-gateway-sdk/dist/types/delegates'

export const fetchDelegates = async (
chainId: string,
query?: DelegatesRequest | undefined,
): Promise<DelegateResponse> => {
const res = await getDelegates(chainId, query)
return res
}
1 change: 1 addition & 0 deletions src/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const SAFE_ROUTES = {
SETTINGS_POLICIES: `${ADDRESSED_ROUTE}/settings/policies`,
SETTINGS_SPENDING_LIMIT: `${ADDRESSED_ROUTE}/settings/spending-limit`,
SETTINGS_ADVANCED: `${ADDRESSED_ROUTE}/settings/advanced`,
SETTINGS_DELEGATES: `${ADDRESSED_ROUTE}/settings/delegates`,
}

export const getNetworkRootRoutes = (): Array<{ chainId: ChainId; route: string; shortName: string }> =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { ReactElement } from 'react'

import { Modal } from 'src/components/Modal'
import Field from 'src/components/forms/Field'
import GnoForm from 'src/components/forms/GnoForm'
import TextField from 'src/components/forms/TextField'
import Block from 'src/components/layout/Block'
import Col from 'src/components/layout/Col'
import Row from 'src/components/layout/Row'
import { composeValidators, mustBeAddressHash, required } from 'src/components/forms/validator'

import { useStyles } from './style'

type DelegateEntry = {
address: string
label: string
}

const formMutators = {
setDelegateAddress: (args, state, utils) => {
utils.changeValue(state, 'address', () => args[0])
},
setDelegateLabel: (args, state, utils) => {
utils.changeValue(state, 'label', () => args[0])
},
}

type AddDelegateModalProps = {
isOpen: boolean
onClose: () => void
onSubmit: (entry: DelegateEntry) => void
}

export const AddDelegateModal = ({ isOpen, onClose, onSubmit }: AddDelegateModalProps): ReactElement => {
const classes = useStyles()

const onFormSubmitted = (values: DelegateEntry) => {
onSubmit(values)
}

return (
<Modal description="Add a new Safe delegate" handleClose={onClose} open={isOpen} title="Add a delegate">
<Modal.Header onClose={onClose}>
<Modal.Header.Title>{'Add a delegate'}</Modal.Header.Title>
</Modal.Header>
<Modal.Body withoutPadding>
<GnoForm formMutators={formMutators} onSubmit={onFormSubmitted}>
{(...args) => {
const formState = args[2]

return (
<>
<Block className={classes.container}>
<Row margin="md">
<Col xs={11}>
<Field
component={TextField}
name="address"
placeholder="Delegate address"
label="Delegate*"
type="text"
validate={composeValidators(required, mustBeAddressHash)}
/>
</Col>
</Row>
<Row margin="md">
<Col xs={11}>
<Field
component={TextField}
name="label"
placeholder="Label"
label="Label*"
type="text"
validate={required}
/>
</Col>
</Row>
</Block>
<Modal.Footer>
<Modal.Footer.Buttons
cancelButtonProps={{ onClick: onClose }}
confirmButtonProps={{
disabled: !formState.valid,
text: 'Add',
}}
/>
</Modal.Footer>
</>
)
}}
</GnoForm>
</Modal.Body>
</Modal>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createStyles, makeStyles } from '@material-ui/core/styles'

import { lg, md } from 'src/theme/variables'

export const useStyles = makeStyles(
createStyles({
heading: {
padding: lg,
justifyContent: 'space-between',
boxSizing: 'border-box',
height: '74px',
},
manage: {
fontSize: lg,
},
container: {
padding: `${md} ${lg}`,
},
close: {
height: '35px',
width: '35px',
},
}),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ReactElement } from 'react'

import { getExplorerInfo } from 'src/config'
import Field from 'src/components/forms/Field'
import GnoForm from 'src/components/forms/GnoForm'
import TextField from 'src/components/forms/TextField'
import { composeValidators, required, validAddressBookName } from 'src/components/forms/validator'
import Block from 'src/components/layout/Block'
import Hairline from 'src/components/layout/Hairline'
import Row from 'src/components/layout/Row'
import Modal, { Modal as GenericModal } from 'src/components/Modal'
import PrefixedEthHashInfo from 'src/components/PrefixedEthHashInfo'
import { ModalHeader } from 'src/routes/safe/components/Balances/SendModal/screens/ModalHeader'

import { useStyles } from './style'

type EditDelegateModalProps = {
isOpen: boolean
onClose: () => void
delegate: string
onSubmit: (label: string) => void
}

export const EditDelegateModal = ({ isOpen, onClose, delegate, onSubmit }: EditDelegateModalProps): ReactElement => {
const classes = useStyles()

const handleSubmit = ({ label }: { label: string }): void => {
onSubmit(label)
onClose()
}

return (
<Modal description="Edit delegate label" handleClose={onClose} open={isOpen} title="Edit delegate label">
<ModalHeader onClose={onClose} title="Edit delegate label" />
<Hairline />
<GnoForm onSubmit={handleSubmit} subscription={{ pristine: true }}>
{(...args) => {
const pristine = args[2].pristine
return (
<>
<Block className={classes.container}>
<Row margin="md">
<Field
component={TextField}
name="label"
placeholder="Label"
label="Label*"
type="text"
validate={composeValidators(required, validAddressBookName)}
/>
</Row>
<Row>
<Block justify="center">
<PrefixedEthHashInfo
hash={delegate}
showCopyBtn
showAvatar
explorerUrl={getExplorerInfo(delegate)}
/>
</Block>
</Row>
</Block>
<GenericModal.Footer>
<GenericModal.Footer.Buttons
cancelButtonProps={{ onClick: onClose }}
confirmButtonProps={{ disabled: pristine, text: 'Save' }}
/>
</GenericModal.Footer>
</>
)
}}
</GnoForm>
</Modal>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { createStyles, makeStyles } from '@material-ui/core'

import { lg, md } from 'src/theme/variables'

export const useStyles = makeStyles(
createStyles({
heading: {
padding: lg,
justifyContent: 'space-between',
boxSizing: 'border-box',
height: '74px',
},
manage: {
fontSize: lg,
},
container: {
padding: `${md} ${lg}`,
minHeight: '200px',
},
close: {
height: '35px',
width: '35px',
},
}),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Text } from '@gnosis.pm/safe-react-components'
import { ReactElement } from 'react'

import { Modal } from 'src/components/Modal'
import GnoForm from 'src/components/forms/GnoForm'

interface RemoveDelegateModalProps {
delegateToDelete: string
isOpen: boolean
onClose: () => void
onSubmit: (address: string) => void
}

export const RemoveDelegateModal = ({
delegateToDelete,
isOpen,
onClose,
onSubmit,
}: RemoveDelegateModalProps): ReactElement => {
const handleDeleteEntrySubmit = () => {
onSubmit(delegateToDelete)
}

return (
<Modal description="Remove delegate" handleClose={onClose} open={isOpen} title="Remove delegate">
<Modal.Header onClose={onClose}>
<Modal.Header.Title>Remove delegate</Modal.Header.Title>
</Modal.Header>
<GnoForm onSubmit={handleDeleteEntrySubmit}>
{() => (
<>
<Modal.Body>
<Text size="xl">
This action will remove{' '}
<Text size="xl" strong as="span">
{delegateToDelete}
</Text>{' '}
from the Safe delegates list.
</Text>
</Modal.Body>
<Modal.Footer>
<Modal.Footer.Buttons
cancelButtonProps={{ onClick: onClose }}
confirmButtonProps={{ color: 'error', text: 'Delete' }}
/>
</Modal.Footer>
</>
)}
</GnoForm>
</Modal>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createStyles, makeStyles } from '@material-ui/core/styles'

import { lg, md } from 'src/theme/variables'

export const useStyles = makeStyles(
createStyles({
heading: {
padding: lg,
justifyContent: 'space-between',
boxSizing: 'border-box',
height: '74px',
},
manage: {
fontSize: lg,
},
container: {
padding: `${md} ${lg}`,
},
close: {
height: '35px',
width: '35px',
},
}),
)
Loading