diff --git a/.gitignore b/.gitignore index 3860e1a68..7662dd582 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* +.idea* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/package.json b/package.json index 6329e82dc..07c577e4a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@testing-library/user-event": "^12.1.10", "array-to-tree": "^3.3.2", "bootstrap": "4.6.0", + "jquery": "^3.6.0", "js-cookie": "^2.2.1", "prop-types": "^15.8.1", "query-string": "^7.1.1", diff --git a/src/App.jsx b/src/App.jsx index 97ed71bb3..355736ac8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -17,7 +17,7 @@ */ // React Imports -import React, { useContext } from "react"; +import React, { useContext, useEffect } from "react"; // Theme Provider import { ThemeProvider } from "styled-components"; @@ -29,6 +29,9 @@ import { GlobalContext, GlobalProvider } from "context"; // Routes import Routes from "Routes"; +// eslint-disable-next-line import/no-extraneous-dependencies +import "popper.js"; + // Global CSS (Bootstrap, Tree View of Folders, Custom Styling) import "bootstrap/dist/css/bootstrap.min.css"; import "react-virtualized-tree/lib/main.css"; @@ -46,6 +49,14 @@ function App() { } function AppWrapper() { + useEffect(() => { + import("jquery").then(($) => { + // jQuery must be installed to the `window`: + // eslint-disable-next-line no-multi-assign + window.$ = window.jQuery = $; + return import("bootstrap"); + }); + }, []); return ( diff --git a/src/Routes.jsx b/src/Routes.jsx index ffd060420..e5661a031 100644 --- a/src/Routes.jsx +++ b/src/Routes.jsx @@ -90,6 +90,7 @@ const UploadDelete = React.lazy(() => import("pages/Organize/Uploads/Delete")); // Admin Pages const GroupCreate = React.lazy(() => import("pages/Admin/Group/Create")); +const DeleteGroup = React.lazy(() => import("pages/Admin/Group/Delete")); const DeleteUser = React.lazy(() => import("pages/Admin/Users/Delete")); const AddUser = React.lazy(() => import("pages/Admin/Users/Add")); const AddLicense = React.lazy(() => import("pages/Admin/License/Create")); @@ -285,6 +286,11 @@ const Routes = () => { path={routes.admin.group.create} component={GroupCreate} /> + { }); }; +// Fetching all deletable groups +export const getAllDeletableGroupsApi = () => { + const url = endpoints.admin.groups.getAllDeletable(); + return sendRequest({ + url, + method: "GET", + headers: { + Authorization: getToken(), + }, + addGroupName: false, + }); +}; + // Creating a group export const createGroupApi = (name) => { const url = endpoints.admin.groups.create(); @@ -50,3 +63,16 @@ export const createGroupApi = (name) => { addGroupName: false, }); }; + +// Delete a group +export const deleteGroupApi = (id) => { + const url = endpoints.admin.groups.delete(id); + return sendRequest({ + url, + method: "DELETE", + headers: { + Authorization: getToken(), + }, + addGroupName: false, + }); +}; diff --git a/src/components/Modals/DeleteConfirmation/index.jsx b/src/components/Modals/DeleteConfirmation/index.jsx new file mode 100644 index 000000000..565c2e31d --- /dev/null +++ b/src/components/Modals/DeleteConfirmation/index.jsx @@ -0,0 +1,71 @@ +import React from "react"; +import PropTypes from "prop-types"; +import Spinner from "react-bootstrap/Spinner"; +import { Button } from "../../Widgets"; + +const DeleteConfirmation = ({ callBack, loading }) => { + return ( + <> + + + ); +}; + +DeleteConfirmation.propTypes = { + callBack: PropTypes.func.isRequired, + loading: PropTypes.bool.isRequired, +}; +export default DeleteConfirmation; diff --git a/src/components/Widgets/Button/index.jsx b/src/components/Widgets/Button/index.jsx index fc438d352..65f0369e8 100644 --- a/src/components/Widgets/Button/index.jsx +++ b/src/components/Widgets/Button/index.jsx @@ -20,12 +20,25 @@ import React from "react"; import PropTypes from "prop-types"; import styles from "styled-components"; -const Button = ({ type, onClick, className, children }) => { +const Button = ({ + type, + onClick, + className, + children, + dataDismiss, + dataToggle, + dataTarget, + disabled = false, +}) => { return ( {children} @@ -37,6 +50,10 @@ Button.propTypes = { onClick: PropTypes.func.isRequired, children: PropTypes.node.isRequired, className: PropTypes.string, + dataTarget: PropTypes.string, + disabled: PropTypes.bool, + dataToggle: PropTypes.string, + dataDismiss: PropTypes.string, }; const ButtonContainer = styles.button` diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index d363df71f..db5b63bbe 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -72,6 +72,8 @@ const endpoints = { groups: { create: () => `${apiUrl}/groups`, getAll: () => `${apiUrl}/groups`, + getAllDeletable: () => `${apiUrl}/groups/deletable`, + delete: (groupId) => `${apiUrl}/groups/${groupId}`, }, }, license: { diff --git a/src/constants/messages.js b/src/constants/messages.js index 9c49b1547..829d5c380 100644 --- a/src/constants/messages.js +++ b/src/constants/messages.js @@ -24,6 +24,7 @@ const messages = { noPageShort: "Error: Page Not Found!", noPageLong: "We could not find the page you were searching for", groupCreate: "Successfully created the group", + deletedGroup: "Successfully deleted the group", deletedUser: "Successfully deleted the user", addedUser: "Successfully added the user", confirmDeletion: "Deletion not confirmed", diff --git a/src/pages/Admin/Group/Delete/index.jsx b/src/pages/Admin/Group/Delete/index.jsx new file mode 100644 index 000000000..a40f7c2d3 --- /dev/null +++ b/src/pages/Admin/Group/Delete/index.jsx @@ -0,0 +1,147 @@ +/* + Copyright (C) 2022 Samuel Dushimimana + + SPDX-License-Identifier: GPL-2.0 + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + version 2 as published by the Free Software Foundation. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +import React, { useEffect, useState } from "react"; +import messages from "constants/messages"; + +// Jquery for handling modal +import $ from "jquery"; + +// Title +import Title from "components/Title"; + +// Widgets +import { Alert, Button, InputContainer } from "components/Widgets"; + +// Required functions for calling APIs +import { deleteGroup, fetchAllDeletableGroups } from "services/groups"; +import DeleteConfirmation from "components/Modals/DeleteConfirmation"; + +const DeleteGroup = () => { + const initialMessage = { + type: "", + text: "", + }; + + const [groups, setGroups] = useState([]); + const [selectedGroupId, setSelectedGroupId] = useState(null); + + // State Variables for handling Error Boundaries + const [loading, setLoading] = useState(false); + const [showMessage, setShowMessage] = useState(false); + const [message, setMessage] = useState(initialMessage); + + useEffect(async () => { + try { + const res = await fetchAllDeletableGroups(); + setGroups(res); + } catch (e) { + setMessage({ + type: "error", + text: e.message, + }); + setShowMessage(true); + } + }, []); + + useEffect(() => { + if (groups.length > 0) { + setSelectedGroupId(groups[0].id); + } + }, [groups]); + + const toggleModal = (modalId, status) => { + // eslint-disable-next-line func-names + $(function () { + if (status === "show") { + $(modalId).modal("show"); + } else { + $(modalId).modal("hide"); + $(".modal-backdrop").remove(); + } + }); + }; + + const deleteItem = async () => { + setLoading(true); + try { + await deleteGroup(selectedGroupId); + setMessage({ + type: "success", + text: messages.deletedGroup, + }); + + setTimeout(() => { + window.location.reload(); + }, 1500); + } catch (error) { + setMessage({ + type: "danger", + text: error.message, + }); + } finally { + setLoading(false); + setShowMessage(true); + toggleModal("#deleteConfirmationModal", "hide"); + } + }; + + return ( + <> + + <div className="main-container my-3"> + {showMessage && ( + <Alert + type={message.type} + setShow={setShowMessage} + message={message.text} + /> + )} + <h1 className="font-size-main-heading">Delete Group</h1> + <br /> + <div className="row"> + <div className="col-12 col-lg-8"> + <form> + <InputContainer + name="group" + type="select" + onChange={(e) => setSelectedGroupId(e.target.value)} + options={groups} + property="name" + > + Select group to delete: + </InputContainer> + <Button + type="button" + dataToggle="modal" + dataTarget="#deleteConfirmationModal" + className="mt-4" + disabled={groups.length === 0} + > + Delete + </Button> + </form> + </div> + <DeleteConfirmation callBack={deleteItem} loading={loading} /> + </div> + </div> + </> + ); +}; + +export default DeleteGroup; diff --git a/src/services/groups.js b/src/services/groups.js index ff91fd019..d44995940 100644 --- a/src/services/groups.js +++ b/src/services/groups.js @@ -1,5 +1,6 @@ /* Copyright (C) 2021 Aman Dwivedi (aman.dwivedi5@gmail.com), Shruti Agarwal (mail2shruti.ag@gmail.com) + Copyright (C) 2022 Samuel Dushimimana (dushsam100@gmail.com) SPDX-License-Identifier: GPL-2.0 @@ -16,7 +17,12 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { getAllGroupsApi, createGroupApi } from "api/groups"; +import { + getAllGroupsApi, + createGroupApi, + deleteGroupApi, + getAllDeletableGroupsApi, +} from "api/groups"; import { setLocalStorage, getLocalStorage } from "shared/storageHelper"; // Fetching all the groups @@ -37,3 +43,17 @@ export const createGroup = (name) => { return res; }); }; + +// Delete a group +export const deleteGroup = (id) => { + return deleteGroupApi(id).then((res) => { + return res; + }); +}; + +// Fetching all deletable groups +export const fetchAllDeletableGroups = () => { + return getAllDeletableGroupsApi().then((res) => { + return res; + }); +}; diff --git a/yarn.lock b/yarn.lock index 8c43d2467..d4bf3e0e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9743,6 +9743,7 @@ __metadata: eslint-plugin-prettier: ^3.4.0 eslint-plugin-react-hooks: ^1.7.0 jest-fetch-mock: 3.0.3 + jquery: ^3.6.0 js-cookie: ^2.2.1 prettier: ^2.6.2 prop-types: ^15.8.1 @@ -12477,6 +12478,13 @@ __metadata: languageName: node linkType: hard +"jquery@npm:^3.6.0": + version: 3.6.0 + resolution: "jquery@npm:3.6.0" + checksum: 8fd5fef4aa48fd374ec716dd1c1df1af407814a228e15c1260ca140de3a697c2a77c30c54ff1d238b6a3ab4ddc445ddeef9adce6c6d28e4869d85eb9d3951c0e + languageName: node + linkType: hard + "js-cookie@npm:^2.2.1": version: 2.2.1 resolution: "js-cookie@npm:2.2.1"