-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add SecurityWarningModal to the layout page
- Loading branch information
1 parent
4e0fdc1
commit 7fab6cf
Showing
9 changed files
with
274 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
85 changes: 85 additions & 0 deletions
85
apps/web/src/components/SecurityWarningModal/SecurityWarningModal.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { SecurityWarningModal } from "./SecurityWarningModal"; | ||
import { | ||
act, | ||
dynamicModalContextMock, | ||
renderInModal, | ||
screen, | ||
userEvent, | ||
waitFor, | ||
} from "../../testUtils"; | ||
|
||
beforeEach(() => { | ||
localStorage.clear(); | ||
}); | ||
|
||
describe("<SecurityWarningModal />", () => { | ||
it("renders the modal with correct title and content", async () => { | ||
await renderInModal(<SecurityWarningModal />); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText("Browser Extension Security Tips")).toBeVisible(); | ||
}); | ||
|
||
expect( | ||
screen.getByText( | ||
"Please carefully review these guidelines to protect your wallet from potential security risks" | ||
) | ||
).toBeVisible(); | ||
}); | ||
|
||
it("renders all accordion items", async () => { | ||
await renderInModal(<SecurityWarningModal />); | ||
|
||
const expectedTitles = [ | ||
"Install Extensions Only from Trusted Sources", | ||
"Review Permissions and Ratings", | ||
"Maintain a Separate Browser for Financial Activities", | ||
"Keep Your Browser Updated", | ||
"Stay Alert to Social Engineering Risks", | ||
]; | ||
|
||
expectedTitles.forEach(title => { | ||
expect(screen.getByText(title)).toBeVisible(); | ||
}); | ||
}); | ||
|
||
it("disables 'Got it' button when not all items are opened", async () => { | ||
await renderInModal(<SecurityWarningModal />); | ||
|
||
const button = screen.getByRole("button", { name: "Got it" }); | ||
expect(button).toBeDisabled(); | ||
}); | ||
|
||
it("enables 'Got it' button when all items are opened", async () => { | ||
const user = userEvent.setup(); | ||
await renderInModal(<SecurityWarningModal />); | ||
|
||
const accordionButtons = screen.getAllByTestId("accordion-button"); | ||
for (const button of accordionButtons) { | ||
await act(() => user.click(button)); | ||
} | ||
|
||
const gotItButton = screen.getByRole("button", { name: "Got it" }); | ||
expect(gotItButton).toBeEnabled(); | ||
}); | ||
|
||
it("sets localStorage and closes modal when 'Got it' is clicked", async () => { | ||
const { onClose } = dynamicModalContextMock; | ||
const user = userEvent.setup(); | ||
await renderInModal(<SecurityWarningModal />); | ||
|
||
const accordionButtons = screen.getAllByTestId("accordion-button"); | ||
for (const button of accordionButtons) { | ||
await act(() => user.click(button)); | ||
} | ||
|
||
const gotItButton = screen.getByRole("button", { name: "Got it" }); | ||
await act(() => user.click(gotItButton)); | ||
|
||
await waitFor(() => { | ||
expect(localStorage.getItem("user:isInformed")).toBe("true"); | ||
}); | ||
|
||
expect(onClose).toHaveBeenCalled(); | ||
}); | ||
}); |
150 changes: 150 additions & 0 deletions
150
apps/web/src/components/SecurityWarningModal/SecurityWarningModal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import { | ||
Accordion, | ||
AccordionButton, | ||
AccordionIcon, | ||
AccordionItem, | ||
AccordionPanel, | ||
Button, | ||
Flex, | ||
Heading, | ||
Icon, | ||
ModalBody, | ||
ModalContent, | ||
ModalFooter, | ||
ModalHeader, | ||
Text, | ||
} from "@chakra-ui/react"; | ||
import { useDynamicModalContext } from "@umami/components"; | ||
import { useState } from "react"; | ||
|
||
import { | ||
AlertCircleIcon, | ||
AlertIcon, | ||
CheckmarkIcon, | ||
LockIcon, | ||
RefreshIcon, | ||
ThumbsUpIcon, | ||
} from "../../assets/icons"; | ||
import { useColor } from "../../styles/useColor"; | ||
|
||
const accordionItems = [ | ||
{ | ||
icon: CheckmarkIcon, | ||
title: "Install Extensions Only from Trusted Sources", | ||
content: | ||
"Use only official platforms like the Chrome Web Store or Firefox Add-ons, as these include security reviews. Avoid third-party websites and direct download links.", | ||
}, | ||
{ | ||
icon: ThumbsUpIcon, | ||
title: "Review Permissions and Ratings", | ||
content: | ||
"Before installing, check extension reviews, download counts, and requested permissions. Extensions that ask for access to sensitive data (like local storage or clipboard) should be trusted and necessary.", | ||
}, | ||
{ | ||
icon: LockIcon, | ||
title: "Maintain a Separate Browser for Financial Activities", | ||
content: | ||
"Use a dedicated browser with no extensions installed. This minimizes risk by isolating financial activities from other browsing.", | ||
}, | ||
{ | ||
icon: RefreshIcon, | ||
title: "Keep Your Browser Updated", | ||
content: | ||
"Regularly update your browser and extensions to ensure you have the latest security features and bug fixes.", | ||
}, | ||
{ | ||
icon: AlertCircleIcon, | ||
title: "Stay Alert to Social Engineering Risks", | ||
content: | ||
"Avoid installing extensions prompted by emails, ads, or pop-ups, as these may use deceptive methods to gain access. Reliable services generally don’t push extensions, so question any unexpected installation requests.", | ||
}, | ||
]; | ||
|
||
export const SecurityWarningModal = () => { | ||
const { onClose } = useDynamicModalContext(); | ||
const color = useColor(); | ||
const [openedAccordionItems, setOpenedAccordionItems] = useState<Set<number>>(new Set()); | ||
|
||
const handleInform = () => { | ||
localStorage.setItem("user:isInformed", "true"); | ||
onClose(); | ||
}; | ||
|
||
return ( | ||
<ModalContent> | ||
<ModalHeader> | ||
<Flex alignItems="center" justifyContent="center" flexDirection="column" gap="16px"> | ||
<AlertIcon width="22px" color={color("400")} /> | ||
<Heading size="xl">Browser Extension Security Tips</Heading> | ||
<Text | ||
width="full" | ||
marginTop="-4px" | ||
color={color("700")} | ||
fontWeight="400" | ||
textAlign="center" | ||
size="md" | ||
> | ||
Please carefully review these guidelines to protect your wallet from potential security | ||
risks | ||
</Text> | ||
</Flex> | ||
</ModalHeader> | ||
<ModalBody> | ||
<Accordion | ||
as={Flex} | ||
flexDirection="column" | ||
gap="12px" | ||
allowToggle | ||
onChange={e => | ||
setOpenedAccordionItems( | ||
prev => new Set([...prev, e as number].filter(item => item > -1)) | ||
) | ||
} | ||
> | ||
{accordionItems.map(({ title, content, icon }, index) => ( | ||
<AccordionItem key={title} border="none"> | ||
<AccordionButton | ||
gap="16px" | ||
padding="12px 24px" | ||
textAlign="left" | ||
background={color("100")} | ||
borderRadius="6px" | ||
_hover={{ | ||
background: color("300"), | ||
}} | ||
_expanded={{ | ||
borderBottomRadius: "0", | ||
}} | ||
cursor="pointer" | ||
data-testid="accordion-button" | ||
> | ||
<Icon | ||
as={icon} | ||
boxSize="24px" | ||
color={openedAccordionItems.has(index) ? color("greenDark") : color("400")} | ||
/> | ||
<Heading width="100%" color={color("900")} size="md"> | ||
{title} | ||
</Heading> | ||
<AccordionIcon /> | ||
</AccordionButton> | ||
<AccordionPanel padding="8px 24px" background={color("100")} borderBottomRadius="6px"> | ||
<Text>{content}</Text> | ||
</AccordionPanel> | ||
</AccordionItem> | ||
))} | ||
</Accordion> | ||
</ModalBody> | ||
<ModalFooter> | ||
<Button | ||
width="full" | ||
isDisabled={openedAccordionItems.size !== accordionItems.length} | ||
onClick={handleInform} | ||
variant="primary" | ||
> | ||
Got it | ||
</Button> | ||
</ModalFooter> | ||
</ModalContent> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./SecurityWarningModal"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters