Skip to content

Commit

Permalink
Highlight risk checkbox on submit
Browse files Browse the repository at this point in the history
  • Loading branch information
katspaugh committed Jul 3, 2023
1 parent 5f21977 commit 8a71c08
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 33 deletions.
12 changes: 4 additions & 8 deletions src/components/tx/SignOrExecuteForm/ExecuteForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,27 @@ import { asError } from '@/services/exceptions/utils'

import css from './styles.module.css'
import commonCss from '@/components/tx-flow/common/styles.module.css'
import RiskConfirmationError from './RiskConfirmationError'
import { TxSecurityContext } from '../security/shared/TxSecurityContext'

const ExecuteForm = ({
safeTx,
txId,
onSubmit,
disableSubmit = false,
origin,
risk,
}: SignOrExecuteProps & {
safeTx?: SafeTransaction
}): ReactElement => {
// Form state
const [isSubmittable, setIsSubmittable] = useState<boolean>(true)
const [submitError, setSubmitError] = useState<Error | undefined>()
const [submitRisk, setSubmitRisk] = useState<boolean>(false)

// Hooks
const currentChain = useCurrentChain()
const { executeTx } = useTxActions()
const [relays] = useRelaysBySafe()
const { setTxFlow } = useContext(TxModalContext)
const { needsRiskConfirmation, isRiskConfirmed, setIsRiskIgnored } = useContext(TxSecurityContext)

// Check that the transaction is executable
const isCreation = !txId
Expand Down Expand Up @@ -72,12 +71,11 @@ const ExecuteForm = ({
const handleSubmit = async (e: SyntheticEvent) => {
e.preventDefault()

if (risk) {
setSubmitRisk(true)
if (needsRiskConfirmation && !isRiskConfirmed) {
setIsRiskIgnored(true)
return
}

setSubmitRisk(false)
setIsSubmittable(false)
setSubmitError(undefined)

Expand Down Expand Up @@ -135,8 +133,6 @@ const ExecuteForm = ({
? 'To save gas costs, avoid creating the transaction.'
: 'To save gas costs, reject this transaction.'}
</ErrorMessage>
) : submitRisk ? (
<RiskConfirmationError />
) : (
submitError && (
<ErrorMessage error={submitError}>Error submitting the transaction. Please try again.</ErrorMessage>
Expand Down
14 changes: 11 additions & 3 deletions src/components/tx/SignOrExecuteForm/RiskConfirmationError.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { useContext } from 'react'
import ErrorMessage from '../ErrorMessage'
import { TxSecurityContext } from '../security/shared/TxSecurityContext'

const RiskConfirmationError = () => (
<ErrorMessage level="warning">Please acknowledge the risk before proceeding.</ErrorMessage>
)
const RiskConfirmationError = () => {
const { isRiskConfirmed, isRiskIgnored } = useContext(TxSecurityContext)

if (isRiskConfirmed || !isRiskIgnored) {
return null
}

return <ErrorMessage level="warning">Please acknowledge the risk before proceeding.</ErrorMessage>
}

export default RiskConfirmationError
12 changes: 4 additions & 8 deletions src/components/tx/SignOrExecuteForm/SignForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,36 @@ import type { SafeTransaction } from '@safe-global/safe-core-sdk-types'
import { TxModalContext } from '@/components/tx-flow'
import { asError } from '@/services/exceptions/utils'
import commonCss from '@/components/tx-flow/common/styles.module.css'
import RiskConfirmationError from './RiskConfirmationError'
import { TxSecurityContext } from '../security/shared/TxSecurityContext'

const SignForm = ({
safeTx,
txId,
onSubmit,
disableSubmit = false,
origin,
risk,
}: SignOrExecuteProps & {
safeTx?: SafeTransaction
}): ReactElement => {
// Form state
const [isSubmittable, setIsSubmittable] = useState<boolean>(true)
const [submitError, setSubmitError] = useState<Error | undefined>()
const [submitRisk, setSubmitRisk] = useState<boolean>(false)

// Hooks
const isOwner = useIsSafeOwner()
const { signTx } = useTxActions()
const { setTxFlow } = useContext(TxModalContext)
const { needsRiskConfirmation, isRiskConfirmed, setIsRiskIgnored } = useContext(TxSecurityContext)

// On modal submit
const handleSubmit = async (e: SyntheticEvent) => {
e.preventDefault()

if (risk) {
setSubmitRisk(true)
if (needsRiskConfirmation && !isRiskConfirmed) {
setIsRiskIgnored(true)
return
}

setSubmitRisk(false)
setIsSubmittable(false)
setSubmitError(undefined)

Expand Down Expand Up @@ -70,8 +68,6 @@ const SignForm = ({
<ErrorMessage>
You are currently not an owner of this Safe Account and won&apos;t be able to submit this transaction.
</ErrorMessage>
) : submitRisk ? (
<RiskConfirmationError />
) : (
submitError && (
<ErrorMessage error={submitError}>Error submitting the transaction. Please try again.</ErrorMessage>
Expand Down
13 changes: 4 additions & 9 deletions src/components/tx/SignOrExecuteForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import ConfirmationTitle, { ConfirmationTitleTypes } from '@/components/tx/SignO
import { useAppSelector } from '@/store'
import { selectSettings } from '@/store/settingsSlice'
import { RedefineBalanceChanges } from '../security/redefine/RedefineBalanceChange'
import { TxSecurityContext } from '../security/shared/TxSecurityContext'
import UnknownContractError from './UnknownContractError'
import RiskConfirmationError from './RiskConfirmationError'

export type SignOrExecuteProps = {
txId?: string
Expand All @@ -25,7 +25,6 @@ export type SignOrExecuteProps = {
onlyExecute?: boolean
disableSubmit?: boolean
origin?: string
risk?: boolean
}

const SignOrExecuteForm = (props: SignOrExecuteProps): ReactElement => {
Expand All @@ -34,13 +33,11 @@ const SignOrExecuteForm = (props: SignOrExecuteProps): ReactElement => {
const isCreation = !props.txId
const isNewExecutableTx = useImmediatelyExecutable() && isCreation
const { safeTx, safeTxError } = useContext(SafeTxContext)
const { needsRiskConfirmation, isRiskConfirmed } = useContext(TxSecurityContext)
const isCorrectNonce = useValidateNonce(safeTx)

// If checkbox is checked and the transaction is executable, execute it, otherwise sign it
const canExecute = isCorrectNonce && (props.isExecutable || isNewExecutableTx)
const willExecute = (props.onlyExecute || shouldExecute) && canExecute
const isRisky = needsRiskConfirmation && !isRiskConfirmed

return (
<>
Expand Down Expand Up @@ -74,11 +71,9 @@ const SignOrExecuteForm = (props: SignOrExecuteProps): ReactElement => {

<UnknownContractError />

{willExecute ? (
<ExecuteForm {...props} safeTx={safeTx} risk={isRisky} />
) : (
<SignForm {...props} safeTx={safeTx} risk={isRisky} />
)}
<RiskConfirmationError />

{willExecute ? <ExecuteForm {...props} safeTx={safeTx} /> : <SignForm {...props} safeTx={safeTx} />}
</TxCard>
</>
)
Expand Down
27 changes: 23 additions & 4 deletions src/components/tx/security/redefine/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext } from 'react'
import { useContext, useEffect, useRef } from 'react'
import { mapRedefineSeverity } from '@/components/tx/security/redefine/useRedefine'
import { TxSecurityContext } from '@/components/tx/security/shared/TxSecurityContext'
import { SecuritySeverity } from '@/services/security/modules/types'
Expand All @@ -22,8 +22,17 @@ import { RedefineHint } from '@/components/tx/security/redefine/RedefineHint'
const MAX_SHOWN_WARNINGS = 3

const RedefineBlock = () => {
const { severity, isLoading, error, needsRiskConfirmation, isRiskConfirmed, setIsRiskConfirmed } =
useContext(TxSecurityContext)
const {
severity,
isLoading,
error,
needsRiskConfirmation,
isRiskConfirmed,
setIsRiskConfirmed,
isRiskIgnored,
setIsRiskIgnored,
} = useContext(TxSecurityContext)
const checkboxRef = useRef<HTMLElement>(null)

const isDarkMode = useDarkMode()
const severityProps = severity !== undefined ? mapRedefineSeverity[severity] : undefined
Expand All @@ -32,6 +41,15 @@ const RedefineBlock = () => {
setIsRiskConfirmed((prev) => !prev)
}

// Highlight checkbox if user tries to submit transaction without confirming risks
useEffect(() => {
if (isRiskIgnored && checkboxRef.current) {
checkboxRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' })
const timeout = setTimeout(() => setIsRiskIgnored(false), 3000)
return () => clearTimeout(timeout)
}
}, [isRiskIgnored, setIsRiskIgnored, checkboxRef])

return (
<div className={css.wrapperBox}>
<Paper
Expand Down Expand Up @@ -81,11 +99,12 @@ const RedefineBlock = () => {
</Paper>
<div>
{needsRiskConfirmation && (
<Box pl={2}>
<Box pl={2} ref={checkboxRef}>
<Track {...MODALS_EVENTS.ACCEPT_RISK}>
<FormControlLabel
label="I understand the risks and would like to continue this transaction"
control={<Checkbox checked={isRiskConfirmed} onChange={toggleConfirmation} />}
className={isRiskIgnored ? css.checkboxError : ''}
/>
</Track>
</Box>
Expand Down
21 changes: 21 additions & 0 deletions src/components/tx/security/redefine/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,24 @@
grid-template-columns: 35% auto;
padding: var(--space-2) 12px;
}

@keyframes popup {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}

.checkboxError {
color: var(--color-error-main);
animation: popup 0.5s ease-in-out;
}

.checkboxError svg {
color: var(--color-error-main) !important;
}
9 changes: 8 additions & 1 deletion src/components/tx/security/shared/TxSecurityContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export const TxSecurityContext = createContext<{
needsRiskConfirmation: boolean
isRiskConfirmed: boolean
setIsRiskConfirmed: Dispatch<SetStateAction<boolean>>
isRiskIgnored: boolean
setIsRiskIgnored: Dispatch<SetStateAction<boolean>>
}>({
warnings: [],
simulationUuid: undefined,
Expand All @@ -24,12 +26,15 @@ export const TxSecurityContext = createContext<{
needsRiskConfirmation: false,
isRiskConfirmed: false,
setIsRiskConfirmed: () => {},
isRiskIgnored: false,
setIsRiskIgnored: () => {},
})

export const TxSecurityProvider = ({ children }: { children: JSX.Element }) => {
const { safeTx } = useContext(SafeTxContext)
const [redefineResponse, redefineError, redefineLoading] = useRedefine(safeTx)
const [isRiskConfirmed, setIsRiskConfirmed] = useState(false)
const [isRiskIgnored, setIsRiskIgnored] = useState(false)

const providedValue = useMemo(
() => ({
Expand All @@ -42,8 +47,10 @@ export const TxSecurityProvider = ({ children }: { children: JSX.Element }) => {
needsRiskConfirmation: !!redefineResponse && redefineResponse.severity >= SecuritySeverity.HIGH,
isRiskConfirmed,
setIsRiskConfirmed,
isRiskIgnored: isRiskIgnored && !isRiskConfirmed,
setIsRiskIgnored,
}),
[isRiskConfirmed, redefineError, redefineLoading, redefineResponse],
[isRiskConfirmed, isRiskIgnored, redefineError, redefineLoading, redefineResponse],
)

return <TxSecurityContext.Provider value={providedValue}>{children}</TxSecurityContext.Provider>
Expand Down

0 comments on commit 8a71c08

Please sign in to comment.