Skip to content

Commit b175625

Browse files
Merge pull request #241 from dushimsam/dushimsam/feat/admin/delete-group
feat(ui): added delete group page Reviewed-by: [email protected] Tested-by: [email protected]
2 parents 6f81795 + b645717 commit b175625

File tree

12 files changed

+314
-3
lines changed

12 files changed

+314
-3
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ npm-debug.log*
55
yarn-debug.log*
66
yarn-error.log*
77
lerna-debug.log*
8+
.idea*
89

910
# Diagnostic reports (https://nodejs.org/api/report.html)
1011
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"@testing-library/user-event": "^12.1.10",
1111
"array-to-tree": "^3.3.2",
1212
"bootstrap": "4.6.0",
13+
"jquery": "^3.6.0",
1314
"js-cookie": "^2.2.1",
1415
"prop-types": "^15.8.1",
1516
"query-string": "^7.1.1",

src/App.jsx

+12-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*/
1818

1919
// React Imports
20-
import React, { useContext } from "react";
20+
import React, { useContext, useEffect } from "react";
2121

2222
// Theme Provider
2323
import { ThemeProvider } from "styled-components";
@@ -29,6 +29,9 @@ import { GlobalContext, GlobalProvider } from "context";
2929
// Routes
3030
import Routes from "Routes";
3131

32+
// eslint-disable-next-line import/no-extraneous-dependencies
33+
import "popper.js";
34+
3235
// Global CSS (Bootstrap, Tree View of Folders, Custom Styling)
3336
import "bootstrap/dist/css/bootstrap.min.css";
3437
import "react-virtualized-tree/lib/main.css";
@@ -46,6 +49,14 @@ function App() {
4649
}
4750

4851
function AppWrapper() {
52+
useEffect(() => {
53+
import("jquery").then(($) => {
54+
// jQuery must be installed to the `window`:
55+
// eslint-disable-next-line no-multi-assign
56+
window.$ = window.jQuery = $;
57+
return import("bootstrap");
58+
});
59+
}, []);
4960
return (
5061
<GlobalProvider>
5162
<App />

src/Routes.jsx

+6
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ const UploadDelete = React.lazy(() => import("pages/Organize/Uploads/Delete"));
9090

9191
// Admin Pages
9292
const GroupCreate = React.lazy(() => import("pages/Admin/Group/Create"));
93+
const DeleteGroup = React.lazy(() => import("pages/Admin/Group/Delete"));
9394
const DeleteUser = React.lazy(() => import("pages/Admin/Users/Delete"));
9495
const AddUser = React.lazy(() => import("pages/Admin/Users/Add"));
9596
const AddLicense = React.lazy(() => import("pages/Admin/License/Create"));
@@ -285,6 +286,11 @@ const Routes = () => {
285286
path={routes.admin.group.create}
286287
component={GroupCreate}
287288
/>
289+
<AdminLayout
290+
exact
291+
path={routes.admin.group.delete}
292+
component={DeleteGroup}
293+
/>
288294
<AdminLayout
289295
exact
290296
path={routes.admin.license.create}

src/api/groups.js

+26
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ export const getAllGroupsApi = () => {
3737
});
3838
};
3939

40+
// Fetching all deletable groups
41+
export const getAllDeletableGroupsApi = () => {
42+
const url = endpoints.admin.groups.getAllDeletable();
43+
return sendRequest({
44+
url,
45+
method: "GET",
46+
headers: {
47+
Authorization: getToken(),
48+
},
49+
addGroupName: false,
50+
});
51+
};
52+
4053
// Creating a group
4154
export const createGroupApi = (name) => {
4255
const url = endpoints.admin.groups.create();
@@ -50,3 +63,16 @@ export const createGroupApi = (name) => {
5063
addGroupName: false,
5164
});
5265
};
66+
67+
// Delete a group
68+
export const deleteGroupApi = (id) => {
69+
const url = endpoints.admin.groups.delete(id);
70+
return sendRequest({
71+
url,
72+
method: "DELETE",
73+
headers: {
74+
Authorization: getToken(),
75+
},
76+
addGroupName: false,
77+
});
78+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import Spinner from "react-bootstrap/Spinner";
4+
import { Button } from "../../Widgets";
5+
6+
const DeleteConfirmation = ({ callBack, loading }) => {
7+
return (
8+
<>
9+
<div
10+
className="modal fade"
11+
id="deleteConfirmationModal"
12+
tabIndex="-1"
13+
role="dialog"
14+
aria-hidden="true"
15+
>
16+
<div className="modal-dialog modal-dialog-centered" role="document">
17+
<div className="modal-content">
18+
<div className="modal-header">
19+
<h5 className="modal-title">Are you sure ?</h5>
20+
<button
21+
type="button"
22+
className="close"
23+
data-dismiss="modal"
24+
aria-label="Close"
25+
>
26+
<span aria-hidden="true">&times;</span>
27+
</button>
28+
</div>
29+
<div className="modal-body">
30+
Do you really want to delete this group ? This process can not be
31+
undone.
32+
</div>
33+
<div className="modal-footer">
34+
<button
35+
type="button"
36+
className="btn btn-secondary border-dark border-rounded px-4 py-2 mr-2"
37+
data-dismiss="modal"
38+
onClick={() => {}}
39+
>
40+
Cancel
41+
</button>
42+
<Button
43+
type="button"
44+
onClick={() => callBack()}
45+
className="btn btn-danger border-dark border-rounded px-5 py-2"
46+
>
47+
{loading ? (
48+
<Spinner
49+
as="span"
50+
animation="border"
51+
size="sm"
52+
role="status"
53+
aria-hidden="true"
54+
/>
55+
) : (
56+
"YES"
57+
)}
58+
</Button>
59+
</div>
60+
</div>
61+
</div>
62+
</div>
63+
</>
64+
);
65+
};
66+
67+
DeleteConfirmation.propTypes = {
68+
callBack: PropTypes.func.isRequired,
69+
loading: PropTypes.bool.isRequired,
70+
};
71+
export default DeleteConfirmation;

src/components/Widgets/Button/index.jsx

+18-1
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,25 @@ import React from "react";
2020
import PropTypes from "prop-types";
2121
import styles from "styled-components";
2222

23-
const Button = ({ type, onClick, className, children }) => {
23+
const Button = ({
24+
type,
25+
onClick,
26+
className,
27+
children,
28+
dataDismiss,
29+
dataToggle,
30+
dataTarget,
31+
disabled = false,
32+
}) => {
2433
return (
2534
<ButtonContainer
2635
type={type}
2736
onClick={onClick}
37+
data-toggle={dataToggle}
38+
data-dismiss={dataDismiss}
39+
data-target={dataTarget}
2840
className={`bg-primary-color text-secondary-color font-demi text-center hover-primary-color ${className}`}
41+
disabled={disabled}
2942
>
3043
{children}
3144
</ButtonContainer>
@@ -37,6 +50,10 @@ Button.propTypes = {
3750
onClick: PropTypes.func.isRequired,
3851
children: PropTypes.node.isRequired,
3952
className: PropTypes.string,
53+
dataTarget: PropTypes.string,
54+
disabled: PropTypes.bool,
55+
dataToggle: PropTypes.string,
56+
dataDismiss: PropTypes.string,
4057
};
4158

4259
const ButtonContainer = styles.button`

src/constants/endpoints.js

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ const endpoints = {
7373
groups: {
7474
create: () => `${apiUrl}/groups`,
7575
getAll: () => `${apiUrl}/groups`,
76+
getAllDeletable: () => `${apiUrl}/groups/deletable`,
77+
delete: (groupId) => `${apiUrl}/groups/${groupId}`,
7678
},
7779
},
7880
license: {

src/constants/messages.js

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const messages = {
2424
noPageShort: "Error: Page Not Found!",
2525
noPageLong: "We could not find the page you were searching for",
2626
groupCreate: "Successfully created the group",
27+
deletedGroup: "Successfully deleted the group",
2728
deletedUser: "Successfully deleted the user",
2829
addedUser: "Successfully added the user",
2930
confirmDeletion: "Deletion not confirmed",
+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
Copyright (C) 2022 Samuel Dushimimana
3+
4+
SPDX-License-Identifier: GPL-2.0
5+
6+
This program is free software; you can redistribute it and/or
7+
modify it under the terms of the GNU General Public License
8+
version 2 as published by the Free Software Foundation.
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License along
15+
with this program; if not, write to the Free Software Foundation, Inc.,
16+
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17+
*/
18+
19+
import React, { useEffect, useState } from "react";
20+
import messages from "constants/messages";
21+
22+
// Jquery for handling modal
23+
import $ from "jquery";
24+
25+
// Title
26+
import Title from "components/Title";
27+
28+
// Widgets
29+
import { Alert, Button, InputContainer } from "components/Widgets";
30+
31+
// Required functions for calling APIs
32+
import { deleteGroup, fetchAllDeletableGroups } from "services/groups";
33+
import DeleteConfirmation from "components/Modals/DeleteConfirmation";
34+
35+
const DeleteGroup = () => {
36+
const initialMessage = {
37+
type: "",
38+
text: "",
39+
};
40+
41+
const [groups, setGroups] = useState([]);
42+
const [selectedGroupId, setSelectedGroupId] = useState(null);
43+
44+
// State Variables for handling Error Boundaries
45+
const [loading, setLoading] = useState(false);
46+
const [showMessage, setShowMessage] = useState(false);
47+
const [message, setMessage] = useState(initialMessage);
48+
49+
useEffect(async () => {
50+
try {
51+
const res = await fetchAllDeletableGroups();
52+
setGroups(res);
53+
} catch (e) {
54+
setMessage({
55+
type: "error",
56+
text: e.message,
57+
});
58+
setShowMessage(true);
59+
}
60+
}, []);
61+
62+
useEffect(() => {
63+
if (groups.length > 0) {
64+
setSelectedGroupId(groups[0].id);
65+
}
66+
}, [groups]);
67+
68+
const toggleModal = (modalId, status) => {
69+
// eslint-disable-next-line func-names
70+
$(function () {
71+
if (status === "show") {
72+
$(modalId).modal("show");
73+
} else {
74+
$(modalId).modal("hide");
75+
$(".modal-backdrop").remove();
76+
}
77+
});
78+
};
79+
80+
const deleteItem = async () => {
81+
setLoading(true);
82+
try {
83+
await deleteGroup(selectedGroupId);
84+
setMessage({
85+
type: "success",
86+
text: messages.deletedGroup,
87+
});
88+
89+
setTimeout(() => {
90+
window.location.reload();
91+
}, 1500);
92+
} catch (error) {
93+
setMessage({
94+
type: "danger",
95+
text: error.message,
96+
});
97+
} finally {
98+
setLoading(false);
99+
setShowMessage(true);
100+
toggleModal("#deleteConfirmationModal", "hide");
101+
}
102+
};
103+
104+
return (
105+
<>
106+
<Title title="Delete Group" />
107+
<div className="main-container my-3">
108+
{showMessage && (
109+
<Alert
110+
type={message.type}
111+
setShow={setShowMessage}
112+
message={message.text}
113+
/>
114+
)}
115+
<h1 className="font-size-main-heading">Delete Group</h1>
116+
<br />
117+
<div className="row">
118+
<div className="col-12 col-lg-8">
119+
<form>
120+
<InputContainer
121+
name="group"
122+
type="select"
123+
onChange={(e) => setSelectedGroupId(e.target.value)}
124+
options={groups}
125+
property="name"
126+
>
127+
Select group to delete:
128+
</InputContainer>
129+
<Button
130+
type="button"
131+
dataToggle="modal"
132+
dataTarget="#deleteConfirmationModal"
133+
className="mt-4"
134+
disabled={groups.length === 0}
135+
>
136+
Delete
137+
</Button>
138+
</form>
139+
</div>
140+
<DeleteConfirmation callBack={deleteItem} loading={loading} />
141+
</div>
142+
</div>
143+
</>
144+
);
145+
};
146+
147+
export default DeleteGroup;

0 commit comments

Comments
 (0)