mirror of
https://github.com/Danneschs/platbik.git
synced 2026-09-23 00:04:35 +02:00
commit
5e992ad1af
@ -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));
|
||||||
|
|||||||
1530
src/frontend/package-lock.json
generated
1530
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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": {
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
93
src/frontend/src/bookmarks/LoginPage.jsx
Normal file
93
src/frontend/src/bookmarks/LoginPage.jsx
Normal 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;
|
||||||
22
src/frontend/src/bookmarks/Logout.jsx
Normal file
22
src/frontend/src/bookmarks/Logout.jsx
Normal 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;
|
||||||
78
src/frontend/src/objects/AuthStatus.js
Normal file
78
src/frontend/src/objects/AuthStatus.js
Normal 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.";
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user