Compare commits

...

1 Commits

Author SHA1 Message Date
theis.gaedigk 07503ec079 added animations to borrow-system 2026-04-19 21:32:56 +02:00
5 changed files with 604 additions and 447 deletions
+325 -257
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@chakra-ui/react": "^3.28.0", "@chakra-ui/react": "^3.28.0",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@lottiefiles/dotlottie-react": "^0.19.0",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@tanstack/react-query": "^5.90.5", "@tanstack/react-query": "^5.90.5",
"i18next": "^25.6.0", "i18next": "^25.6.0",
+28
View File
@@ -0,0 +1,28 @@
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
export const unlockAnimation = () => {
return (
<DotLottieReact
src="https://lottie.host/f839baa1-9c64-44c4-9386-f0e4c87ab208/2Iw1m4k86d.lottie"
autoplay
/>
);
};
export const approvalAnimation = () => {
return (
<DotLottieReact
src="https://lottie.host/b7257009-9e3f-43e2-8112-a176f4696e4c/iQxxqAVOGX.lottie"
autoplay
/>
);
};
export const logoutAnimation = () => {
return (
<DotLottieReact
src="https://lottie.host/4975758c-de38-4d15-9f74-927709751d32/v8FtKpnD1y.lottie"
autoplay
/>
);
};
+24 -4
View File
@@ -18,6 +18,7 @@ import { borrowAbleItemsAtom } from "@/states/Atoms";
import { createLoan } from "@/utils/Fetcher"; import { createLoan } from "@/utils/Fetcher";
import { Header } from "@/components/Header"; import { Header } from "@/components/Header";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { approvalAnimation } from "@/components/dotLottie";
export interface User { export interface User {
username: string; username: string;
@@ -27,6 +28,8 @@ export interface User {
export const HomePage = () => { export const HomePage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [showAnimation, setShowAnimation] = useState(false);
const [borrowableItems, setBorrowableItems] = useAtom(borrowAbleItemsAtom); const [borrowableItems, setBorrowableItems] = useAtom(borrowAbleItemsAtom);
const [startDate, setStartDate] = useState(""); const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState(""); const [endDate, setEndDate] = useState("");
@@ -46,11 +49,26 @@ export const HomePage = () => {
setSelectedItems((prevSelected) => setSelectedItems((prevSelected) =>
prevSelected.includes(itemId) prevSelected.includes(itemId)
? prevSelected.filter((id) => id !== itemId) ? prevSelected.filter((id) => id !== itemId)
: [...prevSelected, itemId] : [...prevSelected, itemId],
); );
}; };
const showApprovalAnimation = (seconds: number) => {
const milliseconds = seconds * 1000;
setShowAnimation(true);
window.setTimeout(() => {
setShowAnimation(false);
}, milliseconds);
};
return ( return (
<>
{showAnimation && (
<div className="fixed inset-0 z-9999 flex items-center justify-center pointer-events-none">
<div>{approvalAnimation()}</div>
</div>
)}
<Container className="px-6 sm:px-8 pt-10"> <Container className="px-6 sm:px-8 pt-10">
<Header /> <Header />
{isMsg && ( {isMsg && (
@@ -158,7 +176,7 @@ export const HomePage = () => {
maxLength={MAX_CHARACTERS} maxLength={MAX_CHARACTERS}
onChange={(e) => { onChange={(e) => {
setNote( setNote(
e.currentTarget.value.slice(0, MAX_CHARACTERS) e.currentTarget.value.slice(0, MAX_CHARACTERS),
); );
}} }}
/> />
@@ -178,16 +196,17 @@ export const HomePage = () => {
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;
} }
showApprovalAnimation(3);
setMsgStatus("success"); setMsgStatus("success");
setMsgTitle(t("success")); setMsgTitle(t("success"));
setMsgDescription(t("loan-success")); setMsgDescription(t("loan-success"));
setIsMsg(true); setIsMsg(true);
} },
) )
} }
> >
@@ -196,5 +215,6 @@ export const HomePage = () => {
)} )}
</Stack> </Stack>
</Container> </Container>
</>
); );
}; };
+52 -12
View File
@@ -4,26 +4,47 @@ import { Button, Card, Field, Input, Stack } from "@chakra-ui/react";
import { setIsLoggedInAtom, triggerLogoutAtom } from "@/states/Atoms"; import { setIsLoggedInAtom, triggerLogoutAtom } from "@/states/Atoms";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { Navigate, useNavigate, useLocation } from "react-router-dom"; import { useNavigate, useLocation } 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 { API_BASE } from "@/config/api.config"; import { API_BASE } from "@/config/api.config";
import { unlockAnimation } from "@/components/dotLottie";
import { logoutAnimation } from "@/components/dotLottie";
export const LoginPage = () => { export const LoginPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [isLoggedIn, setIsLoggedIn] = useAtom(setIsLoggedInAtom); const [isLoggedIn, setIsLoggedIn] = useAtom(setIsLoggedInAtom);
const [triggerLogout, setTriggerLogout] = useAtom(triggerLogoutAtom); const [triggerLogout, setTriggerLogout] = useAtom(triggerLogoutAtom);
const [showAnimation, setShowAnimation] = useState(false);
const [showLogout, setShowLogout] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const from = location.state?.from?.pathname || "/"; const from = location.state?.from?.pathname || "/";
useEffect(() => { useEffect(() => {
if (isLoggedIn) { if (triggerLogout) {
navigate(from, { replace: true }); setShowLogout(true);
window.location.reload(); // if deleted, the user context is not updated in time window.setTimeout(() => {
setShowLogout(false);
}, 4500);
} }
}, [isLoggedIn, navigate, from]);
if (!isLoggedIn) return;
// Existing sessions should redirect immediately, fresh logins wait for animation.
if (!showAnimation) {
navigate(from, { replace: true });
return;
}
const timeoutId = window.setTimeout(() => {
navigate(from, { replace: true });
window.location.reload(); // keeps user context in sync after login
}, 3000);
return () => window.clearTimeout(timeoutId);
}, [isLoggedIn, showAnimation, navigate, from]);
const loginFnc = async (username: string, password: string) => { const loginFnc = async (username: string, password: string) => {
const response = await fetch(`${API_BASE}/api/users/login`, { const response = await fetch(`${API_BASE}/api/users/login`, {
@@ -42,6 +63,8 @@ export const LoginPage = () => {
}; };
} }
setShowAnimation(true);
Cookies.set("token", data.token); Cookies.set("token", data.token);
setIsLoggedIn(true); setIsLoggedIn(true);
return { success: true }; return { success: true };
@@ -62,14 +85,22 @@ export const LoginPage = () => {
return; return;
} }
setTriggerLogout(false); setTriggerLogout(false);
navigate(from, { replace: true });
}; };
if (isLoggedIn) {
return <Navigate to={from} replace />;
}
return ( return (
<>
{showAnimation && (
<div className="fixed inset-0 z-9999 flex items-center justify-center pointer-events-none">
<div>{unlockAnimation()}</div>
</div>
)}
{showLogout && (
<div className="fixed inset-0 z-9999 flex items-center justify-center pointer-events-none">
<div>{logoutAnimation()}</div>
</div>
)}
<div className="flex flex-1 items-center justify-center p-4"> <div className="flex flex-1 items-center justify-center p-4">
<form onSubmit={(e) => e.preventDefault()}> <form onSubmit={(e) => e.preventDefault()}>
<Card.Root maxW="sm"> <Card.Root maxW="sm">
@@ -97,9 +128,17 @@ export const LoginPage = () => {
</Card.Body> </Card.Body>
<Card.Footer justifyContent="flex-end"> <Card.Footer justifyContent="flex-end">
{isError && ( {isError && (
<MyAlert status="error" title={errorMsg} description={errorDsc} /> <MyAlert
status="error"
title={errorMsg}
description={errorDsc}
/>
)} )}
<Button type="submit" onClick={() => handleLogin()} variant="solid"> <Button
type="submit"
onClick={() => handleLogin()}
variant="solid"
>
Login Login
</Button> </Button>
</Card.Footer> </Card.Footer>
@@ -115,5 +154,6 @@ export const LoginPage = () => {
</Card.Root> </Card.Root>
</form> </form>
</div> </div>
</>
); );
}; };