Skip to content

Commit

Permalink
Merge branch 'dev' of github.com:safe-global/web-core into feat/updat…
Browse files Browse the repository at this point in the history
…e-safe-apps-sdk
  • Loading branch information
mmv08 committed Aug 3, 2023
2 parents 7e4e943 + e99b511 commit 2c0f831
Show file tree
Hide file tree
Showing 87 changed files with 1,786 additions and 319 deletions.
8 changes: 7 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
"@next/next/no-img-element": "off",
"unused-imports/no-unused-imports-ts": "error",
"@typescript-eslint/consistent-type-imports": "error",
"no-constant-condition": "warn"
"no-constant-condition": "warn",
"react-hooks/exhaustive-deps": [
"warn",
{
"additionalHooks": "useAsync"
}
]
},
"ignorePatterns": ["node_modules/", ".next/", ".github/"],
"plugins": ["unused-imports", "@typescript-eslint"]
Expand Down
8 changes: 5 additions & 3 deletions cypress/e2e/smoke/create_tx.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ describe('Queue a transaction on 1/N', () => {
it('should create a queued transaction', () => {
cy.get('button[type="submit"]').should('not.be.disabled')

cy.wait(1000)

cy.contains('Native token transfer').should('be.visible')

// Changes nonce to next one
Expand All @@ -69,7 +71,7 @@ describe('Queue a transaction on 1/N', () => {
cy.contains('Estimated fee').should('exist')

// Asserting the sponsored info is present
cy.contains('Execute').should('be.visible')
cy.contains('Execute').scrollIntoView().should('be.visible')

cy.get('span').contains('Estimated fee').next().should('have.css', 'text-decoration-line', 'line-through')
cy.contains('Transactions per hour')
Expand All @@ -96,14 +98,14 @@ describe('Queue a transaction on 1/N', () => {
cy.get('@Paramsform').submit()

// Asserts the execute checkbox is uncheckable
cy.contains('No, only').click()
cy.contains('No, later').click()

cy.get('input[name="nonce"]')
.clear({ force: true })
.type(currentNonce + 10, { force: true })
.type('{enter}', { force: true })

cy.contains('Submit').click()
cy.contains('Sign').click()
})

it('should click the notification and see the transaction queued', () => {
Expand Down
2 changes: 1 addition & 1 deletion cypress/e2e/smoke/nfts.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ describe('Assets > NFTs', () => {
cy.contains('1')
cy.contains('2')
cy.get('b:contains("safeTransferFrom")').should('have.length', 2)
cy.contains('button:not([disabled])', 'Submit')
cy.contains('button:not([disabled])', 'Execute')
})
})
})
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"@safe-global/safe-core-sdk-utils": "^1.7.4",
"@safe-global/safe-deployments": "^1.25.0",
"@safe-global/safe-ethers-lib": "^1.9.4",
"@safe-global/safe-gateway-typescript-sdk": "^3.7.3",
"@safe-global/safe-gateway-typescript-sdk": "^3.8.0",
"@safe-global/safe-modules-deployments": "^1.0.0",
"@safe-global/safe-react-components": "^2.0.5",
"@sentry/react": "^7.28.1",
Expand All @@ -69,6 +69,7 @@
"ethereum-blockies-base64": "^1.0.2",
"ethers": "5.7.2",
"exponential-backoff": "^3.1.0",
"framer-motion": "^10.13.1",
"fuse.js": "^6.6.2",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
Expand Down
4 changes: 4 additions & 0 deletions public/images/common/batch.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions public/images/common/drag.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions public/images/common/empty-batch.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions public/images/common/success.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions src/components/batch/BatchIndicator/BatchTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { type ReactElement, useEffect, useState } from 'react'
import { Box, SvgIcon } from '@mui/material'
import Tooltip, { type TooltipProps, tooltipClasses } from '@mui/material/Tooltip'
import { styled } from '@mui/material/styles'
import SuccessIcon from '@/public/images/common/success.svg'
import { TxEvent, txSubscribe } from '@/services/tx/txEvents'

const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} />
))(({ theme }) => ({
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.background.paper,
color: theme.palette.text.primary,
fontSize: theme.typography.pxToRem(16),
fontWeight: 700,
border: `1px solid ${theme.palette.border.light}`,
marginTop: theme.spacing(2) + ' !important',
},
[`& .${tooltipClasses.arrow}`]: {
color: theme.palette.background.paper,
},
[`& .${tooltipClasses.arrow}:before`]: {
border: `1px solid ${theme.palette.border.light}`,
},
}))

const BatchTooltip = ({ children }: { children: ReactElement }) => {
const [showTooltip, setShowTooltip] = useState<boolean>(false)

// Click outside to close the tooltip
useEffect(() => {
const handleClickOutside = () => setShowTooltip(false)
document.addEventListener('click', handleClickOutside)
return () => document.removeEventListener('click', handleClickOutside)
}, [])

// Show tooltip when tx is added to batch
useEffect(() => {
return txSubscribe(TxEvent.BATCH_ADD, () => setShowTooltip(true))
}, [])

return (
<StyledTooltip
open={showTooltip}
onClose={() => setShowTooltip(false)}
arrow
title={
<Box display="flex" flexDirection="column" alignItems="center" p={2} gap={2}>
<Box fontSize="53px">
<SvgIcon component={SuccessIcon} inheritViewBox fontSize="inherit" />
</Box>
Transaction is added to batch
</Box>
}
>
<div>{children}</div>
</StyledTooltip>
)
}

export default BatchTooltip
24 changes: 24 additions & 0 deletions src/components/batch/BatchIndicator/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Badge, ButtonBase, SvgIcon } from '@mui/material'
import BatchIcon from '@/public/images/common/batch.svg'
import { useDraftBatch } from '@/hooks/useDraftBatch'
import Track from '@/components/common/Track'
import { BATCH_EVENTS } from '@/services/analytics'
import BatchTooltip from './BatchTooltip'

const BatchIndicator = ({ onClick }: { onClick?: () => void }) => {
const { length } = useDraftBatch()

return (
<BatchTooltip>
<Track {...BATCH_EVENTS.BATCH_SIDEBAR_OPEN} label={length}>
<ButtonBase onClick={onClick} sx={{ p: 0.5 }}>
<Badge variant="standard" color="secondary" badgeContent={length}>
<SvgIcon component={BatchIcon} inheritViewBox fontSize="small" />
</Badge>
</ButtonBase>
</Track>
</BatchTooltip>
)
}

export default BatchIndicator
110 changes: 110 additions & 0 deletions src/components/batch/BatchSidebar/BatchTxItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { type SyntheticEvent, useMemo, useCallback } from 'react'
import { Accordion, AccordionDetails, AccordionSummary, Box, ButtonBase, SvgIcon } from '@mui/material'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import css from './styles.module.css'
import { type DraftBatchItem } from '@/store/batchSlice'
import TxType from '@/components/transactions/TxType'
import TxInfo from '@/components/transactions/TxInfo'
import DeleteIcon from '@/public/images/common/delete.svg'
import DragIcon from '@/public/images/common/drag.svg'
import TxData from '@/components/transactions/TxDetails/TxData'
import { MethodDetails } from '@/components/transactions/TxDetails/TxData/DecodedData/MethodDetails'
import { TxDataRow } from '@/components/transactions/TxDetails/Summary/TxDataRow'
import { dateString } from '@/utils/formatters'
import { BATCH_EVENTS, trackEvent } from '@/services/analytics'

type BatchTxItemProps = DraftBatchItem & {
id: string
count: number
onDelete?: (id: string) => void
draggable?: boolean
dragging?: boolean
}

const BatchTxItem = ({
id,
count,
timestamp,
txDetails,
onDelete,
dragging = false,
draggable = false,
}: BatchTxItemProps) => {
const txSummary = useMemo(
() => ({
timestamp,
id: txDetails.txId,
txInfo: txDetails.txInfo,
txStatus: txDetails.txStatus,
safeAppInfo: txDetails.safeAppInfo,
}),
[timestamp, txDetails],
)

const handleDelete = useCallback(
(e: SyntheticEvent) => {
e.stopPropagation()
if (confirm('Are you sure you want to delete this transaction?')) {
onDelete?.(id)
trackEvent(BATCH_EVENTS.BATCH_DELETE_TX)
}
},
[onDelete, id],
)

const handleExpand = () => {
trackEvent(BATCH_EVENTS.BATCH_EXPAND_TX)
}

return (
<Box display="flex" gap={2}>
<div className={css.number}>{count}</div>

<Accordion elevation={0} sx={{ flex: 1 }} onChange={handleExpand}>
<AccordionSummary expandIcon={<ExpandMoreIcon />} disabled={dragging} className={css.accordion}>
<Box flex={1} display="flex" alignItems="center" gap={2} py={0.4}>
{draggable && (
<SvgIcon
component={DragIcon}
inheritViewBox
fontSize="small"
className={css.dragHandle}
onClick={(e: MouseEvent) => e.stopPropagation()}
/>
)}

<TxType tx={txSummary} />

<Box flex={1}>
<TxInfo info={txDetails.txInfo} />
</Box>

{onDelete && (
<>
<Box className={css.separator} />

<ButtonBase onClick={handleDelete} sx={{ p: 0.5 }}>
<SvgIcon component={DeleteIcon} inheritViewBox fontSize="small" />
</ButtonBase>

<Box className={css.separator} mr={2} />
</>
)}
</Box>
</AccordionSummary>

<AccordionDetails>
<div className={css.details}>
<TxData txDetails={txDetails} />

<TxDataRow title="Created:">{timestamp ? dateString(timestamp) : null}</TxDataRow>

{txDetails.txData?.dataDecoded && <MethodDetails data={txDetails.txData.dataDecoded} />}
</div>
</AccordionDetails>
</Accordion>
</Box>
)
}

export default BatchTxItem
43 changes: 43 additions & 0 deletions src/components/batch/BatchSidebar/BatchTxList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Reorder } from 'framer-motion'
import type { DraftBatchItem } from '@/store/batchSlice'
import BatchTxItem from './BatchTxItem'
import { useState } from 'react'

const BatchTxList = ({ txItems, onDelete }: { txItems: DraftBatchItem[]; onDelete?: (id: string) => void }) => {
return (
<>
{txItems.map((item, index) => (
<BatchTxItem key={item.id} count={index + 1} {...item} onDelete={onDelete} />
))}
</>
)
}

export const BatchReorder = ({
txItems,
onDelete,
onReorder,
}: {
txItems: DraftBatchItem[]
onDelete?: (id: string) => void
onReorder: (items: DraftBatchItem[]) => void
}) => {
const [dragging, setDragging] = useState(false)

return (
<Reorder.Group axis="y" values={txItems} onReorder={onReorder}>
{txItems.map((item, index) => (
<Reorder.Item
key={item.id}
value={item}
onDragStart={() => setDragging(true)}
onDragEnd={() => setDragging(false)}
>
<BatchTxItem count={index + 1} {...item} onDelete={onDelete} draggable dragging={dragging} />
</Reorder.Item>
))}
</Reorder.Group>
)
}

export default BatchTxList
51 changes: 51 additions & 0 deletions src/components/batch/BatchSidebar/EmptyBatch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { type ReactNode } from 'react'
import EmptyBatchIcon from '@/public/images/common/empty-batch.svg'
import InfoIcon from '@/public/images/notifications/info.svg'
import AssetsIcon from '@/public/images/sidebar/assets.svg'
import AppsIcon from '@/public/images/apps/apps-icon.svg'
import SettingsIcon from '@/public/images/sidebar/settings.svg'
import { Box, SvgIcon, Typography } from '@mui/material'

const EmptyBatch = ({ children }: { children: ReactNode }) => (
<Box display="flex" flexWrap="wrap" justifyContent="center" textAlign="center" mt={3} px={4}>
<SvgIcon component={EmptyBatchIcon} inheritViewBox sx={{ fontSize: 110 }} />

<Typography variant="h4" fontWeight={700}>
Add an initial transaction to the batch
</Typography>

<Typography variant="body2" mt={2} mb={4} px={8} sx={{ textWrap: 'balance' }}>
Save gas and signatures by adding multiple Safe transactions to a single batch transaction. You can reorder and
delete individual transactions in a batch.
</Typography>

{children}

<Typography variant="body2" color="border.main" mt={8}>
<Box mb={1}>
<SvgIcon component={InfoIcon} inheritViewBox />
</Box>

<b>What type of transactions can you add to the batch?</b>

<Box display="flex" mt={3} gap={6}>
<div>
<SvgIcon component={AssetsIcon} inheritViewBox />
<div>Token and NFT transfers</div>
</div>

<div>
<SvgIcon component={AppsIcon} inheritViewBox />
<div>Safe App transactions</div>
</div>

<div>
<SvgIcon component={SettingsIcon} inheritViewBox />
<div>Safe Account settings</div>
</div>
</Box>
</Typography>
</Box>
)

export default EmptyBatch
Loading

0 comments on commit 2c0f831

Please sign in to comment.