Compare commits
	
		
			22 commits
		
	
	
		
			394b283712
			...
			8c8e57cb50
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 8c8e57cb50 | ||
|   | 25fdf3bb9b | ||
|   | c4632104a8 | ||
|   | 736cebe036 | ||
|   | 9b67a580da | ||
|   | ad4d439887 | ||
|   | d06122864e | ||
|   | f8277617a9 | ||
|   | 6acfdd36b2 | ||
|   | 0b23c5f8f1 | ||
|   | eec3a45509 | ||
|   | 5a85f2bf81 | ||
|   | 7ed986e4eb | ||
|   | 3e000358a7 | ||
|   | 09ec0a0bb0 | ||
|   | 40f7241550 | ||
|   | 25284bb10e | ||
|   | 9c824f1d7b | ||
|   | 42a0745102 | ||
|   | 7879394da3 | ||
|   | ae9ee91bc4 | ||
|   | a22dcb9f4e | 
					 27 changed files with 454 additions and 142 deletions
				
			
		|  | @ -4,6 +4,7 @@ import ( | |||
| 	"embed" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	_ "github.com/mattn/go-sqlite3" | ||||
|  | @ -11,7 +12,9 @@ import ( | |||
| 
 | ||||
| // Interface for the database | ||||
| type Database interface { | ||||
| 	// Insert a new user into the database, password should be hashed before calling | ||||
| 	AddUser(username string, password string) error | ||||
| 
 | ||||
| 	RemoveUser(username string) error | ||||
| 	PromoteToAdmin(username string) error | ||||
| 	GetUserId(username string) (int, error) | ||||
|  | @ -20,6 +23,9 @@ type Database interface { | |||
| 	// AddTimeReport(projectname string, start time.Time, end time.Time) error | ||||
| 	// AddUserToProject(username string, projectname string) error | ||||
| 	// ChangeUserRole(username string, projectname string, role string) error | ||||
| 	// AddTimeReport(projectname string, start time.Time, end time.Time) error | ||||
| 	// AddUserToProject(username string, projectname string) error | ||||
| 	// ChangeUserRole(username string, projectname string, role string) error | ||||
| } | ||||
| 
 | ||||
| // This struct is a wrapper type that holds the database connection | ||||
|  | @ -34,9 +40,8 @@ var scripts embed.FS | |||
| const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" | ||||
| const projectInsert = "INSERT INTO projects (name, description, owner_user_id) SELECT ?, ?, id FROM users WHERE username = ?" | ||||
| const promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?" | ||||
| 
 | ||||
| // const addTimeReport = "" | ||||
| // const addUserToProject = "" | ||||
| const addTimeReport = "INSERT INTO activity (report_id, activity_nbr, start_time, end_time, break, comment) VALUES (?, ?, ?, ?, ?, ?)" // WIP | ||||
| const addUserToProject = "INSERT INTO project_member (project_id, user_id, role) VALUES (?, ?, ?)"                                     // WIP | ||||
| // const changeUserRole = "" | ||||
| 
 | ||||
| // DbConnect connects to the database | ||||
|  | @ -56,13 +61,27 @@ func DbConnect(dbpath string) Database { | |||
| 	return &Db{db} | ||||
| } | ||||
| 
 | ||||
| // func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time) error { | ||||
| func (d *Db) AddTimeReport(projectname string, start time.Time, end time.Time, breakTime uint32) error { // WIP | ||||
| 	_, err := d.Exec(addTimeReport, projectname, 0, start, end, breakTime, false) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // } | ||||
| func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP | ||||
| 	var userid int | ||||
| 	userid, err := d.GetUserId(username) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 
 | ||||
| // func (d *Db) AddUserToProject(username string, projectname string) error { | ||||
| 	var projectid int | ||||
| 	projectid, err2 := d.GetProjectId(projectname) | ||||
| 	if err2 != nil { | ||||
| 		panic(err2) | ||||
| 	} | ||||
| 
 | ||||
| // } | ||||
| 	_, err3 := d.Exec(addUserToProject, projectid, userid, role) | ||||
| 	return err3 | ||||
| } | ||||
| 
 | ||||
| // func (d *Db) ChangeUserRole(username string, projectname string, role string) error { | ||||
| 
 | ||||
|  | @ -87,7 +106,13 @@ func (d *Db) PromoteToAdmin(username string) error { | |||
| 
 | ||||
| func (d *Db) GetUserId(username string) (int, error) { | ||||
| 	var id int | ||||
| 	err := d.Get(&id, "SELECT id FROM users WHERE username = ?", username) | ||||
| 	err := d.Get(&id, "SELECT id FROM users WHERE username = ?", username) // Borde det inte vara "user" i singular | ||||
| 	return id, err | ||||
| } | ||||
| 
 | ||||
| func (d *Db) GetProjectId(projectname string) (int, error) { // WIP, denna kan vara goof | ||||
| 	var id int | ||||
| 	err := d.Get(&id, "SELECT id FROM project WHERE project_name = ?", projectname) | ||||
| 	return id, err | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,7 @@ | |||
| -- Id is a surrogate key for in ternal use | ||||
| -- userId is what is used for external id | ||||
| -- username is what is used for login | ||||
| -- password is the hashed password | ||||
| CREATE TABLE IF NOT EXISTS users ( | ||||
|   id INTEGER PRIMARY KEY, | ||||
|   userId TEXT DEFAULT (HEX(RANDOMBLOB(4))) NOT NULL UNIQUE, | ||||
|  | @ -5,5 +9,6 @@ CREATE TABLE IF NOT EXISTS users ( | |||
|   password VARCHAR(255) NOT NULL | ||||
| ); | ||||
| 
 | ||||
| -- Users are commonly searched by username and userId | ||||
| CREATE INDEX IF NOT EXISTS users_username_index ON users (username); | ||||
| CREATE INDEX IF NOT EXISTS users_userId_index ON users (userId); | ||||
							
								
								
									
										16
									
								
								backend/internal/database/migrations/0070_salts.sql
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								backend/internal/database/migrations/0070_salts.sql
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,16 @@ | |||
| -- It is unclear weather this table will be used | ||||
| 
 | ||||
| -- Create the table to store hash salts | ||||
| CREATE TABLE salts ( | ||||
|     id INTEGER PRIMARY KEY, | ||||
|     salt TEXT NOT NULL | ||||
| ); | ||||
| 
 | ||||
| -- Commented out for now, no time for good practices, which is atrocious | ||||
| -- Create a trigger to automatically generate a salt when inserting a new user record | ||||
| -- CREATE TRIGGER generate_salt_trigger | ||||
| -- AFTER INSERT ON users | ||||
| -- BEGIN | ||||
| --     INSERT INTO salts (salt) VALUES (randomblob(16)); | ||||
| --     UPDATE users SET salt_id = (SELECT last_insert_rowid()) WHERE id = new.id; | ||||
| -- END; | ||||
|  | @ -11,11 +11,11 @@ import ( | |||
| 
 | ||||
| // The actual interface that we will use | ||||
| type GlobalState interface { | ||||
| 	Register(c *fiber.Ctx) error   // To register a new user | ||||
| 	UserDelete(c *fiber.Ctx) error // To delete a user | ||||
| 	Login(c *fiber.Ctx) error      // To get the token | ||||
| 	LoginRenew(c *fiber.Ctx) error // To renew the token | ||||
| 	// CreateProject(c *fiber.Ctx) error        // To create a new project | ||||
| 	Register(c *fiber.Ctx) error      // To register a new user | ||||
| 	UserDelete(c *fiber.Ctx) error    // To delete a user | ||||
| 	Login(c *fiber.Ctx) error         // To get the token | ||||
| 	LoginRenew(c *fiber.Ctx) error    // To renew the token | ||||
| 	CreateProject(c *fiber.Ctx) error // To create a new project | ||||
| 	// GetProjects(c *fiber.Ctx) error          // To get all projects | ||||
| 	// GetProject(c *fiber.Ctx) error           // To get a specific project | ||||
| 	// UpdateProject(c *fiber.Ctx) error        // To update a project | ||||
|  | @ -58,7 +58,7 @@ type GState struct { | |||
| //	@Failure		500	{string}	string	"Internal server error" | ||||
| //	@Router			/api/register [post] | ||||
| func (gs *GState) Register(c *fiber.Ctx) error { | ||||
| 	u := new(types.User) | ||||
| 	u := new(types.NewUser) | ||||
| 	if err := c.BodyParser(u); err != nil { | ||||
| 		return c.Status(400).SendString(err.Error()) | ||||
| 	} | ||||
|  | @ -142,3 +142,24 @@ func (gs *GState) LoginRenew(c *fiber.Ctx) error { | |||
| 	} | ||||
| 	return c.JSON(fiber.Map{"token": t}) | ||||
| } | ||||
| 
 | ||||
| // CreateProject is a simple handler that creates a new project | ||||
| func (gs *GState) CreateProject(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("user").(*jwt.Token) | ||||
| 
 | ||||
| 	p := new(types.NewProject) | ||||
| 	if err := c.BodyParser(p); err != nil { | ||||
| 		return c.Status(400).SendString(err.Error()) | ||||
| 	} | ||||
| 
 | ||||
| 	// Get the username from the token and set it as the owner of the project | ||||
| 	// This is ugly but | ||||
| 	claims := user.Claims.(jwt.MapClaims) | ||||
| 	p.Owner = claims["name"].(string) | ||||
| 
 | ||||
| 	if err := gs.Db.AddProject(p.Name, p.Description, p.Owner); err != nil { | ||||
| 		return c.Status(500).SendString(err.Error()) | ||||
| 	} | ||||
| 
 | ||||
| 	return c.Status(200).SendString("Project added") | ||||
| } | ||||
|  |  | |||
							
								
								
									
										21
									
								
								backend/internal/types/project.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								backend/internal/types/project.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| package types | ||||
| 
 | ||||
| import ( | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| // Project is a struct that holds the information about a project | ||||
| type Project struct { | ||||
| 	ID          int       `json:"id" db:"id"` | ||||
| 	Name        string    `json:"name" db:"name"` | ||||
| 	Description string    `json:"description" db:"description"` | ||||
| 	Owner       string    `json:"owner" db:"owner"` | ||||
| 	Created     time.Time `json:"created" db:"created"` | ||||
| } | ||||
| 
 | ||||
| // As it arrives from the client | ||||
| type NewProject struct { | ||||
| 	Name        string `json:"name"` | ||||
| 	Description string `json:"description"` | ||||
| 	Owner       string `json:"owner"` | ||||
| } | ||||
|  | @ -16,6 +16,11 @@ func (u *User) ToPublicUser() (*PublicUser, error) { | |||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| type NewUser struct { | ||||
| 	Username string `json:"username"` | ||||
| 	Password string `json:"password"` | ||||
| } | ||||
| 
 | ||||
| // PublicUser represents a user that is safe to send over the API (no password) | ||||
| type PublicUser struct { | ||||
| 	UserId   string `json:"userId"` | ||||
|  |  | |||
|  | @ -2,9 +2,9 @@ | |||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="UTF-8" /> | ||||
|     <link rel="icon" type="image/svg+xml" href="/vite.svg" /> | ||||
|     <link rel="icon" type="image/x-icon" href="src/assets/favicon.ico" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|     <title>Vite + React + TS</title> | ||||
|     <title>TTIME</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="root"></div> | ||||
|  |  | |||
|  | @ -3,4 +3,4 @@ export default { | |||
|     tailwindcss: {}, | ||||
|     autoprefixer: {}, | ||||
|   }, | ||||
| } | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										22
									
								
								frontend/src/Components/BasicWindow.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								frontend/src/Components/BasicWindow.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| import Header from "./Header"; | ||||
| import Footer from "./Footer"; | ||||
| 
 | ||||
| function BasicWindow({ | ||||
|   username, | ||||
|   content, | ||||
|   buttons, | ||||
| }: { | ||||
|   username: string; | ||||
|   content: React.ReactNode; | ||||
|   buttons: React.ReactNode; | ||||
| }): JSX.Element { | ||||
|   return ( | ||||
|     <div className="font-sans flex flex-col h-screen bg-white border-2 border-black overflow-auto pt-[110px]"> | ||||
|       <Header username={username} /> | ||||
|       <div className="flex flex-col items-center flex-grow">{content}</div> | ||||
|       <Footer>{buttons}</Footer> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export default BasicWindow; | ||||
							
								
								
									
										18
									
								
								frontend/src/Components/Button.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								frontend/src/Components/Button.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | |||
| function Button({ | ||||
|   text, | ||||
|   onClick, | ||||
| }: { | ||||
|   text: string; | ||||
|   onClick: () => void; | ||||
| }): JSX.Element { | ||||
|   return ( | ||||
|     <button | ||||
|       onClick={onClick} | ||||
|       className="inline-block py-1 px-8 font-bold bg-orange-500 text-white border-2 border-black rounded-full cursor-pointer mt-5 mb-5 transition-colors duration-10 hover:bg-orange-600 hover:text-gray-300 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; font-size: 4vh;" | ||||
|     > | ||||
|       {text} | ||||
|     </button> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export default Button; | ||||
							
								
								
									
										13
									
								
								frontend/src/Components/Footer.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								frontend/src/Components/Footer.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| import React from "react"; | ||||
| 
 | ||||
| function Footer({ children }: { children: React.ReactNode }): JSX.Element { | ||||
|   return ( | ||||
|     <footer className="bg-white"> | ||||
|       <div className="flex justify-end items-center h-16 space-x-6 pr-6"> | ||||
|         {children} | ||||
|       </div> | ||||
|     </footer> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export default Footer; | ||||
							
								
								
									
										54
									
								
								frontend/src/Components/Header.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								frontend/src/Components/Header.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | |||
| import { useState } from "react"; | ||||
| import { Link } from "react-router-dom"; | ||||
| 
 | ||||
| function Header({ username }: { username: string }): JSX.Element { | ||||
|   const [isOpen, setIsOpen] = useState(false); | ||||
| 
 | ||||
|   const handleLogout = (): void => { | ||||
|     // Add any logout logic here
 | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <header | ||||
|       className="fixed top-0 left-0 right-0 border-[1.75px] border-black text-black p-3 pl-5 flex items-center justify-between bg-cover" | ||||
|       style={{ backgroundImage: `url('src/assets/1.jpg')` }} | ||||
|     > | ||||
|       <Link to="/your-projects"> | ||||
|         <img | ||||
|           src="/src/assets/TTIMElogo.png" | ||||
|           alt="TTIME Logo" | ||||
|           className="w-11 h-14 cursor-pointer" | ||||
|         /> | ||||
|       </Link> | ||||
| 
 | ||||
|       <div | ||||
|         className="relative" | ||||
|         onMouseEnter={() => { | ||||
|           setIsOpen(true); | ||||
|         }} | ||||
|         onMouseLeave={() => { | ||||
|           setIsOpen(false); | ||||
|         }} | ||||
|       > | ||||
|         <button className="mr-4 underline font-bold text-white"> | ||||
|           {username} | ||||
|         </button> | ||||
| 
 | ||||
|         {isOpen && ( | ||||
|           <div className="absolute right-0 bg-white border rounded shadow-lg"> | ||||
|             <Link to="/"> | ||||
|               <button | ||||
|                 onClick={handleLogout} | ||||
|                 className="block px-2 py-1 text-black hover:bg-gray-200" | ||||
|               > | ||||
|                 Logout | ||||
|               </button> | ||||
|             </Link> | ||||
|           </div> | ||||
|         )} | ||||
|       </div> | ||||
|     </header> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export default Header; | ||||
							
								
								
									
										26
									
								
								frontend/src/Pages/LoginPage.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								frontend/src/Pages/LoginPage.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,26 @@ | |||
| body{ | ||||
|     overflow: hidden; | ||||
| } | ||||
| 
 | ||||
| @keyframes backgroundTransition { | ||||
|     0% { | ||||
|         background-image: url('src/assets/1.jpg'); | ||||
|         animation-timing-function: ease-out; | ||||
|     } | ||||
|     25% { | ||||
|         background-image: url('src/assets/2.jpg'); | ||||
|         animation-timing-function: ease-in; | ||||
|     } | ||||
|     50% { | ||||
|         background-image: url('src/assets/3.jpg'); | ||||
|         animation-timing-function: ease-out; | ||||
|     } | ||||
|     75% { | ||||
|         background-image: url('src/assets/4.jpg'); | ||||
|         animation-timing-function: ease-in; | ||||
|     } | ||||
|     100% { | ||||
|         background-image: url('src/assets/1.jpg'); | ||||
|         animation-timing-function: ease-out; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										78
									
								
								frontend/src/Pages/LoginPage.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								frontend/src/Pages/LoginPage.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,78 @@ | |||
| import Button from "../Components/Button"; | ||||
| import Logo from "/src/assets/TTIMElogo.png"; | ||||
| import "./LoginPage.css"; | ||||
| import { useEffect } from "react"; | ||||
| import { Link } from "react-router-dom"; | ||||
| 
 | ||||
| const PreloadBackgroundAnimation = (): JSX.Element => { | ||||
|   useEffect(() => { | ||||
|     const images = [ | ||||
|       "src/assets/1.jpg", | ||||
|       "src/assets/2.jpg", | ||||
|       "src/assets/3.jpg", | ||||
|       "src/assets/4.jpg", | ||||
|     ]; | ||||
| 
 | ||||
|     // Pre-load images
 | ||||
|     for (const i of images) { | ||||
|       console.log(i); | ||||
|     } | ||||
| 
 | ||||
|     // Start animation
 | ||||
|     document.body.style.animation = "backgroundTransition 30s infinite"; | ||||
|   }, []); | ||||
| 
 | ||||
|   return <></>; | ||||
| }; | ||||
| 
 | ||||
| function LoginPage(): JSX.Element { | ||||
|   return ( | ||||
|     <> | ||||
|       <PreloadBackgroundAnimation /> | ||||
|       <div | ||||
|         className="flex flex-col h-screen w-screen items-center justify-center" | ||||
|         style={{ | ||||
|           animation: "backgroundTransition 30s infinite", | ||||
|           backgroundSize: "cover", | ||||
|           backgroundAttachment: "fixed", | ||||
|         }} | ||||
|       > | ||||
|         <div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20"> | ||||
|           <img | ||||
|             src={Logo} | ||||
|             className="logo w-[7vw] mb-10 mt-10" | ||||
|             alt="TTIME Logo" | ||||
|           /> | ||||
|           <h1 className="font-sans mb-4 font-bold text-[25px]"> | ||||
|             {" "} | ||||
|             Welcome to TTIME!{" "} | ||||
|           </h1> | ||||
|           <h2 className="font-sans mb-4 text-[15px]"> | ||||
|             {" "} | ||||
|             Please log in to continue{" "} | ||||
|           </h2> | ||||
|           <input | ||||
|             className="border-2 border-black mb-3 rounded-lg w-[20vw] p-1" | ||||
|             type="text" | ||||
|             placeholder="Username" | ||||
|           /> | ||||
|           <input | ||||
|             className="border-2 border-black mb-3 rounded-lg w-[20vw] p-1" | ||||
|             type="password" | ||||
|             placeholder="Password" | ||||
|           /> | ||||
|           <Link to="/your-projects"> | ||||
|             <Button | ||||
|               text="Login" | ||||
|               onClick={(): void => { | ||||
|                 return; | ||||
|               }} | ||||
|             /> | ||||
|           </Link> | ||||
|         </div> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
| 
 | ||||
| export default LoginPage; | ||||
							
								
								
									
										38
									
								
								frontend/src/Pages/ProjectManagerPages/PMProjectPage.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								frontend/src/Pages/ProjectManagerPages/PMProjectPage.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,38 @@ | |||
| import BasicWindow from "../../Components/BasicWindow"; | ||||
| import Button from "../../Components/Button"; | ||||
| 
 | ||||
| function PMProjectPage(): JSX.Element { | ||||
|   const content = ( | ||||
|     <> | ||||
|       <h1 className="font-bold text-[30px] mb-[20px]">ProjectNameExample</h1> | ||||
|       <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[5vh] p-[30px]"> | ||||
|         <h1 className="font-bold underline text-[30px] cursor-pointer"> | ||||
|           Your Time Reports | ||||
|         </h1> | ||||
|         <h1 className="font-bold underline text-[30px] cursor-pointer"> | ||||
|           New Time Report | ||||
|         </h1> | ||||
|         <h1 className="font-bold underline text-[30px] cursor-pointer"> | ||||
|           Statistics | ||||
|         </h1> | ||||
|         <h1 className="font-bold underline text-[30px] cursor-pointer"> | ||||
|           Unsigned Time Reports | ||||
|         </h1> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   const buttons = ( | ||||
|     <> | ||||
|       <Button | ||||
|         text="Back" | ||||
|         onClick={(): void => { | ||||
|           return; | ||||
|         }} | ||||
|       /> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   return <BasicWindow username="Admin" content={content} buttons={buttons} />; | ||||
| } | ||||
| export default PMProjectPage; | ||||
							
								
								
									
										37
									
								
								frontend/src/Pages/UserPages/UserProjectPage.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								frontend/src/Pages/UserPages/UserProjectPage.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import { Link } from "react-router-dom"; | ||||
| import BasicWindow from "../../Components/BasicWindow"; | ||||
| import Button from "../../Components/Button"; | ||||
| 
 | ||||
| function UserProjectPage(): JSX.Element { | ||||
|   const content = ( | ||||
|     <> | ||||
|       <Link to="/settingsPage"> | ||||
|         <h1 className="font-bold text-[30px] mb-[20px]">ProjectNameExample</h1> | ||||
|       </Link> | ||||
|       <div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px]"> | ||||
|         <h1 className="font-bold underline text-[30px] cursor-pointer"> | ||||
|           Your Time Reports | ||||
|         </h1> | ||||
|         <h1 className="font-bold underline text-[30px] cursor-pointer"> | ||||
|           New Time Report | ||||
|         </h1> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   const buttons = ( | ||||
|     <> | ||||
|       <Link to="/your-projects"> | ||||
|         <Button | ||||
|           text="Back" | ||||
|           onClick={(): void => { | ||||
|             return; | ||||
|           }} | ||||
|         /> | ||||
|       </Link> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   return <BasicWindow username="Admin" content={content} buttons={buttons} />; | ||||
| } | ||||
| export default UserProjectPage; | ||||
							
								
								
									
										31
									
								
								frontend/src/Pages/YourProjectsPage.tsx
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								frontend/src/Pages/YourProjectsPage.tsx
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | |||
| import { Link } from "react-router-dom"; | ||||
| import BasicWindow from "../Components/BasicWindow"; | ||||
| 
 | ||||
| function YourProjectsPage(): JSX.Element { | ||||
|   const content = ( | ||||
|     <> | ||||
|       <h1 className="font-bold text-[30px] mb-[20px]">Your Projects</h1> | ||||
|       <div className="border-4 border-black bg-white flex flex-col items-center justify-between min-h-[65vh] h-fit w-[50vw] rounded-3xl content-center overflow-scroll space-y-[10px] p-[30px]"> | ||||
|         <Link to="/project"> | ||||
|           <h1 className="underline text-[24px] cursor-pointer font-bold"> | ||||
|             ProjectNameExample | ||||
|           </h1> | ||||
|         </Link> | ||||
|         <h1 className="underline text-[24px] cursor-pointer font-bold"> | ||||
|           ProjectNameExample | ||||
|         </h1> | ||||
|         <h1 className="underline text-[24px] cursor-pointer font-bold"> | ||||
|           ProjectNameExample | ||||
|         </h1> | ||||
|         <h1 className="underline text-[24px] cursor-pointer font-bold"> | ||||
|           ProjectNameExample | ||||
|         </h1> | ||||
|       </div> | ||||
|     </> | ||||
|   ); | ||||
| 
 | ||||
|   const buttons = <></>; | ||||
| 
 | ||||
|   return <BasicWindow username="Admin" content={content} buttons={buttons} />; | ||||
| } | ||||
| export default YourProjectsPage; | ||||
							
								
								
									
										
											BIN
										
									
								
								frontend/src/assets/1.jpg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/src/assets/1.jpg
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 662 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/src/assets/2.jpg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/src/assets/2.jpg
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 659 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/src/assets/3.jpg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/src/assets/3.jpg
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 668 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/src/assets/4.jpg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/src/assets/4.jpg
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 665 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/src/assets/TTIMElogo.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/src/assets/TTIMElogo.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 261 KiB | 
							
								
								
									
										
											BIN
										
									
								
								frontend/src/assets/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								frontend/src/assets/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 15 KiB | 
|  | @ -6,117 +6,3 @@ | |||
|  * We are using tailwind, so do not add any custom CSS here.  | ||||
|  * Most of this is going to get cleaned up eventually. | ||||
|  */ | ||||
| 
 | ||||
| :root { | ||||
|   font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; | ||||
|   line-height: 1.5; | ||||
|   font-weight: 400; | ||||
| 
 | ||||
|   color-scheme: light dark; | ||||
|   color: rgba(255, 255, 255, 0.87); | ||||
|   background-color: #242424; | ||||
| 
 | ||||
|   font-synthesis: none; | ||||
|   text-rendering: optimizeLegibility; | ||||
|   -webkit-font-smoothing: antialiased; | ||||
|   -moz-osx-font-smoothing: grayscale; | ||||
| } | ||||
| 
 | ||||
| a { | ||||
|   font-weight: 500; | ||||
|   color: #646cff; | ||||
|   text-decoration: inherit; | ||||
| } | ||||
| a:hover { | ||||
|   color: #535bf2; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
|   margin: 0; | ||||
|   display: flex; | ||||
|   place-items: center; | ||||
|   min-width: 320px; | ||||
|   min-height: 100vh; | ||||
| } | ||||
| 
 | ||||
| h1 { | ||||
|   font-size: 3.2em; | ||||
|   line-height: 1.1; | ||||
| } | ||||
| 
 | ||||
| button { | ||||
|   border-radius: 8px; | ||||
|   border: 1px solid transparent; | ||||
|   padding: 0.6em 1.2em; | ||||
|   font-size: 1em; | ||||
|   font-weight: 500; | ||||
|   font-family: inherit; | ||||
|   background-color: #1a1a1a; | ||||
|   cursor: pointer; | ||||
|   transition: border-color 0.25s; | ||||
| } | ||||
| button:hover { | ||||
|   border-color: #646cff; | ||||
| } | ||||
| button:focus, | ||||
| button:focus-visible { | ||||
|   outline: 4px auto -webkit-focus-ring-color; | ||||
| } | ||||
| 
 | ||||
| @media (prefers-color-scheme: light) { | ||||
|   :root { | ||||
|     color: #213547; | ||||
|     background-color: #ffffff; | ||||
|   } | ||||
|   a:hover { | ||||
|     color: #747bff; | ||||
|   } | ||||
|   button { | ||||
|     background-color: #f9f9f9; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| #root { | ||||
|   max-width: 1280px; | ||||
|   margin: 0 auto; | ||||
|   padding: 2rem; | ||||
|   text-align: center; | ||||
| } | ||||
| 
 | ||||
| a { | ||||
|   display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .logo { | ||||
|   transition: filter 0.25s; | ||||
| } | ||||
| 
 | ||||
| .logo:hover { | ||||
|   filter: drop-shadow(0 0 2em #646cffaa); | ||||
| } | ||||
| .logo.react:hover { | ||||
|   filter: drop-shadow(0 0 2em #61dafbaa); | ||||
| } | ||||
| 
 | ||||
| @keyframes logo-spin { | ||||
|   from { | ||||
|     transform: rotate(0deg); | ||||
|   } | ||||
|   to { | ||||
|     transform: rotate(360deg); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media (prefers-reduced-motion: no-preference) { | ||||
|   a:nth-of-type(2) .logo { | ||||
|     animation: logo-spin infinite 20s linear; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .card { | ||||
|   padding: 2em; | ||||
| } | ||||
| 
 | ||||
| .read-the-docs { | ||||
|   color: #888; | ||||
| } | ||||
|  |  | |||
|  | @ -1,19 +1,24 @@ | |||
| import React from "react"; | ||||
| import ReactDOM from "react-dom/client"; | ||||
| import SettingsPage from "./Pages/Settings.tsx"; | ||||
| import HomePage from "./Pages/Home.tsx"; | ||||
| import "./index.css"; | ||||
| import { createBrowserRouter, RouterProvider } from "react-router-dom"; | ||||
| import LoginPage from "./Pages/LoginPage.tsx"; | ||||
| import YourProjectsPage from "./Pages/YourProjectsPage.tsx"; | ||||
| import UserProjectPage from "./Pages/UserPages/UserProjectPage.tsx"; | ||||
| 
 | ||||
| // This is where the routes are mounted
 | ||||
| const router = createBrowserRouter([ | ||||
|   { | ||||
|     path: "/", | ||||
|     element: <HomePage />, | ||||
|     element: <LoginPage />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/settings", | ||||
|     element: <SettingsPage />, | ||||
|     path: "/your-projects", | ||||
|     element: <YourProjectsPage />, | ||||
|   }, | ||||
|   { | ||||
|     path: "/project", | ||||
|     element: <UserProjectPage />, | ||||
|   }, | ||||
| ]); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,13 +1,9 @@ | |||
| /** @type {import('tailwindcss').Config} */ | ||||
| export default { | ||||
|   content: [ | ||||
|     "./index.html", | ||||
|     "./src/**/*.{js,ts,jsx,tsx}", | ||||
|   ], | ||||
|   content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], | ||||
| 
 | ||||
|   theme: { | ||||
|     extend: {}, | ||||
|   }, | ||||
|   plugins: [], | ||||
| } | ||||
| 
 | ||||
| }; | ||||
|  |  | |||
							
								
								
									
										15
									
								
								go.work.sum
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								go.work.sum
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,15 @@ | |||
| github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= | ||||
| github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= | ||||
| github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= | ||||
| github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= | ||||
| github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= | ||||
| golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= | ||||
| golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= | ||||
| golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= | ||||
| golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= | ||||
| golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||
| golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= | ||||
| golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= | ||||
| golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= | ||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue