From 5251ca6f997d39762824f4181b9d2a75de94f737 Mon Sep 17 00:00:00 2001 From: Fishandchips321 Date: Sun, 8 Mar 2026 19:30:00 +0000 Subject: [PATCH] idk a lot of changes for the admin stuff --- Makefile | 2 +- backend/src/Controllers/AuthController.cs | 28 +++- backend/src/Controllers/ServersController.cs | 1 + .../Exceptions/AuthRepositoryExceptions.cs | 7 + .../ControllerBodies/Auth/CreateLoginDTO.cs | 1 + .../src/Repositories/ISessionRepository.cs | 1 + backend/src/Repositories/LoginRepository.cs | 15 ++ backend/src/Repositories/SessionRepository.cs | 12 ++ backend/src/Services/AuthService.cs | 24 +++- backend/src/Services/IAuthService.cs | 3 +- frontend/package-lock.json | 25 +++- frontend/package.json | 4 +- frontend/src/Components/Navbar/Navbar.tsx | 18 ++- frontend/src/Lib/Auth.ts | 43 ++++++ frontend/src/Lib/Servers.ts | 13 +- frontend/src/Pages/Admin/Admin.tsx | 31 ++++ .../src/Pages/Admin/Management.module.scss | 15 ++ .../ServerManagement/ServerManagement.tsx | 114 +++++++++++++++ .../Admin/UserManagement/UserManagement.tsx | 136 ++++++++++++++++++ .../Pages/ManageUser/ManageUser.module.scss | 5 + frontend/src/Pages/ManageUser/ManageUser.tsx | 68 +++++++++ frontend/src/Pages/NotFound/NotFound.tsx | 17 +++ frontend/src/main.tsx | 8 +- 23 files changed, 576 insertions(+), 15 deletions(-) create mode 100644 frontend/src/Pages/Admin/Admin.tsx create mode 100644 frontend/src/Pages/Admin/Management.module.scss create mode 100644 frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx create mode 100644 frontend/src/Pages/Admin/UserManagement/UserManagement.tsx create mode 100644 frontend/src/Pages/ManageUser/ManageUser.module.scss create mode 100644 frontend/src/Pages/ManageUser/ManageUser.tsx create mode 100644 frontend/src/Pages/NotFound/NotFound.tsx diff --git a/Makefile b/Makefile index 3179e9e..46656ba 100644 --- a/Makefile +++ b/Makefile @@ -2,4 +2,4 @@ build: docker build -t foxgirlriley/jellyglass:latest . run: build - docker run -p 5000:5000 foxgirlriley/jellyglass:latest \ No newline at end of file + docker run --rm -p 5000:5000 foxgirlriley/jellyglass:latest \ No newline at end of file diff --git a/backend/src/Controllers/AuthController.cs b/backend/src/Controllers/AuthController.cs index ce96511..d13b802 100644 --- a/backend/src/Controllers/AuthController.cs +++ b/backend/src/Controllers/AuthController.cs @@ -32,6 +32,28 @@ public class AuthController : ControllerBase } [HttpGet] + public async Task GetLoginFromSession() + { + if (!await IsAuthenticated()) + { + return Unauthorized(); + } + + var sessionToken = GetSessionToken(); + + try + { + var login = await _service.GetLoginFromSession(sessionToken!); + + return Ok(login); + } + catch (LoginFailedException) + { + return Unauthorized(); + } + } + + [HttpGet("all")] public async Task GetLogins() { if (!await IsAdminAuthenticated()) @@ -72,7 +94,7 @@ public class AuthController : ControllerBase return Forbid(); } - var newLogin = await _service.CreateLogin(login.Username, login.Password); + var newLogin = await _service.CreateLogin(login.Username, login.Password, login.IsAdmin); return Ok(newLogin); } @@ -80,9 +102,9 @@ public class AuthController : ControllerBase [HttpPut] public async Task UpdateLogin([FromBody] UpdateLoginDTO login) { - if (!await IsAdminAuthenticated()) + if (!await IsAuthenticated()) { - return Forbid(); + return Unauthorized(); } try diff --git a/backend/src/Controllers/ServersController.cs b/backend/src/Controllers/ServersController.cs index 9daf602..9abb395 100644 --- a/backend/src/Controllers/ServersController.cs +++ b/backend/src/Controllers/ServersController.cs @@ -76,6 +76,7 @@ public class ServersController : ControllerBase [HttpPost] public async Task AddServer([FromBody] AddServerDTO server) { + _logger.LogInformation("Started adding server"); if (!await IsAdminAuthenticated()) { return Forbid(); diff --git a/backend/src/Exceptions/AuthRepositoryExceptions.cs b/backend/src/Exceptions/AuthRepositoryExceptions.cs index 040bfae..fb4bd03 100644 --- a/backend/src/Exceptions/AuthRepositoryExceptions.cs +++ b/backend/src/Exceptions/AuthRepositoryExceptions.cs @@ -19,4 +19,11 @@ public class AuthAlreadyExistsException : AuthRepositoryException public AuthAlreadyExistsException() { } public AuthAlreadyExistsException(string message) : base(message) { } public AuthAlreadyExistsException(string message, System.Exception inner) : base(message, inner) { } +} + +public class UserNotDeletableException : AuthRepositoryException +{ + public UserNotDeletableException() { } + public UserNotDeletableException(string message) : base(message) { } + public UserNotDeletableException(string message, System.Exception inner) : base(message, inner) { } } \ No newline at end of file diff --git a/backend/src/Models/ControllerBodies/Auth/CreateLoginDTO.cs b/backend/src/Models/ControllerBodies/Auth/CreateLoginDTO.cs index e861020..5d08be3 100644 --- a/backend/src/Models/ControllerBodies/Auth/CreateLoginDTO.cs +++ b/backend/src/Models/ControllerBodies/Auth/CreateLoginDTO.cs @@ -4,4 +4,5 @@ public class CreateLoginDTO { public required string Username { get; set; } public required string Password { get; set; } + public bool IsAdmin { get; set; } = false; } \ No newline at end of file diff --git a/backend/src/Repositories/ISessionRepository.cs b/backend/src/Repositories/ISessionRepository.cs index 89fa04a..5a9ef34 100644 --- a/backend/src/Repositories/ISessionRepository.cs +++ b/backend/src/Repositories/ISessionRepository.cs @@ -8,4 +8,5 @@ public interface ISessionRepository public Task GetUserSession(string sessionToken); public Task RefreshSessionExpiry(string sessionToken); public Task DeleteSession(string sessionToken); + public Task DeleteUserSessions(string username); } \ No newline at end of file diff --git a/backend/src/Repositories/LoginRepository.cs b/backend/src/Repositories/LoginRepository.cs index 1e23da1..4a01cdb 100644 --- a/backend/src/Repositories/LoginRepository.cs +++ b/backend/src/Repositories/LoginRepository.cs @@ -78,8 +78,23 @@ public class LoginRepository : ILoginRepository public async Task DeleteLogin(string username) { + if ((await _context.Logins.CountAsync()) == 1) //if there is only one user registered, make sure you can't delete it + { + throw new UserNotDeletableException("There is only one user registered"); + } + var login = await GetUserLogin(username); + if (login.IsAdmin) //if they're trying to delete the only admin + { + var admins = _context.Logins.Where(u => u.IsAdmin); + + if ((await admins.CountAsync()) == 1) + { + throw new UserNotDeletableException("You can't delete the only admin user"); + } + } + _context.Logins.Remove(login); await _context.SaveChangesAsync(); diff --git a/backend/src/Repositories/SessionRepository.cs b/backend/src/Repositories/SessionRepository.cs index ace3618..cddb614 100644 --- a/backend/src/Repositories/SessionRepository.cs +++ b/backend/src/Repositories/SessionRepository.cs @@ -61,6 +61,18 @@ public class SessionRepository : ISessionRepository } } + public async Task DeleteUserSessions(string username) + { + var sessions = await _context.Sessions.Include(s => s.Login).Where(s => s.Login.Username == username).ToArrayAsync(); + + foreach (var session in sessions) + { + _context.Sessions.Remove(session); + } + + await _context.SaveChangesAsync(); + } + private static DateTime GenerateExpiryDate() { return DateTime.Now + TimeSpan.FromDays(30); //TODO: Make this configurable diff --git a/backend/src/Services/AuthService.cs b/backend/src/Services/AuthService.cs index eaff320..5da7e8b 100644 --- a/backend/src/Services/AuthService.cs +++ b/backend/src/Services/AuthService.cs @@ -8,11 +8,13 @@ public class AuthService : IAuthService { private ILoginRepository _loginRepo; private ISessionRepository _sessionRepo; + private readonly ILogger _logger; - public AuthService(ILoginRepository loginRepo, ISessionRepository sessionRepo) + public AuthService(ILoginRepository loginRepo, ISessionRepository sessionRepo, ILogger logger) { _loginRepo = loginRepo; _sessionRepo = sessionRepo; + _logger = logger; } public async Task AuthenticateUser(string username, string password) @@ -69,11 +71,13 @@ public class AuthService : IAuthService { var session = await _sessionRepo.GetUserSession(sessionToken); + if (session == null) { throw new SessionNotFoundException(); } + return session.Login.IsAdmin; } @@ -98,11 +102,23 @@ public class AuthService : IAuthService return new UserLoginDTO(login); } - public async Task CreateLogin(string username, string password) + public async Task GetLoginFromSession(string sessionToken) + { + var session = await _sessionRepo.GetUserSession(sessionToken); + + if (session == null) + { + throw new LoginFailedException(); + } + + return new UserLoginDTO(session.Login); + } + + public async Task CreateLogin(string username, string password, bool isAdmin) { var hashedPassword = BCrypt.Net.BCrypt.HashPassword(password); - var newLogin = await _loginRepo.CreateLogin(username, hashedPassword, false); + var newLogin = await _loginRepo.CreateLogin(username, hashedPassword, isAdmin); return new UserLoginDTO(newLogin); } @@ -137,6 +153,8 @@ public class AuthService : IAuthService { var deletedLogin = await _loginRepo.DeleteLogin(username); + await _sessionRepo.DeleteUserSessions(username); + return new UserLoginDTO(deletedLogin); } } \ No newline at end of file diff --git a/backend/src/Services/IAuthService.cs b/backend/src/Services/IAuthService.cs index a83e31f..8c48101 100644 --- a/backend/src/Services/IAuthService.cs +++ b/backend/src/Services/IAuthService.cs @@ -9,7 +9,8 @@ public interface IAuthService public Task IsAdmin(string sessionToken); public Task GetLogins(); public Task GetLogin(string username); - public Task CreateLogin(string username, string password); + public Task GetLoginFromSession(string sessionToken); + public Task CreateLogin(string username, string password, bool isAdmin); public Task UpdateLoginOwnPassword(string sessionToken, string newPassword, string oldPassword); public Task UpdateLoginPassword(string username, string newPassword); public Task DeleteLogin(string username); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 919614e..2bdbdb4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,13 +10,15 @@ "dependencies": { "axios": "^1.13.5", "bootstrap": "^5.3.8", + "immer": "^11.1.4", "js-cookie": "^3.0.5", "react": "^19.2.0", "react-bootstrap": "^2.10.10", "react-dom": "^19.2.0", "react-router-dom": "^7.13.0", "sass": "^1.97.3", - "scss": "^0.2.4" + "scss": "^0.2.4", + "use-immer": "^0.11.0" }, "devDependencies": { "@eslint/js": "^9.39.1", @@ -3222,6 +3224,17 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/immutable": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", @@ -4188,6 +4201,16 @@ "punycode": "^2.1.0" } }, + "node_modules/use-immer": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/use-immer/-/use-immer-0.11.0.tgz", + "integrity": "sha512-RNAqi3GqsWJ4bcCd4LMBgdzvPmTABam24DUaFiKfX9s3MSorNRz9RDZYJkllJoMHUxVLMDetwAuCDeyWNrp1yA==", + "license": "MIT", + "peerDependencies": { + "immer": ">=8.0.0", + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6ac24c3..f726350 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,13 +12,15 @@ "dependencies": { "axios": "^1.13.5", "bootstrap": "^5.3.8", + "immer": "^11.1.4", "js-cookie": "^3.0.5", "react": "^19.2.0", "react-bootstrap": "^2.10.10", "react-dom": "^19.2.0", "react-router-dom": "^7.13.0", "sass": "^1.97.3", - "scss": "^0.2.4" + "scss": "^0.2.4", + "use-immer": "^0.11.0" }, "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/frontend/src/Components/Navbar/Navbar.tsx b/frontend/src/Components/Navbar/Navbar.tsx index 578bd6f..464a089 100644 --- a/frontend/src/Components/Navbar/Navbar.tsx +++ b/frontend/src/Components/Navbar/Navbar.tsx @@ -1,15 +1,23 @@ import { Navbar as BsNavbar, Button, Container } from "react-bootstrap"; // import styles from "./Navbar.module.scss"; import Searchbar from "../Searchbar/Searchbar"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import Cookies from "js-cookie"; +import { GetCurrentUser, type Login } from "../../Lib/Auth"; const Navbar = () => { const [searchText, setSearchText] = useState(""); + const [currentUser, setCurrentUser] = useState(undefined); const navigate = useNavigate(); const session = Cookies.get("session"); + useEffect(() => { + GetCurrentUser().then(user => { + setCurrentUser(user); + }); + }, [session]); + function onLogout() { Cookies.remove("session"); navigate("/login"); @@ -20,8 +28,12 @@ const Navbar = () => { setSearchText(""); } + function onManageUser() { + navigate("/user"); + } + return ( - + JellyGlass @@ -33,7 +45,9 @@ const Navbar = () => { {session && } + {session && currentUser?.isAdmin && } + ) } diff --git a/frontend/src/Lib/Auth.ts b/frontend/src/Lib/Auth.ts index 5c0066a..1c99362 100644 --- a/frontend/src/Lib/Auth.ts +++ b/frontend/src/Lib/Auth.ts @@ -6,8 +6,51 @@ export interface Session { expiresOn: string; } +export interface Login { + username: string; + isAdmin: string; +} + export const logIn = async (username: string, password: string) => { const response = await axios.post(`${apiUrl}/auth/login`, `username=${encodeURI(username)}&password=${encodeURI(password)}`); return response.data; +} + +export const GetLoginFromSessonCookie = async () => { + const response = await axios.get(`${apiUrl}/auth`, { withCredentials: true }); + + return response.data; +} + +export const IsCurrentUserAdmin = async () => { + return (await GetLoginFromSessonCookie()).isAdmin; +} + +export const AddUser = async (username: string, password: string, isAdmin: boolean) => { + const response = await axios.post(`${apiUrl}/auth`, { username: username, password: password, isAdmin: isAdmin }, { withCredentials: true }); + + return response.data; +} + +export const DeleteUser = async (username: string) => { + const response = await axios.delete(`${apiUrl}/auth`, { data: { username: username }, withCredentials: true }); + + return response.data; +} + +export const GetUserList = async () => { + const response = await axios.get>(`${apiUrl}/auth/all`, { withCredentials: true }); + + return response.data; +} + +export const GetCurrentUser = async () => { + const resonse = await axios.get(`${apiUrl}/auth`, { withCredentials: true }); + + return resonse.data; +} + +export const ChangeCurrentUserPassword = async (oldPassword: string, newPassword: string) => { + await axios.put(`${apiUrl}/auth`, { password: oldPassword, newPassword: newPassword }, { withCredentials: true }); } \ No newline at end of file diff --git a/frontend/src/Lib/Servers.ts b/frontend/src/Lib/Servers.ts index 867194e..6920ace 100644 --- a/frontend/src/Lib/Servers.ts +++ b/frontend/src/Lib/Servers.ts @@ -11,10 +11,19 @@ export interface Server { } export const getServerList = async (): Promise> => { - console.log("fetching server list"); const response = await axios.get>(`${apiUrl}/servers/all`, { withCredentials: true }); - console.log(response); + return response.data; +} + +export const RemoveServer = async (serverUrl: string) => { + const response = await axios.delete(`${apiUrl}/servers`, { data: { Url: serverUrl }, withCredentials: true }); + + return response.data; +} + +export const AddServer = async (owner: string, url: string, apiToken: string) => { + const response = await axios.post(`${apiUrl}/servers`, { url: url, owner: owner, apiToken: apiToken }, { withCredentials: true }); return response.data; } \ No newline at end of file diff --git a/frontend/src/Pages/Admin/Admin.tsx b/frontend/src/Pages/Admin/Admin.tsx new file mode 100644 index 0000000..3835cc4 --- /dev/null +++ b/frontend/src/Pages/Admin/Admin.tsx @@ -0,0 +1,31 @@ +import { useEffect } from "react"; +import ServerManagement from "./ServerManagement/ServerManagement"; +import UserManagement from "./UserManagement/UserManagement"; +import { IsCurrentUserAdmin } from "../../Lib/Auth"; +import { useNavigate } from "react-router-dom"; + + +const Admin = () => { + const navigate = useNavigate(); + + + useEffect(() => { + IsCurrentUserAdmin().then(isAdmin => { + if (!isAdmin) { + navigate("/"); + } + }).catch(err => { + navigate("/"); + alert(err); + }) + }, [navigate]) + + return ( +
+ + +
+ ) +} + +export default Admin; \ No newline at end of file diff --git a/frontend/src/Pages/Admin/Management.module.scss b/frontend/src/Pages/Admin/Management.module.scss new file mode 100644 index 0000000..c5e4476 --- /dev/null +++ b/frontend/src/Pages/Admin/Management.module.scss @@ -0,0 +1,15 @@ +.form { + margin: auto; + margin-top: 20px; + max-width: 400px; +} + +.formButton { + margin-top: 10px; +} + +.table { + margin: auto; + margin-top: 20px; + max-width: 1800px; +} \ No newline at end of file diff --git a/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx b/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx new file mode 100644 index 0000000..17cbc48 --- /dev/null +++ b/frontend/src/Pages/Admin/ServerManagement/ServerManagement.tsx @@ -0,0 +1,114 @@ +import { useEffect } from "react"; +import { Button, Form, Spinner, Table } from "react-bootstrap"; +import { AddServer, getServerList, RemoveServer, type Server } from "../../../Lib/Servers"; +import { useImmer } from "use-immer"; +import styles from "../Management.module.scss"; + + +const ServerManagement = () => { + const [servers, setServers] = useImmer | undefined>(undefined); + + const [addServerInfo, setAddServerInfo] = useImmer({ + url: "", + owner: "", + apiToken: "" + }); + + useEffect(() => { + getServerList().then(serverList => { + setServers(serverList); + }).catch(err => { + setServers([]); + alert(err); + }) + }, [setServers]); + + const onServerRemove = (url: string) => { + RemoveServer(url).then(server => { + setServers(draft => { + const index = draft?.findIndex(s => s.url == server.url); + + if (index! > -1) { + draft?.splice(index!, 1); + } + }); + }).catch(err => { + alert(err); + }) + } + + const onServerAdd = () => { + AddServer(addServerInfo.owner, addServerInfo.url, addServerInfo.apiToken).then(result => { + if (result.errored) { + alert("Server was added, but is not working. Check the logs for details"); + } + + setServers(draft => { + if (!draft) { + draft = [result]; + } + else { + draft.push(result); + } + + setAddServerInfo(draft => { + draft.apiToken = ""; + draft.owner = ""; + draft.url = ""; + }); + }) + }).catch(err => { + alert(err); + }) + } + + return ( + <> +
+

Add Server

+ + Owner + setAddServerInfo(draft => { draft.owner = e.target.value })} value={addServerInfo.owner} /> + + + Url + setAddServerInfo(draft => { draft.url = e.target.value })} value={addServerInfo.url} /> + + + ApiToken + setAddServerInfo(draft => { draft.apiToken = e.target.value })} value={addServerInfo.apiToken} /> + + +
+ + + + + + + + + + + { + servers ? + servers?.map(server => { + return ( + + + + + + + ); + }) + : + + } + +
Server OwnerServer URLServer StatusRemove
{server.owner}{server.url}{!server.errored ? "Online" : "Errored"}
+ + ) +} + +export default ServerManagement; \ No newline at end of file diff --git a/frontend/src/Pages/Admin/UserManagement/UserManagement.tsx b/frontend/src/Pages/Admin/UserManagement/UserManagement.tsx new file mode 100644 index 0000000..023256c --- /dev/null +++ b/frontend/src/Pages/Admin/UserManagement/UserManagement.tsx @@ -0,0 +1,136 @@ +import { useImmer } from "use-immer"; +import { AddUser, DeleteUser, GetCurrentUser, GetUserList, type Login } from "../../../Lib/Auth"; +import { useEffect, useState } from "react"; +import { Button, Form, Spinner, Table } from "react-bootstrap"; +import styles from "../Management.module.scss"; + + +const UserManagement = () => { + const [currentUser, setCurrentUser] = useState(undefined); + const [users, setUsers] = useImmer | undefined>(undefined); + + const [addUserData, setAddUserData] = useImmer({ + username: "", + password: "", + isAdmin: false + }); + + useEffect(() => { + GetUserList().then(data => { + setUsers(data); + }).catch(err => { + alert(err); + setUsers([]); + }) + }, [setUsers]); + + useEffect(() => { + GetCurrentUser().then(data => { + setCurrentUser(data); + }).catch(err => { + alert(err); + }); + }, []); + + const onCreateUser = () => { + AddUser(addUserData.username, addUserData.password, addUserData.isAdmin).then(user => { + if (!users) { + setUsers([user]); + } + else { + setUsers(draft => { draft?.push(user) }); + } + + setAddUserData(draft => { + draft.username = ""; + draft.password = ""; + }); + }).catch(err => { + alert(err); + }) + } + + const isDeleteDisabled = (user: Login) => { + if (users?.length === 1) { + return true; + } + + if (user.username == currentUser?.username) { + return true; + } + + let adminCount = 0; + + users?.forEach(u => { + if (u.isAdmin) { + adminCount += 1; + } + }) + + if (adminCount === 1 && user.isAdmin) { + return true; + } + + return false; + } + + const onDeleteUser = (user: Login) => { + DeleteUser(user.username).then(user => { + setUsers(draft => { + const index = draft?.findIndex(u => u.username == user.username); + + if (index! > -1) { + draft?.splice(index!, 1); + } + }); + }).catch(err => { + alert(err); + }); + } + + return ( + <> +
+

Add User

+ + Username + setAddUserData(draft => { draft.username = e.target.value })} value={addUserData.username} /> + + + Password + setAddUserData(draft => { draft.password = e.target.value })} value={addUserData.password} /> + + + setAddUserData(draft => { draft.isAdmin = e.target.checked })} checked={addUserData.isAdmin} label="Is Admin" /> + + +
+ + + + + + + + + + {users ? + users.map(user => { + return ( + + + + + + ) + }) + : + + } + +
UsernameIs AdminDelete
{user.username}{user.isAdmin ? "Yes" : "No"}
+ + ) +} + +export default UserManagement; \ No newline at end of file diff --git a/frontend/src/Pages/ManageUser/ManageUser.module.scss b/frontend/src/Pages/ManageUser/ManageUser.module.scss new file mode 100644 index 0000000..23e5055 --- /dev/null +++ b/frontend/src/Pages/ManageUser/ManageUser.module.scss @@ -0,0 +1,5 @@ +.changePasswordForm { + margin: auto; + margin-top: 50px; + max-width: 400px; +} \ No newline at end of file diff --git a/frontend/src/Pages/ManageUser/ManageUser.tsx b/frontend/src/Pages/ManageUser/ManageUser.tsx new file mode 100644 index 0000000..fb0f2a5 --- /dev/null +++ b/frontend/src/Pages/ManageUser/ManageUser.tsx @@ -0,0 +1,68 @@ +import { Button, Form } from "react-bootstrap"; +import styles from "./ManageUser.module.scss"; +import { useImmer } from "use-immer"; +import { ChangeCurrentUserPassword } from "../../Lib/Auth"; +import { useNavigate } from "react-router-dom"; + + +const ManageUser = () => { + const navigate = useNavigate(); + + const [passwordInputs, setPasswordInputs] = useImmer({ + oldPassword: "", + newPassword: "", + repeatedNewPassword: "" + }); + + const onChangePassword = () => { + if (passwordInputs.newPassword === "" || passwordInputs.oldPassword === "" || passwordInputs.repeatedNewPassword === "") { + alert("Please enter your old and new passwords"); + return; + } + + if (passwordInputs.newPassword !== passwordInputs.repeatedNewPassword) { + alert("Passwords do not match!"); + return; + } + + + + ChangeCurrentUserPassword(passwordInputs.oldPassword, passwordInputs.newPassword).then(() => { + navigate("/"); + }).catch(err => { + if (err.response) { + if (err.response.status === 401) { + alert("Password was incorrect"); + return; + } + } + alert(err); + }); + } + + return ( +
+
+

Change Password

+ + Old Password + setPasswordInputs(draft => { draft.oldPassword = e.target.value })} value={passwordInputs.oldPassword} /> + + + New Password + setPasswordInputs(draft => { draft.newPassword = e.target.value })} value={passwordInputs.newPassword} /> + + + Repeat New Password + setPasswordInputs(draft => { draft.repeatedNewPassword = e.target.value })} value={passwordInputs.repeatedNewPassword} /> + + +
+
+ ) +} + +export default ManageUser; \ No newline at end of file diff --git a/frontend/src/Pages/NotFound/NotFound.tsx b/frontend/src/Pages/NotFound/NotFound.tsx new file mode 100644 index 0000000..2131320 --- /dev/null +++ b/frontend/src/Pages/NotFound/NotFound.tsx @@ -0,0 +1,17 @@ +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom" + + +const NotFound = () => { + const navigate = useNavigate(); + + useEffect(() => { + navigate("/"); + }) + + return ( + <> + ) +} + +export default NotFound; \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index e39a684..787be00 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -6,15 +6,21 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom' import Navbar from './Components/Navbar/Navbar.tsx' import Search from './Pages/Search/Search.tsx'; import Login from './Pages/Login/Login.tsx'; +import Admin from './Pages/Admin/Admin.tsx'; +import NotFound from './Pages/NotFound/NotFound.tsx'; +import ManageUser from './Pages/ManageUser/ManageUser.tsx'; createRoot(document.getElementById('root')!).render( - } /> + } /> } /> } /> + } /> + } /> + } /> ,