From b7cf4ee61fd93f55f80a26f89cbc6b89c4697f91 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Tue, 24 May 2022 15:59:40 +0200 Subject: [PATCH 01/12] feat: add Delegates management route --- .../AppLayout/Sidebar/useSidebarItems.tsx | 5 +++ src/routes/routes.ts | 1 + .../components/Settings/Delegates/index.tsx | 31 +++++++++++++++++++ src/routes/safe/components/Settings/index.tsx | 2 ++ 4 files changed, 39 insertions(+) create mode 100644 src/routes/safe/components/Settings/Delegates/index.tsx diff --git a/src/components/AppLayout/Sidebar/useSidebarItems.tsx b/src/components/AppLayout/Sidebar/useSidebarItems.tsx index 5e5b10c3a9..a82749eefb 100644 --- a/src/components/AppLayout/Sidebar/useSidebarItems.tsx +++ b/src/components/AppLayout/Sidebar/useSidebarItems.tsx @@ -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 [ diff --git a/src/routes/routes.ts b/src/routes/routes.ts index e55c8149bd..353f30942c 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -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 }> => diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx new file mode 100644 index 0000000000..5ce972a9eb --- /dev/null +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -0,0 +1,31 @@ +import { ReactElement } from 'react' + +import Block from 'src/components/layout/Block' +import Heading from 'src/components/layout/Heading' +import Paragraph from 'src/components/layout/Paragraph/index' +import styled from 'styled-components' +import { lg } from 'src/theme/variables' + +const StyledBlock = styled(Block)` + minheight: 420px; +` + +const StyledHeading = styled(Heading)` + padding: ${lg}; + padding-bottom: 0; +` + +const StyledParagraph = styled(Paragraph)` + padding-left: ${lg}; +` + +const Delegates = (): ReactElement => { + return ( + + Manage Safe Delegates + Get, add and delete delegates. + + ) +} + +export default Delegates diff --git a/src/routes/safe/components/Settings/index.tsx b/src/routes/safe/components/Settings/index.tsx index fbb5cd20d5..54ab3784c6 100644 --- a/src/routes/safe/components/Settings/index.tsx +++ b/src/routes/safe/components/Settings/index.tsx @@ -24,6 +24,7 @@ const RemoveSafeModal = lazy(() => import('./RemoveSafeModal')) const SafeDetails = lazy(() => import('./SafeDetails')) const ThresholdSettings = lazy(() => import('./ThresholdSettings')) const Appearance = lazy(() => import('./Appearance')) +const Delegates = lazy(() => import('./Delegates')) export const OWNERS_SETTINGS_TAB_TEST_ID = 'owner-settings-tab' @@ -123,6 +124,7 @@ const Settings = (): React.ReactElement => { } /> } /> } /> + } /> From 414b510040da1bc4dcdbc22a8ac0bcb1a85e5ea8 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Tue, 24 May 2022 19:01:12 +0200 Subject: [PATCH 02/12] feat: display stringified delegate list --- .../components/Settings/Delegates/index.tsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index 5ce972a9eb..b2b2930b53 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -1,10 +1,13 @@ -import { ReactElement } from 'react' +import { ReactElement, useEffect, useState } from 'react' import Block from 'src/components/layout/Block' import Heading from 'src/components/layout/Heading' import Paragraph from 'src/components/layout/Paragraph/index' import styled from 'styled-components' import { lg } from 'src/theme/variables' +import { useSelector } from 'react-redux' +import { currentSafeWithNames } from 'src/logic/safe/store/selectors' +import { getChainInfo } from 'src/config' const StyledBlock = styled(Block)` minheight: 420px; @@ -20,10 +23,26 @@ const StyledParagraph = styled(Paragraph)` ` const Delegates = (): ReactElement => { + const { address: safeAddress } = useSelector(currentSafeWithNames) + const [delegatesList, setDelegatesList] = useState([]) + const { transactionService } = getChainInfo() + + useEffect(() => { + if (!safeAddress || !transactionService) return + + const url = `${transactionService}/api/v1/safes/${safeAddress}/delegates/` + fetch(url) + .then((response) => response.json()) + .then((data) => { + setDelegatesList(data.results) + }) + }, [safeAddress, transactionService]) + return ( Manage Safe Delegates Get, add and delete delegates. +
{JSON.stringify(delegatesList, undefined, 2)}
) } From accb4d2dc4a40022623cea90e340f25df65578de Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 25 May 2022 09:35:32 +0200 Subject: [PATCH 03/12] improve settings styles --- src/config/index.ts | 3 +-- .../safe/components/Settings/Delegates/index.tsx | 12 ++++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/config/index.ts b/src/config/index.ts index f6baef91ed..8555c8eff7 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -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 diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index b2b2930b53..1b7b2b3c6e 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -1,27 +1,23 @@ import { ReactElement, useEffect, useState } from 'react' +import { useSelector } from 'react-redux' +import styled from 'styled-components' import Block from 'src/components/layout/Block' import Heading from 'src/components/layout/Heading' import Paragraph from 'src/components/layout/Paragraph/index' -import styled from 'styled-components' import { lg } from 'src/theme/variables' -import { useSelector } from 'react-redux' import { currentSafeWithNames } from 'src/logic/safe/store/selectors' import { getChainInfo } from 'src/config' const StyledBlock = styled(Block)` minheight: 420px; + padding: ${lg}; ` const StyledHeading = styled(Heading)` - padding: ${lg}; padding-bottom: 0; ` -const StyledParagraph = styled(Paragraph)` - padding-left: ${lg}; -` - const Delegates = (): ReactElement => { const { address: safeAddress } = useSelector(currentSafeWithNames) const [delegatesList, setDelegatesList] = useState([]) @@ -41,7 +37,7 @@ const Delegates = (): ReactElement => { return ( Manage Safe Delegates - Get, add and delete delegates. + Get, add and delete delegates.
{JSON.stringify(delegatesList, undefined, 2)}
) From e115a19a530de44a7c8ed6ee81f050b2385dfd8b Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Wed, 25 May 2022 11:34:16 +0200 Subject: [PATCH 04/12] display a table with the delegates --- .../components/Settings/Delegates/index.tsx | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index 1b7b2b3c6e..b54039a5ab 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -1,6 +1,7 @@ -import { ReactElement, useEffect, useState } from 'react' +import { ReactElement, useEffect, useMemo, useState } from 'react' import { useSelector } from 'react-redux' import styled from 'styled-components' +import { Table, TableHeader, TableRow, Text } from '@gnosis.pm/safe-react-components' import Block from 'src/components/layout/Block' import Heading from 'src/components/layout/Heading' @@ -8,6 +9,22 @@ import Paragraph from 'src/components/layout/Paragraph/index' import { lg } from 'src/theme/variables' import { currentSafeWithNames } from 'src/logic/safe/store/selectors' import { getChainInfo } from 'src/config' +import { checksumAddress } from 'src/utils/checksumAddress' + +type Page = { + next?: string + previous?: string + results: Array +} + +type Delegate = { + safe?: string + delegate: string + delegator: string + label: string +} + +type DelegateResponse = Page const StyledBlock = styled(Block)` minheight: 420px; @@ -20,9 +37,19 @@ const StyledHeading = styled(Heading)` const Delegates = (): ReactElement => { const { address: safeAddress } = useSelector(currentSafeWithNames) - const [delegatesList, setDelegatesList] = useState([]) + const [delegatesList, setDelegatesList] = useState([]) const { transactionService } = getChainInfo() + const headerCells: TableHeader[] = useMemo( + () => [ + { id: 'delegate', label: 'Delegate' }, + { id: 'delegator', label: 'Delegator' }, + { id: 'label', label: 'Label' }, + ], + [], + ) + const rows: TableRow[] = useMemo(() => [], []) + useEffect(() => { if (!safeAddress || !transactionService) return @@ -34,11 +61,28 @@ const Delegates = (): ReactElement => { }) }, [safeAddress, transactionService]) + useEffect(() => { + if (delegatesList.length) { + let index = 0 + for (const obj of delegatesList) { + rows.push({ + id: `${index++}`, + cells: [ + { id: 'delegate', content: {checksumAddress(obj.delegate)} }, + { id: 'delegator', content: {checksumAddress(obj.delegator)} }, + { id: 'label', content: {obj.label} }, + ], + }) + } + } + }, [delegatesList, rows]) + return ( Manage Safe Delegates Get, add and delete delegates.
{JSON.stringify(delegatesList, undefined, 2)}
+ ) } From 7c0fd70c1fcaebc62b013d05c6433d7915dec548 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Thu, 26 May 2022 12:37:09 +0200 Subject: [PATCH 05/12] add delegate --- .../Delegates/AddDelegateModal/index.tsx | 95 +++++++++++++++++++ .../Delegates/AddDelegateModal/style.ts | 24 +++++ .../components/Settings/Delegates/index.tsx | 75 +++++++++++++-- 3 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 src/routes/safe/components/Settings/Delegates/AddDelegateModal/index.tsx create mode 100644 src/routes/safe/components/Settings/Delegates/AddDelegateModal/style.ts diff --git a/src/routes/safe/components/Settings/Delegates/AddDelegateModal/index.tsx b/src/routes/safe/components/Settings/Delegates/AddDelegateModal/index.tsx new file mode 100644 index 0000000000..32221969aa --- /dev/null +++ b/src/routes/safe/components/Settings/Delegates/AddDelegateModal/index.tsx @@ -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 ( + + + {'Add a delegate'} + + + + {(...args) => { + const formState = args[2] + + return ( + <> + + + + + + + + + + + + + + + + + ) + }} + + + + ) +} diff --git a/src/routes/safe/components/Settings/Delegates/AddDelegateModal/style.ts b/src/routes/safe/components/Settings/Delegates/AddDelegateModal/style.ts new file mode 100644 index 0000000000..5511eedcec --- /dev/null +++ b/src/routes/safe/components/Settings/Delegates/AddDelegateModal/style.ts @@ -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', + }, + }), +) diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index b54039a5ab..7ad4ad6d80 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -1,7 +1,7 @@ -import { ReactElement, useEffect, useMemo, useState } from 'react' +import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react' import { useSelector } from 'react-redux' import styled from 'styled-components' -import { Table, TableHeader, TableRow, Text } from '@gnosis.pm/safe-react-components' +import { ButtonLink, Table, TableHeader, TableRow, Text } from '@gnosis.pm/safe-react-components' import Block from 'src/components/layout/Block' import Heading from 'src/components/layout/Heading' @@ -10,7 +10,12 @@ import { lg } from 'src/theme/variables' import { currentSafeWithNames } from 'src/logic/safe/store/selectors' import { getChainInfo } from 'src/config' import { checksumAddress } from 'src/utils/checksumAddress' +import { getWeb3 } from 'src/logic/wallets/getWeb3' +import { AddDelegateModal } from 'src/routes/safe/components/Settings/Delegates/AddDelegateModal' +import { userAccountSelector } from 'src/logic/wallets/store/selectors' +import { keccak256, fromAscii } from 'web3-utils' +// TODO: these types will come from the Client GW SDK once #72 is merged type Page = { next?: string previous?: string @@ -37,8 +42,10 @@ const StyledHeading = styled(Heading)` const Delegates = (): ReactElement => { const { address: safeAddress } = useSelector(currentSafeWithNames) - const [delegatesList, setDelegatesList] = useState([]) + const userAccount = useSelector(userAccountSelector) const { transactionService } = getChainInfo() + const [delegatesList, setDelegatesList] = useState([]) + const [addDelegateModalOpen, setAddDelegateModalOpen] = useState(false) const headerCells: TableHeader[] = useMemo( () => [ @@ -50,9 +57,7 @@ const Delegates = (): ReactElement => { ) const rows: TableRow[] = useMemo(() => [], []) - useEffect(() => { - if (!safeAddress || !transactionService) return - + const fetchDelegates = useCallback(() => { const url = `${transactionService}/api/v1/safes/${safeAddress}/delegates/` fetch(url) .then((response) => response.json()) @@ -61,6 +66,22 @@ const Delegates = (): ReactElement => { }) }, [safeAddress, transactionService]) + const getSignature = async (delegate) => { + const totp = Math.floor(Date.now() / 1000 / 3600) + const web3 = getWeb3() + const msg = checksumAddress(delegate) + totp + + const hashMessage = keccak256(fromAscii(msg)) + const signature = await web3.eth.sign(hashMessage, userAccount) + + return signature + } + + useEffect(() => { + if (!safeAddress || !transactionService) return + fetchDelegates() + }, [fetchDelegates, safeAddress, transactionService]) + useEffect(() => { if (delegatesList.length) { let index = 0 @@ -77,12 +98,54 @@ const Delegates = (): ReactElement => { } }, [delegatesList, rows]) + const handleAddDelegate = async ({ address, label }) => { + // close Add delegate modal + setAddDelegateModalOpen(false) + + const delegate = checksumAddress(address) + + const signature = await getSignature(delegate) + const requestOptions = { + method: 'POST', + headers: { 'Content-type': 'application/json' }, + body: JSON.stringify({ + safe: safeAddress, + delegate: delegate, + signature: signature, + label: label, + }), + } + + const url = `${transactionService}/api/v1/safes/${safeAddress}/delegates/` + fetch(url, requestOptions) + .then((response) => response.json()) + .then(() => { + fetchDelegates() + }) + } + return ( Manage Safe Delegates Get, add and delete delegates. + { + setAddDelegateModalOpen(true) + }} + color="primary" + iconType="add" + iconSize="sm" + textSize="xl" + > + Add delegate +
{JSON.stringify(delegatesList, undefined, 2)}
+ setAddDelegateModalOpen(false)} + onSubmit={handleAddDelegate} + /> ) } From e1a61c8650017b8a1758e68b491fcdb329b9cb94 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Thu, 26 May 2022 16:06:09 +0200 Subject: [PATCH 06/12] style delegate table and show edit/remove buttons --- .../components/Settings/Delegates/columns.ts | 48 +++++++ .../components/Settings/Delegates/index.tsx | 117 +++++++++++++----- .../components/Settings/Delegates/style.ts | 61 +++++++++ 3 files changed, 196 insertions(+), 30 deletions(-) create mode 100644 src/routes/safe/components/Settings/Delegates/columns.ts create mode 100644 src/routes/safe/components/Settings/Delegates/style.ts diff --git a/src/routes/safe/components/Settings/Delegates/columns.ts b/src/routes/safe/components/Settings/Delegates/columns.ts new file mode 100644 index 0000000000..b3d8fa8e3c --- /dev/null +++ b/src/routes/safe/components/Settings/Delegates/columns.ts @@ -0,0 +1,48 @@ +import { TableCellProps } from '@material-ui/core/TableCell/TableCell' + +export const DELEGATE_ADDRESS_ID = 'delegate' +export const DELEGATOR_ADDRESS_ID = 'delegator' +export const DELEGATE_LABEL_ID = 'label' +export const ACTIONS_ID = 'actions' +export const EDIT_DELEGATE_BUTTON = 'edit-entry-btn' +export const REMOVE_DELEGATE_BUTTON = 'remove-entry-btn' + +type DelegatesTableColumn = { + id: string + label: string + width?: number + custom?: boolean + align?: TableCellProps['align'] +} + +export const generateColumns = (): Array => { + const delegateColumn = { + id: DELEGATE_ADDRESS_ID, + label: 'Delegate', + width: 150, + custom: false, + align: 'left', + } + + const delegatorColumn = { + id: DELEGATOR_ADDRESS_ID, + label: 'Delegator', + width: 150, + custom: false, + align: 'left', + } + + const labelColumn = { + id: DELEGATE_LABEL_ID, + label: 'Label', + custom: false, + } + + const actionsColumn = { + id: ACTIONS_ID, + label: '', + custom: true, + } + + return [delegateColumn, delegatorColumn, labelColumn, actionsColumn] +} diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index 7ad4ad6d80..1bc8bef170 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -1,19 +1,29 @@ -import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react' +import { ReactElement, useCallback, useEffect, useState } from 'react' import { useSelector } from 'react-redux' import styled from 'styled-components' -import { ButtonLink, Table, TableHeader, TableRow, Text } from '@gnosis.pm/safe-react-components' +import { makeStyles, TableCell, TableContainer, TableRow } from '@material-ui/core' +import { ButtonLink, Icon } from '@gnosis.pm/safe-react-components' +import { keccak256, fromAscii } from 'web3-utils' +import cn from 'classnames' import Block from 'src/components/layout/Block' import Heading from 'src/components/layout/Heading' import Paragraph from 'src/components/layout/Paragraph/index' import { lg } from 'src/theme/variables' import { currentSafeWithNames } from 'src/logic/safe/store/selectors' -import { getChainInfo } from 'src/config' +import { getChainInfo, getExplorerInfo } from 'src/config' import { checksumAddress } from 'src/utils/checksumAddress' import { getWeb3 } from 'src/logic/wallets/getWeb3' import { AddDelegateModal } from 'src/routes/safe/components/Settings/Delegates/AddDelegateModal' import { userAccountSelector } from 'src/logic/wallets/store/selectors' -import { keccak256, fromAscii } from 'web3-utils' +import Table from 'src/components/Table' +import { cellWidth } from 'src/components/Table/TableHead' +import { DELEGATE_ADDRESS_ID, DELEGATOR_ADDRESS_ID, generateColumns } from './columns' +import { styles } from './style' +import PrefixedEthHashInfo from 'src/components/PrefixedEthHashInfo' +import Row from 'src/components/layout/Row' +import ButtonHelper from 'src/components/ButtonHelper' +import { grantedSelector } from 'src/routes/safe/container/selector' // TODO: these types will come from the Client GW SDK once #72 is merged type Page = { @@ -40,22 +50,19 @@ const StyledHeading = styled(Heading)` padding-bottom: 0; ` +const useStyles = makeStyles(styles) + const Delegates = (): ReactElement => { const { address: safeAddress } = useSelector(currentSafeWithNames) const userAccount = useSelector(userAccountSelector) + const granted = useSelector(grantedSelector) const { transactionService } = getChainInfo() const [delegatesList, setDelegatesList] = useState([]) const [addDelegateModalOpen, setAddDelegateModalOpen] = useState(false) + const columns = generateColumns() + const autoColumns = columns.filter(({ custom }) => !custom) - const headerCells: TableHeader[] = useMemo( - () => [ - { id: 'delegate', label: 'Delegate' }, - { id: 'delegator', label: 'Delegator' }, - { id: 'label', label: 'Label' }, - ], - [], - ) - const rows: TableRow[] = useMemo(() => [], []) + const classes = useStyles(styles) const fetchDelegates = useCallback(() => { const url = `${transactionService}/api/v1/safes/${safeAddress}/delegates/` @@ -82,22 +89,6 @@ const Delegates = (): ReactElement => { fetchDelegates() }, [fetchDelegates, safeAddress, transactionService]) - useEffect(() => { - if (delegatesList.length) { - let index = 0 - for (const obj of delegatesList) { - rows.push({ - id: `${index++}`, - cells: [ - { id: 'delegate', content: {checksumAddress(obj.delegate)} }, - { id: 'delegator', content: {checksumAddress(obj.delegator)} }, - { id: 'label', content: {obj.label} }, - ], - }) - } - } - }, [delegatesList, rows]) - const handleAddDelegate = async ({ address, label }) => { // close Add delegate modal setAddDelegateModalOpen(false) @@ -140,7 +131,73 @@ const Delegates = (): ReactElement => { Add delegate
{JSON.stringify(delegatesList, undefined, 2)}
-
+ +
+ {(data) => + data.map((row, index) => { + const hideBorderBottom = index >= 3 && index === data.size - 1 && classes.noBorderBottom + return ( + + {autoColumns.map((column) => { + const displayEthHash = [DELEGATE_ADDRESS_ID, DELEGATOR_ADDRESS_ID].includes(column.id) + return ( + + {displayEthHash ? ( + + + + ) : ( + row[column.id] + )} + + ) + })} + + + { + // setSelectedEntry({ + // entry: row, + // isOwnerAddress: userOwner, + // }) + // setEditCreateEntryModalOpen(true) + }} + > + + + { + // setSelectedEntry({ entry: row }) + // setDeleteEntryModalOpen(true) + }} + > + + + + + + ) + }) + } +
+ setAddDelegateModalOpen(false)} diff --git a/src/routes/safe/components/Settings/Delegates/style.ts b/src/routes/safe/components/Settings/Delegates/style.ts new file mode 100644 index 0000000000..bad8f9c03c --- /dev/null +++ b/src/routes/safe/components/Settings/Delegates/style.ts @@ -0,0 +1,61 @@ +import { background, lg, md, sm } from 'src/theme/variables' +import { createStyles } from '@material-ui/core' + +export const styles = createStyles({ + formContainer: { + minHeight: '250px', + }, + title: { + padding: lg, + paddingBottom: 0, + }, + annotation: { + paddingLeft: lg, + }, + hide: { + '&:hover': { + backgroundColor: `${background}`, + }, + '&:hover $actions': { + visibility: 'initial', + }, + }, + actions: { + justifyContent: 'flex-end', + alignItems: 'center', + visibility: 'hidden', + minWidth: '100px', + gap: md, + }, + noBorderBottom: { + '& > td': { + borderBottom: 'none', + }, + }, + controlsRow: { + backgroundColor: 'white', + padding: lg, + borderRadius: sm, + }, + editEntryButton: { + cursor: 'pointer', + }, + editEntryButtonNonOwner: { + cursor: 'pointer', + }, + removeEntryButton: { + cursor: 'pointer', + }, + removeEntryButtonDisabled: { + cursor: 'default', + }, + removeEntryButtonNonOwner: { + cursor: 'pointer', + }, + leftIcon: { + marginRight: sm, + }, + iconSmall: { + fontSize: 16, + }, +}) From 7501928143d2ee9b6e186c02ca4e62b22a75ea05 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Thu, 26 May 2022 22:25:31 +0200 Subject: [PATCH 07/12] remove delegate --- .../Delegates/RemoveDelegateModal/index.tsx | 52 +++++++++++++++++++ .../Delegates/RemoveDelegateModal/style.ts | 24 +++++++++ .../components/Settings/Delegates/columns.ts | 4 +- .../components/Settings/Delegates/index.tsx | 42 +++++++++++++-- 4 files changed, 116 insertions(+), 6 deletions(-) create mode 100644 src/routes/safe/components/Settings/Delegates/RemoveDelegateModal/index.tsx create mode 100644 src/routes/safe/components/Settings/Delegates/RemoveDelegateModal/style.ts diff --git a/src/routes/safe/components/Settings/Delegates/RemoveDelegateModal/index.tsx b/src/routes/safe/components/Settings/Delegates/RemoveDelegateModal/index.tsx new file mode 100644 index 0000000000..e95b815412 --- /dev/null +++ b/src/routes/safe/components/Settings/Delegates/RemoveDelegateModal/index.tsx @@ -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 ( + + + Remove delegate + + + {() => ( + <> + + + This action will remove{' '} + + {delegateToDelete} + {' '} + from the Safe delegates list. + + + + + + + )} + + + ) +} diff --git a/src/routes/safe/components/Settings/Delegates/RemoveDelegateModal/style.ts b/src/routes/safe/components/Settings/Delegates/RemoveDelegateModal/style.ts new file mode 100644 index 0000000000..5511eedcec --- /dev/null +++ b/src/routes/safe/components/Settings/Delegates/RemoveDelegateModal/style.ts @@ -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', + }, + }), +) diff --git a/src/routes/safe/components/Settings/Delegates/columns.ts b/src/routes/safe/components/Settings/Delegates/columns.ts index b3d8fa8e3c..d6739437fc 100644 --- a/src/routes/safe/components/Settings/Delegates/columns.ts +++ b/src/routes/safe/components/Settings/Delegates/columns.ts @@ -19,7 +19,7 @@ export const generateColumns = (): Array => { const delegateColumn = { id: DELEGATE_ADDRESS_ID, label: 'Delegate', - width: 150, + width: 170, custom: false, align: 'left', } @@ -27,7 +27,7 @@ export const generateColumns = (): Array => { const delegatorColumn = { id: DELEGATOR_ADDRESS_ID, label: 'Delegator', - width: 150, + width: 170, custom: false, align: 'left', } diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index 1bc8bef170..34335100c5 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -24,6 +24,7 @@ import PrefixedEthHashInfo from 'src/components/PrefixedEthHashInfo' import Row from 'src/components/layout/Row' import ButtonHelper from 'src/components/ButtonHelper' import { grantedSelector } from 'src/routes/safe/container/selector' +import { RemoveDelegateModal } from 'src/routes/safe/components/Settings/Delegates/RemoveDelegateModal' // TODO: these types will come from the Client GW SDK once #72 is merged type Page = { @@ -59,6 +60,8 @@ const Delegates = (): ReactElement => { const { transactionService } = getChainInfo() const [delegatesList, setDelegatesList] = useState([]) const [addDelegateModalOpen, setAddDelegateModalOpen] = useState(false) + const [removeDelegateModalOpen, setRemoveDelegateModalOpen] = useState(false) + const [addressToRemove, setAddressToRemove] = useState('') const columns = generateColumns() const autoColumns = columns.filter(({ custom }) => !custom) @@ -75,10 +78,10 @@ const Delegates = (): ReactElement => { const getSignature = async (delegate) => { const totp = Math.floor(Date.now() / 1000 / 3600) - const web3 = getWeb3() const msg = checksumAddress(delegate) + totp - const hashMessage = keccak256(fromAscii(msg)) + + const web3 = getWeb3() const signature = await web3.eth.sign(hashMessage, userAccount) return signature @@ -115,6 +118,28 @@ const Delegates = (): ReactElement => { }) } + const handleRemoveDelegate = async (address: string) => { + // close Remove delegate modal + setRemoveDelegateModalOpen(false) + + const delegate = checksumAddress(address) + const signature = await getSignature(delegate) + + const requestOptions = { + method: 'DELETE', + headers: { 'Content-type': 'application/json' }, + body: JSON.stringify({ + signature: signature, + }), + } + + const url = `${transactionService}/api/v1/safes/${safeAddress}/delegates/${delegate}/` + fetch(url, requestOptions).then(() => { + setAddressToRemove('') + fetchDelegates() + }) + } + return ( Manage Safe Delegates @@ -178,8 +203,8 @@ const Delegates = (): ReactElement => { { - // setSelectedEntry({ entry: row }) - // setDeleteEntryModalOpen(true) + setRemoveDelegateModalOpen(true) + setAddressToRemove(row[DELEGATE_ADDRESS_ID]) }} > { onClose={() => setAddDelegateModalOpen(false)} onSubmit={handleAddDelegate} /> + { + setRemoveDelegateModalOpen(false) + setAddressToRemove('') + }} + onSubmit={handleRemoveDelegate} + /> ) } From ca494ff6fa4f22b4889119fa387b4e05adef3796 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Thu, 26 May 2022 23:53:47 +0200 Subject: [PATCH 08/12] edit delegate label --- .../Delegates/EditDelegateModal/index.tsx | 76 +++++++++++++++++++ .../Delegates/EditDelegateModal/style.ts | 25 ++++++ .../components/Settings/Delegates/index.tsx | 62 ++++++++++----- .../components/Settings/Delegates/style.ts | 6 -- 4 files changed, 143 insertions(+), 26 deletions(-) create mode 100644 src/routes/safe/components/Settings/Delegates/EditDelegateModal/index.tsx create mode 100644 src/routes/safe/components/Settings/Delegates/EditDelegateModal/style.ts diff --git a/src/routes/safe/components/Settings/Delegates/EditDelegateModal/index.tsx b/src/routes/safe/components/Settings/Delegates/EditDelegateModal/index.tsx new file mode 100644 index 0000000000..8a9bdd1de3 --- /dev/null +++ b/src/routes/safe/components/Settings/Delegates/EditDelegateModal/index.tsx @@ -0,0 +1,76 @@ +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 => { + console.log('submit label', label) + onSubmit(label) + onClose() + } + + return ( + + + + + {(...args) => { + const pristine = args[2].pristine + return ( + <> + + + + + + + + + + + + + + + ) + }} + + + ) +} diff --git a/src/routes/safe/components/Settings/Delegates/EditDelegateModal/style.ts b/src/routes/safe/components/Settings/Delegates/EditDelegateModal/style.ts new file mode 100644 index 0000000000..5b50e7e869 --- /dev/null +++ b/src/routes/safe/components/Settings/Delegates/EditDelegateModal/style.ts @@ -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', + }, + }), +) diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index 34335100c5..98fc676f8e 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -14,7 +14,6 @@ import { currentSafeWithNames } from 'src/logic/safe/store/selectors' import { getChainInfo, getExplorerInfo } from 'src/config' import { checksumAddress } from 'src/utils/checksumAddress' import { getWeb3 } from 'src/logic/wallets/getWeb3' -import { AddDelegateModal } from 'src/routes/safe/components/Settings/Delegates/AddDelegateModal' import { userAccountSelector } from 'src/logic/wallets/store/selectors' import Table from 'src/components/Table' import { cellWidth } from 'src/components/Table/TableHead' @@ -23,8 +22,9 @@ import { styles } from './style' import PrefixedEthHashInfo from 'src/components/PrefixedEthHashInfo' import Row from 'src/components/layout/Row' import ButtonHelper from 'src/components/ButtonHelper' -import { grantedSelector } from 'src/routes/safe/container/selector' +import { AddDelegateModal } from 'src/routes/safe/components/Settings/Delegates/AddDelegateModal' import { RemoveDelegateModal } from 'src/routes/safe/components/Settings/Delegates/RemoveDelegateModal' +import { EditDelegateModal } from 'src/routes/safe/components/Settings/Delegates/EditDelegateModal' // TODO: these types will come from the Client GW SDK once #72 is merged type Page = { @@ -56,10 +56,11 @@ const useStyles = makeStyles(styles) const Delegates = (): ReactElement => { const { address: safeAddress } = useSelector(currentSafeWithNames) const userAccount = useSelector(userAccountSelector) - const granted = useSelector(grantedSelector) const { transactionService } = getChainInfo() const [delegatesList, setDelegatesList] = useState([]) const [addDelegateModalOpen, setAddDelegateModalOpen] = useState(false) + const [editDelegateModalOpen, setEditDelegateModalOpen] = useState(false) + const [delegateToEdit, setDelegateToEdit] = useState('') const [removeDelegateModalOpen, setRemoveDelegateModalOpen] = useState(false) const [addressToRemove, setAddressToRemove] = useState('') const columns = generateColumns() @@ -118,6 +119,32 @@ const Delegates = (): ReactElement => { }) } + const handleEditDelegateLabel = async (label) => { + // close Edit delegate modal + setEditDelegateModalOpen(false) + + const delegate = checksumAddress(delegateToEdit) + const signature = await getSignature(delegate) + + const requestOptions = { + method: 'POST', + headers: { 'Content-type': 'application/json' }, + body: JSON.stringify({ + safe: safeAddress, + delegate: delegate, + signature: signature, + label: label, + }), + } + + const url = `${transactionService}/api/v1/safes/${safeAddress}/delegates/` + fetch(url, requestOptions) + .then((response) => response.json()) + .then(() => { + fetchDelegates() + }) + } + const handleRemoveDelegate = async (address: string) => { // close Remove delegate modal setRemoveDelegateModalOpen(false) @@ -187,24 +214,16 @@ const Delegates = (): ReactElement => { { - // setSelectedEntry({ - // entry: row, - // isOwnerAddress: userOwner, - // }) - // setEditCreateEntryModalOpen(true) + setDelegateToEdit(row[DELEGATE_ADDRESS_ID]) + setEditDelegateModalOpen(true) }} > - + { - setRemoveDelegateModalOpen(true) setAddressToRemove(row[DELEGATE_ADDRESS_ID]) + setRemoveDelegateModalOpen(true) }} > { type="delete" color="error" tooltip="Remove delegate" - className={granted ? classes.removeEntryButton : classes.removeEntryButtonNonOwner} + className={classes.removeEntryButton} /> @@ -228,13 +247,16 @@ const Delegates = (): ReactElement => { onClose={() => setAddDelegateModalOpen(false)} onSubmit={handleAddDelegate} /> + setEditDelegateModalOpen(false)} + onSubmit={handleEditDelegateLabel} + /> { - setRemoveDelegateModalOpen(false) - setAddressToRemove('') - }} + onClose={() => setRemoveDelegateModalOpen(false)} onSubmit={handleRemoveDelegate} />
diff --git a/src/routes/safe/components/Settings/Delegates/style.ts b/src/routes/safe/components/Settings/Delegates/style.ts index bad8f9c03c..cac07842dd 100644 --- a/src/routes/safe/components/Settings/Delegates/style.ts +++ b/src/routes/safe/components/Settings/Delegates/style.ts @@ -40,18 +40,12 @@ export const styles = createStyles({ editEntryButton: { cursor: 'pointer', }, - editEntryButtonNonOwner: { - cursor: 'pointer', - }, removeEntryButton: { cursor: 'pointer', }, removeEntryButtonDisabled: { cursor: 'default', }, - removeEntryButtonNonOwner: { - cursor: 'pointer', - }, leftIcon: { marginRight: sm, }, From 865b4ae002eb6ca089cec4bb873f2e216275195f Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Fri, 27 May 2022 00:02:02 +0200 Subject: [PATCH 09/12] make table non sortable --- src/routes/safe/components/Settings/Delegates/columns.ts | 4 ++++ src/routes/safe/components/Settings/Delegates/index.tsx | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/routes/safe/components/Settings/Delegates/columns.ts b/src/routes/safe/components/Settings/Delegates/columns.ts index d6739437fc..5e5029d324 100644 --- a/src/routes/safe/components/Settings/Delegates/columns.ts +++ b/src/routes/safe/components/Settings/Delegates/columns.ts @@ -22,6 +22,7 @@ export const generateColumns = (): Array => { width: 170, custom: false, align: 'left', + static: true, } const delegatorColumn = { @@ -30,18 +31,21 @@ export const generateColumns = (): Array => { width: 170, custom: false, align: 'left', + static: true, } const labelColumn = { id: DELEGATE_LABEL_ID, label: 'Label', custom: false, + static: true, } const actionsColumn = { id: ACTIONS_ID, label: '', custom: true, + static: true, } return [delegateColumn, delegatorColumn, labelColumn, actionsColumn] diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index 98fc676f8e..79a0ede60e 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -184,7 +184,14 @@ const Delegates = (): ReactElement => {
{JSON.stringify(delegatesList, undefined, 2)}
- +
{(data) => data.map((row, index) => { const hideBorderBottom = index >= 3 && index === data.size - 1 && classes.noBorderBottom From 2bac6887fd4eb034cf665bfa8340c8edaebd28b8 Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Sun, 29 May 2022 11:35:09 +0200 Subject: [PATCH 10/12] only a Safe owner can manage delegates --- .../components/Settings/Delegates/index.tsx | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index 79a0ede60e..7279c96292 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -25,6 +25,7 @@ import ButtonHelper from 'src/components/ButtonHelper' import { AddDelegateModal } from 'src/routes/safe/components/Settings/Delegates/AddDelegateModal' import { RemoveDelegateModal } from 'src/routes/safe/components/Settings/Delegates/RemoveDelegateModal' import { EditDelegateModal } from 'src/routes/safe/components/Settings/Delegates/EditDelegateModal' +import { grantedSelector } from 'src/routes/safe/container/selector' // TODO: these types will come from the Client GW SDK once #72 is merged type Page = { @@ -51,6 +52,10 @@ const StyledHeading = styled(Heading)` padding-bottom: 0; ` +const StyledButtonLink = styled(ButtonLink)<{ isDisabled: boolean }>` + display: ${({ isDisabled }) => (isDisabled ? 'none' : 'flex')}; +` + const useStyles = makeStyles(styles) const Delegates = (): ReactElement => { @@ -65,6 +70,7 @@ const Delegates = (): ReactElement => { const [addressToRemove, setAddressToRemove] = useState('') const columns = generateColumns() const autoColumns = columns.filter(({ custom }) => !custom) + const granted = useSelector(grantedSelector) const classes = useStyles(styles) @@ -171,7 +177,7 @@ const Delegates = (): ReactElement => { Manage Safe Delegates Get, add and delete delegates. - { setAddDelegateModalOpen(true) }} @@ -179,9 +185,10 @@ const Delegates = (): ReactElement => { iconType="add" iconSize="sm" textSize="xl" + isDisabled={!granted} > Add delegate - +
{JSON.stringify(delegatesList, undefined, 2)}
{ })} - { - setDelegateToEdit(row[DELEGATE_ADDRESS_ID]) - setEditDelegateModalOpen(true) - }} - > - - - { - setAddressToRemove(row[DELEGATE_ADDRESS_ID]) - setRemoveDelegateModalOpen(true) - }} - > - - + {granted && ( + <> + { + setDelegateToEdit(row[DELEGATE_ADDRESS_ID]) + setEditDelegateModalOpen(true) + }} + > + + + { + setAddressToRemove(row[DELEGATE_ADDRESS_ID]) + setRemoveDelegateModalOpen(true) + }} + > + + + + )} From 8707fcd5a91953dcd81d17867fb1ce209fae89ae Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Thu, 9 Jun 2022 18:22:48 +0200 Subject: [PATCH 11/12] feat: fetch delegates through the Client GW --- package.json | 2 +- .../components/Settings/Delegates/index.tsx | 32 +++++-------------- yarn.lock | 5 ++- 3 files changed, 11 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 1355027dbf..7d344ad473 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index 7279c96292..f7b787476b 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -26,22 +26,9 @@ import { AddDelegateModal } from 'src/routes/safe/components/Settings/Delegates/ import { RemoveDelegateModal } from 'src/routes/safe/components/Settings/Delegates/RemoveDelegateModal' import { EditDelegateModal } from 'src/routes/safe/components/Settings/Delegates/EditDelegateModal' import { grantedSelector } from 'src/routes/safe/container/selector' - -// TODO: these types will come from the Client GW SDK once #72 is merged -type Page = { - next?: string - previous?: string - results: Array -} - -type Delegate = { - safe?: string - delegate: string - delegator: string - label: string -} - -type DelegateResponse = Page +import { DelegateResponse } from '@gnosis.pm/safe-react-gateway-sdk/dist/types/delegates' +import { getDelegates } from '@gnosis.pm/safe-react-gateway-sdk' +import { currentChainId } from 'src/logic/config/store/selectors' const StyledBlock = styled(Block)` minheight: 420px; @@ -71,17 +58,14 @@ const Delegates = (): ReactElement => { const columns = generateColumns() const autoColumns = columns.filter(({ custom }) => !custom) const granted = useSelector(grantedSelector) + const chainId = useSelector(currentChainId) const classes = useStyles(styles) - const fetchDelegates = useCallback(() => { - const url = `${transactionService}/api/v1/safes/${safeAddress}/delegates/` - fetch(url) - .then((response) => response.json()) - .then((data) => { - setDelegatesList(data.results) - }) - }, [safeAddress, transactionService]) + const fetchDelegates = useCallback(async () => { + const { results } = await getDelegates(chainId, { safe: safeAddress }) + setDelegatesList(results) + }, [chainId, safeAddress]) const getSignature = async (delegate) => { const totp = Math.floor(Date.now() / 1000 / 3600) diff --git a/yarn.lock b/yarn.lock index a12df895c6..66374ee779 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1913,10 +1913,9 @@ dependencies: isomorphic-unfetch "^3.1.0" -"@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": version "3.0.1" - resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-react-gateway-sdk/-/safe-react-gateway-sdk-3.0.1.tgz#0588072cf8aa4d83d46d12ff8d94e2ea7d29ad38" - integrity sha512-k4SgNW2VmODHFfd+sRd5Zuh3KVvSyD/EGqms87PnaW5bkerfl49stsW2F1hnO6hMaJDEKcgpzds7UjDHLLZ6OQ== + resolved "git+https://github.com/safe-global/safe-react-gateway-sdk#68023a8bbace5b36568bd1f72559ee07b27ce729" dependencies: cross-fetch "^3.1.5" From dde0166c88105ffe63a474426eef0ff8cfdae42d Mon Sep 17 00:00:00 2001 From: Diogo Soares Date: Fri, 10 Jun 2022 11:01:11 +0200 Subject: [PATCH 12/12] feat: use client gw sdk delete endpoints --- src/logic/delegates/api/delegates.ts | 10 ++ .../Delegates/EditDelegateModal/index.tsx | 1 - .../components/Settings/Delegates/index.tsx | 92 +++++++++---------- 3 files changed, 54 insertions(+), 49 deletions(-) create mode 100644 src/logic/delegates/api/delegates.ts diff --git a/src/logic/delegates/api/delegates.ts b/src/logic/delegates/api/delegates.ts new file mode 100644 index 0000000000..83c3831413 --- /dev/null +++ b/src/logic/delegates/api/delegates.ts @@ -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 => { + const res = await getDelegates(chainId, query) + return res +} diff --git a/src/routes/safe/components/Settings/Delegates/EditDelegateModal/index.tsx b/src/routes/safe/components/Settings/Delegates/EditDelegateModal/index.tsx index 8a9bdd1de3..b8c027e08a 100644 --- a/src/routes/safe/components/Settings/Delegates/EditDelegateModal/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/EditDelegateModal/index.tsx @@ -25,7 +25,6 @@ export const EditDelegateModal = ({ isOpen, onClose, delegate, onSubmit }: EditD const classes = useStyles() const handleSubmit = ({ label }: { label: string }): void => { - console.log('submit label', label) onSubmit(label) onClose() } diff --git a/src/routes/safe/components/Settings/Delegates/index.tsx b/src/routes/safe/components/Settings/Delegates/index.tsx index f7b787476b..3e8dea8836 100644 --- a/src/routes/safe/components/Settings/Delegates/index.tsx +++ b/src/routes/safe/components/Settings/Delegates/index.tsx @@ -1,4 +1,4 @@ -import { ReactElement, useCallback, useEffect, useState } from 'react' +import { ReactElement, useEffect, useState } from 'react' import { useSelector } from 'react-redux' import styled from 'styled-components' import { makeStyles, TableCell, TableContainer, TableRow } from '@material-ui/core' @@ -27,8 +27,9 @@ import { RemoveDelegateModal } from 'src/routes/safe/components/Settings/Delegat import { EditDelegateModal } from 'src/routes/safe/components/Settings/Delegates/EditDelegateModal' import { grantedSelector } from 'src/routes/safe/container/selector' import { DelegateResponse } from '@gnosis.pm/safe-react-gateway-sdk/dist/types/delegates' -import { getDelegates } from '@gnosis.pm/safe-react-gateway-sdk' +import { addDelegate, deleteSafeDelegate } from '@gnosis.pm/safe-react-gateway-sdk' import { currentChainId } from 'src/logic/config/store/selectors' +import { fetchDelegates } from 'src/logic/delegates/api/delegates' const StyledBlock = styled(Block)` minheight: 420px; @@ -62,11 +63,6 @@ const Delegates = (): ReactElement => { const classes = useStyles(styles) - const fetchDelegates = useCallback(async () => { - const { results } = await getDelegates(chainId, { safe: safeAddress }) - setDelegatesList(results) - }, [chainId, safeAddress]) - const getSignature = async (delegate) => { const totp = Math.floor(Date.now() / 1000 / 3600) const msg = checksumAddress(delegate) + totp @@ -80,33 +76,33 @@ const Delegates = (): ReactElement => { useEffect(() => { if (!safeAddress || !transactionService) return - fetchDelegates() - }, [fetchDelegates, safeAddress, transactionService]) + fetchDelegates(chainId, { safe: safeAddress }).then((delegates) => { + setDelegatesList(delegates.results) + }) + }, [chainId, safeAddress, transactionService]) const handleAddDelegate = async ({ address, label }) => { // close Add delegate modal setAddDelegateModalOpen(false) const delegate = checksumAddress(address) - const signature = await getSignature(delegate) - const requestOptions = { - method: 'POST', - headers: { 'Content-type': 'application/json' }, - body: JSON.stringify({ + + try { + await addDelegate(chainId, { safe: safeAddress, - delegate: delegate, - signature: signature, - label: label, - }), + delegate, + delegator: userAccount, + signature, + label, + }) + } catch (e) { + console.error(e) } - const url = `${transactionService}/api/v1/safes/${safeAddress}/delegates/` - fetch(url, requestOptions) - .then((response) => response.json()) - .then(() => { - fetchDelegates() - }) + fetchDelegates(chainId, { safe: safeAddress }).then(({ results }) => { + setDelegatesList(results) + }) } const handleEditDelegateLabel = async (label) => { @@ -116,23 +112,21 @@ const Delegates = (): ReactElement => { const delegate = checksumAddress(delegateToEdit) const signature = await getSignature(delegate) - const requestOptions = { - method: 'POST', - headers: { 'Content-type': 'application/json' }, - body: JSON.stringify({ + try { + await addDelegate(chainId, { safe: safeAddress, - delegate: delegate, - signature: signature, - label: label, - }), + delegate, + delegator: userAccount, + signature, + label, + }) + } catch (e) { + console.error(e) } - const url = `${transactionService}/api/v1/safes/${safeAddress}/delegates/` - fetch(url, requestOptions) - .then((response) => response.json()) - .then(() => { - fetchDelegates() - }) + fetchDelegates(chainId, { safe: safeAddress }).then(({ results }) => { + setDelegatesList(results) + }) } const handleRemoveDelegate = async (address: string) => { @@ -142,18 +136,20 @@ const Delegates = (): ReactElement => { const delegate = checksumAddress(address) const signature = await getSignature(delegate) - const requestOptions = { - method: 'DELETE', - headers: { 'Content-type': 'application/json' }, - body: JSON.stringify({ - signature: signature, - }), + try { + await deleteSafeDelegate(chainId, safeAddress, delegate, { + safe: safeAddress, + delegate, + // delegator: userAccount, + signature, + }) + } catch (e) { + console.error(e) } - const url = `${transactionService}/api/v1/safes/${safeAddress}/delegates/${delegate}/` - fetch(url, requestOptions).then(() => { - setAddressToRemove('') - fetchDelegates() + setAddressToRemove('') + fetchDelegates(chainId, { safe: safeAddress }).then(({ results }) => { + setDelegatesList(results) }) }