Merge pull request #1 from Danneschs/feature/login

Feature/login
This commit is contained in:
Danneschs 2026-09-20 19:00:55 +02:00 committed by GitHub
commit 5e992ad1af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 694 additions and 1560 deletions

View File

@ -20,7 +20,7 @@ public class Program
Config.Initialize(currencyArg); Config.Initialize(currencyArg);
var builder = WebApplication.CreateBuilder(args); 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."); ?? throw new InvalidOperationException("JWT key is not configured. Please set environment variable JWT__Key.");
var key = Encoding.UTF8.GetBytes(jwtKey); var key = Encoding.UTF8.GetBytes(jwtKey);
builder.Services.AddSingleton<IJwtKeyProvider>(new JwtKeyProvider(jwtKey)); builder.Services.AddSingleton<IJwtKeyProvider>(new JwtKeyProvider(jwtKey));

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@danneschs/libnik-ui": "^0.1.0", "@danneschs/libnik-ui": "^0.1.1",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@fontsource/roboto": "^5.2.5", "@fontsource/roboto": "^5.2.5",
@ -25,6 +25,7 @@
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-router-dom": "^7.4.0", "react-router-dom": "^7.4.0",
"uuid": "^14.0.2",
"zustand": "^5.0.3" "zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {

View File

@ -4,8 +4,7 @@ import Grid from '@main/Grid.jsx';
import AllPurchases from "@bookmarks/AllPurchases.jsx"; import AllPurchases from "@bookmarks/AllPurchases.jsx";
import MyPurchases from "@bookmarks/MyPurchases.jsx"; import MyPurchases from "@bookmarks/MyPurchases.jsx";
import MyCommitments from "@bookmarks/MyCommitments.jsx"; import MyCommitments from "@bookmarks/MyCommitments.jsx";
import About from '@bookmarks/About.jsx'; import LoginPage from '@bookmarks/LoginPage.jsx';
import Auth from '@bookmarks/Auth.jsx';
/** /**
* Provides all routes for the application. * Provides all routes for the application.
@ -19,8 +18,7 @@ function App() {
<Route path="/vsechny-nakupy" element={<Grid title="Seznam všech nákupů"><AllPurchases /></Grid>} /> <Route path="/vsechny-nakupy" element={<Grid title="Seznam všech nákupů"><AllPurchases /></Grid>} />
<Route path="/moje-nakupy" element={<Grid title="Seznam nákupů uživatele"><MyPurchases /></Grid>} /> <Route path="/moje-nakupy" element={<Grid title="Seznam nákupů uživatele"><MyPurchases /></Grid>} />
<Route path="/zavazkove-vztahy" element={<Grid title="Seznam závazkových vztahů uživatele"><MyCommitments /></Grid>} /> <Route path="/zavazkove-vztahy" element={<Grid title="Seznam závazkových vztahů uživatele"><MyCommitments /></Grid>} />
<Route path="/o-aplikaci" element={<About />} /> <Route path="/prihlaseni" element={<Grid title="Přihlášení"><LoginPage /></Grid>} />
<Route path="/prihlaseni" element={<Auth />} />
<Route path="*" element={<Navigate to="/vsechny-nakupy" />} /> <Route path="*" element={<Navigate to="/vsechny-nakupy" />} />
</Routes> </Routes>
); );

View File

@ -3,7 +3,7 @@ import { useNavigate, useLocation } from "react-router-dom";
import { AuthContext } from "@auth/AuthContext"; import { AuthContext } from "@auth/AuthContext";
import Auth from "@bookmarks/Auth.jsx"; import Auth from "@bookmarks/Logout.jsx";
import { GenericGrid } from "@danneschs/libnik-ui"; import { GenericGrid } from "@danneschs/libnik-ui";
import FullPageSpinner from "@auth/FullPageSpinner"; import FullPageSpinner from "@auth/FullPageSpinner";
import NotificationsDialog from "@dialogs/NotificationsDialog.jsx"; import NotificationsDialog from "@dialogs/NotificationsDialog.jsx";
@ -12,14 +12,14 @@ import { getAllNotificationsService } from "@api/transactionLogs";
import { getAllRegistrationRequestsService } from "@api/pendingUsers"; import { getAllRegistrationRequestsService } from "@api/pendingUsers";
import { registerFromRequestService } from "@api/users"; import { registerFromRequestService } from "@api/users";
import { getCurrencyFormatService } from "@api/configs"; 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"; import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
/** Nav-link definitions for this app. */ /** Nav-link definitions for this app. */
const APP_NAV_LINKS = [ const APP_NAV_LINKS = [
{ label: "Všechny nákupy", to: "/vsechny-nakupy", key: "vsechny-nakupy" }, { label: "Všechny nákupy", to: "/vsechny-nakupy", key: "vsechny-nakupy" },
{ label: "Moje nákupy", to: "/moje-nakupy", key: "moje-nakupy" }, { label: "Moje nákupy", to: "/moje-nakupy", key: "moje-nakupy" },
{ label: "Závazkové vztahy", to: "/zavazkove-vztahy", key: "zavazkove-vztahy" }, { label: "Závazkové vztahy", to: "/zavazkove-vztahy", key: "zavazkove-vztahy" },
]; ];
/** /**
@ -30,233 +30,187 @@ const APP_NAV_LINKS = [
* @param {ReactNode} children Main page content. * @param {ReactNode} children Main page content.
*/ */
function Grid({ title, children }) { function Grid({ title, children }) {
const [showAuth, setShowAuth] = useState(false); const [showLogout, setShowLogout] = useState(false);
const [showLogout, setShowLogout] = useState(false); const [showNotifications, setShowNotifications] = useState(false);
const [showNotifications, setShowNotifications] = useState(false); const [myNotifications, setMyNotifications] = useState([]);
const [myNotifications, setMyNotifications] = useState([]); const location = useLocation();
const location = useLocation(); const [activeLink, setActiveLink] = useState("");
const [activeLink, setActiveLink] = useState(""); const [showRegistrationRequests, setShowRegistrationRequests] = useState(false);
const [showSuccessRegistrationRequest, setShowSuccessRegistrationRequest] = useState(false); const [allRegistrationRequests, setAllRegistrationRequests] = useState([]);
const [showFailedRegistrationRequest, setShowFailedRegistrationRequest] = useState(false); const [showConfirmRegister, setShowConfirmRegister] = useState(false);
const [showRegistrationRequests, setShowRegistrationRequests] = useState(false); const [clickedRegisterRequestId, setClickedRegisterRequestId] = useState(0);
const [allRegistrationRequests, setAllRegistrationRequests] = useState([]); const [toastBarSettings, setToastBarSettings] = useState(null);
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 // Update activeLink when route changes
useEffect(() => { useEffect(() => {
if (loading) return; if (loading) return;
getCurrencyFormatService(); getCurrencyFormatService();
setActiveLink(location.pathname.substring(1)); setActiveLink(location.pathname.substring(1));
const fetchNotifications = async () => { const fetchNotifications = async () => {
const notifications = await getAllNotificationsService(localStorage.getItem("jwtToken")); const notifications = await getAllNotificationsService(localStorage.getItem("jwtToken"));
setMyNotifications(notifications || []); setMyNotifications(notifications || []);
}; };
const fetchAllRegistrationRequests = async () => { const fetchAllRegistrationRequests = async () => {
const requests = await getAllRegistrationRequestsService(localStorage.getItem("jwtToken")); const requests = await getAllRegistrationRequestsService(localStorage.getItem("jwtToken"));
setAllRegistrationRequests(requests || []); setAllRegistrationRequests(requests || []);
}; };
if (!currentUser) { if (currentUser) {
setShowAuth(true); fetchNotifications();
} else { if (currentUser.roleCode === "admin") {
fetchNotifications(); fetchAllRegistrationRequests();
if (currentUser.roleCode === "admin") { }
fetchAllRegistrationRequests(); }
} }, [location.pathname, currentUser, loading]);
}
}, [location.pathname, currentUser, loading]);
const handleOnSuccessRegistrationRequest = () => { const handleClick = (link) => {
setShowAuth(false); setActiveLink(link);
setShowSuccessRegistrationRequest(true); handleCloseNotifications(false);
}; };
const handleOnFailedRegistrationRequest = () => { const handleRegisterFromRequest = async (id) => {
setShowAuth(false); setShowConfirmRegister(true);
setShowFailedRegistrationRequest(true); setClickedRegisterRequestId(id);
}; };
const handleClick = (link) => { const setSuccessToastBar = (message) => {
setActiveLink(link); setToastBarSettings({
handleCloseNotifications(false); message: message,
}; type: "success",
onClose: () => setToastBarSettings(null),
});
};
const handleRegisterFromRequest = async (id) => { const setErrorToastBar = (message) => {
setShowConfirmRegister(true); setToastBarSettings({
setClickedRegisterRequestId(id); message: message,
}; type: "error",
onClose: () => setToastBarSettings(null),
});
};
const setSuccessToastBar = (message) => { const registerFromRequest = async (id) => {
setToastBarSettings({ const token = localStorage.getItem("jwtToken");
message: message, try {
type: "success", const result = await registerFromRequestService(token, id);
onClose: () => setToastBarSettings(null), 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) => { const handleShowNotifications = async () => {
setToastBarSettings({ setShowNotifications(true);
message: message, };
type: "error",
onClose: () => setToastBarSettings(null),
});
};
const registerFromRequest = async (id) => { const handleCloseNotifications = async (shouldMarkAsRead = true) => {
const token = localStorage.getItem("jwtToken"); const token = localStorage.getItem("jwtToken");
try { if (shouldMarkAsRead) {
const result = await registerFromRequestService(token, id); try {
if (result) { await Promise.all(myNotifications.map((element) => element.markAsRead(token)));
setSuccessToastBar("Uživatel úspěšně zaregistrován."); } catch (error) {
setAllRegistrationRequests((prevRequests) => prevRequests.filter((req) => req.id !== id)); //setErrorToastBar("Nepodařilo se označit notifikace jako přečtené.");
} console.error("Failed to mark notifications as read:", error);
} catch (error) { }
setErrorToastBar("Registrace uživatele se nezdařila."); }
console.error("Failed to register from request:", error); setShowNotifications(false);
} };
};
const handleSetShowLoginWithActiveLink = (link) => { const getUnreadNotificationsCount = () => {
setShowAuth(true); return myNotifications.filter((notification) => !notification.isRead).length;
setActiveLink(link); };
};
const handleShowNotifications = async () => { const getUnreadRegisterRequestsCount = () => {
setShowNotifications(true); if (amIAdmin) {
}; return allRegistrationRequests.filter((request) => !request.isRead).length;
}
return 0;
};
const handleCloseNotifications = async (shouldMarkAsRead = true) => { const handleCloseRegistrationRequests = async () => {
const token = localStorage.getItem("jwtToken"); const token = localStorage.getItem("jwtToken");
if (shouldMarkAsRead) { try {
try { await Promise.all(allRegistrationRequests.map((element) => element.markAsRead(token)));
await Promise.all(myNotifications.map((element) => element.markAsRead(token))); } catch (error) {
} catch (error) { //setErrorToastBar("Nepodařilo se označit žádosti o registraci jako přečtené.");
//setErrorToastBar("Nepodařilo se označit notifikace jako přečtené."); console.error("Failed to mark registration requests as read:", error);
console.error("Failed to mark notifications as read:", error); }
} setShowRegistrationRequests(false);
} };
setShowNotifications(false);
};
const getUnreadNotificationsCount = () => { const navLinks = APP_NAV_LINKS.map((link) => ({
return myNotifications.filter((notification) => !notification.isRead).length; ...link,
}; isActive: activeLink === link.key,
onClick: () => handleClick(link.key),
}));
const getUnreadRegisterRequestsCount = () => { const dialogs = (
if (amIAdmin) { <>
return allRegistrationRequests.filter((request) => !request.isRead).length; {showNotifications && currentUser && (
} <NotificationsDialog
return 0; onClose={handleCloseNotifications}
}; notifications={myNotifications}
handleNavigateToCommitments={() => {
handleClick("zavazkove-vztahy");
navigate("/zavazkove-vztahy");
}}
/>
)}
{showLogout && currentUser && <Auth onClose={() => setShowLogout(false)} />}
{showRegistrationRequests && currentUser && amIAdmin && (
<RegistrationRequestsDialog
onClose={handleCloseRegistrationRequests}
registrationRequests={allRegistrationRequests}
handleRegisterFromRequest={handleRegisterFromRequest}
/>
)}
{showConfirmRegister && (
<ConfirmationDialog
title="Potvrzení registrace"
content="Opravdu chcete zaregistrovat tohoto uživatele?"
onConfirm={() => {
registerFromRequest(clickedRegisterRequestId);
setShowConfirmRegister(false);
}}
onCancel={() => setShowConfirmRegister(false)}
/>
)}
{toastBarSettings && <ToastBar {...toastBarSettings} />}
</>
);
const handleCloseRegistrationRequests = async () => { return (
const token = localStorage.getItem("jwtToken"); <GenericGrid
try { logo="PLATBÍK"
await Promise.all(allRegistrationRequests.map((element) => element.markAsRead(token))); title={title}
} catch (error) { navLinks={navLinks}
//setErrorToastBar("Nepodařilo se označit žádosti o registraci jako přečtené."); userDisplayName={currentUser ? `${currentUser.name} ${currentUser.surname}` : null}
console.error("Failed to mark registration requests as read:", error); onLoginClick={() => navigate("/prihlaseni", { state: { from: location.pathname } })}
} onLogoutClick={() => setShowLogout(true)}
setShowRegistrationRequests(false); notificationCount={getUnreadNotificationsCount()}
}; onNotificationsClick={handleShowNotifications}
adminBadgeCount={amIAdmin ? getUnreadRegisterRequestsCount() : 0}
// Build nav links: onClick is auth-aware logged-in users navigate normally, onAdminClick={amIAdmin ? () => setShowRegistrationRequests(true) : undefined}
// guests are shown the login dialog first. AdminIcon={amIAdmin ? AutoFixHighIcon : null}
const navLinks = APP_NAV_LINKS.map((link) => ({ loading={loading}
...link, loadingFallback={<FullPageSpinner variant="determinate" value={80} />}
isActive: activeLink === link.key, dialogs={dialogs}>
onClick: currentUser {children}
? () => handleClick(link.key) </GenericGrid>
: () => handleSetShowLoginWithActiveLink(link.key), );
}));
const dialogs = (
<>
{showNotifications && currentUser && (
<NotificationsDialog
onClose={handleCloseNotifications}
notifications={myNotifications}
handleNavigateToCommitments={() => {
handleClick("zavazkove-vztahy");
navigate("/zavazkove-vztahy");
}}
/>
)}
{showAuth && !currentUser && (
<Auth
onFail={handleOnFailedRegistrationRequest}
onSuccess={handleOnSuccessRegistrationRequest}
onClose={() => setShowAuth(false)}
/>
)}
{showLogout && currentUser && <Auth onClose={() => setShowLogout(false)} />}
{showSuccessRegistrationRequest && (
<MessageDialog
title="Úspěch"
message="Požadavek na registraci byl odeslán. Nyní se čeká na schválení administrátorem."
onClose={() => setShowSuccessRegistrationRequest(false)}
/>
)}
{showFailedRegistrationRequest && (
<MessageDialog
title="Neočekávaná chyba"
message="Požadavek na registraci se nezdařil. Zkuste to prosím znovu."
onClose={() => setShowFailedRegistrationRequest(false)}
/>
)}
{showRegistrationRequests && currentUser && amIAdmin && (
<RegistrationRequestsDialog
onClose={handleCloseRegistrationRequests}
registrationRequests={allRegistrationRequests}
handleRegisterFromRequest={handleRegisterFromRequest}
/>
)}
{showConfirmRegister && (
<ConfirmationDialog
title="Potvrzení registrace"
content="Opravdu chcete zaregistrovat tohoto uživatele?"
onConfirm={() => {
registerFromRequest(clickedRegisterRequestId);
setShowConfirmRegister(false);
}}
onCancel={() => setShowConfirmRegister(false)}
/>
)}
{toastBarSettings && <ToastBar {...toastBarSettings} />}
</>
);
return (
<GenericGrid
logo="PLATBÍK"
title={title}
navLinks={navLinks}
userDisplayName={currentUser ? `${currentUser.name} ${currentUser.surname}` : null}
onLoginClick={() => 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={<FullPageSpinner variant="determinate" value={80} />}
dialogs={dialogs}
>
{children}
</GenericGrid>
);
} }
export default Grid; export default Grid;

View File

@ -1,4 +1,5 @@
import { URL } from "@api/url.js"; import { URL } from "@api/url.js";
import { LoginStatus, RegistrationStatus } from "@objects/AuthStatus.js";
export async function loginService(email, password) { export async function loginService(email, password) {
// Try to login the user // Try to login the user
@ -10,14 +11,31 @@ export async function loginService(email, password) {
body: JSON.stringify({ email, password }), body: JSON.stringify({ email, password }),
}); });
if (!response.ok) { const loginStatus = new LoginStatus();
return null; // Invalid credentials or user not found
// 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 data = await response.json();
const token = data.token; const token = data.token;
// Get user information after successful login const me = await getCurrentUserService(token); // Get user information after successful login
return getCurrentUserService(token);
if (me === null) {
loginStatus.setInvalidCredentials();
} else {
loginStatus.addUser(me.token, me.user);
}
return loginStatus;
} }
export async function getCurrentUserService(token) { export async function getCurrentUserService(token) {
@ -63,12 +81,24 @@ export async function requestRegistrationService(newUserDto) {
body: JSON.stringify(newUserDto), body: JSON.stringify(newUserDto),
}); });
if (!response.ok) { const registrationStatus = new RegistrationStatus();
const errorData = await response.json();
return { success: false, errors: errorData }; // Registration failed // 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) { export async function getAllUsersService(token) {

View File

@ -3,6 +3,7 @@ import { AuthContext } from "@auth/AuthContext";
import { loginService, getCurrentUserService, requestRegistrationService } from "@api/users"; import { loginService, getCurrentUserService, requestRegistrationService } from "@api/users";
import RegisterUserDto from "@objects/RegisterUserDto"; import RegisterUserDto from "@objects/RegisterUserDto";
import { LoginStatus, RegistrationStatus, LoginMessage } from "@objects/AuthStatus";
/** /**
* AuthProvider component to provide authentication context for children components * AuthProvider component to provide authentication context for children components
@ -23,7 +24,7 @@ function AuthProvider({ children }) {
const fetchUser = async () => { const fetchUser = async () => {
try { try {
const { user: loggedUser } = await getCurrentUserService(tokenFromStorage); const loggedUser = await getCurrentUserService(tokenFromStorage);
// Invalid token or user not found // Invalid token or user not found
if (!loggedUser) { if (!loggedUser) {
@ -45,21 +46,19 @@ function AuthProvider({ children }) {
const login = async (email, password) => { const login = async (email, password) => {
try { try {
const credentials = await loginService(email, password); const loginStatus = await loginService(email, password);
const { token, user } = credentials || {};
// Invalid credentials or user not found if (!loginStatus.success) {
if (!credentials || !token || !user) {
//console.error("Invalid login response:", { token, user });
setLogoutState(); setLogoutState();
return false; return loginStatus;
} else {
setLoggedInState(loginStatus.token, loginStatus.user);
return loginStatus;
} }
setLoggedInState(token, user);
return true;
} catch (err) { } catch (err) {
console.error("Login failed:", err);
setLogoutState(); 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 register = async (name, surname, accountNumber, email, password) => {
const newUserDto = new RegisterUserDto(name, surname, accountNumber, email, password); const newUserDto = new RegisterUserDto(name, surname, accountNumber, email, password);
let registerServiceResponse = null;
try { try {
registerServiceResponse = await requestRegistrationService(newUserDto); return 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
}
} catch (err) { } catch (err) {
console.error("Registration failed:", err); console.error("Registration failed:", err);
return new RegistrationStatus(); // unexpected error
} }
return registerServiceResponse;
}; };
return ( return (
@ -101,8 +94,7 @@ function AuthProvider({ children }) {
login, login,
logout, logout,
register, register,
}} }}>
>
{children} {children}
</AuthContext.Provider> </AuthContext.Provider>
); );

View File

@ -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 (
<GenericDialog
sxSize="small"
onClose={onClose}
dialogTitle="O aplikaci"
dialogContent={<></>}
dialogActions={<GenericButton triggerOnEnter={true} onClick={onClose} name="OK" />}
/>
);
}
export default About;

View File

@ -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 <RegisterForm onFail={onFail} onSuccess={onSuccess} onClose={onClose} handleRegister={register} />;
}
if (currentUser) {
return <LogoutForm onClose={onClose} handleLogout={handleLogout} />;
}
return <LoginForm onClose={onClose} handleLogin={handleLogin} setShowRegister={setShowRegister} />;
}
export default Auth;

View File

@ -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 (
<>
<AuthFormPage handleLogin={handleLogin} handleRegister={handleRegister} onLoginSuccess={onLoginSuccess} />
{notResponding && (
<MessageDialog title="Chyba" message={notRespondingMessage} onClose={() => setNotResponding(false)} />
)}
{registeringUserAlreadyExists && (
<MessageDialog
title="Chyba"
message={registrationStatus.message}
onClose={() => setRegisteringUserAlreadyExists(false)}
/>
)}
{registrationSuccessful && (
<MessageDialog
title="Úspěch"
message={registrationStatus.message}
onClose={() => setRegistrationSuccessful(false)}
/>
)}
</>
);
}
export default LoginPage;

View File

@ -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 <LogoutFormDialog onClose={onClose} handleLogout={handleLogout} />;
}
export default Auth;

View File

@ -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.";
}
}