From 97478be5d66a851e61dea8444708c28be8e174eb Mon Sep 17 00:00:00 2001 From: Danesh Date: Sun, 20 Sep 2026 18:58:33 +0200 Subject: [PATCH] added login page instead of modals --- src/backend/Program.cs | 2 +- src/frontend/package-lock.json | 8 +- src/frontend/package.json | 2 +- src/frontend/src/App.jsx | 6 +- src/frontend/src/Grid.jsx | 376 ++++++++++------------- src/frontend/src/api/users.js | 46 ++- src/frontend/src/auth/AuthProvider.jsx | 34 +- src/frontend/src/bookmarks/About.jsx | 22 -- src/frontend/src/bookmarks/Auth.jsx | 42 --- src/frontend/src/bookmarks/LoginPage.jsx | 93 ++++++ src/frontend/src/bookmarks/Logout.jsx | 22 ++ src/frontend/src/objects/AuthStatus.js | 78 +++++ 12 files changed, 417 insertions(+), 314 deletions(-) delete mode 100644 src/frontend/src/bookmarks/About.jsx delete mode 100644 src/frontend/src/bookmarks/Auth.jsx create mode 100644 src/frontend/src/bookmarks/LoginPage.jsx create mode 100644 src/frontend/src/bookmarks/Logout.jsx create mode 100644 src/frontend/src/objects/AuthStatus.js diff --git a/src/backend/Program.cs b/src/backend/Program.cs index bbb52ee..4df4468 100644 --- a/src/backend/Program.cs +++ b/src/backend/Program.cs @@ -20,7 +20,7 @@ public class Program Config.Initialize(currencyArg); var builder = WebApplication.CreateBuilder(args); - var jwtKey = builder.Configuration["Jwt:Key"] + var jwtKey = builder.Configuration["Jwt__Key"] ?? throw new InvalidOperationException("JWT key is not configured. Please set environment variable JWT__Key."); var key = Encoding.UTF8.GetBytes(jwtKey); builder.Services.AddSingleton(new JwtKeyProvider(jwtKey)); diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index f7d7c35..13c3909 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -8,7 +8,7 @@ "name": "platbik-fe", "version": "0.0.0", "dependencies": { - "@danneschs/libnik-ui": "^0.1.0", + "@danneschs/libnik-ui": "^0.1.1", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@fontsource/roboto": "^5.2.5", @@ -337,9 +337,9 @@ } }, "node_modules/@danneschs/libnik-ui": { - "version": "0.1.0", - "resolved": "https://npm.pkg.github.com/download/@danneschs/libnik-ui/0.1.0/90750e9618f9f4680eb117cbc5dac9f0b55ff42d", - "integrity": "sha512-Wb+cTHBvba6hrv3YWBI932cyslgCs6d7l+LW1q7otod/O5Q+tPm2tnBeb3c9HTXEZtZ92jBAzegodMX+S5GDfw==", + "version": "0.1.1", + "resolved": "https://npm.pkg.github.com/download/@danneschs/libnik-ui/0.1.1/6d4fe37afa53500a7117ac7da0f095082f5dc76b", + "integrity": "sha512-XdvxezqBF6X03SxJEDm5dbooTJR1/TYBMvHz3SjI8w3JaqVDZfdyZWDEcS/TWBdvJrjR6uS0QJj2jFg42XNYnw==", "peerDependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", diff --git a/src/frontend/package.json b/src/frontend/package.json index 2178ab2..9b7674a 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -12,7 +12,7 @@ "preview": "vite preview" }, "dependencies": { - "@danneschs/libnik-ui": "^0.1.0", + "@danneschs/libnik-ui": "^0.1.1", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", "@fontsource/roboto": "^5.2.5", diff --git a/src/frontend/src/App.jsx b/src/frontend/src/App.jsx index 3459c36..9bcae41 100644 --- a/src/frontend/src/App.jsx +++ b/src/frontend/src/App.jsx @@ -4,8 +4,7 @@ import Grid from '@main/Grid.jsx'; import AllPurchases from "@bookmarks/AllPurchases.jsx"; import MyPurchases from "@bookmarks/MyPurchases.jsx"; import MyCommitments from "@bookmarks/MyCommitments.jsx"; -import About from '@bookmarks/About.jsx'; -import Auth from '@bookmarks/Auth.jsx'; +import LoginPage from '@bookmarks/LoginPage.jsx'; /** * Provides all routes for the application. @@ -19,8 +18,7 @@ function App() { } /> } /> } /> - } /> - } /> + } /> } /> ); diff --git a/src/frontend/src/Grid.jsx b/src/frontend/src/Grid.jsx index b2f6bec..3b72538 100644 --- a/src/frontend/src/Grid.jsx +++ b/src/frontend/src/Grid.jsx @@ -3,7 +3,7 @@ import { useNavigate, useLocation } from "react-router-dom"; import { AuthContext } from "@auth/AuthContext"; -import Auth from "@bookmarks/Auth.jsx"; +import Auth from "@bookmarks/Logout.jsx"; import { GenericGrid } from "@danneschs/libnik-ui"; import FullPageSpinner from "@auth/FullPageSpinner"; import NotificationsDialog from "@dialogs/NotificationsDialog.jsx"; @@ -12,14 +12,14 @@ import { getAllNotificationsService } from "@api/transactionLogs"; import { getAllRegistrationRequestsService } from "@api/pendingUsers"; import { registerFromRequestService } from "@api/users"; import { getCurrencyFormatService } from "@api/configs"; -import { ToastBar, ConfirmationDialog, MessageDialog } from "@danneschs/libnik-ui"; +import { ToastBar, ConfirmationDialog } from "@danneschs/libnik-ui"; import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh"; /** Nav-link definitions for this app. */ const APP_NAV_LINKS = [ -{ label: "Všechny nákupy", to: "/vsechny-nakupy", key: "vsechny-nakupy" }, -{ label: "Moje nákupy", to: "/moje-nakupy", key: "moje-nakupy" }, -{ label: "Závazkové vztahy", to: "/zavazkove-vztahy", key: "zavazkove-vztahy" }, + { label: "Všechny nákupy", to: "/vsechny-nakupy", key: "vsechny-nakupy" }, + { label: "Moje nákupy", to: "/moje-nakupy", key: "moje-nakupy" }, + { label: "Závazkové vztahy", to: "/zavazkove-vztahy", key: "zavazkove-vztahy" }, ]; /** @@ -30,233 +30,187 @@ const APP_NAV_LINKS = [ * @param {ReactNode} children Main page content. */ function Grid({ title, children }) { -const [showAuth, setShowAuth] = useState(false); -const [showLogout, setShowLogout] = useState(false); -const [showNotifications, setShowNotifications] = useState(false); -const [myNotifications, setMyNotifications] = useState([]); -const location = useLocation(); -const [activeLink, setActiveLink] = useState(""); -const [showSuccessRegistrationRequest, setShowSuccessRegistrationRequest] = useState(false); -const [showFailedRegistrationRequest, setShowFailedRegistrationRequest] = useState(false); -const [showRegistrationRequests, setShowRegistrationRequests] = useState(false); -const [allRegistrationRequests, setAllRegistrationRequests] = useState([]); -const [showConfirmRegister, setShowConfirmRegister] = useState(false); -const [clickedRegisterRequestId, setClickedRegisterRequestId] = useState(0); -const [toastBarSettings, setToastBarSettings] = useState(null); + const [showLogout, setShowLogout] = useState(false); + const [showNotifications, setShowNotifications] = useState(false); + const [myNotifications, setMyNotifications] = useState([]); + const location = useLocation(); + const [activeLink, setActiveLink] = useState(""); + const [showRegistrationRequests, setShowRegistrationRequests] = useState(false); + const [allRegistrationRequests, setAllRegistrationRequests] = useState([]); + const [showConfirmRegister, setShowConfirmRegister] = useState(false); + const [clickedRegisterRequestId, setClickedRegisterRequestId] = useState(0); + const [toastBarSettings, setToastBarSettings] = useState(null); -const { currentUser, loading } = useContext(AuthContext); + const { currentUser, loading } = useContext(AuthContext); -const navigate = useNavigate(); + const navigate = useNavigate(); -const amIAdmin = currentUser?.roleCode === "admin"; + const amIAdmin = currentUser?.roleCode === "admin"; -// Update activeLink when route changes -useEffect(() => { -if (loading) return; -getCurrencyFormatService(); + // Update activeLink when route changes + useEffect(() => { + if (loading) return; + getCurrencyFormatService(); -setActiveLink(location.pathname.substring(1)); + setActiveLink(location.pathname.substring(1)); -const fetchNotifications = async () => { -const notifications = await getAllNotificationsService(localStorage.getItem("jwtToken")); -setMyNotifications(notifications || []); -}; + const fetchNotifications = async () => { + const notifications = await getAllNotificationsService(localStorage.getItem("jwtToken")); + setMyNotifications(notifications || []); + }; -const fetchAllRegistrationRequests = async () => { -const requests = await getAllRegistrationRequestsService(localStorage.getItem("jwtToken")); -setAllRegistrationRequests(requests || []); -}; + const fetchAllRegistrationRequests = async () => { + const requests = await getAllRegistrationRequestsService(localStorage.getItem("jwtToken")); + setAllRegistrationRequests(requests || []); + }; -if (!currentUser) { -setShowAuth(true); -} else { -fetchNotifications(); -if (currentUser.roleCode === "admin") { -fetchAllRegistrationRequests(); -} -} -}, [location.pathname, currentUser, loading]); + if (currentUser) { + fetchNotifications(); + if (currentUser.roleCode === "admin") { + fetchAllRegistrationRequests(); + } + } + }, [location.pathname, currentUser, loading]); -const handleOnSuccessRegistrationRequest = () => { -setShowAuth(false); -setShowSuccessRegistrationRequest(true); -}; + const handleClick = (link) => { + setActiveLink(link); + handleCloseNotifications(false); + }; -const handleOnFailedRegistrationRequest = () => { -setShowAuth(false); -setShowFailedRegistrationRequest(true); -}; + const handleRegisterFromRequest = async (id) => { + setShowConfirmRegister(true); + setClickedRegisterRequestId(id); + }; -const handleClick = (link) => { -setActiveLink(link); -handleCloseNotifications(false); -}; + const setSuccessToastBar = (message) => { + setToastBarSettings({ + message: message, + type: "success", + onClose: () => setToastBarSettings(null), + }); + }; -const handleRegisterFromRequest = async (id) => { -setShowConfirmRegister(true); -setClickedRegisterRequestId(id); -}; + const setErrorToastBar = (message) => { + setToastBarSettings({ + message: message, + type: "error", + onClose: () => setToastBarSettings(null), + }); + }; -const setSuccessToastBar = (message) => { -setToastBarSettings({ -message: message, -type: "success", -onClose: () => setToastBarSettings(null), -}); -}; + const registerFromRequest = async (id) => { + const token = localStorage.getItem("jwtToken"); + try { + const result = await registerFromRequestService(token, id); + if (result) { + setSuccessToastBar("Uživatel úspěšně zaregistrován."); + setAllRegistrationRequests((prevRequests) => prevRequests.filter((req) => req.id !== id)); + } + } catch (error) { + setErrorToastBar("Registrace uživatele se nezdařila."); + console.error("Failed to register from request:", error); + } + }; -const setErrorToastBar = (message) => { -setToastBarSettings({ -message: message, -type: "error", -onClose: () => setToastBarSettings(null), -}); -}; + const handleShowNotifications = async () => { + setShowNotifications(true); + }; -const registerFromRequest = async (id) => { -const token = localStorage.getItem("jwtToken"); -try { -const result = await registerFromRequestService(token, id); -if (result) { -setSuccessToastBar("Uživatel úspěšně zaregistrován."); -setAllRegistrationRequests((prevRequests) => prevRequests.filter((req) => req.id !== id)); -} -} catch (error) { -setErrorToastBar("Registrace uživatele se nezdařila."); -console.error("Failed to register from request:", error); -} -}; + const handleCloseNotifications = async (shouldMarkAsRead = true) => { + const token = localStorage.getItem("jwtToken"); + if (shouldMarkAsRead) { + try { + await Promise.all(myNotifications.map((element) => element.markAsRead(token))); + } catch (error) { + //setErrorToastBar("Nepodařilo se označit notifikace jako přečtené."); + console.error("Failed to mark notifications as read:", error); + } + } + setShowNotifications(false); + }; -const handleSetShowLoginWithActiveLink = (link) => { -setShowAuth(true); -setActiveLink(link); -}; + const getUnreadNotificationsCount = () => { + return myNotifications.filter((notification) => !notification.isRead).length; + }; -const handleShowNotifications = async () => { -setShowNotifications(true); -}; + const getUnreadRegisterRequestsCount = () => { + if (amIAdmin) { + return allRegistrationRequests.filter((request) => !request.isRead).length; + } + return 0; + }; -const handleCloseNotifications = async (shouldMarkAsRead = true) => { -const token = localStorage.getItem("jwtToken"); -if (shouldMarkAsRead) { -try { -await Promise.all(myNotifications.map((element) => element.markAsRead(token))); -} catch (error) { -//setErrorToastBar("Nepodařilo se označit notifikace jako přečtené."); -console.error("Failed to mark notifications as read:", error); -} -} -setShowNotifications(false); -}; + const handleCloseRegistrationRequests = async () => { + const token = localStorage.getItem("jwtToken"); + try { + await Promise.all(allRegistrationRequests.map((element) => element.markAsRead(token))); + } catch (error) { + //setErrorToastBar("Nepodařilo se označit žádosti o registraci jako přečtené."); + console.error("Failed to mark registration requests as read:", error); + } + setShowRegistrationRequests(false); + }; -const getUnreadNotificationsCount = () => { -return myNotifications.filter((notification) => !notification.isRead).length; -}; + const navLinks = APP_NAV_LINKS.map((link) => ({ + ...link, + isActive: activeLink === link.key, + onClick: () => handleClick(link.key), + })); -const getUnreadRegisterRequestsCount = () => { -if (amIAdmin) { -return allRegistrationRequests.filter((request) => !request.isRead).length; -} -return 0; -}; + const dialogs = ( + <> + {showNotifications && currentUser && ( + { + handleClick("zavazkove-vztahy"); + navigate("/zavazkove-vztahy"); + }} + /> + )} + {showLogout && currentUser && setShowLogout(false)} />} + {showRegistrationRequests && currentUser && amIAdmin && ( + + )} + {showConfirmRegister && ( + { + registerFromRequest(clickedRegisterRequestId); + setShowConfirmRegister(false); + }} + onCancel={() => setShowConfirmRegister(false)} + /> + )} + {toastBarSettings && } + + ); -const handleCloseRegistrationRequests = async () => { -const token = localStorage.getItem("jwtToken"); -try { -await Promise.all(allRegistrationRequests.map((element) => element.markAsRead(token))); -} catch (error) { -//setErrorToastBar("Nepodařilo se označit žádosti o registraci jako přečtené."); -console.error("Failed to mark registration requests as read:", error); -} -setShowRegistrationRequests(false); -}; - -// Build nav links: onClick is auth-aware — logged-in users navigate normally, -// guests are shown the login dialog first. -const navLinks = APP_NAV_LINKS.map((link) => ({ -...link, -isActive: activeLink === link.key, -onClick: currentUser -? () => handleClick(link.key) -: () => handleSetShowLoginWithActiveLink(link.key), -})); - -const dialogs = ( -<> -{showNotifications && currentUser && ( - { -handleClick("zavazkove-vztahy"); -navigate("/zavazkove-vztahy"); -}} -/> -)} -{showAuth && !currentUser && ( - setShowAuth(false)} -/> -)} -{showLogout && currentUser && setShowLogout(false)} />} -{showSuccessRegistrationRequest && ( - setShowSuccessRegistrationRequest(false)} -/> -)} -{showFailedRegistrationRequest && ( - setShowFailedRegistrationRequest(false)} -/> -)} -{showRegistrationRequests && currentUser && amIAdmin && ( - -)} -{showConfirmRegister && ( - { -registerFromRequest(clickedRegisterRequestId); -setShowConfirmRegister(false); -}} -onCancel={() => setShowConfirmRegister(false)} -/> -)} -{toastBarSettings && } - -); - -return ( - setShowAuth(true)} -onLogoutClick={() => setShowLogout(true)} -notificationCount={getUnreadNotificationsCount()} -onNotificationsClick={handleShowNotifications} -adminBadgeCount={amIAdmin ? getUnreadRegisterRequestsCount() : 0} -onAdminClick={amIAdmin ? () => setShowRegistrationRequests(true) : undefined} -AdminIcon={amIAdmin ? AutoFixHighIcon : null} -loading={loading} -loadingFallback={} -dialogs={dialogs} -> -{children} - -); + return ( + navigate("/prihlaseni", { state: { from: location.pathname } })} + onLogoutClick={() => setShowLogout(true)} + notificationCount={getUnreadNotificationsCount()} + onNotificationsClick={handleShowNotifications} + adminBadgeCount={amIAdmin ? getUnreadRegisterRequestsCount() : 0} + onAdminClick={amIAdmin ? () => setShowRegistrationRequests(true) : undefined} + AdminIcon={amIAdmin ? AutoFixHighIcon : null} + loading={loading} + loadingFallback={} + dialogs={dialogs}> + {children} + + ); } export default Grid; diff --git a/src/frontend/src/api/users.js b/src/frontend/src/api/users.js index 7959830..d93ee54 100644 --- a/src/frontend/src/api/users.js +++ b/src/frontend/src/api/users.js @@ -1,4 +1,5 @@ import { URL } from "@api/url.js"; +import { LoginStatus, RegistrationStatus } from "@objects/AuthStatus.js"; export async function loginService(email, password) { // Try to login the user @@ -10,14 +11,31 @@ export async function loginService(email, password) { body: JSON.stringify({ email, password }), }); - if (!response.ok) { - return null; // Invalid credentials or user not found + const loginStatus = new LoginStatus(); + + // server is not responding + if (response.status >= 500) { + loginStatus.setNotResponding(); + return loginStatus; + } else if (response.status === 400 || response.status === 401) { + //400 - invalid format + //401 - invalid credentials + loginStatus.setInvalidCredentials(); + return loginStatus; } + // checking the user info, if exists const data = await response.json(); const token = data.token; - // Get user information after successful login - return getCurrentUserService(token); + const me = await getCurrentUserService(token); // Get user information after successful login + + if (me === null) { + loginStatus.setInvalidCredentials(); + } else { + loginStatus.addUser(me.token, me.user); + } + + return loginStatus; } export async function getCurrentUserService(token) { @@ -63,12 +81,24 @@ export async function requestRegistrationService(newUserDto) { body: JSON.stringify(newUserDto), }); - if (!response.ok) { - const errorData = await response.json(); - return { success: false, errors: errorData }; // Registration failed + const registrationStatus = new RegistrationStatus(); + + // server is not responding + if (response.status >= 500) { + registrationStatus.setNotResponding(); + return registrationStatus; + } else if (response.status === 400) { + //400 - invalid format + const errors = response.json(); + registrationStatus.setErrors(errors); + } else if (response.status === 409) { + //409 - user or registration request with this email already exists + registrationStatus.setAlreadyExists(); + } else if (response.ok) { + registrationStatus.setOk(); // Registration request successful } - return { success: true }; // Registration successful + return registrationStatus; } export async function getAllUsersService(token) { diff --git a/src/frontend/src/auth/AuthProvider.jsx b/src/frontend/src/auth/AuthProvider.jsx index 1bbc39e..2280f3b 100644 --- a/src/frontend/src/auth/AuthProvider.jsx +++ b/src/frontend/src/auth/AuthProvider.jsx @@ -3,6 +3,7 @@ import { AuthContext } from "@auth/AuthContext"; import { loginService, getCurrentUserService, requestRegistrationService } from "@api/users"; import RegisterUserDto from "@objects/RegisterUserDto"; +import { LoginStatus, RegistrationStatus, LoginMessage } from "@objects/AuthStatus"; /** * AuthProvider component to provide authentication context for children components @@ -23,7 +24,7 @@ function AuthProvider({ children }) { const fetchUser = async () => { try { - const { user: loggedUser } = await getCurrentUserService(tokenFromStorage); + const loggedUser = await getCurrentUserService(tokenFromStorage); // Invalid token or user not found if (!loggedUser) { @@ -45,21 +46,19 @@ function AuthProvider({ children }) { const login = async (email, password) => { try { - const credentials = await loginService(email, password); - const { token, user } = credentials || {}; + const loginStatus = await loginService(email, password); - // Invalid credentials or user not found - if (!credentials || !token || !user) { - //console.error("Invalid login response:", { token, user }); + if (!loginStatus.success) { setLogoutState(); - return false; + return loginStatus; + } else { + setLoggedInState(loginStatus.token, loginStatus.user); + return loginStatus; } - setLoggedInState(token, user); - return true; } catch (err) { - console.error("Login failed:", err); setLogoutState(); - return false; + console.error("Login failed:", err); + return new LoginMessage(false, LoginStatus.getUnexpectedMessage()); } }; @@ -79,18 +78,12 @@ function AuthProvider({ children }) { const register = async (name, surname, accountNumber, email, password) => { const newUserDto = new RegisterUserDto(name, surname, accountNumber, email, password); - let registerServiceResponse = null; try { - registerServiceResponse = await requestRegistrationService(newUserDto); - if (registerServiceResponse.success) { - // Optionally, you can auto-login after registration - //const loginSuccess = await login(email, password); - return { success: true }; // Request for registration was successful - } + return await requestRegistrationService(newUserDto); } catch (err) { console.error("Registration failed:", err); + return new RegistrationStatus(); // unexpected error } - return registerServiceResponse; }; return ( @@ -101,8 +94,7 @@ function AuthProvider({ children }) { login, logout, register, - }} - > + }}> {children} ); diff --git a/src/frontend/src/bookmarks/About.jsx b/src/frontend/src/bookmarks/About.jsx deleted file mode 100644 index 7c501af..0000000 --- a/src/frontend/src/bookmarks/About.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import { GenericDialog, GenericButton } from "@danneschs/libnik-ui"; - -/** - * About dialog component - * @param {*} props (onClose) - * @returns - */ -function About(props) { - const { onClose } = props; - - return ( - } - dialogActions={} - /> - ); -} - -export default About; diff --git a/src/frontend/src/bookmarks/Auth.jsx b/src/frontend/src/bookmarks/Auth.jsx deleted file mode 100644 index 0beb2f4..0000000 --- a/src/frontend/src/bookmarks/Auth.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useContext, useState } from "react"; - -import { AuthContext } from "@auth/AuthContext.js"; -import { LoginForm, RegisterForm, LogoutForm } from "@danneschs/libnik-ui"; - -/** - * Auth component for handling user authentication - * @param {*} onFail - Callback function for failed authentication - * @param {*} onSuccess - Callback function for successful authentication - * @param {*} onClose - Callback function for closing the authentication dialog - * @returns - */ -function Auth({ onFail, onSuccess, onClose }) { - const [showRegister, setShowRegister] = useState(false); - const { currentUser, login, logout, register } = useContext(AuthContext); - - const handleLogin = async (email, password) => { - const success = await login(email, password); - if (success) { - onClose(); - return true; - } - return false; - }; - - const handleLogout = () => { - logout(); - onClose(); - }; - - if (showRegister) { - return ; - } - - if (currentUser) { - return ; - } - - return ; -} - -export default Auth; diff --git a/src/frontend/src/bookmarks/LoginPage.jsx b/src/frontend/src/bookmarks/LoginPage.jsx new file mode 100644 index 0000000..4c1becc --- /dev/null +++ b/src/frontend/src/bookmarks/LoginPage.jsx @@ -0,0 +1,93 @@ +import { useContext, useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; + +import { AuthContext } from "@auth/AuthContext.js"; +import { AuthFormPage, MessageDialog } from "@danneschs/libnik-ui"; + +import { LoginMessage } from "@objects/AuthStatus"; + +const DEFAULT_REDIRECT = "/vsechny-nakupy"; + +/** + * Login page + * @description uses libnik-ui's AuthFormPage component for the Login/Register page form + */ +function LoginPage() { + const { login, register } = useContext(AuthContext); + const navigate = useNavigate(); + const location = useLocation(); + const from = location.state?.from ?? DEFAULT_REDIRECT; + + const [registrationStatus, setRegistrationStatus] = useState(null); + const [registeringUserAlreadyExists, setRegisteringUserAlreadyExists] = useState(false); + const [registrationSuccessful, setRegistrationSuccessful] = useState(false); + + const [notResponding, setNotResponding] = useState(false); + const [notRespondingMessage, setNotRespondingMessage] = useState(null); + + const onLoginSuccess = () => { + navigate(from, { replace: true }); + }; + + async function handleLogin(email, password) { + const loginStatus = await login(email, password); + if (!loginStatus.isServerResponding) { + // server is not responding + setNotResponding(true); + setNotRespondingMessage(loginStatus.message); + return new LoginMessage(false, loginStatus.message); + } else if (!loginStatus.success) { + // server is responding, but something went wrong + return new LoginMessage(false, loginStatus.message); + } else { + return new LoginMessage(true, loginStatus.message); + } + } + + async function handleRegister(name, surname, accountNumber, email, password) { + const registrationStatus = await register(name, surname, accountNumber, email, password); + + setRegistrationStatus({ ...registrationStatus }); + + if (!registrationStatus.isServerResponding) { + // server is not responding + setNotResponding(true); + setNotRespondingMessage(registrationStatus.message); + } else if (registrationStatus.alreadyExists) { + // user already exists -- form + modal handle + setRegisteringUserAlreadyExists(true); + } else if (registrationStatus.success) { + // successful registration -- modal handles + setRegistrationSuccessful(true); + } + + // either invalid data format -- form handles + return registrationStatus; + } + + return ( + <> + + + {notResponding && ( + setNotResponding(false)} /> + )} + + {registeringUserAlreadyExists && ( + setRegisteringUserAlreadyExists(false)} + /> + )} + {registrationSuccessful && ( + setRegistrationSuccessful(false)} + /> + )} + + ); +} +export default LoginPage; diff --git a/src/frontend/src/bookmarks/Logout.jsx b/src/frontend/src/bookmarks/Logout.jsx new file mode 100644 index 0000000..9eb756a --- /dev/null +++ b/src/frontend/src/bookmarks/Logout.jsx @@ -0,0 +1,22 @@ +import { useContext } from "react"; + +import { AuthContext } from "@auth/AuthContext.js"; +import { LogoutFormDialog } from "@danneschs/libnik-ui"; + +/** + * Auth component for handling logout confirmation + * @param {*} onClose - Callback function for closing the logout confirmation dialog + * @returns + */ +function Auth({ onClose }) { + const { logout } = useContext(AuthContext); + + const handleLogout = () => { + logout(); + onClose(); + }; + + return ; +} + +export default Auth; diff --git a/src/frontend/src/objects/AuthStatus.js b/src/frontend/src/objects/AuthStatus.js new file mode 100644 index 0000000..96675a3 --- /dev/null +++ b/src/frontend/src/objects/AuthStatus.js @@ -0,0 +1,78 @@ +export class LoginStatus { + constructor() { + this.success = false; + this.isServerResponding = false; + this.message = LoginStatus.getUnexpectedMessage(); + this.token = null; + this.user = null; + } + + setNotResponding() { + this.success = false; + this.isServerResponding = false; + this.message = "Server neodpovídá. Zkuste to prosím později."; + } + + setInvalidCredentials() { + this.success = false; + this.isServerResponding = true; + this.message = "Neplatné přihlašovací údaje."; + } + + addUser(token, user) { + this.success = true; + this.isServerResponding = true; + this.message = "Přihlášení bylo úspěšné."; + this.token = token; + this.user = user; + } + + static getUnexpectedMessage() { + return "Nastala neočekávaná chyba. Zkuste to znovu později."; + } +} + +export class LoginMessage { + constructor(loggedIn, message) { + this.loggedIn = loggedIn; + this.message = message; + } +} + +export class RegistrationStatus { + constructor() { + this.success = false; + this.isServerResponding = false; + this.message = "Nastala neočekávaná chyba. Zkuste to znovu později."; + this.alreadyExists = false; + this.errors = null; + } + + setNotResponding() { + this.success = false; + this.isServerResponding = false; + this.alreadyExists = false; + this.message = "Server neodpovídá. Zkuste to prosím později."; + } + + setErrors(errors) { + this.success = false; + this.alreadyExists = false; + this.isServerResponding = true; + this.errors = errors; + } + + setAlreadyExists() { + this.success = false; + this.isServerResponding = true; + this.alreadyExists = true; + this.message = "Uživatel s tímto emailem již existuje."; + } + + setOk() { + this.success = true; + this.isServerResponding = true; + this.alreadyExists = false; + this.message = "Požadavek na registraci byl odeslán. Nyní se čeká na schválení administrátorem."; + } +}