Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/js/components/AdminPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,14 @@ export function AdminSendRedisValue({
e.preventDefault()
showModal(
<ConfirmationModal
title={title}
message={`Are you sure you want to send value "${valueToSend}" to key "${keyToSend}"?`}
onConfirm={() => {
handleSubmit(e)
showModal(null)
showModal(null, null)
}}
onCancel={() => showModal(null)}
/>
onCancel={() => showModal(null, null)}
/>,
title
)
}

Expand Down Expand Up @@ -419,14 +419,14 @@ export function AdminDangerPanel({
e.preventDefault()
showModal(
<ConfirmationModal
title="Clear Redis"
message="Are you sure you want to clear Redis?"
onConfirm={() => {
clearRedis()
showModal(null)
showModal(null, null)
}}
onCancel={() => showModal(null)}
/>
onCancel={() => showModal(null, null)}
/>,
"Clear Redis"
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/js/components/Detector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,13 @@ const ResetButton = ({ redisKey }: { redisKey: string }) => {
const onClick = () => {
showModal(
<ConfirmationModal
title="Confirm Reset"
message="Are you sure you want to restart these workers?"
onConfirm={() => {
void handleReset()
}}
onCancel={closeModal}
/>
/>,
"Confirm Reset"
)
}

Expand Down
61 changes: 47 additions & 14 deletions src/js/components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ConfirmationModalProps } from "./componentTypes"

export const ModalProvider = ({ children }: { children: React.ReactNode }) => {
const [modalContent, setModalContent] = useState<React.ReactNode | null>(null)
const [modalHeader, setModalHeader] = useState<string | null>(null)

// Create or get modal-root element lazily
const getModalRoot = () => {
Expand All @@ -23,6 +24,7 @@ export const ModalProvider = ({ children }: { children: React.ReactNode }) => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setModalContent(null)
setModalHeader(null)
}
}

Expand All @@ -32,18 +34,51 @@ export const ModalProvider = ({ children }: { children: React.ReactNode }) => {
}
}, [])

useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const modalContentElement = document.querySelector(
".modal-wrapper"
) as HTMLElement
if (
modalContentElement &&
!modalContentElement.contains(event.target as Node)
) {
setModalContent(null)
setModalHeader(null)
}
}

if (modalContent) {
document.addEventListener("mousedown", handleClickOutside)
} else {
document.removeEventListener("mousedown", handleClickOutside)
}

return () => {
document.removeEventListener("mousedown", handleClickOutside)
}
}, [modalContent])

return (
<ModalContext.Provider value={{ modalContent, setModalContent }}>
<ModalContext.Provider
value={{ modalHeader, setModalHeader, modalContent, setModalContent }}
>
{children}
{modalContent &&
createPortal(
<div className="modal-overlay">
<div className="modal-content">
<button
className="modal-close"
onClick={() => setModalContent(null)}
></button>
{modalContent}
<div className="modal-wrapper">
<div className="modal-header">
{modalHeader && <h2>{modalHeader}</h2>}
<button
className="modal-close"
onClick={() => {
setModalContent(null)
setModalHeader(null)
}}
></button>
</div>
<div className="modal-content">{modalContent}</div>
</div>
</div>,
getModalRoot()
Expand All @@ -53,19 +88,17 @@ export const ModalProvider = ({ children }: { children: React.ReactNode }) => {
}

export const ConfirmationModal = ({
title = "Confirmation",
message = "Are you sure?",
onConfirm = () => {},
onCancel = () => {},
}: ConfirmationModalProps) => {
return (
<div>
<div className="modal-header">
<h2>{title}</h2>
</div>
<div className="confirmation-modal">
<p>{message}</p>
<button onClick={onConfirm}>Yes</button>
<button onClick={onCancel}>No</button>
<div className="confirmation-buttons">
<button onClick={onConfirm}>Yes</button>
<button onClick={onCancel}>No</button>
</div>
</div>
)
}
1 change: 0 additions & 1 deletion src/js/components/TableFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export function FilterDialog({

return (
<>
<h2>filter on {column}</h2>
{!!filterOn.value && (
<div className="filter-info">
<p>Currently filtering by: {filterOn.value}</p>
Expand Down
10 changes: 5 additions & 5 deletions src/js/components/TableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,16 @@ function ChannelHeader({
const handleColumnClick = (event: React.MouseEvent, column: string) => {
handleSortClick(event, column, setSortOn)
if (!event.shiftKey) {
const header = `Filter on: ${column}`
showModal(
<FilterDialog
column={channel.name}
setFilterOn={setFilterOn}
filterOn={filterOn}
filteredRowsCount={filteredRowsCount}
unfilteredRowsCount={unfilteredRowsCount}
/>
/>,
header
)
}
}
Expand Down Expand Up @@ -437,9 +439,6 @@ function FoldoutCell({ seqNum, columnName, data }: TableFoldoutCellProps) {
const handleClick = () => {
const content = (
<div className="cell-dict-modal">
<div className="modal-header">
<h3>{`Seq Num: ${seqNum} - ${columnName}`}</h3>
</div>
<table className="cell-dict">
<tbody>
{Object.entries(data).map(
Expand All @@ -455,7 +454,8 @@ function FoldoutCell({ seqNum, columnName, data }: TableFoldoutCellProps) {
</table>
</div>
)
showModal(content)
const header = `Seq Num: ${seqNum} - ${columnName}`
showModal(content, header)
}
return (
<button onClick={handleClick} className="button button-table">
Expand Down
4 changes: 4 additions & 0 deletions src/js/components/componentTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1140,10 +1140,14 @@ export interface CalendarYearProps {

/**
* @description Context type for managing modal content in the application.
* @param {string | null} modalHeader - Current modal header or null if none.
* @param {React.ReactNode | null} modalContent - Current modal content or null if none.
* @param {(content: React.ReactNode | null) => void} setModalContent - Function to update modal content.
* @param {(header: string | null) => void} setModalHeader - Function to update modal header.
*/
export interface ModalContextType {
modalHeader: string | null
modalContent: React.ReactNode | null
setModalContent: (content: React.ReactNode | null) => void
setModalHeader: (header: string | null) => void
}
2 changes: 2 additions & 0 deletions src/js/components/contexts/contexts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ export const useRedisEndpoint = () => {
/* istanbul ignore next */
export const ModalContext = createContext<ModalContextType>({
modalContent: null,
modalHeader: null,
setModalHeader: () => {},
setModalContent: () => {},
})
15 changes: 8 additions & 7 deletions src/js/components/tests/AdminPanels.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ jest.mock("../DropDownMenu", () => ({
}))

jest.mock("../Modal", () => ({
// eslint-disable-next-line react/prop-types
ModalProvider: ({ children }) => (
<div data-testid="modal-provider">{children}</div>
),

// eslint-disable-next-line react/prop-types
ConfirmationModal: ({ title, message, onConfirm, onCancel }) => (
<div data-testid="confirmation-modal">
<h2>{title}</h2>
Expand Down Expand Up @@ -560,8 +561,8 @@ describe("AdminSendRedisValue Component", () => {
render(
<AdminSendRedisValue
{...defaultProps}
requiresConfirmation={true}
title="Dangerous Action"
requiresConfirmation={true}
valueToSend="danger_value"
/>
)
Expand All @@ -572,10 +573,10 @@ describe("AdminSendRedisValue Component", () => {
expect(mockShowModal).toHaveBeenCalledWith(
expect.objectContaining({
props: expect.objectContaining({
title: "Dangerous Action",
message: expect.stringContaining('value "danger_value"'),
}),
})
}),
"Dangerous Action"
)
})
})
Expand Down Expand Up @@ -686,10 +687,10 @@ describe("AdminDangerPanel Component", () => {
expect(mockShowModal).toHaveBeenCalledWith(
expect.objectContaining({
props: expect.objectContaining({
title: "Clear Redis",
message: "Are you sure you want to clear Redis?",
}),
})
}),
"Clear Redis"
)
})

Expand Down Expand Up @@ -749,7 +750,7 @@ describe("AdminDangerPanel Component", () => {
const cancelButton = render(modalCall).getByTestId("cancel-button")
fireEvent.click(cancelButton)

expect(mockShowModal).toHaveBeenCalledWith(null)
expect(mockShowModal).toHaveBeenCalledWith(null, null)
expect(simplePost).not.toHaveBeenCalled()
})
})
Expand Down
12 changes: 3 additions & 9 deletions src/js/components/tests/Modal.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,21 +182,14 @@ describe("ConfirmationModal", () => {
it("renders with default props", () => {
render(<ConfirmationModal />)

expect(screen.getByText("Confirmation")).toBeInTheDocument()
expect(screen.getByText("Are you sure?")).toBeInTheDocument()
expect(screen.getByText("Yes")).toBeInTheDocument()
expect(screen.getByText("No")).toBeInTheDocument()
})

it("renders with custom props", () => {
render(
<ConfirmationModal
title="Delete Item"
message="This action cannot be undone."
/>
)
render(<ConfirmationModal message="This action cannot be undone." />)

expect(screen.getByText("Delete Item")).toBeInTheDocument()
expect(
screen.getByText("This action cannot be undone.")
).toBeInTheDocument()
Expand Down Expand Up @@ -244,7 +237,8 @@ describe("ConfirmationModal", () => {
message="Are you sure you want to delete this file?"
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
/>,
"Delete File"
)
}
data-testid="show-confirmation"
Expand Down
Loading