Merge branch 'frontend' into gruppDM
This commit is contained in:
		
						commit
						7df1654bdc
					
				
					 42 changed files with 942 additions and 483 deletions
				
			
		|  | @ -34,6 +34,7 @@ clean: | |||
| 	rm -f plantuml.jar | ||||
| 	rm -f erd.png | ||||
| 	rm -f config.toml | ||||
| 	rm -f database.txt | ||||
| 
 | ||||
| # Test target
 | ||||
| test: db.sqlite3 | ||||
|  | @ -105,6 +106,7 @@ docs: | |||
| 	swag init -outputTypes go | ||||
| 
 | ||||
| api: ./docs/swagger.json | ||||
| 	rm ../frontend/src/API/GenApi.ts | ||||
| 	npx swagger-typescript-api \
 | ||||
| 		--api-class-name GenApi \
 | ||||
| 		--path ./docs/swagger.json \
 | ||||
|  |  | |||
|  | @ -21,21 +21,21 @@ const docTemplate = `{ | |||
|     "paths": { | ||||
|         "/login": { | ||||
|             "post": { | ||||
|                 "description": "logs the user in and returns a jwt token", | ||||
|                 "description": "Logs in a user and returns a JWT token", | ||||
|                 "consumes": [ | ||||
|                     "application/json" | ||||
|                 ], | ||||
|                 "produces": [ | ||||
|                     "text/plain" | ||||
|                     "application/json" | ||||
|                 ], | ||||
|                 "tags": [ | ||||
|                     "User" | ||||
|                     "Auth" | ||||
|                 ], | ||||
|                 "summary": "login", | ||||
|                 "summary": "Login", | ||||
|                 "parameters": [ | ||||
|                     { | ||||
|                         "description": "login info", | ||||
|                         "name": "NewUser", | ||||
|                         "description": "User credentials", | ||||
|                         "name": "body", | ||||
|                         "in": "body", | ||||
|                         "required": true, | ||||
|                         "schema": { | ||||
|  | @ -45,9 +45,9 @@ const docTemplate = `{ | |||
|                 ], | ||||
|                 "responses": { | ||||
|                     "200": { | ||||
|                         "description": "Successfully signed token for user", | ||||
|                         "description": "JWT token", | ||||
|                         "schema": { | ||||
|                             "type": "Token" | ||||
|                             "$ref": "#/definitions/types.Token" | ||||
|                         } | ||||
|                     }, | ||||
|                     "400": { | ||||
|  | @ -71,29 +71,26 @@ const docTemplate = `{ | |||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "/loginerenew": { | ||||
|         "/loginrenew": { | ||||
|             "post": { | ||||
|                 "security": [ | ||||
|                     { | ||||
|                         "bererToken": [] | ||||
|                         "JWT": [] | ||||
|                     } | ||||
|                 ], | ||||
|                 "description": "renews the users token", | ||||
|                 "consumes": [ | ||||
|                 "description": "Renews the users token.", | ||||
|                 "produces": [ | ||||
|                     "application/json" | ||||
|                 ], | ||||
|                 "produces": [ | ||||
|                     "text/plain" | ||||
|                 ], | ||||
|                 "tags": [ | ||||
|                     "User" | ||||
|                     "Auth" | ||||
|                 ], | ||||
|                 "summary": "LoginRenews", | ||||
|                 "responses": { | ||||
|                     "200": { | ||||
|                         "description": "Successfully signed token for user", | ||||
|                         "schema": { | ||||
|                             "type": "Token" | ||||
|                             "$ref": "#/definitions/types.Token" | ||||
|                         } | ||||
|                     }, | ||||
|                     "401": { | ||||
|  | @ -113,7 +110,12 @@ const docTemplate = `{ | |||
|         }, | ||||
|         "/promoteToAdmin": { | ||||
|             "post": { | ||||
|                 "description": "promote chosen user to admin", | ||||
|                 "security": [ | ||||
|                     { | ||||
|                         "JWT": [] | ||||
|                     } | ||||
|                 ], | ||||
|                 "description": "Promote chosen user to site admin", | ||||
|                 "consumes": [ | ||||
|                     "application/json" | ||||
|                 ], | ||||
|  | @ -139,7 +141,7 @@ const docTemplate = `{ | |||
|                     "200": { | ||||
|                         "description": "Successfully promoted user", | ||||
|                         "schema": { | ||||
|                             "type": "json" | ||||
|                             "$ref": "#/definitions/types.Token" | ||||
|                         } | ||||
|                     }, | ||||
|                     "400": { | ||||
|  | @ -173,7 +175,7 @@ const docTemplate = `{ | |||
|                     "text/plain" | ||||
|                 ], | ||||
|                 "tags": [ | ||||
|                     "User" | ||||
|                     "Auth" | ||||
|                 ], | ||||
|                 "summary": "Register", | ||||
|                 "parameters": [ | ||||
|  | @ -211,6 +213,11 @@ const docTemplate = `{ | |||
|         }, | ||||
|         "/userdelete/{username}": { | ||||
|             "delete": { | ||||
|                 "security": [ | ||||
|                     { | ||||
|                         "JWT": [] | ||||
|                     } | ||||
|                 ], | ||||
|                 "description": "UserDelete deletes a user from the database", | ||||
|                 "consumes": [ | ||||
|                     "application/json" | ||||
|  | @ -252,12 +259,14 @@ const docTemplate = `{ | |||
|         }, | ||||
|         "/users/all": { | ||||
|             "get": { | ||||
|                 "description": "lists all users", | ||||
|                 "consumes": [ | ||||
|                     "application/json" | ||||
|                 "security": [ | ||||
|                     { | ||||
|                         "JWT": [] | ||||
|                     } | ||||
|                 ], | ||||
|                 "description": "lists all users", | ||||
|                 "produces": [ | ||||
|                     "text/plain" | ||||
|                     "application/json" | ||||
|                 ], | ||||
|                 "tags": [ | ||||
|                     "User" | ||||
|  | @ -265,9 +274,12 @@ const docTemplate = `{ | |||
|                 "summary": "ListsAllUsers", | ||||
|                 "responses": { | ||||
|                     "200": { | ||||
|                         "description": "Successfully signed token for user", | ||||
|                         "description": "Successfully returned all users", | ||||
|                         "schema": { | ||||
|                             "type": "json" | ||||
|                             "type": "array", | ||||
|                             "items": { | ||||
|                                 "type": "string" | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     "401": { | ||||
|  | @ -291,16 +303,27 @@ const docTemplate = `{ | |||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "password": { | ||||
|                     "type": "string" | ||||
|                     "type": "string", | ||||
|                     "example": "password123" | ||||
|                 }, | ||||
|                 "username": { | ||||
|                     "type": "string", | ||||
|                     "example": "username123" | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "types.Token": { | ||||
|             "type": "object", | ||||
|             "properties": { | ||||
|                 "token": { | ||||
|                     "type": "string" | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     "securityDefinitions": { | ||||
|         "bererToken": { | ||||
|         "JWT": { | ||||
|             "description": "Use the JWT token provided by the login endpoint to authenticate requests.  **Prefix the token with \"Bearer \".**", | ||||
|             "type": "apiKey", | ||||
|             "name": "Authorization", | ||||
|             "in": "header" | ||||
|  |  | |||
|  | @ -4,15 +4,16 @@ import ( | |||
| 	db "ttime/internal/database" | ||||
| 
 | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/golang-jwt/jwt/v5" | ||||
| 	"github.com/gofiber/fiber/v2/log" | ||||
| ) | ||||
| 
 | ||||
| // GetUserProjects returns all projects that the user is a member of | ||||
| func GetUserProjects(c *fiber.Ctx) error { | ||||
| 	// First we get the username from the token | ||||
| 	user := c.Locals("user").(*jwt.Token) | ||||
| 	claims := user.Claims.(jwt.MapClaims) | ||||
| 	username := claims["name"].(string) | ||||
| 	username := c.Params("username") | ||||
| 	if username == "" { | ||||
| 		log.Info("No username provided") | ||||
| 		return c.Status(400).SendString("No username provided") | ||||
| 	} | ||||
| 
 | ||||
| 	// Then dip into the database to get the projects | ||||
| 	projects, err := db.GetDb(c).GetProjectsForUser(username) | ||||
|  |  | |||
|  | @ -24,7 +24,13 @@ func ProjectRoleChange(c *fiber.Ctx) error { | |||
| 		return c.Status(400).SendString(err.Error()) | ||||
| 	} | ||||
| 
 | ||||
| 	log.Info("Changing role for user: ", username, " in project: ", data.Projectname, " to: ", data.Role) | ||||
| 	// Check if user is trying to change its own role | ||||
| 	if username == data.UserName { | ||||
| 		log.Info("Can't change your own role") | ||||
| 		return c.Status(403).SendString("Can't change your own role") | ||||
| 	} | ||||
| 
 | ||||
| 	log.Info("Changing role for user: ", data.UserName, " in project: ", data.Projectname, " to: ", data.Role) | ||||
| 
 | ||||
| 	// Dubble diping and checcking if current user is | ||||
| 	if ismanager, err := db.GetDb(c).IsProjectManager(username, data.Projectname); err != nil { | ||||
|  | @ -36,7 +42,7 @@ func ProjectRoleChange(c *fiber.Ctx) error { | |||
| 	} | ||||
| 
 | ||||
| 	// Change the user's role within the project in the database | ||||
| 	if err := db.GetDb(c).ChangeUserRole(username, data.Projectname, data.Role); err != nil { | ||||
| 	if err := db.GetDb(c).ChangeUserRole(data.UserName, data.Projectname, data.Role); err != nil { | ||||
| 		return c.Status(500).SendString(err.Error()) | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,16 +7,17 @@ import ( | |||
| 	"github.com/gofiber/fiber/v2/log" | ||||
| ) | ||||
| 
 | ||||
| // ListAllUsers is a handler that returns a list of all users in the application database | ||||
| // @Summary		ListsAllUsers | ||||
| // @Description	lists all users | ||||
| // @Tags			User | ||||
| // @Accept			json | ||||
| // @Produce		plain | ||||
| // @Success		200	{json}		json	"Successfully signed token for user" | ||||
| // @Failure		401	{string}	string	"Unauthorized" | ||||
| // @Failure		500	{string}	string	"Internal server error" | ||||
| // @Router			/users/all [get] | ||||
| //	@Summary		ListsAllUsers | ||||
| //	@Description	lists all users | ||||
| //	@Tags			User | ||||
| //	@Produce		json | ||||
| //	@Security		JWT | ||||
| //	@Success		200	{array}		string	"Successfully returned all users" | ||||
| //	@Failure		401	{string}	string	"Unauthorized" | ||||
| //	@Failure		500	{string}	string	"Internal server error" | ||||
| //	@Router			/users/all [get] | ||||
| // | ||||
| // ListAllUsers returns a list of all users in the application database | ||||
| func ListAllUsers(c *fiber.Ctx) error { | ||||
| 	// Get all users from the database | ||||
| 	users, err := db.GetDb(c).GetAllUsersApplication() | ||||
|  |  | |||
|  | @ -10,18 +10,19 @@ import ( | |||
| 	"github.com/golang-jwt/jwt/v5" | ||||
| ) | ||||
| 
 | ||||
| // Login is a simple login handler that returns a JWT token | ||||
| // @Summary		login | ||||
| // @Description	logs the user in and returns a jwt token | ||||
| // @Tags			User | ||||
| // @Accept			json | ||||
| // @Param			NewUser	body	types.NewUser	true	"login info" | ||||
| // @Produce		plain | ||||
| // @Success		200	Token		types.Token	"Successfully signed token for user" | ||||
| // @Failure		400	{string}	string		"Bad request" | ||||
| // @Failure		401	{string}	string		"Unauthorized" | ||||
| // @Failure		500	{string}	string		"Internal server error" | ||||
| // @Router			/login [post] | ||||
| //	@Summary		Login | ||||
| //	@Description	Logs in a user and returns a JWT token | ||||
| //	@Tags			Auth | ||||
| //	@Accept			json | ||||
| //	@Produce		json | ||||
| //	@Param			body	body		types.NewUser	true	"User credentials" | ||||
| //	@Success		200		{object}	types.Token		"JWT token" | ||||
| //	@Failure		400		{string}	string			"Bad request" | ||||
| //	@Failure		401		{string}	string			"Unauthorized" | ||||
| //	@Failure		500		{string}	string			"Internal server error" | ||||
| //	@Router			/login [post] | ||||
| // | ||||
| // Login logs in a user and returns a JWT token | ||||
| func Login(c *fiber.Ctx) error { | ||||
| 	// The body type is identical to a NewUser | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,34 +9,40 @@ import ( | |||
| 	"github.com/golang-jwt/jwt/v5" | ||||
| ) | ||||
| 
 | ||||
| // LoginRenew is a simple handler that renews the token | ||||
| // @Summary		LoginRenews | ||||
| // @Description	renews the users token | ||||
| // @Security		bererToken | ||||
| // @Tags			User | ||||
| // @Accept			json | ||||
| // @Produce		plain | ||||
| // @Success		200	Token		types.Token	"Successfully signed token for user" | ||||
| // @Failure		401	{string}	string		"Unauthorized" | ||||
| // @Failure		500	{string}	string		"Internal server error" | ||||
| // @Router			/loginerenew [post] | ||||
| //	@Summary		LoginRenews | ||||
| //	@Description	Renews the users token. | ||||
| //	@Tags			Auth | ||||
| //	@Produce		json | ||||
| //	@Security		JWT | ||||
| //	@Success		200	{object}	types.Token	"Successfully signed token for user" | ||||
| //	@Failure		401	{string}	string		"Unauthorized" | ||||
| //	@Failure		500	{string}	string		"Internal server error" | ||||
| //	@Router			/loginrenew [post] | ||||
| // | ||||
| // LoginRenew renews the users token | ||||
| func LoginRenew(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("user").(*jwt.Token) | ||||
| 
 | ||||
| 	log.Info("Renewing token for user:", user.Claims.(jwt.MapClaims)["name"]) | ||||
| 
 | ||||
| 	// Renewing the token means we trust whatever is already in the token | ||||
| 	claims := user.Claims.(jwt.MapClaims) | ||||
| 
 | ||||
| 	// 72 hour expiration time | ||||
| 	claims["exp"] = time.Now().Add(time.Hour * 72).Unix() | ||||
| 	renewed := jwt.MapClaims{ | ||||
| 
 | ||||
| 	// Create token with old claims, but new expiration time | ||||
| 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ | ||||
| 		"name":  claims["name"], | ||||
| 		"admin": claims["admin"], | ||||
| 		"exp":   claims["exp"], | ||||
| 	} | ||||
| 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, renewed) | ||||
| 	}) | ||||
| 
 | ||||
| 	// Sign it with top secret key | ||||
| 	t, err := token.SignedString([]byte("secret")) | ||||
| 	if err != nil { | ||||
| 		log.Warn("Error signing token") | ||||
| 		return c.SendStatus(fiber.StatusInternalServerError) | ||||
| 		return c.SendStatus(fiber.StatusInternalServerError) // 500 | ||||
| 	} | ||||
| 
 | ||||
| 	log.Info("Successfully renewed token for user:", user.Claims.(jwt.MapClaims)["name"]) | ||||
|  |  | |||
|  | @ -8,17 +8,20 @@ import ( | |||
| 	"github.com/gofiber/fiber/v2/log" | ||||
| ) | ||||
| 
 | ||||
| // @Summary		PromoteToAdmin | ||||
| // @Description	promote chosen user to admin | ||||
| // @Tags			User | ||||
| // @Accept			json | ||||
| // @Produce		plain | ||||
| // @Param			NewUser	body		types.NewUser	true	"user info" | ||||
| // @Success		200		{json}		json			"Successfully promoted user" | ||||
| // @Failure		400		{string}	string			"Bad request" | ||||
| // @Failure		401		{string}	string			"Unauthorized" | ||||
| // @Failure		500		{string}	string			"Internal server error" | ||||
| // @Router			/promoteToAdmin [post] | ||||
| //	@Summary		PromoteToAdmin | ||||
| //	@Description	Promote chosen user to site admin | ||||
| //	@Tags			User | ||||
| //	@Accept			json | ||||
| //	@Produce		plain | ||||
| //	@Security		JWT | ||||
| //	@Param			NewUser	body		types.NewUser	true	"user info" | ||||
| //	@Success		200		{object}	types.Token		"Successfully promoted user" | ||||
| //	@Failure		400		{string}	string			"Bad request" | ||||
| //	@Failure		401		{string}	string			"Unauthorized" | ||||
| //	@Failure		500		{string}	string			"Internal server error" | ||||
| //	@Router			/promoteToAdmin [post] | ||||
| // | ||||
| // PromoteToAdmin promotes a user to a site admin | ||||
| func PromoteToAdmin(c *fiber.Ctx) error { | ||||
| 	// Extract the username from the request body | ||||
| 	var newUser types.NewUser | ||||
|  |  | |||
|  | @ -8,11 +8,9 @@ import ( | |||
| 	"github.com/gofiber/fiber/v2/log" | ||||
| ) | ||||
| 
 | ||||
| // Register is a simple handler that registers a new user | ||||
| // | ||||
| //	@Summary		Register | ||||
| //	@Description	Register a new user | ||||
| //	@Tags			User | ||||
| //	@Tags			Auth | ||||
| //	@Accept			json | ||||
| //	@Produce		plain | ||||
| //	@Param			NewUser	body		types.NewUser	true	"User to register" | ||||
|  | @ -20,6 +18,8 @@ import ( | |||
| //	@Failure		400		{string}	string			"Bad request" | ||||
| //	@Failure		500		{string}	string			"Internal server error" | ||||
| //	@Router			/register [post] | ||||
| // | ||||
| // Register is a simple handler that registers a new user | ||||
| func Register(c *fiber.Ctx) error { | ||||
| 	u := new(types.NewUser) | ||||
| 	if err := c.BodyParser(u); err != nil { | ||||
|  |  | |||
|  | @ -8,19 +8,19 @@ import ( | |||
| 	"github.com/golang-jwt/jwt/v5" | ||||
| ) | ||||
| 
 | ||||
| // This path should obviously be protected in the future | ||||
| // UserDelete deletes a user from the database | ||||
| // | ||||
| //	@Summary		UserDelete | ||||
| //	@Description	UserDelete deletes a user from the database | ||||
| //	@Tags			User | ||||
| //	@Accept			json | ||||
| //	@Produce		plain | ||||
| //	@Security		JWT | ||||
| //	@Success		200	{string}	string	"User deleted" | ||||
| //	@Failure		403	{string}	string	"You can only delete yourself" | ||||
| //	@Failure		500	{string}	string	"Internal server error" | ||||
| //	@Failure		401	{string}	string	"Unauthorized" | ||||
| //	@Router			/userdelete/{username} [delete] | ||||
| // | ||||
| // UserDelete deletes a user from the database | ||||
| func UserDelete(c *fiber.Ctx) error { | ||||
| 	// Read from path parameters | ||||
| 	username := c.Params("username") | ||||
|  |  | |||
|  | @ -18,8 +18,8 @@ func (u *User) ToPublicUser() (*PublicUser, error) { | |||
| 
 | ||||
| // Should be used when registering, for example | ||||
| type NewUser struct { | ||||
| 	Username string `json:"username"` | ||||
| 	Password string `json:"password"` | ||||
| 	Username string `json:"username" example:"username123"` | ||||
| 	Password string `json:"password" example:"password123"` | ||||
| } | ||||
| 
 | ||||
| // PublicUser represents a user that is safe to send over the API (no password) | ||||
|  |  | |||
|  | @ -25,15 +25,16 @@ import ( | |||
| //	@license.name	AGPL | ||||
| //	@license.url	https://www.gnu.org/licenses/agpl-3.0.html | ||||
| 
 | ||||
| //@securityDefinitions.apikey bererToken | ||||
| //@in header | ||||
| //@name Authorization | ||||
| //	@securityDefinitions.apikey	JWT | ||||
| //	@in							header | ||||
| //	@name						Authorization | ||||
| //	@description				Use the JWT token provided by the login endpoint to authenticate requests.  **Prefix the token with "Bearer ".** | ||||
| 
 | ||||
| //	@host		localhost:8080 | ||||
| //	@BasePath	/api | ||||
| 
 | ||||
| // @externalDocs.description	OpenAPI | ||||
| // @externalDocs.url			https://swagger.io/resources/open-api/ | ||||
| //	@externalDocs.description	OpenAPI | ||||
| //	@externalDocs.url			https://swagger.io/resources/open-api/ | ||||
| 
 | ||||
| /** | ||||
| Main function for starting the server and initializing configurations. | ||||
|  | @ -111,7 +112,7 @@ func main() { | |||
| 
 | ||||
| 	// All project related routes | ||||
| 	// projectGroup := api.Group("/project") // Not currently in use | ||||
| 	api.Get("/getUserProjects", projects.GetUserProjects) | ||||
| 	api.Get("/getUserProjects/:username", projects.GetUserProjects) | ||||
| 	api.Get("/project/:projectId", projects.GetProject) | ||||
| 	api.Get("/checkIfProjectManager/:projectName", projects.IsProjectManagerHandler) | ||||
| 	api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject) | ||||
|  |  | |||
							
								
								
									
										2
									
								
								frontend/.prettierignore
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								frontend/.prettierignore
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| goTypes.ts | ||||
| GenApi.ts | ||||
|  | @ -1,13 +1,14 @@ | |||
| import { NewProjMember } from "../Components/AddMember"; | ||||
| import { ProjectRoleChange } from "../Components/ChangeRole"; | ||||
| import { ProjectMember } from "../Components/GetUsersInProject"; | ||||
| import { | ||||
|   NewWeeklyReport, | ||||
|   NewUser, | ||||
|   User, | ||||
|   Project, | ||||
|   NewProject, | ||||
|   UserProjectMember, | ||||
|   WeeklyReport, | ||||
|   StrNameChange, | ||||
|   NewProjMember, | ||||
| } from "../Types/goTypes"; | ||||
| 
 | ||||
| /** | ||||
|  | @ -73,10 +74,7 @@ interface API { | |||
|    * @param {string} token The authentication token. | ||||
|    * @returns {Promise<APIResponse<Project>>} A promise resolving to an API response with the created project. | ||||
|    */ | ||||
|   createProject( | ||||
|     project: NewProject, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<Project>>; | ||||
|   createProject(project: NewProject, token: string): Promise<APIResponse<void>>; | ||||
| 
 | ||||
|   /** Submits a weekly report | ||||
|    * @param {NewWeeklyReport} weeklyReport The weekly report object. | ||||
|  | @ -113,10 +111,14 @@ interface API { | |||
|   ): Promise<APIResponse<WeeklyReport[]>>; | ||||
| 
 | ||||
|   /** Gets all the projects of a user | ||||
|    * @param {string} username - The authentication token. | ||||
|    * @param {string} token - The authentication token. | ||||
|    * @returns {Promise<APIResponse<Project[]>>} A promise containing the API response with the user's projects. | ||||
|    */ | ||||
|   getUserProjects(token: string): Promise<APIResponse<Project[]>>; | ||||
|   getUserProjects( | ||||
|     username: string, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<Project[]>>; | ||||
| 
 | ||||
|   /** Gets a project by its id. | ||||
|    * @param {number} id The id of the project to retrieve. | ||||
|  | @ -133,7 +135,7 @@ interface API { | |||
|   getAllUsersProject( | ||||
|     projectName: string, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<UserProjectMember[]>>; | ||||
|   ): Promise<APIResponse<ProjectMember[]>>; | ||||
|   /** | ||||
|    * Changes the username of a user in the database. | ||||
|    * @param {StrNameChange} data The object containing the previous and new username. | ||||
|  | @ -144,10 +146,21 @@ interface API { | |||
|     data: StrNameChange, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<void>>; | ||||
|   /** | ||||
|    * Changes the role of a user in the database. | ||||
|    * @param {RoleChange} roleInfo The object containing the previous and new username. | ||||
|    * @param {string} token The authentication token. | ||||
|    * @returns {Promise<APIResponse<void>>} A promise resolving to an API response. | ||||
|    */ | ||||
|   changeUserRole( | ||||
|     roleInfo: ProjectRoleChange, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<void>>; | ||||
| 
 | ||||
|   addUserToProject( | ||||
|     user: NewProjMember, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<NewProjMember>>; | ||||
|   ): Promise<APIResponse<void>>; | ||||
| 
 | ||||
|   removeProject( | ||||
|     projectName: string, | ||||
|  | @ -249,7 +262,7 @@ export const api: API = { | |||
|   async createProject( | ||||
|     project: NewProject, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<Project>> { | ||||
|   ): Promise<APIResponse<void>> { | ||||
|     try { | ||||
|       const response = await fetch("/api/project", { | ||||
|         method: "POST", | ||||
|  | @ -263,18 +276,17 @@ export const api: API = { | |||
|       if (!response.ok) { | ||||
|         return { success: false, message: "Failed to create project" }; | ||||
|       } else { | ||||
|         const data = (await response.json()) as Project; | ||||
|         return { success: true, data }; | ||||
|         return { success: true }; | ||||
|       } | ||||
|     } catch (e) { | ||||
|       return { success: false, message: "Failed to create project" }; | ||||
|       return { success: false, message: "Failed to create project!" }; | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   async addUserToProject( | ||||
|     user: NewProjMember, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<NewProjMember>> { | ||||
|   ): Promise<APIResponse<void>> { | ||||
|     try { | ||||
|       const response = await fetch("/api/addUserToProject", { | ||||
|         method: "PUT", | ||||
|  | @ -316,9 +328,39 @@ export const api: API = { | |||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   async getUserProjects(token: string): Promise<APIResponse<Project[]>> { | ||||
|   async changeUserRole( | ||||
|     roleInfo: ProjectRoleChange, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<void>> { | ||||
|     try { | ||||
|       const response = await fetch("/api/getUserProjects", { | ||||
|       const response = await fetch("/api/ProjectRoleChange", { | ||||
|         method: "POST", | ||||
|         headers: { | ||||
|           "Content-Type": "application/json", | ||||
|           Authorization: "Bearer " + token, | ||||
|         }, | ||||
|         body: JSON.stringify(roleInfo), | ||||
|       }); | ||||
| 
 | ||||
|       if (!response.ok) { | ||||
|         if (response.status === 403) { | ||||
|           return { success: false, message: "Cannot change your own role" }; | ||||
|         } | ||||
|         return { success: false, message: "Could not change role" }; | ||||
|       } else { | ||||
|         return { success: true }; | ||||
|       } | ||||
|     } catch (e) { | ||||
|       return { success: false, message: "Could not change role" }; | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   async getUserProjects( | ||||
|     username: string, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<Project[]>> { | ||||
|     try { | ||||
|       const response = await fetch(`/api/getUserProjects/${username}`, { | ||||
|         method: "GET", | ||||
|         headers: { | ||||
|           "Content-Type": "application/json", | ||||
|  | @ -510,7 +552,7 @@ export const api: API = { | |||
|   async getAllUsersProject( | ||||
|     projectName: string, | ||||
|     token: string, | ||||
|   ): Promise<APIResponse<UserProjectMember[]>> { | ||||
|   ): Promise<APIResponse<ProjectMember[]>> { | ||||
|     try { | ||||
|       const response = await fetch(`/api/getUsersProject/${projectName}`, { | ||||
|         method: "GET", | ||||
|  | @ -526,7 +568,7 @@ export const api: API = { | |||
|           message: "Failed to get users", | ||||
|         }); | ||||
|       } else { | ||||
|         const data = (await response.json()) as UserProjectMember[]; | ||||
|         const data = (await response.json()) as ProjectMember[]; | ||||
|         return Promise.resolve({ success: true, data }); | ||||
|       } | ||||
|     } catch (e) { | ||||
|  |  | |||
							
								
								
									
										358
									
								
								frontend/src/API/GenApi.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								frontend/src/API/GenApi.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,358 @@ | |||
| /* eslint-disable */ | ||||
| /* tslint:disable */ | ||||
| /* | ||||
|  * --------------------------------------------------------------- | ||||
|  * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ## | ||||
|  * ##                                                           ## | ||||
|  * ## AUTHOR: acacode                                           ## | ||||
|  * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
 | ||||
|  * --------------------------------------------------------------- | ||||
|  */ | ||||
| 
 | ||||
| export interface TypesNewUser { | ||||
|   /** @example "password123" */ | ||||
|   password?: string; | ||||
|   /** @example "username123" */ | ||||
|   username?: string; | ||||
| } | ||||
| 
 | ||||
| export interface TypesToken { | ||||
|   token?: string; | ||||
| } | ||||
| 
 | ||||
| export type QueryParamsType = Record<string | number, any>; | ||||
| export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">; | ||||
| 
 | ||||
| export interface FullRequestParams extends Omit<RequestInit, "body"> { | ||||
|   /** set parameter to `true` for call `securityWorker` for this request */ | ||||
|   secure?: boolean; | ||||
|   /** request path */ | ||||
|   path: string; | ||||
|   /** content type of request body */ | ||||
|   type?: ContentType; | ||||
|   /** query params */ | ||||
|   query?: QueryParamsType; | ||||
|   /** format of response (i.e. response.json() -> format: "json") */ | ||||
|   format?: ResponseFormat; | ||||
|   /** request body */ | ||||
|   body?: unknown; | ||||
|   /** base url */ | ||||
|   baseUrl?: string; | ||||
|   /** request cancellation token */ | ||||
|   cancelToken?: CancelToken; | ||||
| } | ||||
| 
 | ||||
| export type RequestParams = Omit<FullRequestParams, "body" | "method" | "query" | "path">; | ||||
| 
 | ||||
| export interface ApiConfig<SecurityDataType = unknown> { | ||||
|   baseUrl?: string; | ||||
|   baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">; | ||||
|   securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void; | ||||
|   customFetch?: typeof fetch; | ||||
| } | ||||
| 
 | ||||
| export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response { | ||||
|   data: D; | ||||
|   error: E; | ||||
| } | ||||
| 
 | ||||
| type CancelToken = Symbol | string | number; | ||||
| 
 | ||||
| export enum ContentType { | ||||
|   Json = "application/json", | ||||
|   FormData = "multipart/form-data", | ||||
|   UrlEncoded = "application/x-www-form-urlencoded", | ||||
|   Text = "text/plain", | ||||
| } | ||||
| 
 | ||||
| export class HttpClient<SecurityDataType = unknown> { | ||||
|   public baseUrl: string = "//localhost:8080/api"; | ||||
|   private securityData: SecurityDataType | null = null; | ||||
|   private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"]; | ||||
|   private abortControllers = new Map<CancelToken, AbortController>(); | ||||
|   private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams); | ||||
| 
 | ||||
|   private baseApiParams: RequestParams = { | ||||
|     credentials: "same-origin", | ||||
|     headers: {}, | ||||
|     redirect: "follow", | ||||
|     referrerPolicy: "no-referrer", | ||||
|   }; | ||||
| 
 | ||||
|   constructor(apiConfig: ApiConfig<SecurityDataType> = {}) { | ||||
|     Object.assign(this, apiConfig); | ||||
|   } | ||||
| 
 | ||||
|   public setSecurityData = (data: SecurityDataType | null) => { | ||||
|     this.securityData = data; | ||||
|   }; | ||||
| 
 | ||||
|   protected encodeQueryParam(key: string, value: any) { | ||||
|     const encodedKey = encodeURIComponent(key); | ||||
|     return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`; | ||||
|   } | ||||
| 
 | ||||
|   protected addQueryParam(query: QueryParamsType, key: string) { | ||||
|     return this.encodeQueryParam(key, query[key]); | ||||
|   } | ||||
| 
 | ||||
|   protected addArrayQueryParam(query: QueryParamsType, key: string) { | ||||
|     const value = query[key]; | ||||
|     return value.map((v: any) => this.encodeQueryParam(key, v)).join("&"); | ||||
|   } | ||||
| 
 | ||||
|   protected toQueryString(rawQuery?: QueryParamsType): string { | ||||
|     const query = rawQuery || {}; | ||||
|     const keys = Object.keys(query).filter((key) => "undefined" !== typeof query[key]); | ||||
|     return keys | ||||
|       .map((key) => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key))) | ||||
|       .join("&"); | ||||
|   } | ||||
| 
 | ||||
|   protected addQueryParams(rawQuery?: QueryParamsType): string { | ||||
|     const queryString = this.toQueryString(rawQuery); | ||||
|     return queryString ? `?${queryString}` : ""; | ||||
|   } | ||||
| 
 | ||||
|   private contentFormatters: Record<ContentType, (input: any) => any> = { | ||||
|     [ContentType.Json]: (input: any) => | ||||
|       input !== null && (typeof input === "object" || typeof input === "string") ? JSON.stringify(input) : input, | ||||
|     [ContentType.Text]: (input: any) => (input !== null && typeof input !== "string" ? JSON.stringify(input) : input), | ||||
|     [ContentType.FormData]: (input: any) => | ||||
|       Object.keys(input || {}).reduce((formData, key) => { | ||||
|         const property = input[key]; | ||||
|         formData.append( | ||||
|           key, | ||||
|           property instanceof Blob | ||||
|             ? property | ||||
|             : typeof property === "object" && property !== null | ||||
|             ? JSON.stringify(property) | ||||
|             : `${property}`, | ||||
|         ); | ||||
|         return formData; | ||||
|       }, new FormData()), | ||||
|     [ContentType.UrlEncoded]: (input: any) => this.toQueryString(input), | ||||
|   }; | ||||
| 
 | ||||
|   protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams { | ||||
|     return { | ||||
|       ...this.baseApiParams, | ||||
|       ...params1, | ||||
|       ...(params2 || {}), | ||||
|       headers: { | ||||
|         ...(this.baseApiParams.headers || {}), | ||||
|         ...(params1.headers || {}), | ||||
|         ...((params2 && params2.headers) || {}), | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => { | ||||
|     if (this.abortControllers.has(cancelToken)) { | ||||
|       const abortController = this.abortControllers.get(cancelToken); | ||||
|       if (abortController) { | ||||
|         return abortController.signal; | ||||
|       } | ||||
|       return void 0; | ||||
|     } | ||||
| 
 | ||||
|     const abortController = new AbortController(); | ||||
|     this.abortControllers.set(cancelToken, abortController); | ||||
|     return abortController.signal; | ||||
|   }; | ||||
| 
 | ||||
|   public abortRequest = (cancelToken: CancelToken) => { | ||||
|     const abortController = this.abortControllers.get(cancelToken); | ||||
| 
 | ||||
|     if (abortController) { | ||||
|       abortController.abort(); | ||||
|       this.abortControllers.delete(cancelToken); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   public request = async <T = any, E = any>({ | ||||
|     body, | ||||
|     secure, | ||||
|     path, | ||||
|     type, | ||||
|     query, | ||||
|     format, | ||||
|     baseUrl, | ||||
|     cancelToken, | ||||
|     ...params | ||||
|   }: FullRequestParams): Promise<HttpResponse<T, E>> => { | ||||
|     const secureParams = | ||||
|       ((typeof secure === "boolean" ? secure : this.baseApiParams.secure) && | ||||
|         this.securityWorker && | ||||
|         (await this.securityWorker(this.securityData))) || | ||||
|       {}; | ||||
|     const requestParams = this.mergeRequestParams(params, secureParams); | ||||
|     const queryString = query && this.toQueryString(query); | ||||
|     const payloadFormatter = this.contentFormatters[type || ContentType.Json]; | ||||
|     const responseFormat = format || requestParams.format; | ||||
| 
 | ||||
|     return this.customFetch(`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`, { | ||||
|       ...requestParams, | ||||
|       headers: { | ||||
|         ...(requestParams.headers || {}), | ||||
|         ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}), | ||||
|       }, | ||||
|       signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null, | ||||
|       body: typeof body === "undefined" || body === null ? null : payloadFormatter(body), | ||||
|     }).then(async (response) => { | ||||
|       const r = response as HttpResponse<T, E>; | ||||
|       r.data = null as unknown as T; | ||||
|       r.error = null as unknown as E; | ||||
| 
 | ||||
|       const data = !responseFormat | ||||
|         ? r | ||||
|         : await response[responseFormat]() | ||||
|             .then((data) => { | ||||
|               if (r.ok) { | ||||
|                 r.data = data; | ||||
|               } else { | ||||
|                 r.error = data; | ||||
|               } | ||||
|               return r; | ||||
|             }) | ||||
|             .catch((e) => { | ||||
|               r.error = e; | ||||
|               return r; | ||||
|             }); | ||||
| 
 | ||||
|       if (cancelToken) { | ||||
|         this.abortControllers.delete(cancelToken); | ||||
|       } | ||||
| 
 | ||||
|       if (!response.ok) throw data; | ||||
|       return data; | ||||
|     }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @title TTime API | ||||
|  * @version 0.0.1 | ||||
|  * @license AGPL (https://www.gnu.org/licenses/agpl-3.0.html)
 | ||||
|  * @baseUrl //localhost:8080/api
 | ||||
|  * @externalDocs https://swagger.io/resources/open-api/
 | ||||
|  * @contact | ||||
|  * | ||||
|  * This is the API for TTime, a time tracking application. | ||||
|  */ | ||||
| export class GenApi<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> { | ||||
|   login = { | ||||
|     /** | ||||
|      * @description Logs in a user and returns a JWT token | ||||
|      * | ||||
|      * @tags Auth | ||||
|      * @name LoginCreate | ||||
|      * @summary Login | ||||
|      * @request POST:/login | ||||
|      */ | ||||
|     loginCreate: (body: TypesNewUser, params: RequestParams = {}) => | ||||
|       this.request<TypesToken, string>({ | ||||
|         path: `/login`, | ||||
|         method: "POST", | ||||
|         body: body, | ||||
|         type: ContentType.Json, | ||||
|         format: "json", | ||||
|         ...params, | ||||
|       }), | ||||
|   }; | ||||
|   loginrenew = { | ||||
|     /** | ||||
|      * @description Renews the users token. | ||||
|      * | ||||
|      * @tags Auth | ||||
|      * @name LoginrenewCreate | ||||
|      * @summary LoginRenews | ||||
|      * @request POST:/loginrenew | ||||
|      * @secure | ||||
|      */ | ||||
|     loginrenewCreate: (params: RequestParams = {}) => | ||||
|       this.request<TypesToken, string>({ | ||||
|         path: `/loginrenew`, | ||||
|         method: "POST", | ||||
|         secure: true, | ||||
|         format: "json", | ||||
|         ...params, | ||||
|       }), | ||||
|   }; | ||||
|   promoteToAdmin = { | ||||
|     /** | ||||
|      * @description Promote chosen user to site admin | ||||
|      * | ||||
|      * @tags User | ||||
|      * @name PromoteToAdminCreate | ||||
|      * @summary PromoteToAdmin | ||||
|      * @request POST:/promoteToAdmin | ||||
|      * @secure | ||||
|      */ | ||||
|     promoteToAdminCreate: (NewUser: TypesNewUser, params: RequestParams = {}) => | ||||
|       this.request<TypesToken, string>({ | ||||
|         path: `/promoteToAdmin`, | ||||
|         method: "POST", | ||||
|         body: NewUser, | ||||
|         secure: true, | ||||
|         type: ContentType.Json, | ||||
|         ...params, | ||||
|       }), | ||||
|   }; | ||||
|   register = { | ||||
|     /** | ||||
|      * @description Register a new user | ||||
|      * | ||||
|      * @tags Auth | ||||
|      * @name RegisterCreate | ||||
|      * @summary Register | ||||
|      * @request POST:/register | ||||
|      */ | ||||
|     registerCreate: (NewUser: TypesNewUser, params: RequestParams = {}) => | ||||
|       this.request<string, string>({ | ||||
|         path: `/register`, | ||||
|         method: "POST", | ||||
|         body: NewUser, | ||||
|         type: ContentType.Json, | ||||
|         ...params, | ||||
|       }), | ||||
|   }; | ||||
|   userdelete = { | ||||
|     /** | ||||
|      * @description UserDelete deletes a user from the database | ||||
|      * | ||||
|      * @tags User | ||||
|      * @name UserdeleteDelete | ||||
|      * @summary UserDelete | ||||
|      * @request DELETE:/userdelete/{username} | ||||
|      * @secure | ||||
|      */ | ||||
|     userdeleteDelete: (username: string, params: RequestParams = {}) => | ||||
|       this.request<string, string>({ | ||||
|         path: `/userdelete/${username}`, | ||||
|         method: "DELETE", | ||||
|         secure: true, | ||||
|         type: ContentType.Json, | ||||
|         ...params, | ||||
|       }), | ||||
|   }; | ||||
|   users = { | ||||
|     /** | ||||
|      * @description lists all users | ||||
|      * | ||||
|      * @tags User | ||||
|      * @name GetUsers | ||||
|      * @summary ListsAllUsers | ||||
|      * @request GET:/users/all | ||||
|      * @secure | ||||
|      */ | ||||
|     getUsers: (params: RequestParams = {}) => | ||||
|       this.request<string[], string>({ | ||||
|         path: `/users/all`, | ||||
|         method: "GET", | ||||
|         secure: true, | ||||
|         format: "json", | ||||
|         ...params, | ||||
|       }), | ||||
|   }; | ||||
| } | ||||
|  | @ -1,5 +1,10 @@ | |||
| import { APIResponse, api } from "../API/API"; | ||||
| import { NewProjMember } from "../Types/goTypes"; | ||||
| 
 | ||||
| export interface NewProjMember { | ||||
|   username: string; | ||||
|   role: string; | ||||
|   projectname: string; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Tries to add a member to a project | ||||
|  | @ -21,7 +26,7 @@ function AddMember(props: { memberToAdd: NewProjMember }): boolean { | |||
|       props.memberToAdd, | ||||
|       localStorage.getItem("accessToken") ?? "", | ||||
|     ) | ||||
|     .then((response: APIResponse<NewProjMember>) => { | ||||
|     .then((response: APIResponse<void>) => { | ||||
|       if (response.success) { | ||||
|         alert("Member added"); | ||||
|         added = true; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { useState } from "react"; | ||||
| import { APIResponse, api } from "../API/API"; | ||||
| import { NewProject, Project } from "../Types/goTypes"; | ||||
| import { NewProject } from "../Types/goTypes"; | ||||
| import InputField from "./InputField"; | ||||
| import Logo from "../assets/Logo.svg"; | ||||
| import Button from "./Button"; | ||||
|  | @ -10,27 +10,26 @@ import Button from "./Button"; | |||
|  * @param {Object} props - Project name and description | ||||
|  * @returns {boolean} True if created, false if not | ||||
|  */ | ||||
| function CreateProject(props: { name: string; description: string }): boolean { | ||||
| function CreateProject(props: { name: string; description: string }): void { | ||||
|   const project: NewProject = { | ||||
|     name: props.name, | ||||
|     description: props.description, | ||||
|   }; | ||||
| 
 | ||||
|   let created = false; | ||||
| 
 | ||||
|   api | ||||
|     .createProject(project, localStorage.getItem("accessToken") ?? "") | ||||
|     .then((response: APIResponse<Project>) => { | ||||
|     .then((response: APIResponse<void>) => { | ||||
|       if (response.success) { | ||||
|         created = true; | ||||
|         alert("Project added!"); | ||||
|       } else { | ||||
|         alert("Project NOT added!"); | ||||
|         console.error(response.message); | ||||
|       } | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       alert("Project NOT added!"); | ||||
|       console.error("An error occurred during creation:", error); | ||||
|     }); | ||||
|   return created; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | @ -48,7 +47,10 @@ function AddProject(): JSX.Element { | |||
|           className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit" | ||||
|           onSubmit={(e) => { | ||||
|             e.preventDefault(); | ||||
|             CreateProject({ name: name, description: description }); | ||||
|             CreateProject({ | ||||
|               name: name, | ||||
|               description: description, | ||||
|             }); | ||||
|           }} | ||||
|         > | ||||
|           <img | ||||
|  | @ -64,6 +66,7 @@ function AddProject(): JSX.Element { | |||
|             type="text" | ||||
|             value={name} | ||||
|             onChange={(e) => { | ||||
|               e.preventDefault(); | ||||
|               setName(e.target.value); | ||||
|             }} | ||||
|           /> | ||||
|  | @ -72,6 +75,7 @@ function AddProject(): JSX.Element { | |||
|             type="text" | ||||
|             value={description} | ||||
|             onChange={(e) => { | ||||
|               e.preventDefault(); | ||||
|               setDescription(e.target.value); | ||||
|             }} | ||||
|           /> | ||||
|  |  | |||
|  | @ -1,8 +1,7 @@ | |||
| import { useState } from "react"; | ||||
| import { NewProjMember } from "../Types/goTypes"; | ||||
| import Button from "./Button"; | ||||
| import GetAllUsers from "./GetAllUsers"; | ||||
| import AddMember from "./AddMember"; | ||||
| import AddMember, { NewProjMember } from "./AddMember"; | ||||
| import BackButton from "./BackButton"; | ||||
| 
 | ||||
| /** | ||||
|  |  | |||
							
								
								
									
										37
									
								
								frontend/src/Components/ChangeRole.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								frontend/src/Components/ChangeRole.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import { APIResponse, api } from "../API/API"; | ||||
| 
 | ||||
| export interface ProjectRoleChange { | ||||
|   username: string; | ||||
|   role: "project_manager" | "member" | ""; | ||||
|   projectname: string; | ||||
| } | ||||
| 
 | ||||
| export default function ChangeRole(roleChangeInfo: ProjectRoleChange): void { | ||||
|   if ( | ||||
|     roleChangeInfo.username === "" || | ||||
|     roleChangeInfo.role === "" || | ||||
|     roleChangeInfo.projectname === "" | ||||
|   ) { | ||||
|     // FOR DEBUG
 | ||||
|     // console.log(roleChangeInfo.role + ": Role");
 | ||||
|     // console.log(roleChangeInfo.projectname + ": P-Name");
 | ||||
|     // console.log(roleChangeInfo.username + ": U-name");
 | ||||
|     alert("You have to select a role"); | ||||
|     return; | ||||
|   } | ||||
|   api | ||||
|     .changeUserRole(roleChangeInfo, localStorage.getItem("accessToken") ?? "") | ||||
|     .then((response: APIResponse<void>) => { | ||||
|       if (response.success) { | ||||
|         alert("Role changed successfully"); | ||||
|         location.reload(); | ||||
|       } else { | ||||
|         alert(response.message); | ||||
|         console.error(response.message); | ||||
|       } | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       alert(error); | ||||
|       console.error("An error occurred during change:", error); | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										77
									
								
								frontend/src/Components/ChangeRoleView.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								frontend/src/Components/ChangeRoleView.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,77 @@ | |||
| import { useState } from "react"; | ||||
| import Button from "./Button"; | ||||
| import ChangeRole, { ProjectRoleChange } from "./ChangeRole"; | ||||
| 
 | ||||
| export default function ChangeRoles(props: { | ||||
|   projectName: string; | ||||
|   username: string; | ||||
| }): JSX.Element { | ||||
|   const [selectedRole, setSelectedRole] = useState< | ||||
|     "project_manager" | "member" | "" | ||||
|   >(""); | ||||
| 
 | ||||
|   const handleRoleChange = ( | ||||
|     event: React.ChangeEvent<HTMLInputElement>, | ||||
|   ): void => { | ||||
|     if (event.target.value === "member") { | ||||
|       setSelectedRole(event.target.value); | ||||
|     } else if (event.target.value === "project_manager") { | ||||
|       setSelectedRole(event.target.value); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => { | ||||
|     event.preventDefault(); | ||||
|     const roleChangeInfo: ProjectRoleChange = { | ||||
|       username: props.username, | ||||
|       projectname: props.projectName, | ||||
|       role: selectedRole, | ||||
|     }; | ||||
|     ChangeRole(roleChangeInfo); | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <div> | ||||
|       <h1 className="font-bold text-[22px] mb-[15px]">Change role:</h1> | ||||
|       <form | ||||
|         className="border-2 border-black rounded-lg p-2" | ||||
|         onSubmit={handleSubmit} | ||||
|       > | ||||
|         <div className="self-start text-left font-medium"> | ||||
|           <div className="hover:font-bold"> | ||||
|             <label> | ||||
|               <input | ||||
|                 type="radio" | ||||
|                 value="project_manager" | ||||
|                 checked={selectedRole === "project_manager"} | ||||
|                 onChange={handleRoleChange} | ||||
|                 className="ml-2 mr-2 mb-5" | ||||
|               /> | ||||
|               Project manager | ||||
|             </label> | ||||
|           </div> | ||||
|           <div className="hover:font-bold"> | ||||
|             <label> | ||||
|               <input | ||||
|                 type="radio" | ||||
|                 value="member" | ||||
|                 checked={selectedRole === "member"} | ||||
|                 onChange={handleRoleChange} | ||||
|                 className="ml-2 mr-2" | ||||
|               /> | ||||
|               Member | ||||
|             </label> | ||||
|           </div> | ||||
|         </div> | ||||
| 
 | ||||
|         <Button | ||||
|           text="Change" | ||||
|           onClick={(): void => { | ||||
|             return; | ||||
|           }} | ||||
|           type="submit" | ||||
|         /> | ||||
|       </form> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|  | @ -1,61 +1,26 @@ | |||
| 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<HTMLInputElement>): void => { | ||||
|     setNewUsername(e.target.value); | ||||
|   }; | ||||
| 
 | ||||
|   const handleSubmit = async (): Promise<void> => { | ||||
|     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, | ||||
|       ); | ||||
| import { APIResponse, api } from "../API/API"; | ||||
| import { StrNameChange } from "../Types/goTypes"; | ||||
| 
 | ||||
| function ChangeUsername(props: { nameChange: StrNameChange }): void { | ||||
|   if (props.nameChange.newName === "") { | ||||
|     alert("You have to select a new name"); | ||||
|     return; | ||||
|   } | ||||
|   api | ||||
|     .changeUserName(props.nameChange, localStorage.getItem("accessToken") ?? "") | ||||
|     .then((response: APIResponse<void>) => { | ||||
|       if (response.success) { | ||||
|         // Optionally, add a success message or redirect the user
 | ||||
|         console.log("Username changed successfully"); | ||||
|         alert("Name changed successfully"); | ||||
|         location.reload(); | ||||
|       } else { | ||||
|         // Handle the error message
 | ||||
|         console.error("Failed to change username:", response.message); | ||||
|         setErrorMessage(response.message ?? "Failed to change username"); | ||||
|         alert("Name not changed"); | ||||
|         console.error(response.message); | ||||
|       } | ||||
|     } 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); | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       alert("Name not changed"); | ||||
|       console.error("An error occurred during change:", error); | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <div> | ||||
|       <InputField | ||||
|         label="New Username" | ||||
|         type="text" | ||||
|         value={newUsername} | ||||
|         onChange={handleChange} | ||||
|       /> | ||||
|       {errorMessage && <div>{errorMessage}</div>} | ||||
|       <button onClick={handleButtonClick}>Update Username</button> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export default ChangeUsername; | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import { useState, useEffect } from "react"; | ||||
| import { useState } from "react"; | ||||
| import { Project } from "../Types/goTypes"; | ||||
| import { useNavigate } from "react-router-dom"; | ||||
| import GetProjects from "./GetProjects"; | ||||
| import { api } from "../API/API"; | ||||
| 
 | ||||
| /** | ||||
|  | @ -11,16 +12,10 @@ function DisplayUserProject(): JSX.Element { | |||
|   const [projects, setProjects] = useState<Project[]>([]); | ||||
|   const navigate = useNavigate(); | ||||
| 
 | ||||
|   const getProjects = async (): Promise<void> => { | ||||
|     const token = localStorage.getItem("accessToken") ?? ""; | ||||
|     const response = await api.getUserProjects(token); | ||||
|     console.log(response); | ||||
|     if (response.success) { | ||||
|       setProjects(response.data ?? []); | ||||
|     } else { | ||||
|       console.error(response.message); | ||||
|     } | ||||
|   }; | ||||
|   GetProjects({ | ||||
|     setProjectsProp: setProjects, | ||||
|     username: localStorage.getItem("username") ?? "", | ||||
|   }); | ||||
| 
 | ||||
|   const handleProjectClick = async (projectName: string): Promise<void> => { | ||||
|     const token = localStorage.getItem("accessToken") ?? ""; | ||||
|  | @ -41,11 +36,6 @@ function DisplayUserProject(): JSX.Element { | |||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   // Call getProjects when the component mounts
 | ||||
|   useEffect(() => { | ||||
|     void getProjects(); | ||||
|   }, []); | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       <h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1> | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ import { api } from "../API/API"; | |||
|  */ | ||||
| function GetProjects(props: { | ||||
|   setProjectsProp: Dispatch<React.SetStateAction<Project[]>>; | ||||
|   username: string; | ||||
| }): void { | ||||
|   const setProjects: Dispatch<React.SetStateAction<Project[]>> = | ||||
|     props.setProjectsProp; | ||||
|  | @ -19,7 +20,7 @@ function GetProjects(props: { | |||
|     const fetchUsers = async (): Promise<void> => { | ||||
|       try { | ||||
|         const token = localStorage.getItem("accessToken") ?? ""; | ||||
|         const response = await api.getUserProjects(token); | ||||
|         const response = await api.getUserProjects(props.username, token); | ||||
|         if (response.success) { | ||||
|           setProjects(response.data ?? []); | ||||
|         } else { | ||||
|  | @ -31,7 +32,7 @@ function GetProjects(props: { | |||
|     }; | ||||
| 
 | ||||
|     void fetchUsers(); | ||||
|   }, [setProjects]); | ||||
|   }, [props.username, setProjects]); | ||||
| } | ||||
| 
 | ||||
| export default GetProjects; | ||||
|  |  | |||
|  | @ -1,7 +1,11 @@ | |||
| import { Dispatch, useEffect } from "react"; | ||||
| import { UserProjectMember } from "../Types/goTypes"; | ||||
| import { api } from "../API/API"; | ||||
| 
 | ||||
| export interface ProjectMember { | ||||
|   Username: string; | ||||
|   UserRole: string; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Gets all projects that user is a member of | ||||
|  * @param props - A setStateAction for the array you want to put projects in | ||||
|  | @ -12,9 +16,9 @@ import { api } from "../API/API"; | |||
|  */ | ||||
| function GetUsersInProject(props: { | ||||
|   projectName: string; | ||||
|   setUsersProp: Dispatch<React.SetStateAction<UserProjectMember[]>>; | ||||
|   setUsersProp: Dispatch<React.SetStateAction<ProjectMember[]>>; | ||||
| }): void { | ||||
|   const setUsers: Dispatch<React.SetStateAction<UserProjectMember[]>> = | ||||
|   const setUsers: Dispatch<React.SetStateAction<ProjectMember[]>> = | ||||
|     props.setUsersProp; | ||||
|   useEffect(() => { | ||||
|     const fetchUsers = async (): Promise<void> => { | ||||
|  |  | |||
							
								
								
									
										77
									
								
								frontend/src/Components/MemberInfoModal.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								frontend/src/Components/MemberInfoModal.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,77 @@ | |||
| import Button from "./Button"; | ||||
| import DeleteUser from "./DeleteUser"; | ||||
| import UserProjectListAdmin from "./UserProjectListAdmin"; | ||||
| import { useState } from "react"; | ||||
| import ChangeRoleView from "./ChangeRoleView"; | ||||
| 
 | ||||
| function MemberInfoModal(props: { | ||||
|   isVisible: boolean; | ||||
|   username: string; | ||||
|   onClose: () => void; | ||||
| }): JSX.Element { | ||||
|   const [showRoles, setShowRoles] = useState(false); | ||||
|   if (!props.isVisible) return <></>; | ||||
| 
 | ||||
|   const handleChangeRole = (): void => { | ||||
|     if (showRoles) { | ||||
|       setShowRoles(false); | ||||
|     } else { | ||||
|       setShowRoles(true); | ||||
|     } | ||||
|   }; | ||||
|   return ( | ||||
|     <div | ||||
|       className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm  | ||||
|       flex justify-center items-center" | ||||
|     > | ||||
|       <div className="border-4 border-black bg-white p-2 rounded-lg text-center flex flex-col"> | ||||
|         <p className="font-bold text-[30px]">{props.username}</p> | ||||
|         <p | ||||
|           className="mb-[20px] hover:font-bold hover:cursor-pointer underline" | ||||
|           onClick={handleChangeRole} | ||||
|         > | ||||
|           (Change Role) | ||||
|         </p> | ||||
|         {showRoles && ( | ||||
|           <ChangeRoleView | ||||
|             projectName={localStorage.getItem("projectName") ?? ""} | ||||
|             username={props.username} | ||||
|           /> | ||||
|         )} | ||||
|         <div> | ||||
|           <h2 className="font-bold text-[22px] mb-[20px]"> | ||||
|             Member of these projects: | ||||
|           </h2> | ||||
|           <div className="pr-6 pl-6"> | ||||
|             <UserProjectListAdmin username={props.username} /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div className="items-center space-x-6 pr-6 pl-6"> | ||||
|           <Button | ||||
|             text={"Delete"} | ||||
|             onClick={function (): void { | ||||
|               if ( | ||||
|                 window.confirm("Are you sure you want to delete this user?") | ||||
|               ) { | ||||
|                 DeleteUser({ | ||||
|                   usernameToDelete: props.username, | ||||
|                 }); | ||||
|               } | ||||
|             }} | ||||
|             type="button" | ||||
|           /> | ||||
|           <Button | ||||
|             text={"Close"} | ||||
|             onClick={function (): void { | ||||
|               setShowRoles(false); | ||||
|               props.onClose(); | ||||
|             }} | ||||
|             type="button" | ||||
|           /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export default MemberInfoModal; | ||||
|  | @ -1,7 +1,6 @@ | |||
| import { useState } from "react"; | ||||
| import Button from "./Button"; | ||||
| import { UserProjectMember } from "../Types/goTypes"; | ||||
| import GetUsersInProject from "./GetUsersInProject"; | ||||
| import GetUsersInProject, { ProjectMember } from "./GetUsersInProject"; | ||||
| import { Link } from "react-router-dom"; | ||||
| 
 | ||||
| function ProjectInfoModal(props: { | ||||
|  | @ -10,7 +9,7 @@ function ProjectInfoModal(props: { | |||
|   onClose: () => void; | ||||
|   onClick: (username: string) => void; | ||||
| }): JSX.Element { | ||||
|   const [users, setUsers] = useState<UserProjectMember[]>([]); | ||||
|   const [users, setUsers] = useState<ProjectMember[]>([]); | ||||
|   GetUsersInProject({ projectName: props.projectname, setUsersProp: setUsers }); | ||||
|   if (!props.isVisible) return <></>; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| import { useState } from "react"; | ||||
| import { NewProject } from "../Types/goTypes"; | ||||
| import ProjectInfoModal from "./ProjectInfoModal"; | ||||
| import UserInfoModal from "./UserInfoModal"; | ||||
| import MemberInfoModal from "./MemberInfoModal"; | ||||
| 
 | ||||
| /** | ||||
|  * A list of projects for admin manage projects page, that sets an onClick | ||||
|  | @ -51,13 +51,8 @@ export function ProjectListAdmin(props: { | |||
|         isVisible={projectModalVisible} | ||||
|         projectname={projectname} | ||||
|       /> | ||||
|       <UserInfoModal | ||||
|         manageMember={true} | ||||
|       <MemberInfoModal | ||||
|         onClose={handleCloseUser} | ||||
|         //TODO: CHANGE TO REMOVE USER FROM PROJECT
 | ||||
|         onDelete={() => { | ||||
|           return; | ||||
|         }} | ||||
|         isVisible={userModalVisible} | ||||
|         username={username} | ||||
|       /> | ||||
|  |  | |||
|  | @ -1,31 +1,15 @@ | |||
| import { useEffect, useState } from "react"; | ||||
| import { useState } from "react"; | ||||
| import { Link, useParams } from "react-router-dom"; | ||||
| import { api } from "../API/API"; | ||||
| import { UserProjectMember } from "../Types/goTypes"; | ||||
| import GetUsersInProject, { ProjectMember } from "./GetUsersInProject"; | ||||
| 
 | ||||
| function ProjectMembers(): JSX.Element { | ||||
|   const { projectName } = useParams(); | ||||
|   const [projectMembers, setProjectMembers] = useState<UserProjectMember[]>([]); | ||||
|   const [projectMembers, setProjectMembers] = useState<ProjectMember[]>([]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const getProjectMembers = async (): Promise<void> => { | ||||
|       const token = localStorage.getItem("accessToken") ?? ""; | ||||
|       const response = await api.getAllUsersProject(projectName ?? "", token); | ||||
|       console.log(response); | ||||
|       if (response.success) { | ||||
|         setProjectMembers(response.data ?? []); | ||||
|       } else { | ||||
|         console.error(response.message); | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     void getProjectMembers(); | ||||
|   }, [projectName]); | ||||
| 
 | ||||
|   interface ProjectMember { | ||||
|     Username: string; | ||||
|     UserRole: string; | ||||
|   } | ||||
|   GetUsersInProject({ | ||||
|     projectName: projectName ?? "", | ||||
|     setUsersProp: setProjectMembers, | ||||
|   }); | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|  |  | |||
|  | @ -22,6 +22,8 @@ export default function Register(): JSX.Element { | |||
|     const response = await api.registerUser(newUser); | ||||
|     if (response.success) { | ||||
|       alert("User added!"); | ||||
|       setPassword(""); | ||||
|       setUsername(""); | ||||
|     } else { | ||||
|       alert("User not added"); | ||||
|       setErrMessage(response.message ?? "Unknown error"); | ||||
|  |  | |||
|  | @ -1,34 +1,38 @@ | |||
| import { Link } from "react-router-dom"; | ||||
| import Button from "./Button"; | ||||
| import DeleteUser from "./DeleteUser"; | ||||
| import UserProjectListAdmin from "./UserProjectListAdmin"; | ||||
| import { useState } from "react"; | ||||
| import InputField from "./InputField"; | ||||
| import ChangeUsername from "./ChangeUsername"; | ||||
| import { StrNameChange } from "../Types/goTypes"; | ||||
| 
 | ||||
| function UserInfoModal(props: { | ||||
|   isVisible: boolean; | ||||
|   manageMember: boolean; | ||||
|   username: string; | ||||
|   onClose: () => void; | ||||
|   onDelete: (username: string) => void; | ||||
| }): JSX.Element { | ||||
|   if (!props.isVisible) return <></>; | ||||
|   const ManageUserOrMember = (check: boolean): JSX.Element => { | ||||
|     if (check) { | ||||
|       return ( | ||||
|         <Link to="/AdminChangeRole"> | ||||
|           <p className="mb-[20px] hover:font-bold hover:cursor-pointer underline"> | ||||
|             (Change Role) | ||||
|           </p> | ||||
|         </Link> | ||||
|       ); | ||||
|   const [showInput, setShowInput] = useState(false); | ||||
|   const [newUsername, setNewUsername] = useState(""); | ||||
|   if (!props.isVisible) { | ||||
|     return <></>; | ||||
|   } | ||||
| 
 | ||||
|   const handleChangeNameView = (): void => { | ||||
|     if (showInput) { | ||||
|       setShowInput(false); | ||||
|     } else { | ||||
|       setShowInput(true); | ||||
|     } | ||||
|     return ( | ||||
|       <Link to="/AdminChangeUserName"> | ||||
|         <p className="mb-[20px] hover:font-bold hover:cursor-pointer underline"> | ||||
|           (Change Username) | ||||
|         </p> | ||||
|       </Link> | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   const handleClickChangeName = (): void => { | ||||
|     const nameChange: StrNameChange = { | ||||
|       prevName: props.username, | ||||
|       newName: newUsername, | ||||
|     }; | ||||
|     ChangeUsername({ nameChange: nameChange }); | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <div | ||||
|       className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm  | ||||
|  | @ -36,13 +40,38 @@ function UserInfoModal(props: { | |||
|     > | ||||
|       <div className="border-4 border-black bg-white p-2 rounded-lg text-center flex flex-col"> | ||||
|         <p className="font-bold text-[30px]">{props.username}</p> | ||||
|         {ManageUserOrMember(props.manageMember)} | ||||
|         <p | ||||
|           className="mb-[20px] hover:font-bold hover:cursor-pointer underline" | ||||
|           onClick={handleChangeNameView} | ||||
|         > | ||||
|           (Change Username) | ||||
|         </p> | ||||
|         {showInput && ( | ||||
|           <div className="border-2 border-black rounded-lg p-2"> | ||||
|             <InputField | ||||
|               label={"New username"} | ||||
|               type={"text"} | ||||
|               value={newUsername} | ||||
|               onChange={function (e): void { | ||||
|                 e.defaultPrevented; | ||||
|                 setNewUsername(e.target.value); | ||||
|               }} | ||||
|             /> | ||||
|             <Button | ||||
|               text={"Change"} | ||||
|               onClick={function (): void { | ||||
|                 handleClickChangeName(); | ||||
|               }} | ||||
|               type={"submit"} | ||||
|             /> | ||||
|           </div> | ||||
|         )} | ||||
|         <div> | ||||
|           <h2 className="font-bold text-[22px] mb-[20px]"> | ||||
|             Member of these projects: | ||||
|           </h2> | ||||
|           <div className="pr-6 pl-6"> | ||||
|             <UserProjectListAdmin /> | ||||
|             <UserProjectListAdmin username={props.username} /> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div className="items-center space-x-6 pr-6 pl-6"> | ||||
|  | @ -62,6 +91,8 @@ function UserInfoModal(props: { | |||
|           <Button | ||||
|             text={"Close"} | ||||
|             onClick={function (): void { | ||||
|               setNewUsername(""); | ||||
|               setShowInput(false); | ||||
|               props.onClose(); | ||||
|             }} | ||||
|             type="button" | ||||
|  |  | |||
|  | @ -1,6 +1,5 @@ | |||
| import { useState } from "react"; | ||||
| import UserInfoModal from "./UserInfoModal"; | ||||
| import DeleteUser from "./DeleteUser"; | ||||
| 
 | ||||
| /** | ||||
|  * A list of users for admin manage users page, that sets an onClick | ||||
|  | @ -30,9 +29,7 @@ export function UserListAdmin(props: { users: string[] }): JSX.Element { | |||
|   return ( | ||||
|     <> | ||||
|       <UserInfoModal | ||||
|         manageMember={false} | ||||
|         onClose={handleClose} | ||||
|         onDelete={() => DeleteUser} | ||||
|         isVisible={modalVisible} | ||||
|         username={username} | ||||
|       /> | ||||
|  |  | |||
|  | @ -1,29 +1,11 @@ | |||
| import { useEffect, useState } from "react"; | ||||
| import { api } from "../API/API"; | ||||
| import { useState } from "react"; | ||||
| import { Project } from "../Types/goTypes"; | ||||
| import GetProjects from "./GetProjects"; | ||||
| 
 | ||||
| function UserProjectListAdmin(): JSX.Element { | ||||
| function UserProjectListAdmin(props: { username: string }): JSX.Element { | ||||
|   const [projects, setProjects] = useState<Project[]>([]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     const fetchProjects = async (): Promise<void> => { | ||||
|       try { | ||||
|         const token = localStorage.getItem("accessToken") ?? ""; | ||||
|         // const username = props.username;
 | ||||
| 
 | ||||
|         const response = await api.getUserProjects(token); | ||||
|         if (response.success) { | ||||
|           setProjects(response.data ?? []); | ||||
|         } else { | ||||
|           console.error("Failed to fetch projects:", response.message); | ||||
|         } | ||||
|       } catch (error) { | ||||
|         console.error("Error fetching projects:", error); | ||||
|       } | ||||
|     }; | ||||
| 
 | ||||
|     void fetchProjects(); | ||||
|   }, []); | ||||
|   GetProjects({ setProjectsProp: setProjects, username: props.username }); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="border-2 border-black bg-white p-2 rounded-lg text-center"> | ||||
|  |  | |||
							
								
								
									
										23
									
								
								frontend/src/Containers/GenApiDemo.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								frontend/src/Containers/GenApiDemo.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| import { useEffect } from "react"; | ||||
| import { GenApi } from "../API/GenApi"; | ||||
| 
 | ||||
| // Instantiation of the API
 | ||||
| const api = new GenApi(); | ||||
| 
 | ||||
| export function GenApiDemo(): JSX.Element { | ||||
|   // Sync wrapper around the loginCreate method
 | ||||
|   const register = async (): Promise<void> => { | ||||
|     const response = await api.login.loginCreate({ | ||||
|       username: "admin", | ||||
|       password: "admin", | ||||
|     }); | ||||
|     console.log(response.data); // This should be the inner type of the response
 | ||||
|   }; | ||||
| 
 | ||||
|   // Call the wrapper
 | ||||
|   useEffect(() => { | ||||
|     void register(); | ||||
|   }); | ||||
| 
 | ||||
|   return <></>; | ||||
| } | ||||
|  | @ -1,28 +0,0 @@ | |||
| import BackButton from "../../Components/BackButton"; | ||||
| import BasicWindow from "../../Components/BasicWindow"; | ||||
| import Button from "../../Components/Button"; | ||||
| import ChangeUsername from "../../Components/ChangeUsername"; | ||||
| 
 | ||||
| function AdminChangeUsername(): JSX.Element { | ||||
|   const content = ( | ||||
|     <> | ||||
|       <ChangeUsername /> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   const buttons = ( | ||||
|     <> | ||||
|       <Button | ||||
|         text="Finish" | ||||
|         onClick={(): void => { | ||||
|           return; | ||||
|         }} | ||||
|         type="button" | ||||
|       /> | ||||
|       <BackButton /> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   return <BasicWindow content={content} buttons={buttons} />; | ||||
| } | ||||
| export default AdminChangeUsername; | ||||
|  | @ -9,7 +9,10 @@ import { useState } from "react"; | |||
| 
 | ||||
| function AdminManageProjects(): JSX.Element { | ||||
|   const [projects, setProjects] = useState<Project[]>([]); | ||||
|   GetProjects({ setProjectsProp: setProjects }); | ||||
|   GetProjects({ | ||||
|     setProjectsProp: setProjects, | ||||
|     username: localStorage.getItem("username") ?? "", | ||||
|   }); | ||||
|   const content = ( | ||||
|     <> | ||||
|       <h1 className="font-bold text-[30px] mb-[20px]">Manage Projects</h1> | ||||
|  |  | |||
|  | @ -1,23 +0,0 @@ | |||
| import BackButton from "../../Components/BackButton"; | ||||
| import BasicWindow from "../../Components/BasicWindow"; | ||||
| import Button from "../../Components/Button"; | ||||
| 
 | ||||
| function AdminProjectChangeUserRole(): JSX.Element { | ||||
|   const content = <></>; | ||||
| 
 | ||||
|   const buttons = ( | ||||
|     <> | ||||
|       <Button | ||||
|         text="Change" | ||||
|         onClick={(): void => { | ||||
|           return; | ||||
|         }} | ||||
|         type="button" | ||||
|       /> | ||||
|       <BackButton /> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   return <BasicWindow content={content} buttons={buttons} />; | ||||
| } | ||||
| export default AdminProjectChangeUserRole; | ||||
|  | @ -1,23 +0,0 @@ | |||
| import BackButton from "../../Components/BackButton"; | ||||
| import BasicWindow from "../../Components/BasicWindow"; | ||||
| import Button from "../../Components/Button"; | ||||
| 
 | ||||
| function AdminProjectManageMembers(): JSX.Element { | ||||
|   const content = <></>; | ||||
| 
 | ||||
|   const buttons = ( | ||||
|     <> | ||||
|       <Button | ||||
|         text="Add Member" | ||||
|         onClick={(): void => { | ||||
|           return; | ||||
|         }} | ||||
|         type="button" | ||||
|       /> | ||||
|       <BackButton /> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   return <BasicWindow content={content} buttons={buttons} />; | ||||
| } | ||||
| export default AdminProjectManageMembers; | ||||
|  | @ -1,33 +0,0 @@ | |||
| import { useParams } from "react-router-dom"; | ||||
| import { api } from "../../API/API"; | ||||
| import BackButton from "../../Components/BackButton"; | ||||
| import BasicWindow from "../../Components/BasicWindow"; | ||||
| import Button from "../../Components/Button"; | ||||
| 
 | ||||
| async function handleDeleteProject( | ||||
|   projectName: string, | ||||
|   token: string, | ||||
| ): Promise<void> { | ||||
|   await api.removeProject(projectName, token); | ||||
| } | ||||
| 
 | ||||
| function AdminProjectPage(): JSX.Element { | ||||
|   const content = <></>; | ||||
|   const { projectName } = useParams(); | ||||
|   const token = localStorage.getItem("accessToken"); | ||||
| 
 | ||||
|   const buttons = ( | ||||
|     <> | ||||
|       <Button | ||||
|         text="Delete" | ||||
|         onClick={() => handleDeleteProject(projectName, token)} | ||||
|         type="button" | ||||
|       /> | ||||
|       <BackButton /> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   return <BasicWindow content={content} buttons={buttons} />; | ||||
| } | ||||
| 
 | ||||
| export default AdminProjectPage; | ||||
|  | @ -1,23 +0,0 @@ | |||
| import BackButton from "../../Components/BackButton"; | ||||
| import BasicWindow from "../../Components/BasicWindow"; | ||||
| import Button from "../../Components/Button"; | ||||
| 
 | ||||
| function AdminProjectViewMemberInfo(): JSX.Element { | ||||
|   const content = <></>; | ||||
| 
 | ||||
|   const buttons = ( | ||||
|     <> | ||||
|       <Button | ||||
|         text="Remove" | ||||
|         onClick={(): void => { | ||||
|           return; | ||||
|         }} | ||||
|         type="button" | ||||
|       /> | ||||
|       <BackButton /> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   return <BasicWindow content={content} buttons={buttons} />; | ||||
| } | ||||
| export default AdminProjectViewMemberInfo; | ||||
|  | @ -1,28 +0,0 @@ | |||
| import BasicWindow from "../../Components/BasicWindow"; | ||||
| import Button from "../../Components/Button"; | ||||
| import BackButton from "../../Components/BackButton"; | ||||
| import UserProjectListAdmin from "../../Components/UserProjectListAdmin"; | ||||
| 
 | ||||
| function AdminViewUserInfo(): JSX.Element { | ||||
|   const content = ( | ||||
|     <> | ||||
|       <UserProjectListAdmin /> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   const buttons = ( | ||||
|     <> | ||||
|       <Button | ||||
|         text="Delete" | ||||
|         onClick={(): void => { | ||||
|           return; | ||||
|         }} | ||||
|         type="button" | ||||
|       /> | ||||
|       <BackButton /> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   return <BasicWindow content={content} buttons={buttons} />; | ||||
| } | ||||
| export default AdminViewUserInfo; | ||||
|  | @ -124,6 +124,44 @@ export interface WeeklyReport { | |||
|    */ | ||||
|   signedBy?: number /* int */; | ||||
| } | ||||
| export interface UpdateWeeklyReport { | ||||
|   /** | ||||
|    * The name of the project, as it appears in the database | ||||
|    */ | ||||
|   projectName: string; | ||||
|   /** | ||||
|    * The name of the user | ||||
|    */ | ||||
|   userName: string; | ||||
|   /** | ||||
|    * The week number | ||||
|    */ | ||||
|   week: number /* int */; | ||||
|   /** | ||||
|    * Total time spent on development | ||||
|    */ | ||||
|   developmentTime: number /* int */; | ||||
|   /** | ||||
|    * Total time spent in meetings | ||||
|    */ | ||||
|   meetingTime: number /* int */; | ||||
|   /** | ||||
|    * Total time spent on administrative tasks | ||||
|    */ | ||||
|   adminTime: number /* int */; | ||||
|   /** | ||||
|    * Total time spent on personal projects | ||||
|    */ | ||||
|   ownWorkTime: number /* int */; | ||||
|   /** | ||||
|    * Total time spent on studying | ||||
|    */ | ||||
|   studyTime: number /* int */; | ||||
|   /** | ||||
|    * Total time spent on testing | ||||
|    */ | ||||
|   testingTime: number /* int */; | ||||
| } | ||||
| 
 | ||||
| //////////
 | ||||
| // source: project.go
 | ||||
|  | @ -151,16 +189,9 @@ export interface NewProject { | |||
|  */ | ||||
| export interface RoleChange { | ||||
|   username: string; | ||||
|   role: "project_manager" | "user"; | ||||
|   role: 'project_manager' | 'user'; | ||||
|   projectname: string; | ||||
| } | ||||
| 
 | ||||
| export interface NewProjMember { | ||||
|   username: string; | ||||
|   projectname: string; | ||||
|   role: string; | ||||
| } | ||||
| 
 | ||||
| export interface NameChange { | ||||
|   id: number /* int */; | ||||
|   name: string; | ||||
|  | @ -191,11 +222,6 @@ export interface PublicUser { | |||
|   userId: string; | ||||
|   username: string; | ||||
| } | ||||
| 
 | ||||
| export interface UserProjectMember { | ||||
|   Username: string; | ||||
|   UserRole: string; | ||||
| } | ||||
| /** | ||||
|  * wrapper type for token | ||||
|  */ | ||||
|  |  | |||
|  | @ -18,17 +18,11 @@ import PMTotalTimeRole from "./Pages/ProjectManagerPages/PMTotalTimeRole.tsx"; | |||
| import PMUnsignedReports from "./Pages/ProjectManagerPages/PMUnsignedReports.tsx"; | ||||
| import PMViewUnsignedReport from "./Pages/ProjectManagerPages/PMViewUnsignedReport.tsx"; | ||||
| import AdminManageUsers from "./Pages/AdminPages/AdminManageUsers.tsx"; | ||||
| import AdminViewUserInfo from "./Pages/AdminPages/AdminViewUserInfo.tsx"; | ||||
| import AdminManageProjects from "./Pages/AdminPages/AdminManageProjects.tsx"; | ||||
| import AdminAddProject from "./Pages/AdminPages/AdminAddProject.tsx"; | ||||
| import AdminAddUser from "./Pages/AdminPages/AdminAddUser.tsx"; | ||||
| import AdminChangeUsername from "./Pages/AdminPages/AdminChangeUsername.tsx"; | ||||
| import AdminProjectAddMember from "./Pages/AdminPages/AdminProjectAddMember.tsx"; | ||||
| import AdminProjectChangeUserRole from "./Pages/AdminPages/AdminProjectChangeUserRole.tsx"; | ||||
| import AdminProjectManageMembers from "./Pages/AdminPages/AdminProjectManageMembers.tsx"; | ||||
| import AdminProjectStatistics from "./Pages/AdminPages/AdminProjectStatistics.tsx"; | ||||
| import AdminProjectViewMemberInfo from "./Pages/AdminPages/AdminProjectViewMemberInfo.tsx"; | ||||
| import AdminProjectPage from "./Pages/AdminPages/AdminProjectPage.tsx"; | ||||
| import NotFoundPage from "./Pages/NotFoundPage.tsx"; | ||||
| import UnauthorizedPage from "./Pages/UnauthorizedPage.tsx"; | ||||
| import PMViewOtherUsersTR from "./Pages/ProjectManagerPages/PMViewOtherUsersTR.tsx"; | ||||
|  | @ -100,34 +94,14 @@ const router = createBrowserRouter([ | |||
|     path: "/PMViewUnsignedReport/:projectName/:username/:fetchedWeek", | ||||
|     element: <PMViewUnsignedReport />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/adminChangeUsername", | ||||
|     element: <AdminChangeUsername />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/adminProjectAddMember", | ||||
|     element: <AdminProjectAddMember />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/adminProjectChangeUserRole", | ||||
|     element: <AdminProjectChangeUserRole />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/adminProjectManageMembers", | ||||
|     element: <AdminProjectManageMembers />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/adminProjectPage", | ||||
|     element: <AdminProjectPage />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/adminProjectStatistics", | ||||
|     element: <AdminProjectStatistics />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/adminProjectViewMembers", | ||||
|     element: <AdminProjectViewMemberInfo />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/addProject", | ||||
|     element: <AdminAddProject />, | ||||
|  | @ -136,10 +110,6 @@ const router = createBrowserRouter([ | |||
|     path: "/adminAddUser", | ||||
|     element: <AdminAddUser />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/adminUserInfo", | ||||
|     element: <AdminViewUserInfo />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/adminManageProject", | ||||
|     element: <AdminManageProjects />, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Davenludd
						Davenludd