diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..6545e4d1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.intelliSenseEngineFallback": "enabled" +} \ No newline at end of file diff --git a/backend/src/routes/userRoute.js b/backend/src/routes/userRoute.js index 2fc61fcb..981e3ecb 100644 --- a/backend/src/routes/userRoute.js +++ b/backend/src/routes/userRoute.js @@ -159,6 +159,18 @@ router.post("/resetpassword", (req, res) => { } }); +router.post("/adminReset", authorize(true), (req, res) => { + // Reset Password + userController + .updateUserPassword(req, req.body.email) + .then((response) => { + res.status(200).json(response); + }) + .catch((err) => { + res.status(404).json(err); + }); +}); + router.delete("/:userId", authorize(), (req, res) => { userController .deleteUser(req.params.userId) diff --git a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css index 9434048f..ce487bcb 100644 --- a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css +++ b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.css @@ -1,7 +1,7 @@ #invoice-template { color: #000; margin: 10px auto; - border: 1px solid #ddd; + padding: 10px; } #invoice-header { font-size: 12px; diff --git a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js index 57b8cef1..45c15a33 100644 --- a/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js +++ b/frontend/src/components/GenerateInvoice/InvoiceTemplate/index.js @@ -102,7 +102,7 @@ const InvoiceTemplate = ({ customer, costcenterMap, invoicNum }) => { /> -
+ {/*
{ onChange={handleChange} />
-
+
*/} @@ -175,10 +175,6 @@ function InvoiceDetails({ billingData }) { Total Due: ${subtotal} - - Amount Paid: - $0 - diff --git a/frontend/src/components/UserManagementTable/index.css b/frontend/src/components/UserManagementTable/index.css index 0b32f4ff..2107abb6 100644 --- a/frontend/src/components/UserManagementTable/index.css +++ b/frontend/src/components/UserManagementTable/index.css @@ -20,11 +20,27 @@ border: none; } +.userTable .ant-table-footer { + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + background-color: #ECEDF3; +} + .addService { display: flex; align-items: center; gap: 12px; } + +.resetPasswordButton{ + text-align: center; + cursor: pointer; +} + +.SaveNewPassword{ + cursor: pointer; + margin-top: 10px;; +} .ServiceQuestionSelect { border: 0; background-color: #eeeeee; @@ -86,10 +102,10 @@ So try and beat it with CSS specificity */ border: 1px solid #d9d9d9; } .ant-table-container, -.ant-table-container table > tbody > tr:last-child td:first-child { - border-bottom-left-radius: 10px; +.userTable .ant-table-container table > tbody > tr:last-child td:first-child { + border-bottom-left-radius: 0px; } .ant-table-container, -.ant-table-container table > tbody > tr:last-child td:last-child { - border-bottom-right-radius: 10px; +.userTable .ant-table-container table > tbody > tr:last-child td:last-child { + border-bottom-right-radius: 0px; } diff --git a/frontend/src/components/UserManagementTable/index.js b/frontend/src/components/UserManagementTable/index.js index d778d6e9..8ef3f717 100644 --- a/frontend/src/components/UserManagementTable/index.js +++ b/frontend/src/components/UserManagementTable/index.js @@ -1,10 +1,12 @@ import React, { useState, useRef, useContext, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { Table, Form, Popconfirm, Input, Select } from "antd"; +import { Table, Form, Popconfirm, Input, Select, Popover } from "antd"; import "antd/dist/antd.min.css"; import "./index.css"; -import { DELETE_USER, GET_ORGANIZATION, LOAD_USERLIST, UPDATE_USER } from "../../redux/actions/userActions"; +import Add from "../../assets/AddBlack.png"; +import { ADMIN_RESET_PASSWORD, DELETE_USER, GET_ORGANIZATION, LOAD_USERLIST, POST_USER, UPDATE_USER } from "../../redux/actions/userActions"; import RejectUser from "../../assets/RejectUser.png"; +import uuid from "react-uuid"; const UserManagementTable = () => { const columns = [ @@ -87,6 +89,43 @@ const UserManagementTable = () => { /> ) : null, }, + { + title: "", + dataIndex: "operation", + render: (_, record) => + dataSource.length >= 1 ? ( + + +
{ + const password = document.getElementsByClassName("LoginInput")[0].value; + if (password && password !== "") { + dispatch({ + type: ADMIN_RESET_PASSWORD, + payload: { + email: record.email, + password: password, + }, + }); + document.getElementsByClassName("LoginInput")[0].value = ""; + setResetPasswordRecord(null); + } + }}>Save
+ + } + title="Change User's Password" + trigger="click" + open={resetPasswordRecord === record.user_id} + onOpenChange={(open) => { + setResetPasswordRecord(open ? record.user_id : null); + }} + > +
Reset Password
+
+ ) : null, + width: "5%", + }, { title: "", dataIndex: "operation", @@ -111,6 +150,7 @@ const UserManagementTable = () => { ]; const dispatch = useDispatch(); + const [resetPasswordRecord, setResetPasswordRecord] = useState(); const dataSource = useSelector((state) => state.userReducer.userList); const orgList = useSelector((state) => state.userReducer.organizationList); @@ -125,6 +165,22 @@ const UserManagementTable = () => { const handleSave = (row) => { dispatch({ type: UPDATE_USER, payload: row }); }; + const handleAdd = () => { + const newID = uuid(); + const newData = { + user_id: newID, + fk_organization_id: null, + username: "New User", + email: "New User Email", + employee: false, + password: "password", + adminCreated: true, + }; + dispatch({ + type: POST_USER, + payload: newData + }); + }; const EditableContext = React.createContext(null); @@ -234,6 +290,14 @@ const UserManagementTable = () => { rowClassName={(_, index) => index % 2 === 0 ? "editable-row" : "editable-row-dark"} dataSource={dataSource} columns={renderedColumns} + footer={() => { + return ( +
+ Add +
Add
+
+ ) + }} /> ); }; diff --git a/frontend/src/redux/actions/userActions.js b/frontend/src/redux/actions/userActions.js index 6a4e5e9e..afb16805 100644 --- a/frontend/src/redux/actions/userActions.js +++ b/frontend/src/redux/actions/userActions.js @@ -5,6 +5,7 @@ export const POST_USER = "POST_USER"; export const UPDATE_USER = "UPDATE_USER"; export const REQUEST_RESET = "REQUEST_RESET"; export const RESET_PASSWORD = "RESET_PASSWORD"; +export const ADMIN_RESET_PASSWORD = "ADMIN_RESET_PASSWORD"; export const SIGNUP_USER = "SIGNUP_USER"; export const AUTHENTICATE_USER = "AUTHENTICATE_USER"; export const SET_CURRENT_USER = "SET_CURRENT_USER"; diff --git a/frontend/src/redux/api/userApi.js b/frontend/src/redux/api/userApi.js index 8149078b..26ff10e8 100644 --- a/frontend/src/redux/api/userApi.js +++ b/frontend/src/redux/api/userApi.js @@ -120,6 +120,27 @@ export const resetPasswordApi = async (payload) => { } }; +export const adminResetPasswordApi = async (payload) => { + try { + const token = JSON.parse(localStorage.getItem("currentUser")).token; + var headers = { + Authorization: `Bearer ${token}`, + }; + var data = JSON.stringify({ + email: payload.email, + password: payload.password, + }); + + const user = await axios.post("user/adminReset", data, { + headers: headers, + }); + + return user; + } catch (err) { + return console.error(err); + } +}; + export const updateUserApi = async (payload) => { try { const token = JSON.parse(localStorage.getItem("currentUser")).token; diff --git a/frontend/src/redux/saga/userSaga.js b/frontend/src/redux/saga/userSaga.js index f03ab3de..c292a514 100644 --- a/frontend/src/redux/saga/userSaga.js +++ b/frontend/src/redux/saga/userSaga.js @@ -23,6 +23,7 @@ import { UPDATE_USER, REQUEST_RESET, RESET_PASSWORD, + ADMIN_RESET_PASSWORD, } from "../actions/userActions"; import { getProjectAssignmentApi } from "../api/projectApi"; import { @@ -41,6 +42,7 @@ import { updateUserApi, requestResetApi, resetPasswordApi, + adminResetPasswordApi, } from "../api/userApi"; import { ErrorToast, SuccessToast } from "../../components/Toasts"; @@ -110,9 +112,24 @@ export function* resetPasswordSaga({ payload }) { } } +export function* adminResetPasswordSaga({ payload }) { + const resetRes = yield call(adminResetPasswordApi, payload); + if (resetRes) { + SuccessToast("Password reset successfully!"); + } else { + ErrorToast("Password reset failed. Please try again."); + } +} + export function* postUserSaga({ payload }) { const newUser = yield call(saveUserApi, payload); - if (newUser) { + if (payload.adminCreated) { + SuccessToast("Account created successfully!"); + yield call(approveUserList, { users: [payload.user_id]}); + yield delay(1000); + yield loadUserlistSaga(); + yield loadPendingUserListSaga(); + } else if (newUser) { SuccessToast("Account created successfully! Please login to continue."); payload.navTo(); } else { @@ -182,4 +199,5 @@ export default function* userSaga() { yield takeLatest(GET_USER, getUserSaga); yield takeLatest(REQUEST_RESET, requestResetSaga); yield takeLatest(RESET_PASSWORD, resetPasswordSaga); + yield takeLatest(ADMIN_RESET_PASSWORD, adminResetPasswordSaga); }