1
0
mirror of https://gitlab.nic.cz/turris/reforis/foris-js.git synced 2024-11-14 17:35:35 +01:00

Compare commits

..

2 Commits

Author SHA1 Message Date
Aleksandr Gumroian
4df4c6e91f Merge branch 'add-action-btn-with-modal' into 'dev'
Add ActionButtonWithModal component and remove RebootButton

See merge request turris/reforis/foris-js!254
2024-11-12 17:39:21 +01:00
Aleksandr Gumroian
c9f2b24095
Replace RebootButton with ActionButtonWithModal component and update documentation 2024-11-12 17:39:01 +01:00
4 changed files with 89 additions and 27 deletions

View File

@ -6,7 +6,9 @@
*/ */
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useAPIPost } from "../../api/hooks"; import { useAPIPost } from "../../api/hooks";
import { API_STATE } from "../../api/utils"; import { API_STATE } from "../../api/utils";
import Button from "../../bootstrap/Button"; import Button from "../../bootstrap/Button";
@ -19,13 +21,21 @@ import {
import { useAlert } from "../../context/alertContext/AlertContext"; import { useAlert } from "../../context/alertContext/AlertContext";
ActionButtonWithModal.propTypes = { ActionButtonWithModal.propTypes = {
/** Component that triggers the action. */
actionTrigger: PropTypes.elementType.isRequired, actionTrigger: PropTypes.elementType.isRequired,
/** URL to send the action to. */
actionUrl: PropTypes.string.isRequired, actionUrl: PropTypes.string.isRequired,
/** Title of the modal. */
modalTitle: PropTypes.string.isRequired, modalTitle: PropTypes.string.isRequired,
/** Message of the modal. */
modalMessage: PropTypes.string.isRequired, modalMessage: PropTypes.string.isRequired,
/** Text of the action button in the modal. */
modalActionText: PropTypes.string, modalActionText: PropTypes.string,
/** Props for the action button in the modal. */
modalActionProps: PropTypes.object, modalActionProps: PropTypes.object,
/** Message to display on successful action. */
successMessage: PropTypes.string, successMessage: PropTypes.string,
/** Message to display on failed action. */
errorMessage: PropTypes.string, errorMessage: PropTypes.string,
}; };
@ -89,6 +99,7 @@ ActionModal.propTypes = {
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
message: PropTypes.string.isRequired, message: PropTypes.string.isRequired,
actionText: PropTypes.string, actionText: PropTypes.string,
actionProps: PropTypes.object,
}; };
function ActionModal({ function ActionModal({

View File

@ -5,17 +5,32 @@ reboot of the device.
```jsx ```jsx
import React, { useEffect, createContext } from "react"; import React, { useEffect, createContext } from "react";
import RebootButton from "./RebootButton";
import { AlertContextProvider } from "../context/alertContext/AlertContext"; import Button from "../../bootstrap/Button";
import { AlertContextProvider } from "../../context/alertContext/AlertContext";
import ActionButtonWithModal from "./ActionButtonWithModal";
window.AlertContext = React.createContext(); window.AlertContext = React.createContext();
const RebootButtonExample = () => { const RebootButtonExample = () => {
const ActionButton = (props) => {
return <Button {...props}>Action</Button>;
};
return ( return (
<AlertContextProvider> <AlertContextProvider>
<div id="modal-container" /> <div id="modal-container" />
<div id="alert-container" /> <div id="alert-container" />
<RebootButton /> <ActionButtonWithModal
actionTrigger={ActionButton}
actionUrl="/reforis/api/action"
modalTitle="Warning!"
modalMessage="Are you sure you want to perform this action?"
modalActionText="Confirm action"
modalActionProps={{ className: "btn-danger" }}
successMessage="Action request succeeded."
errorMessage="Action request failed."
/>
</AlertContextProvider> </AlertContextProvider>
); );
}; };

View File

@ -7,6 +7,8 @@
import React from "react"; import React from "react";
import Button from "bootstrap/Button";
import { import {
fireEvent, fireEvent,
getByText, getByText,
@ -22,29 +24,43 @@ import ActionButtonWithModal from "../ActionButtonWithModal/ActionButtonWithModa
describe("<ActionButtonWithModal/>", () => { describe("<ActionButtonWithModal/>", () => {
let componentContainer; let componentContainer;
const ActionButton = (props) => (
<Button type="button" {...props}>
Action
</Button>
);
beforeEach(() => { beforeEach(() => {
const { container } = render( const { container } = render(
<> <>
<div id="modal-container" /> <div id="modal-container" />
<div id="alert-container" /> <div id="alert-container" />
<ActionButtonWithModal /> <ActionButtonWithModal
actionTrigger={ActionButton}
actionUrl="/reforis/api/action"
modalTitle="Warning!"
modalMessage="Are you sure you want to perform this action?"
modalActionText="Confirm action"
modalActionProps={{ className: "btn-danger" }}
successMessage="Action request succeeded."
errorMessage="Action request failed."
/>
</> </>
); );
componentContainer = container; componentContainer = container;
}); });
it("Render.", () => { it("Render button.", () => {
expect(componentContainer).toMatchSnapshot(); expect(componentContainer).toMatchSnapshot();
}); });
it("Render modal.", () => { it("Render modal.", () => {
expect(queryByText(componentContainer, "Confirm action")).toBeNull();
fireEvent.click(getByText(componentContainer, "Action")); fireEvent.click(getByText(componentContainer, "Action"));
expect(componentContainer).toMatchSnapshot(); expect(componentContainer).toMatchSnapshot();
}); });
it("Confirm action.", () => { it("Confirm action.", () => {
fireEvent.click(getByText(componentContainer, "action")); fireEvent.click(getByText(componentContainer, "Action"));
fireEvent.click(getByText(componentContainer, "Confirm action")); fireEvent.click(getByText(componentContainer, "Confirm action"));
expect(mockAxios.post).toHaveBeenCalledWith( expect(mockAxios.post).toHaveBeenCalledWith(
"/reforis/api/action", "/reforis/api/action",
@ -61,4 +77,16 @@ describe("<ActionButtonWithModal/>", () => {
expect(mockSetAlert).toBeCalledWith("Action request failed.") expect(mockSetAlert).toBeCalledWith("Action request failed.")
); );
}); });
it("Show success alert on successful action.", async () => {
fireEvent.click(getByText(componentContainer, "Action"));
fireEvent.click(getByText(componentContainer, "Confirm action"));
mockAxios.mockResponse({ status: 200 });
await wait(() =>
expect(mockSetAlert).toBeCalledWith(
"Action request succeeded.",
"success"
)
);
});
}); });

View File

@ -1,6 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<RebootButton/> Render modal. 1`] = ` exports[`<ActionButtonWithModal/> Render button. 1`] = `
<div>
<div
id="modal-container"
/>
<div
id="alert-container"
/>
<button
class="btn btn-primary d-inline-flex justify-content-center align-items-center"
type="button"
>
<span>
Action
</span>
</button>
</div>
`;
exports[`<ActionButtonWithModal/> Render modal. 1`] = `
<div> <div>
<div <div
id="modal-container" id="modal-container"
@ -35,8 +54,10 @@ exports[`<RebootButton/> Render modal. 1`] = `
<div <div
class="modal-body" class="modal-body"
> >
<p> <p
Are you sure you want to restart the router? class="mb-0"
>
Are you sure you want to perform this action?
</p> </p>
</div> </div>
<div <div
@ -55,7 +76,7 @@ exports[`<RebootButton/> Render modal. 1`] = `
type="button" type="button"
> >
<span> <span>
Confirm reboot Confirm action
</span> </span>
</button> </button>
</div> </div>
@ -63,28 +84,15 @@ exports[`<RebootButton/> Render modal. 1`] = `
</div> </div>
</div> </div>
</div> </div>
<button
class="btn btn-danger d-inline-flex justify-content-center align-items-center"
type="button"
>
<span>
Reboot
</span>
</button>
</div>
`;
exports[`<RebootButton/> Render. 1`] = `
<div>
<div <div
id="modal-container" id="alert-container"
/> />
<button <button
class="btn btn-danger d-inline-flex justify-content-center align-items-center" class="btn btn-primary d-inline-flex justify-content-center align-items-center"
type="button" type="button"
> >
<span> <span>
Reboot Action
</span> </span>
</button> </button>
</div> </div>