Compare commits

..

No commits in common. "c13378d3b964c85b77f7f1c467fd3c70a35a3ba3" and "151d6de39b15346f57a4294e5922a5fc08a5a73c" have entirely different histories.

39 changed files with 114 additions and 237 deletions

View file

@ -4,6 +4,7 @@ import (
"embed" "embed"
"os" "os"
"path/filepath" "path/filepath"
"time"
"ttime/internal/types" "ttime/internal/types"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
@ -14,21 +15,19 @@ import (
type Database interface { type Database interface {
// Insert a new user into the database, password should be hashed before calling // Insert a new user into the database, password should be hashed before calling
AddUser(username string, password string) error AddUser(username string, password string) error
CheckUser(username string, password string) bool
RemoveUser(username string) error RemoveUser(username string) error
PromoteToAdmin(username string) error PromoteToAdmin(username string) error
GetUserId(username string) (int, error) GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error AddProject(name string, description string, username string) error
Migrate(dirname string) error Migrate(dirname string) error
GetProjectId(projectname string) (int, error) GetProjectId(projectname string) (int, error)
AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error AddTimeReport(projectName string, userName string, activityType string, start time.Time, end time.Time) error
AddUserToProject(username string, projectname string, role string) error AddUserToProject(username string, projectname string, role string) error
ChangeUserRole(username string, projectname string, role string) error ChangeUserRole(username string, projectname string, role string) error
GetAllUsersProject(projectname string) ([]UserProjectMember, error) GetAllUsersProject(projectname string) ([]UserProjectMember, error)
GetAllUsersApplication() ([]string, error) GetAllUsersApplication() ([]string, error)
GetProjectsForUser(username string) ([]types.Project, error) GetProjectsForUser(username string) ([]types.Project, error)
GetAllProjects() ([]types.Project, error) GetAllProjects() ([]types.Project, error)
GetProject(projectId int) (types.Project, error)
GetUserRole(username string, projectname string) (string, error) GetUserRole(username string, projectname string) (string, error)
} }
@ -50,16 +49,27 @@ var scripts embed.FS
const userInsert = "INSERT INTO users (username, password) VALUES (?, ?)" 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 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 promoteToAdmin = "INSERT INTO site_admin (admin_id) SELECT id FROM users WHERE username = ?"
const addWeeklyReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?), const addTimeReport = `WITH UserLookup AS (SELECT id FROM users WHERE username = ?),
ProjectLookup AS (SELECT id FROM projects WHERE name = ?) ProjectLookup AS (SELECT id FROM projects WHERE name = ?)
INSERT INTO weekly_reports (project_id, user_id, week, development_time, meeting_time, admin_time, own_work_time, study_time, testing_time) INSERT INTO time_reports (project_id, user_id, activity_type, start, end)
VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?, ?, ?, ?, ?);` VALUES ((SELECT id FROM ProjectLookup), (SELECT id FROM UserLookup),?, ?, ?);`
const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP const addUserToProject = "INSERT INTO user_roles (user_id, project_id, p_role) VALUES (?, ?, ?)" // WIP
const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?" const changeUserRole = "UPDATE user_roles SET p_role = ? WHERE user_id = ? AND project_id = ?"
const getProjectsForUser = `SELECT projects.id, projects.name, projects.description, projects.owner_user_id const getProjectsForUser = `
FROM projects JOIN user_roles ON projects.id = user_roles.project_id SELECT
JOIN users ON user_roles.user_id = users.id WHERE users.username = ?;` projects.id,
projects.name,
projects.description,
projects.owner_user_id
FROM
projects
JOIN
user_roles ON projects.id = user_roles.project_id
JOIN
users ON user_roles.user_id = users.id
WHERE
users.username = ?;`
// DbConnect connects to the database // DbConnect connects to the database
func DbConnect(dbpath string) Database { func DbConnect(dbpath string) Database {
@ -78,42 +88,23 @@ func DbConnect(dbpath string) Database {
return &Db{db} return &Db{db}
} }
func (d *Db) CheckUser(username string, password string) bool {
var dbPassword string
err := d.Get(&dbPassword, "SELECT password FROM users WHERE username = ?", username)
if err != nil {
return false
}
return dbPassword == password
}
// GetProjectsForUser retrieves all projects associated with a specific user.
func (d *Db) GetProjectsForUser(username string) ([]types.Project, error) { func (d *Db) GetProjectsForUser(username string) ([]types.Project, error) {
var projects []types.Project var projects []types.Project
err := d.Select(&projects, getProjectsForUser, username) err := d.Select(&projects, getProjectsForUser, username)
return projects, err return projects, err
} }
// GetAllProjects retrieves all projects from the database.
func (d *Db) GetAllProjects() ([]types.Project, error) { func (d *Db) GetAllProjects() ([]types.Project, error) {
var projects []types.Project var projects []types.Project
err := d.Select(&projects, "SELECT * FROM projects") err := d.Select(&projects, "SELECT * FROM projects")
return projects, err return projects, err
} }
// GetProject retrieves a specific project by its ID. func (d *Db) AddTimeReport(projectName string, userName string, activityType string, start time.Time, end time.Time) error { // WIP
func (d *Db) GetProject(projectId int) (types.Project, error) { _, err := d.Exec(addTimeReport, userName, projectName, activityType, start, end)
var project types.Project
err := d.Select(&project, "SELECT * FROM projects WHERE id = ?")
return project, err
}
func (d *Db) AddWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error {
_, err := d.Exec(addWeeklyReport, userName, projectName, week, developmentTime, meetingTime, adminTime, ownWorkTime, studyTime, testingTime)
return err return err
} }
// AddUserToProject adds a user to a project with a specified role.
func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP func (d *Db) AddUserToProject(username string, projectname string, role string) error { // WIP
var userid int var userid int
userid, err := d.GetUserId(username) userid, err := d.GetUserId(username)
@ -131,28 +122,23 @@ func (d *Db) AddUserToProject(username string, projectname string, role string)
return err3 return err3
} }
// ChangeUserRole changes the role of a user within a project.
func (d *Db) ChangeUserRole(username string, projectname string, role string) error { func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
// Get the user ID
var userid int var userid int
userid, err := d.GetUserId(username) userid, err := d.GetUserId(username)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// Get the project ID
var projectid int var projectid int
projectid, err2 := d.GetProjectId(projectname) projectid, err2 := d.GetProjectId(projectname)
if err2 != nil { if err2 != nil {
panic(err2) panic(err2)
} }
// Execute the SQL query to change the user's role
_, err3 := d.Exec(changeUserRole, role, userid, projectid) _, err3 := d.Exec(changeUserRole, role, userid, projectid)
return err3 return err3
} }
// GetUserRole retrieves the role of a user within a project.
func (d *Db) GetUserRole(username string, projectname string) (string, error) { func (d *Db) GetUserRole(username string, projectname string) (string, error) {
var role string var role string
err := d.Get(&role, "SELECT p_role FROM user_roles WHERE user_id = (SELECT id FROM users WHERE username = ?) AND project_id = (SELECT id FROM projects WHERE name = ?)", username, projectname) err := d.Get(&role, "SELECT p_role FROM user_roles WHERE user_id = (SELECT id FROM users WHERE username = ?) AND project_id = (SELECT id FROM projects WHERE name = ?)", username, projectname)

View file

@ -2,6 +2,7 @@ package database
import ( import (
"testing" "testing"
"time"
) )
// Tests are not guaranteed to be sequential // Tests are not guaranteed to be sequential
@ -92,7 +93,7 @@ func TestPromoteToAdmin(t *testing.T) {
} }
} }
func TestAddWeeklyReport(t *testing.T) { func TestAddTimeReport(t *testing.T) {
db, err := setupState() db, err := setupState()
if err != nil { if err != nil {
t.Error("setupState failed:", err) t.Error("setupState failed:", err)
@ -108,9 +109,12 @@ func TestAddWeeklyReport(t *testing.T) {
t.Error("AddProject failed:", err) t.Error("AddProject failed:", err)
} }
err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1) var now = time.Now()
var then = now.Add(time.Hour)
err = db.AddTimeReport("testproject", "testuser", "activity", now, then)
if err != nil { if err != nil {
t.Error("AddWeeklyReport failed:", err) t.Error("AddTimeReport failed:", err)
} }
} }
@ -130,9 +134,12 @@ func TestAddUserToProject(t *testing.T) {
t.Error("AddProject failed:", err) t.Error("AddProject failed:", err)
} }
err = db.AddWeeklyReport("testproject", "testuser", 1, 1, 1, 1, 1, 1, 1) var now = time.Now()
var then = now.Add(time.Hour)
err = db.AddTimeReport("testproject", "testuser", "activity", now, then)
if err != nil { if err != nil {
t.Error("AddWeeklyReport failed:", err) t.Error("AddTimeReport failed:", err)
} }
err = db.AddUserToProject("testuser", "testproject", "user") err = db.AddUserToProject("testuser", "testproject", "user")
@ -336,38 +343,3 @@ func TestGetProjectsForUser(t *testing.T) {
t.Error("GetProjectsForUser failed: expected 1, got", len(projects)) t.Error("GetProjectsForUser failed: expected 1, got", len(projects))
} }
} }
func TestAddProject(t *testing.T) {
db, err := setupState()
if err != nil {
t.Error("setupState failed:", err)
}
err = db.AddUser("testuser", "password")
if err != nil {
t.Error("AddUser failed:", err)
}
err = db.AddProject("testproject", "description", "testuser")
if err != nil {
t.Error("AddProject failed:", err)
}
// Retrieve the added project to verify its existence
projects, err := db.GetAllProjects()
if err != nil {
t.Error("GetAllProjects failed:", err)
}
// Check if the project was added successfully
found := false
for _, project := range projects {
if project.Name == "testproject" {
found = true
break
}
}
if !found {
t.Error("Added project not found")
}
}

View file

@ -0,0 +1,22 @@
CREATE TABLE IF NOT EXISTS time_reports (
id INTEGER PRIMARY KEY,
project_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
activity_type TEXT NOT NULL,
start DATETIME NOT NULL,
end DATETIME NOT NULL,
FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
FOREIGN KEY (activity_type) REFERENCES activity_types (name) ON DELETE CASCADE
);
CREATE TRIGGER IF NOT EXISTS time_reports_start_before_end
BEFORE INSERT ON time_reports
FOR EACH ROW
BEGIN
SELECT
CASE
WHEN NEW.start >= NEW.end THEN
RAISE (ABORT, 'start must be before end')
END;
END;

View file

@ -1,14 +0,0 @@
CREATE TABLE weekly_reports (
user_id INTEGER,
project_id INTEGER,
week INTEGER,
development_time INTEGER,
meeting_time INTEGER,
admin_time INTEGER,
own_work_time INTEGER,
study_time INTEGER,
testing_time INTEGER,
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (project_id) REFERENCES projects(id)
PRIMARY KEY (user_id, project_id, week)
)

View file

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS report_collection (
id INTEGER PRIMARY KEY,
owner_id INTEGER NOT NULL,
project_id INTEGER NOT NULL,
date DATE NOT NULL,
signed_by INTEGER, -- NULL if not signed
FOREIGN KEY (owner_id) REFERENCES users (id)
FOREIGN KEY (signed_by) REFERENCES users (id)
);

View file

@ -0,0 +1,16 @@
-- It is unclear weather this table will be used
-- Create the table to store hash salts
CREATE TABLE IF NOT EXISTS 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;

View file

@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS activity_types (
name TEXT PRIMARY KEY
);
INSERT OR IGNORE INTO activity_types (name) VALUES ('Development');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Meeting');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Administration');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Own Work');
INSERT OR IGNORE INTO activity_types (name) VALUES ('Studies');
INSErt OR IGNORE INTO activity_types (name) VALUES ('Testing');

View file

@ -1,7 +1,6 @@
package handlers package handlers
import ( import (
"strconv"
"time" "time"
"ttime/internal/database" "ttime/internal/database"
"ttime/internal/types" "ttime/internal/types"
@ -18,7 +17,6 @@ type GlobalState interface {
LoginRenew(c *fiber.Ctx) error // To renew the token LoginRenew(c *fiber.Ctx) error // To renew the token
CreateProject(c *fiber.Ctx) error // To create a new project CreateProject(c *fiber.Ctx) error // To create a new project
GetUserProjects(c *fiber.Ctx) error // To get all projects GetUserProjects(c *fiber.Ctx) error // To get all projects
SubmitWeeklyReport(c *fiber.Ctx) error
// GetProject(c *fiber.Ctx) error // To get a specific project // GetProject(c *fiber.Ctx) error // To get a specific project
// UpdateProject(c *fiber.Ctx) error // To update a project // UpdateProject(c *fiber.Ctx) error // To update a project
// DeleteProject(c *fiber.Ctx) error // To delete a project // DeleteProject(c *fiber.Ctx) error // To delete a project
@ -78,17 +76,12 @@ func (gs *GState) Register(c *fiber.Ctx) error {
// This path should obviously be protected in the future // This path should obviously be protected in the future
// UserDelete deletes a user from the database // UserDelete deletes a user from the database
func (gs *GState) UserDelete(c *fiber.Ctx) error { func (gs *GState) UserDelete(c *fiber.Ctx) error {
// Read from path parameters u := new(types.User)
username := c.Params("username") if err := c.BodyParser(u); err != nil {
return c.Status(400).SendString(err.Error())
// Read username from Locals
auth_username := c.Locals("user").(*jwt.Token).Claims.(jwt.MapClaims)["name"].(string)
if username != auth_username {
return c.Status(403).SendString("You can only delete yourself")
} }
if err := gs.Db.RemoveUser(username); err != nil { if err := gs.Db.RemoveUser(u.Username); err != nil {
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }
@ -110,7 +103,8 @@ func (gs *GState) Login(c *fiber.Ctx) error {
user := c.FormValue("user") user := c.FormValue("user")
pass := c.FormValue("pass") pass := c.FormValue("pass")
if !gs.Db.CheckUser(user, pass) { // Throws Unauthorized error
if user != "user" || pass != "pass" {
return c.SendStatus(fiber.StatusUnauthorized) return c.SendStatus(fiber.StatusUnauthorized)
} }
@ -164,9 +158,9 @@ func (gs *GState) CreateProject(c *fiber.Ctx) error {
// Get the username from the token and set it as the owner of the project // Get the username from the token and set it as the owner of the project
// This is ugly but // This is ugly but
claims := user.Claims.(jwt.MapClaims) claims := user.Claims.(jwt.MapClaims)
owner := claims["name"].(string) p.Owner = claims["name"].(string)
if err := gs.Db.AddProject(p.Name, p.Description, owner); err != nil { if err := gs.Db.AddProject(p.Name, p.Description, p.Owner); err != nil {
return c.Status(500).SendString(err.Error()) return c.Status(500).SendString(err.Error())
} }
@ -231,42 +225,3 @@ func (gs *GState) ProjectRoleChange(c *fiber.Ctx) error {
// Return a success message // Return a success message
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)
} }
// GetProject retrieves a specific project by its ID
func (gs *GState) GetProject(c *fiber.Ctx) error {
// Extract the project ID from the request parameters or body
projectID := c.Params("projectID")
// Parse the project ID into an integer
projectIDInt, err := strconv.Atoi(projectID)
if err != nil {
return c.Status(400).SendString("Invalid project ID")
}
// Get the project from the database by its ID
project, err := gs.Db.GetProject(projectIDInt)
if err != nil {
return c.Status(500).SendString(err.Error())
}
// Return the project as JSON
return c.JSON(project)
}
func (gs *GState) SubmitWeeklyReport(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
report := new(types.NewWeeklyReport)
if err := c.BodyParser(report); err != nil {
return c.Status(400).SendString(err.Error())
}
if err := gs.Db.AddWeeklyReport(report.ProjectName, username, report.Week, report.DevelopmentTime, report.MeetingTime, report.AdminTime, report.OwnWorkTime, report.StudyTime, report.TestingTime); err != nil {
return c.Status(500).SendString(err.Error())
}
return c.Status(200).SendString("Time report added")
}

View file

@ -1,21 +0,0 @@
package types
// This is what should be submitted to the server, the username will be derived from the JWT token
type NewWeeklyReport struct {
// The name of the project, as it appears in the database
ProjectName string
// The week number
Week int
// Total time spent on development
DevelopmentTime int
// Total time spent in meetings
MeetingTime int
// Total time spent on administrative tasks
AdminTime int
// Total time spent on personal projects
OwnWorkTime int
// Total time spent on studying
StudyTime int
// Total time spent on testing
TestingTime int
}

View file

@ -8,8 +8,9 @@ type Project struct {
Owner string `json:"owner" db:"owner_user_id"` Owner string `json:"owner" db:"owner_user_id"`
} }
// As it arrives from the client, Owner is derived from the JWT token // As it arrives from the client
type NewProject struct { type NewProject struct {
Name string `json:"name"` Name string `json:"name"`
Description string `json:"description"` Description string `json:"description"`
Owner string `json:"owner"`
} }

View file

@ -16,7 +16,6 @@ func (u *User) ToPublicUser() (*PublicUser, error) {
}, nil }, nil
} }
// Should be used when registering, for example
type NewUser struct { type NewUser struct {
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`

View file

@ -68,10 +68,9 @@ func main() {
SigningKey: jwtware.SigningKey{Key: []byte("secret")}, SigningKey: jwtware.SigningKey{Key: []byte("secret")},
})) }))
server.Post("/api/submitReport", gs.SubmitWeeklyReport)
server.Get("/api/getUserProjects", gs.GetUserProjects) server.Get("/api/getUserProjects", gs.GetUserProjects)
server.Post("/api/loginrenew", gs.LoginRenew) server.Post("/api/loginrenew", gs.LoginRenew)
server.Delete("/api/userdelete/:username", gs.UserDelete) // Perhaps just use POST to avoid headaches server.Delete("/api/userdelete", gs.UserDelete) // Perhaps just use POST to avoid headaches
server.Post("/api/project", gs.CreateProject) server.Post("/api/project", gs.CreateProject)
// Announce the port we are listening on and start the server // Announce the port we are listening on and start the server

View file

@ -1,17 +1,14 @@
function Button({ function Button({
text, text,
onClick, onClick,
type,
}: { }: {
text: string; text: string;
onClick: () => void; onClick: () => void;
type: "submit" | "button" | "reset";
}): JSX.Element { }): JSX.Element {
return ( return (
<button <button
onClick={onClick} 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;" 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;"
type={type}
> >
{text} {text}
</button> </button>

View file

@ -1,8 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { NewUser } from "../Types/Users"; import { NewUser } from "../Types/Users";
import { api } from "../API/API"; import { api } from "../API/API";
import Logo from "../assets/Logo.svg";
import Button from "./Button";
export default function Register(): JSX.Element { export default function Register(): JSX.Element {
const [username, setUsername] = useState(""); const [username, setUsername] = useState("");
@ -14,32 +12,25 @@ export default function Register(): JSX.Element {
}; };
return ( return (
<div className="flex flex-col h-screen w-screen items-center justify-center"> <div>
<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"> <div className="w-full max-w-xs">
<form <form
className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit" className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
void handleRegister(); void handleRegister();
}} }}
> >
<img <h3 className="pb-2">Register new user</h3>
src={Logo}
className="logo w-[7vw] mb-10 mt-10"
alt="TTIME Logo"
/>
<h3 className="pb-4 mb-2 text-center font-bold text-[18px]">
Register New User
</h3>
<div className="mb-4"> <div className="mb-4">
<label <label
className="block text-gray-700 text-sm font-sans font-bold mb-2" className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="username" htmlFor="username"
> >
Username Username
</label> </label>
<input <input
className="appearance-none border-2 border-black rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
id="username" id="username"
type="text" type="text"
placeholder="Username" placeholder="Username"
@ -51,13 +42,13 @@ export default function Register(): JSX.Element {
</div> </div>
<div className="mb-6"> <div className="mb-6">
<label <label
className="block text-gray-700 text-sm font-sans font-bold mb-2" className="block text-gray-700 text-sm font-bold mb-2"
htmlFor="password" htmlFor="password"
> >
Password Password
</label> </label>
<input <input
className="appearance-none border-2 border-black rounded-2xl w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
id="password" id="password"
type="password" type="password"
placeholder="Choose your password" placeholder="Choose your password"
@ -68,13 +59,12 @@ export default function Register(): JSX.Element {
/> />
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Button <button
text="Register" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
onClick={(): void => {
return;
}}
type="submit" type="submit"
/> >
Register
</button>
</div> </div>
</form> </form>
<p className="text-center text-gray-500 text-xs"></p> <p className="text-center text-gray-500 text-xs"></p>

View file

@ -11,14 +11,12 @@ function AdminAddProject(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,14 +11,12 @@ function AdminAddUser(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,14 +11,12 @@ function AdminChangeUsername(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,14 +11,12 @@ function AdminManageProjects(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,14 +11,12 @@ function AdminManageUsers(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,14 +11,12 @@ function AdminProjectAddMember(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,14 +11,12 @@ function AdminProjectChangeUserRole(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,14 +11,12 @@ function AdminProjectManageMembers(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,14 +11,12 @@ function AdminProjectPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,7 +11,6 @@ function AdminProjectStatistics(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,14 +11,12 @@ function AdminProjectViewMemberInfo(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,14 +11,12 @@ function AdminViewUserInfo(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -67,7 +67,6 @@ function LoginPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</Link> </Link>
<Link to="/register"> <Link to="/register">
@ -76,7 +75,6 @@ function LoginPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</Link> </Link>
</div> </div>

View file

@ -11,14 +11,12 @@ function ChangeRole(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,7 +11,6 @@ function PMOtherUsersTR(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,21 +11,18 @@ function PMProjectMembers(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Time / Role" text="Time / Role"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -29,7 +29,6 @@ function PMProjectPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -19,7 +19,6 @@ function PMTotalTimeActivity(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,7 +11,6 @@ function PMTotalTimeRole(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -11,7 +11,6 @@ function PMUnsignedReports(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -19,21 +19,18 @@ function PMViewUnsignedReport(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Save" text="Save"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -17,14 +17,12 @@ function UserEditTimeReportPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Button <Button
text="Back" text="Back"
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );

View file

@ -18,7 +18,6 @@ function UserNewTimeReportPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
<Link to="/project"> <Link to="/project">
<Button <Button
@ -26,7 +25,6 @@ function UserNewTimeReportPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</Link> </Link>
</> </>

View file

@ -27,7 +27,6 @@ function UserProjectPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</Link> </Link>
</> </>

View file

@ -11,7 +11,6 @@ function UserViewTimeReportsPage(): JSX.Element {
onClick={(): void => { onClick={(): void => {
return; return;
}} }}
type="button"
/> />
</> </>
); );