Skip to content

Commit 47a2c3e

Browse files
fix: restyle account security/delete sections and add mobile navbar gap (#676)
* fix: full-bleed account security/delete sections with white copy and a delete acknowledgement gate * fix: add a top gap below the navbar on account section screens (mobile) * fix: use present tense in the delete acknowledgement copy
1 parent 0c376a2 commit 47a2c3e

13 files changed

Lines changed: 267 additions & 64 deletions

File tree

src/components/account/DeleteAccount/DeleteAccountSection/DeleteAccountSection.spec.tsx

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import type { ReactNode } from 'react'
1+
import type { ChangeEventHandler, ReactNode } from 'react'
22
import { fireEvent, render, screen } from '@testing-library/react'
33
import { DeleteAccountSection } from './DeleteAccountSection'
44

55
type ChildrenProps = { children?: ReactNode; 'data-role'?: string }
66
type ButtonProps = ChildrenProps & { onClick?: () => void; disabled?: boolean }
7+
type ControlProps = { control?: ReactNode; label?: ReactNode }
8+
type CheckboxProps = { checked?: boolean; onChange?: ChangeEventHandler<HTMLInputElement>; 'data-role'?: string }
79

810
jest.mock('@mui/icons-material/AccountCircleRounded', () => ({ __esModule: true, default: () => <span /> }))
911
jest.mock('@mui/icons-material/CardGiftcardRounded', () => ({ __esModule: true, default: () => <span /> }))
@@ -37,6 +39,16 @@ jest.mock('./DeleteAccountSection.styled', () => ({
3739
{children}
3840
</button>
3941
),
42+
AcknowledgeControl: ({ control, label }: ControlProps) => (
43+
<label>
44+
{control}
45+
{label}
46+
</label>
47+
),
48+
AcknowledgeCheckbox: ({ checked, onChange, 'data-role': dataRole }: CheckboxProps) => (
49+
<input type="checkbox" checked={checked} onChange={onChange} data-role={dataRole} />
50+
),
51+
AcknowledgeLabel: ({ children }: ChildrenProps) => <span>{children}</span>,
4052
DeleteButton: ({ children, onClick, disabled, 'data-role': dataRole }: ButtonProps) => (
4153
<button type="button" data-role={dataRole} onClick={onClick} disabled={disabled}>
4254
{children}
@@ -89,12 +101,37 @@ describe('DeleteAccountSection', () => {
89101
expect(screen.getByText('account.delete.asset_warning_title')).toBeInTheDocument()
90102
})
91103

92-
it('should open the confirm modal when the delete button is clicked', () => {
104+
it('should render the acknowledgement checkbox above the delete button', () => {
93105
renderSection(ADDRESS)
94106

95-
fireEvent.click(screen.getByRole('button', { name: 'account.delete.delete_button' }))
107+
expect(screen.getByRole('checkbox', { name: 'account.delete.acknowledge' })).toBeInTheDocument()
108+
})
109+
110+
describe('when the acknowledgement checkbox is unchecked', () => {
111+
it('should keep the delete button disabled', () => {
112+
renderSection(ADDRESS)
113+
114+
expect(screen.getByRole('button', { name: 'account.delete.delete_button' })).toBeDisabled()
115+
})
116+
})
117+
118+
describe('when the acknowledgement checkbox is checked', () => {
119+
it('should enable the delete button', () => {
120+
renderSection(ADDRESS)
121+
122+
fireEvent.click(screen.getByRole('checkbox', { name: 'account.delete.acknowledge' }))
123+
124+
expect(screen.getByRole('button', { name: 'account.delete.delete_button' })).toBeEnabled()
125+
})
96126

97-
expect(onOpenConfirmModal).toHaveBeenCalledTimes(1)
127+
it('should open the confirm modal when the delete button is clicked', () => {
128+
renderSection(ADDRESS)
129+
130+
fireEvent.click(screen.getByRole('checkbox', { name: 'account.delete.acknowledge' }))
131+
fireEvent.click(screen.getByRole('button', { name: 'account.delete.delete_button' }))
132+
133+
expect(onOpenConfirmModal).toHaveBeenCalledTimes(1)
134+
})
98135
})
99136

100137
it('should navigate to wallets when a non-Magic account clicks the export key link', () => {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
jest.mock('decentraland-ui2', () => {
2+
const actual = jest.requireActual('../../../../__test-utils__/styledMock')
3+
return { ...actual, Typography: actual.Box, Button: actual.Box, Checkbox: actual.Box, FormControlLabel: actual.Box }
4+
})
5+
6+
import { render } from '@testing-library/react'
7+
import {
8+
AcknowledgeCheckbox,
9+
AcknowledgeControl,
10+
AcknowledgeLabel,
11+
AssetWarningBox,
12+
AssetWarningDescription,
13+
AssetWarningTextWrapper,
14+
AssetWarningTitle,
15+
BannerTextWrapper,
16+
ConsequenceIcon,
17+
ConsequenceItem,
18+
ConsequenceText,
19+
ConsequenceTitle,
20+
ConsequencesList,
21+
Container,
22+
DangerBanner,
23+
DangerBannerDescription,
24+
DangerBannerTitle,
25+
DeleteButton,
26+
ExportKeyDescription,
27+
ExportKeyLink,
28+
WarningCard,
29+
WarningDescription
30+
} from './DeleteAccountSection.styled'
31+
32+
describe('DeleteAccountSection styled components', () => {
33+
it('renders every styled component', () => {
34+
render(
35+
<>
36+
<Container />
37+
<DangerBanner />
38+
<BannerTextWrapper />
39+
<DangerBannerTitle />
40+
<DangerBannerDescription />
41+
<WarningCard />
42+
<WarningDescription />
43+
<ConsequencesList />
44+
<ConsequenceItem />
45+
<ConsequenceIcon />
46+
<ConsequenceText />
47+
<ConsequenceTitle />
48+
<AssetWarningBox />
49+
<AssetWarningTextWrapper />
50+
<AssetWarningTitle />
51+
<AssetWarningDescription />
52+
<ExportKeyDescription />
53+
<ExportKeyLink />
54+
<AcknowledgeControl control={<span />} label="acknowledge" />
55+
<AcknowledgeCheckbox />
56+
<AcknowledgeLabel />
57+
<DeleteButton />
58+
</>
59+
)
60+
})
61+
})

src/components/account/DeleteAccount/DeleteAccountSection/DeleteAccountSection.styled.ts

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
1-
import { Box, Button, Typography, styled } from 'decentraland-ui2'
1+
import { Box, Button, Checkbox, FormControlLabel, Typography, styled } from 'decentraland-ui2'
22

33
// Figma 797:78245 — Delete Account section. Colours mirror the DCL design
4-
// tokens used across the Account area: destructive DCL Red (#FF2D55), warning amber (#FFA500),
5-
// text #FCFCFC, muted #A09BA8. The warning card sits on the translucent panel (rgba(0,0,0,0.2)).
6-
// Hardcoded hexes follow the sibling Account styled files.
7-
8-
const Container = styled(Box)({
4+
// tokens used across the Account area: destructive DCL Red (#FF2D55) and warning amber (#FFA500) stay
5+
// as semantic accents; all body copy is white (#FCFCFC), never muted. Hardcoded hexes follow the
6+
// sibling Account styled files.
7+
8+
// The section is wrapped in the same translucent rounded panel that surrounds the Security section
9+
// (mirrors SecuritySection's Container), and stretches the full width of the account content column
10+
// (no maxWidth) so it matches the other account sections.
11+
const Container = styled(Box)(({ theme }) => ({
912
display: 'flex',
1013
flexDirection: 'column',
11-
gap: 16,
12-
maxWidth: 660
13-
})
14+
gap: theme.spacing(2),
15+
padding: theme.spacing(2),
16+
borderRadius: theme.spacing(2),
17+
background: 'rgba(0, 0, 0, 0.2)',
18+
[theme.breakpoints.up('md')]: {
19+
padding: theme.spacing(3)
20+
}
21+
}))
1422

1523
const DangerBanner = styled(Box)({
1624
display: 'flex',
@@ -36,16 +44,14 @@ const DangerBannerTitle = styled(Typography)({
3644
})
3745

3846
const DangerBannerDescription = styled(Typography)({
39-
color: '#A09BA8',
47+
color: '#FCFCFC',
4048
fontSize: 13,
4149
lineHeight: '20px'
4250
})
4351

44-
const WarningCard = styled(Box)({
45-
padding: 24,
46-
borderRadius: 8,
47-
backgroundColor: 'rgba(0, 0, 0, 0.2)'
48-
})
52+
// The section's outer Container is now the single translucent panel (matching the Security section),
53+
// so this no longer draws its own background — it only groups the "lost forever" copy + list.
54+
const WarningCard = styled(Box)({})
4955

5056
const WarningDescription = styled(Typography)({
5157
color: '#FCFCFC',
@@ -65,7 +71,7 @@ const ConsequenceItem = styled('li')({
6571
display: 'flex',
6672
gap: 10,
6773
marginBottom: 12,
68-
color: '#CFCDD4',
74+
color: '#FCFCFC',
6975
fontSize: 14,
7076
lineHeight: '22px',
7177
alignItems: 'flex-start',
@@ -87,7 +93,7 @@ const ConsequenceIcon = styled('span')({
8793
})
8894

8995
const ConsequenceText = styled('span')({
90-
color: '#CFCDD4'
96+
color: '#FCFCFC'
9197
})
9298

9399
const ConsequenceTitle = styled('span')({
@@ -119,49 +125,76 @@ const AssetWarningTitle = styled(Typography)({
119125
})
120126

121127
const AssetWarningDescription = styled(Typography)({
122-
color: '#A09BA8',
128+
color: '#FCFCFC',
123129
fontSize: 13,
124130
lineHeight: '20px'
125131
})
126132

127133
const ExportKeyDescription = styled(Typography)({
128-
color: '#A09BA8',
134+
color: '#FCFCFC',
129135
fontSize: 13,
130136
lineHeight: '20px',
131137
marginTop: 4
132138
})
133139

140+
// Rendered with variant="outlined" color="inherit" so it picks up the theme's NEUTRAL outlined
141+
// styling — a white (text.primary) label + subtle border that stays white on hover. The default
142+
// (primary) outlined variant turns the label red on hover via a high-specificity theme rule that a
143+
// styled() override can't beat, so we switch the colour scheme instead of fighting it. Only layout
144+
// (spacing + left alignment) is customised here; the rest comes from the theme.
134145
const ExportKeyLink = styled(Button)({
135-
color: '#FFA500',
136-
fontSize: 13,
137-
fontWeight: 600,
138-
marginTop: 4,
146+
marginTop: 8,
147+
alignSelf: 'flex-start'
148+
})
149+
150+
// Confirmation gate above the delete button: the user must tick it to enable deletion.
151+
const AcknowledgeControl = styled(FormControlLabel)({
152+
alignItems: 'flex-start',
153+
margin: 0,
154+
marginTop: 8,
155+
gap: 8
156+
})
157+
158+
const AcknowledgeCheckbox = styled(Checkbox)({
139159
padding: 0,
140-
minWidth: 'auto',
141-
textTransform: 'none',
142-
justifyContent: 'flex-start',
143-
alignSelf: 'flex-start',
144-
['&:hover']: {
145-
textDecoration: 'underline',
146-
backgroundColor: 'transparent'
160+
color: 'rgba(252, 252, 252, 0.6)',
161+
['&.Mui-checked']: {
162+
color: '#FF2D55'
147163
}
148164
})
149165

150-
const DeleteButton = styled(Button)({
166+
const AcknowledgeLabel = styled(Typography)({
167+
color: '#FCFCFC',
168+
fontSize: 13,
169+
lineHeight: '20px',
170+
// The row is top-aligned (AcknowledgeControl uses alignItems: flex-start) so multi-line copy flows
171+
// correctly beneath the first line. Nudge the label down 2px so the first line's optical centre
172+
// lines up with the 24px checkbox instead of sitting slightly above it — (24 - 20) / 2.
173+
marginTop: 2
174+
})
175+
176+
// Figma 797:78245 — left-aligned destructive action. On mobile it stretches the full width of the
177+
// section (alignSelf: stretch); from md up it shrinks to its natural width, still left-aligned.
178+
const DeleteButton = styled(Button)(({ theme }) => ({
151179
marginTop: 8,
152-
// Figma 797:78245 — the destructive action sits at the bottom-right of the section.
153-
alignSelf: 'flex-end',
180+
alignSelf: 'stretch',
154181
backgroundColor: '#FF2D55',
182+
[theme.breakpoints.up('md')]: {
183+
alignSelf: 'flex-start'
184+
},
155185
['&:hover']: {
156186
backgroundColor: '#E0264B'
157187
},
158188
['&.Mui-disabled']: {
159189
backgroundColor: 'rgba(255, 45, 85, 0.4)',
160190
color: 'rgba(252, 252, 252, 0.6)'
161191
}
162-
})
192+
}))
163193

164194
export {
195+
AcknowledgeCheckbox,
196+
AcknowledgeControl,
197+
AcknowledgeLabel,
165198
AssetWarningBox,
166199
AssetWarningDescription,
167200
AssetWarningTextWrapper,

src/components/account/DeleteAccount/DeleteAccountSection/DeleteAccountSection.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useState } from 'react'
12
// eslint-disable-next-line @typescript-eslint/naming-convention
23
import AccountCircleRoundedIcon from '@mui/icons-material/AccountCircleRounded'
34
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -17,6 +18,9 @@ import WarningAmberRoundedIcon from '@mui/icons-material/WarningAmberRounded'
1718
import { useFormatMessage } from '../../../../hooks/adapters/useFormatMessage'
1819
import { DeleteAccountSectionProps } from './DeleteAccountSection.types'
1920
import {
21+
AcknowledgeCheckbox,
22+
AcknowledgeControl,
23+
AcknowledgeLabel,
2024
AssetWarningBox,
2125
AssetWarningDescription,
2226
AssetWarningTextWrapper,
@@ -57,6 +61,9 @@ const CONSEQUENCES = [
5761
const DeleteAccountSection = (props: DeleteAccountSectionProps) => {
5862
const { address, isMagic, onOpenConfirmModal, onGoToWallets, onGoToSecurity } = props
5963
const t = useFormatMessage()
64+
// Deletion is gated on an explicit acknowledgement that the private key is backed up and the action
65+
// is irreversible — the delete button stays disabled until this is ticked.
66+
const [hasAcknowledged, setHasAcknowledged] = useState(false)
6067

6168
return (
6269
<Container data-role="delete-account-section">
@@ -95,7 +102,8 @@ const DeleteAccountSection = (props: DeleteAccountSectionProps) => {
95102
{t(isMagic ? 'account.delete.export_key_description_magic' : 'account.delete.export_key_description')}
96103
</ExportKeyDescription>
97104
<ExportKeyLink
98-
variant="text"
105+
variant="outlined"
106+
color="inherit"
99107
onClick={isMagic ? onGoToSecurity : onGoToWallets}
100108
data-role={isMagic ? 'delete-account-go-to-security' : 'delete-account-go-to-wallets'}
101109
>
@@ -104,7 +112,23 @@ const DeleteAccountSection = (props: DeleteAccountSectionProps) => {
104112
</AssetWarningTextWrapper>
105113
</AssetWarningBox>
106114

107-
<DeleteButton variant="contained" disabled={!address} onClick={onOpenConfirmModal} data-role="delete-account-open-confirm">
115+
<AcknowledgeControl
116+
control={
117+
<AcknowledgeCheckbox
118+
checked={hasAcknowledged}
119+
onChange={event => setHasAcknowledged(event.target.checked)}
120+
data-role="delete-account-acknowledge"
121+
/>
122+
}
123+
label={<AcknowledgeLabel>{t('account.delete.acknowledge')}</AcknowledgeLabel>}
124+
/>
125+
126+
<DeleteButton
127+
variant="contained"
128+
disabled={!address || !hasAcknowledged}
129+
onClick={onOpenConfirmModal}
130+
data-role="delete-account-open-confirm"
131+
>
108132
{t('account.delete.delete_button')}
109133
</DeleteButton>
110134
</Container>

0 commit comments

Comments
 (0)