6 Commits

10 changed files with 163 additions and 26 deletions

View File

@@ -11,6 +11,9 @@ import { UserContext, type User } from "./states/Context";
import { triggerLogoutAtom } from "@/states/Atoms"; import { triggerLogoutAtom } from "@/states/Atoms";
import { MyLoansPage } from "./pages/MyLoansPage"; import { MyLoansPage } from "./pages/MyLoansPage";
import Landingpage from "./pages/Landingpage"; import Landingpage from "./pages/Landingpage";
import { changeLanguage } from "i18next";
import { Box, Flex } from "@chakra-ui/react";
import { Footer } from "./components/Footer";
const API_BASE = const API_BASE =
(import.meta as any).env?.VITE_BACKEND_URL || (import.meta as any).env?.VITE_BACKEND_URL ||
@@ -44,24 +47,42 @@ function App() {
}; };
verifyToken(); verifyToken();
} }
// set initial language
if (!Cookies.get("language")) {
const getBrowserLanguage = () => {
const lang = navigator.languages?.[0] || navigator.language || "en";
return lang.split("-")[0].toLowerCase();
};
changeLanguage(getBrowserLanguage());
Cookies.set("language", getBrowserLanguage());
}
if (Cookies.get("language")) {
changeLanguage(Cookies.get("language") || "en");
}
}, []); }, []);
return ( return (
<> <Flex direction="column" minH="100vh">
<UserContext.Provider value={user}> <Box as="main" flex="1">
<BrowserRouter> <UserContext.Provider value={user}>
<Routes> <BrowserRouter>
<Route element={<ProtectedRoutes />}> <Routes>
<Route path="/" element={<HomePage />} /> <Route element={<ProtectedRoutes />}>
<Route path="/my-loans" element={<MyLoansPage />} /> <Route path="/" element={<HomePage />} />
<Route path="/landing" element={<Landingpage />} /> <Route path="/my-loans" element={<MyLoansPage />} />
</Route> <Route path="/landing" element={<Landingpage />} />
</Route>
<Route path="/login" element={<LoginPage />} /> <Route path="/login" element={<LoginPage />} />
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
</UserContext.Provider> </UserContext.Provider>
</> </Box>
<Footer />
</Flex>
); );
} }

View File

@@ -0,0 +1,19 @@
import { Box } from "@chakra-ui/react";
export const Footer = () => {
return (
<Box
as="footer"
py={4}
textAlign="center"
position="fixed"
bottom="0"
left="0"
right="0"
>
Made with by Theis Gaedigk - Year 2019 at MCS-Bochum
<br />
v2.0
</Box>
);
};

View File

@@ -26,6 +26,7 @@ import {
LogOut, LogOut,
CalendarPlus, CalendarPlus,
MoreVertical, MoreVertical,
Flag,
} from "lucide-react"; } from "lucide-react";
import { useUserContext } from "@/states/Context"; import { useUserContext } from "@/states/Context";
import { useState } from "react"; import { useState } from "react";
@@ -102,6 +103,7 @@ export const Header = () => {
Cookies.remove("token"); Cookies.remove("token");
setIsLoggedIn(false); setIsLoggedIn(false);
setTriggerLogout(true); setTriggerLogout(true);
navigate("/login", { replace: true });
}; };
return ( return (
@@ -166,6 +168,21 @@ export const Header = () => {
</HStack> </HStack>
} }
/> />
<Menu.Item
value="change-language"
onSelect={() => {
const currentLang = Cookies.get("language") || "en";
const newLang = currentLang === "en" ? "de" : "en";
Cookies.set("language", newLang);
window.location.reload();
}}
children={
<HStack gap={3}>
<LifeBuoy size={16} />
<Text as="span">{t("change-language")}</Text>
</HStack>
}
/>
<Menu.Item <Menu.Item
value="help" value="help"
onSelect={() => onSelect={() =>
@@ -276,6 +293,21 @@ export const Header = () => {
</HStack> </HStack>
</Button> </Button>
<Button
variant="ghost"
onClick={() => {
const currentLang = Cookies.get("language") || "en";
const newLang = currentLang === "en" ? "de" : "en";
Cookies.set("language", newLang);
window.location.reload();
}}
>
<HStack gap={2}>
<Flag size={18} />
<Text as="span">{t("change-language")}</Text>
</HStack>
</Button>
<a <a
href="https://git.the1s.de/Matthias-Claudius-Schule/borrow-system/wiki" href="https://git.the1s.de/Matthias-Claudius-Schule/borrow-system/wiki"
target="_blank" target="_blank"

View File

@@ -5,6 +5,10 @@ import "./index.css";
import App from "./App.tsx"; import App from "./App.tsx";
import i18n from "./utils/i18n"; // import i18n configuration DO NOT REMOVE import i18n from "./utils/i18n"; // import i18n configuration DO NOT REMOVE
// code below is to avoid linter error for unused import
let i18nUnused = i18n;
console.log(i18nUnused);
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(
<StrictMode> <StrictMode>
<Provider> <Provider>

View File

@@ -46,7 +46,7 @@ export const HomePage = () => {
}; };
return ( return (
<Container maxW="7xl" className="px-6 sm:px-8 pt-10"> <Container className="px-6 sm:px-8 pt-10">
<Header /> <Header />
{isMsg && ( {isMsg && (
<MyAlert <MyAlert
@@ -92,9 +92,7 @@ export const HomePage = () => {
if (response && response.status === "error") { if (response && response.status === "error") {
setMsgStatus("error"); setMsgStatus("error");
setMsgTitle(response.title || t("error")); setMsgTitle(response.title || t("error"));
setMsgDescription( setMsgDescription(response.description || t("unknown-error"));
response.description || t("unknown-error")
);
setIsMsg(true); setIsMsg(true);
return; return;
} }
@@ -147,15 +145,13 @@ export const HomePage = () => {
if (response.status === "error") { if (response.status === "error") {
setMsgStatus("error"); setMsgStatus("error");
setMsgTitle(response.title || t("error")); setMsgTitle(response.title || t("error"));
setMsgDescription( setMsgDescription(response.description || t("unknown-error"));
response.description || t("unknown-error")
);
setIsMsg(true); setIsMsg(true);
return; return;
} }
setMsgStatus("success"); setMsgStatus("success");
setMsgTitle(t("success")); setMsgTitle(t("success"));
setMsgDescription(t("loan-success")) ; setMsgDescription(t("loan-success"));
setIsMsg(true); setIsMsg(true);
}) })
} }

View File

@@ -7,6 +7,7 @@ import Cookies from "js-cookie";
import { Navigate, useNavigate } from "react-router-dom"; import { Navigate, useNavigate } from "react-router-dom";
import { PasswordInput } from "@/components/ui/password-input"; import { PasswordInput } from "@/components/ui/password-input";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Footer } from "@/components/Footer";
const API_BASE = const API_BASE =
(import.meta as any).env?.VITE_BACKEND_URL || (import.meta as any).env?.VITE_BACKEND_URL ||
@@ -23,6 +24,7 @@ export const LoginPage = () => {
useEffect(() => { useEffect(() => {
if (isLoggedIn) { if (isLoggedIn) {
navigate("/", { replace: true }); navigate("/", { replace: true });
window.location.reload(); // Mit Tobais besprechen, ob das so bleiben soll
} }
}, [isLoggedIn, navigate]); }, [isLoggedIn, navigate]);
@@ -115,6 +117,7 @@ export const LoginPage = () => {
</Card.Footer> </Card.Footer>
</Card.Root> </Card.Root>
</form> </form>
<Footer />
</div> </div>
); );
}; };

View File

@@ -117,7 +117,7 @@ export const MyLoansPage = () => {
return ( return (
<> <>
<Container maxW="7xl" className="px-6 sm:px-8 pt-10"> <Container className="px-6 sm:px-8 pt-10">
<Header /> <Header />
{isMsg && ( {isMsg && (
<MyAlert <MyAlert

View File

@@ -1,5 +1,6 @@
import i18n from "i18next"; import i18n from "i18next";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import Cookies from "js-cookie";
import enLang from "./locales/en/en.json"; import enLang from "./locales/en/en.json";
import deLang from "./locales/de/de.json"; import deLang from "./locales/de/de.json";
@@ -21,7 +22,7 @@ i18n
.init({ .init({
resources, resources,
fallbackLng: "en", // use en if detected lng is not available fallbackLng: "en", // use en if detected lng is not available
lng: "de", // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources lng: Cookies.get("language") || "en", // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources
// you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage // you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage
// if you're using a language detector, do not define the lng option // if you're using a language detector, do not define the lng option

View File

@@ -58,5 +58,6 @@
"sure-delete-loan-0": "Möchten Sie die Ausleihe mit dem ", "sure-delete-loan-0": "Möchten Sie die Ausleihe mit dem ",
"sure-delete-loan-1": " Ausleihcode wirklich löschen?", "sure-delete-loan-1": " Ausleihcode wirklich löschen?",
"sure-delete-loan-2": "Für den Admin bleibt sie weiterhin sichtbar.", "sure-delete-loan-2": "Für den Admin bleibt sie weiterhin sichtbar.",
"delete": "Löschen" "delete": "Löschen",
"change-language": "Sprache ändern"
} }

View File

@@ -1,3 +1,63 @@
{ {
"greeting": "Welcome back, " "greeting": "Welcome back, ",
"err_pw_change": "Password change failed",
"pw_mismatch": "Please check your input",
"pw_success": "Password changed successfully",
"pw_success_desc": "Your password was changed successfully.",
"create-loan": "Create loan",
"my-loans": "My loans",
"change-password": "Change password",
"help": "Help",
"source-code": "Source code",
"logout": "Log out",
"old-password": "Old password",
"new-password": "New password",
"confirm-password": "Repeat new password",
"cancel": "Cancel",
"save": "Save",
"start-date": "Start date",
"end-date": "End date",
"missing-fields": "Missing fields",
"missing-fields-desc": "Please provide start and end date.",
"error": "Error",
"unknown-error": "Unknown frontend error",
"get-borrowable-items": "Fetch available items",
"loading": "Loading...",
"item": "Item",
"success": "Success",
"loan-success": "Loan created successfully",
"error-by-loading": "Error while loading",
"unexpected-date-format_loan": "Unexpected date format received. (Loans)",
"unexpected-date-format_device": "Unexpected date format received. (Device)",
"error-fetching-loans": "The loans could not be retrieved.",
"all-loans": "All loans",
"username": "Username",
"rented-items": "Borrowed items",
"return-date": "Return date",
"take-date": "Collection date",
"no-loans-found": "No loans found.",
"all-devices": "All devices",
"rent-role": "Loan role",
"legend": "Legend",
"in-locker": "In locker",
"not-in-locker": "Not in locker",
"login-failed": "Login failed",
"login": "Log in",
"enter-credentials": "Please enter your credentials below.",
"password": "Password",
"logout-success": "Successfully logged out",
"logout-success-desc": "You have been logged out successfully.",
"network-error-fetching-loans": "Network error while loading loans.",
"error-deleting-loan": "The loan could not be deleted.",
"loan-deletion-success": "The loan was deleted successfully.",
"network-error-deleting-loan": "Network error while deleting the loan.",
"loan-code": "Loan code",
"devices": "Devices",
"actions": "Actions",
"sure": "Are you sure?",
"sure-delete-loan-0": "Do you really want to delete the loan with the ",
"sure-delete-loan-1": " loan code?",
"sure-delete-loan-2": "It will remain visible to the admin.",
"delete": "Delete",
"change-language": "Change language"
} }