diff --git a/backend/internal/database/sample_data/0010_sample_data.sql b/backend/internal/database/sample_data/0010_sample_data.sql index 092fbb0..ab74f1a 100644 --- a/backend/internal/database/sample_data/0010_sample_data.sql +++ b/backend/internal/database/sample_data/0010_sample_data.sql @@ -7,6 +7,8 @@ VALUES ("user", "123"); INSERT OR IGNORE INTO users(username, password) VALUES ("user2", "123"); +INSERT OR IGNORE INTO site_admin VALUES (1); + INSERT OR IGNORE INTO projects(name,description,owner_user_id) VALUES ("projecttest","test project", 1); diff --git a/backend/internal/handlers/handlers_user_related.go b/backend/internal/handlers/handlers_user_related.go index 39788ae..bc4ae2d 100644 --- a/backend/internal/handlers/handlers_user_related.go +++ b/backend/internal/handlers/handlers_user_related.go @@ -59,9 +59,9 @@ func (gs *GState) UserDelete(c *fiber.Ctx) error { // Read username from Locals auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string) - if username != auth_username { - log.Info("User tried to delete another user") - return c.Status(403).SendString("You can only delete yourself") + if username == auth_username { + log.Info("User tried to delete itself") + return c.Status(403).SendString("You can't delete yourself") } if err := gs.Db.RemoveUser(username); err != nil { diff --git a/frontend/src/API/API.ts b/frontend/src/API/API.ts index e7aab0c..b31fe9b 100644 --- a/frontend/src/API/API.ts +++ b/frontend/src/API/API.ts @@ -6,6 +6,8 @@ import { NewProject, UserProjectMember, WeeklyReport, + StrNameChange, + NewProjMember, } from "../Types/goTypes"; /** @@ -132,6 +134,20 @@ interface API { projectName: string, token: string, ): Promise>; + /** + * Changes the username of a user in the database. + * @param {StrNameChange} data The object containing the previous and new username. + * @param {string} token The authentication token. + * @returns {Promise>} A promise resolving to an API response. + */ + changeUserName( + data: StrNameChange, + token: string, + ): Promise>; + addUserToProject( + user: NewProjMember, + token: string, + ): Promise>; removeProject( projectName: string, @@ -174,19 +190,17 @@ export const api: API = { ): Promise> { try { const response = await fetch(`/api/userdelete/${username}`, { - method: "POST", + method: "DELETE", headers: { "Content-Type": "application/json", Authorization: "Bearer " + token, }, body: JSON.stringify(username), }); - if (!response.ok) { - return { success: false, message: "Failed to remove user" }; + return { success: false, message: "Could not remove user" }; } else { - const data = (await response.json()) as User; - return { success: true, data }; + return { success: true }; } } catch (e) { return { success: false, message: "Failed to remove user" }; @@ -248,6 +262,30 @@ export const api: API = { } }, + async addUserToProject( + user: NewProjMember, + token: string, + ): Promise> { + try { + const response = await fetch("/api/addUserToProject", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + body: JSON.stringify(user), + }); + + if (!response.ok) { + return { success: false, message: "Failed to add member" }; + } else { + return { success: true, message: "Added member" }; + } + } catch (e) { + return { success: false, message: "Failed to add member" }; + } + }, + async renewToken(token: string): Promise> { try { const response = await fetch("/api/loginrenew", { @@ -490,6 +528,30 @@ export const api: API = { } }, + async changeUserName( + data: StrNameChange, + token: string, + ): Promise> { + try { + const response = await fetch("/api/changeUserName", { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + return { success: false, message: "Failed to change username" }; + } else { + return { success: true }; + } + } catch (e) { + return { success: false, message: "Failed to change username" }; + } + }, + async removeProject( projectName: string, token: string, diff --git a/frontend/src/Components/AddMember.tsx b/frontend/src/Components/AddMember.tsx new file mode 100644 index 0000000..d29be68 --- /dev/null +++ b/frontend/src/Components/AddMember.tsx @@ -0,0 +1,39 @@ +import { APIResponse, api } from "../API/API"; +import { NewProjMember } from "../Types/goTypes"; + +/** + * Tries to add a member to a project + * @param {Object} props - A NewProjMember + * @returns {boolean} True if added, false if not + */ +function AddMember(props: { memberToAdd: NewProjMember }): boolean { + let added = false; + if ( + props.memberToAdd.username === "" || + props.memberToAdd.role === "" || + props.memberToAdd.projectname === "" + ) { + alert("All fields must be filled before adding"); + return added; + } + api + .addUserToProject( + props.memberToAdd, + localStorage.getItem("accessToken") ?? "", + ) + .then((response: APIResponse) => { + if (response.success) { + alert("Member added"); + added = true; + } else { + alert("Member not added"); + console.error(response.message); + } + }) + .catch((error) => { + console.error("An error occurred during member add:", error); + }); + return added; +} + +export default AddMember; diff --git a/frontend/src/Components/AddUserToProject.tsx b/frontend/src/Components/AddUserToProject.tsx new file mode 100644 index 0000000..9f4439b --- /dev/null +++ b/frontend/src/Components/AddUserToProject.tsx @@ -0,0 +1,92 @@ +import { useState } from "react"; +import { NewProjMember } from "../Types/goTypes"; +import Button from "./Button"; +import GetAllUsers from "./GetAllUsers"; +import AddMember from "./AddMember"; +import BackButton from "./BackButton"; + +/** + * Provides UI for adding a member to a project. + * @returns {JSX.Element} - Returns the component UI for adding a member + */ +function AddUserToProject(): JSX.Element { + const [name, setName] = useState(""); + const [users, setUsers] = useState([]); + const [role, setRole] = useState(""); + GetAllUsers({ setUsersProp: setUsers }); + + const handleClick = (): boolean => { + const newMember: NewProjMember = { + username: name, + projectname: localStorage.getItem("projectName") ?? "", + role: role, + }; + return AddMember({ memberToAdd: newMember }); + }; + + return ( +
+

+ User chosen: [{name}] +

+

+ Role chosen: [{role}] +

+

+ Project chosen: [{localStorage.getItem("projectName") ?? ""}] +

+

Choose role:

+
+
    +
  • { + setRole("member"); + }} + > + {"Member"} +
  • +
  • { + setRole("project_manager"); + }} + > + {"Project manager"} +
  • +
+
+

Choose user:

+
+
    +
    + {users.map((user) => ( +
  • { + setName(user); + }} + > + {user} +
  • + ))} +
+
+
+
+

+
+ ); +} + +export default AddUserToProject; diff --git a/frontend/src/Components/ChangeUsername.tsx b/frontend/src/Components/ChangeUsername.tsx index 3c35e94..e297a04 100644 --- a/frontend/src/Components/ChangeUsername.tsx +++ b/frontend/src/Components/ChangeUsername.tsx @@ -1,23 +1,48 @@ import React, { useState } from "react"; import InputField from "./InputField"; +import { api } from "../API/API"; function ChangeUsername(): JSX.Element { const [newUsername, setNewUsername] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); const handleChange = (e: React.ChangeEvent): void => { setNewUsername(e.target.value); }; - // const handleSubmit = async (): Promise => { - // try { - // // Call the API function to update the username - // await api.updateUsername(newUsername); - // // Optionally, add a success message or redirect the user - // } catch (error) { - // console.error("Error updating username:", error); - // // Optionally, handle the error - // } - // }; + const handleSubmit = async (): Promise => { + try { + // Call the API function to change the username + const token = localStorage.getItem("accessToken"); + if (!token) { + throw new Error("Access token not found"); + } + + const response = await api.changeUserName( + { prevName: "currentName", newName: newUsername }, + token, + ); + + if (response.success) { + // Optionally, add a success message or redirect the user + console.log("Username changed successfully"); + } else { + // Handle the error message + console.error("Failed to change username:", response.message); + setErrorMessage(response.message ?? "Failed to change username"); + } + } catch (error) { + console.error("Error changing username:", error); + // Optionally, handle the error + setErrorMessage("Failed to change username"); + } + }; + + const handleButtonClick = (): void => { + handleSubmit().catch((error) => { + console.error("Error in handleSubmit:", error); + }); + }; return (
@@ -27,6 +52,8 @@ function ChangeUsername(): JSX.Element { value={newUsername} onChange={handleChange} /> + {errorMessage &&
{errorMessage}
} +
); } diff --git a/frontend/src/Components/DeleteUser.tsx b/frontend/src/Components/DeleteUser.tsx index db49724..d1dbc7f 100644 --- a/frontend/src/Components/DeleteUser.tsx +++ b/frontend/src/Components/DeleteUser.tsx @@ -11,7 +11,6 @@ import { api, APIResponse } from "../API/API"; */ function DeleteUser(props: { usernameToDelete: string }): boolean { - //console.log(props.usernameToDelete); FOR DEBUG let removed = false; api .removeUser( @@ -20,12 +19,16 @@ function DeleteUser(props: { usernameToDelete: string }): boolean { ) .then((response: APIResponse) => { if (response.success) { + alert("User has been deleted!"); + location.reload(); removed = true; } else { + alert("User has not been deleted"); console.error(response.message); } }) .catch((error) => { + alert("User has not been deleted"); console.error("An error occurred during creation:", error); }); return removed; diff --git a/frontend/src/Components/ProjectInfoModal.tsx b/frontend/src/Components/ProjectInfoModal.tsx index b153e9c..3075b19 100644 --- a/frontend/src/Components/ProjectInfoModal.tsx +++ b/frontend/src/Components/ProjectInfoModal.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import Button from "./Button"; import { UserProjectMember } from "../Types/goTypes"; import GetUsersInProject from "./GetUsersInProject"; +import { Link } from "react-router-dom"; function ProjectInfoModal(props: { isVisible: boolean; @@ -18,9 +19,12 @@ function ProjectInfoModal(props: { className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm flex justify-center items-center" > -
+
-

Project members:

+

+ {localStorage.getItem("projectName") ?? ""} +

+

Project members:

    @@ -50,6 +54,15 @@ function ProjectInfoModal(props: { }} type="button" /> + +