Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 195f270064 | |||
| 3d592c5c76 | |||
| 08104d32db | |||
| cc7c024892 | |||
| 3eb452aeab | |||
| f46a654184 | |||
| 863409aed9 | |||
| 052137a697 | |||
| 2f3583ccd0 | |||
| 9da72cc5bf | |||
| c633627b7c | |||
| 5259c41b13 | |||
| 3d9e3814fe | |||
| b44edb2b1d | |||
| a72fabc0a0 | |||
| 1406f28f86 | |||
| 38d1091e9b | |||
| f82efecb8c | |||
| 1f12bc8839 | |||
| f19750f6f3 | |||
| 808b3fd5c4 | |||
| 0891598eb9 | |||
| 39ff02f2e7 | |||
| cc67fb4f85 | |||
| 75ff4aadc1 | |||
| 6f998d07c1 | |||
| f2bb326040 | |||
| 8c701db900 | |||
| d1664338a6 | |||
| 1a2624cd9e | |||
| a138190cc6 | |||
| 993e0cd74b | |||
| dab004a7b6 | |||
| d039336f39 | |||
| 4c781e9325 | |||
| 451e6b3646 |
@@ -117,8 +117,3 @@ ToDo.txt
|
||||
|
||||
# only in development branch
|
||||
next-env.d.ts
|
||||
|
||||
# psd files from footage
|
||||
footage/*.psd
|
||||
|
||||
icon/
|
||||
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 428 KiB |
|
Before Width: | Height: | Size: 416 KiB |
@@ -2,11 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
href="/icon_borrow-system-frontend_dark.png"
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Ausleihsystem</title>
|
||||
</head>
|
||||
|
||||
@@ -14,7 +14,7 @@ server {
|
||||
}
|
||||
|
||||
location /backend/ {
|
||||
proxy_pass http://borrow_system-backend_v2:8004/;
|
||||
proxy_pass http://borrow_system-backend_v2:8102/;
|
||||
}
|
||||
|
||||
location ~* \.(?:js|mjs|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "admin",
|
||||
"private": true,
|
||||
"version": "v2.1.2 (dev)",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -12,7 +12,6 @@
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^3.28.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@lottiefiles/dotlottie-react": "^0.19.0",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"i18next": "^25.6.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shapes-icon lucide-shapes"><path d="M8.3 10a.7.7 0 0 1-.626-1.079L11.4 3a.7.7 0 0 1 1.198-.043L16.3 8.9a.7.7 0 0 1-.572 1.1Z"/><rect x="3" y="14" width="7" height="7" rx="1"/><circle cx="17.5" cy="17.5" r="3.5"/></svg>
|
||||
|
After Width: | Height: | Size: 420 B |
@@ -1,28 +0,0 @@
|
||||
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
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,15 +1,23 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import { ChakraProvider, defaultSystem } from "@chakra-ui/react"
|
||||
import {
|
||||
ColorModeProvider,
|
||||
type ColorModeProviderProps,
|
||||
} from "./color-mode"
|
||||
import { ChakraProvider, defaultSystem } from "@chakra-ui/react";
|
||||
import * as React from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import { ColorModeProvider as ThemeColorModeProvider } from "./color-mode";
|
||||
|
||||
export function Provider(props: ColorModeProviderProps) {
|
||||
export interface ColorModeProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function ColorModeProvider({ children }: ColorModeProviderProps) {
|
||||
// Wrap children with the real color-mode provider
|
||||
return <ThemeColorModeProvider>{children}</ThemeColorModeProvider>;
|
||||
}
|
||||
|
||||
export function Provider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<ChakraProvider value={defaultSystem}>
|
||||
<ColorModeProvider {...props} />
|
||||
<ColorModeProvider>{children}</ColorModeProvider>
|
||||
</ChakraProvider>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import { borrowAbleItemsAtom } from "@/states/Atoms";
|
||||
import { createLoan } from "@/utils/Fetcher";
|
||||
import { Header } from "@/components/Header";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { approvalAnimation } from "@/components/dotLottie";
|
||||
|
||||
export interface User {
|
||||
username: string;
|
||||
@@ -28,8 +27,6 @@ export interface User {
|
||||
export const HomePage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [showAnimation, setShowAnimation] = useState(false);
|
||||
|
||||
const [borrowableItems, setBorrowableItems] = useAtom(borrowAbleItemsAtom);
|
||||
const [startDate, setStartDate] = useState("");
|
||||
const [endDate, setEndDate] = useState("");
|
||||
@@ -49,26 +46,11 @@ export const HomePage = () => {
|
||||
setSelectedItems((prevSelected) =>
|
||||
prevSelected.includes(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 (
|
||||
<>
|
||||
{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">
|
||||
<Header />
|
||||
{isMsg && (
|
||||
@@ -176,7 +158,7 @@ export const HomePage = () => {
|
||||
maxLength={MAX_CHARACTERS}
|
||||
onChange={(e) => {
|
||||
setNote(
|
||||
e.currentTarget.value.slice(0, MAX_CHARACTERS),
|
||||
e.currentTarget.value.slice(0, MAX_CHARACTERS)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
@@ -196,17 +178,16 @@ export const HomePage = () => {
|
||||
setMsgStatus("error");
|
||||
setMsgTitle(response.title || t("error"));
|
||||
setMsgDescription(
|
||||
response.description || t("unknown-error"),
|
||||
response.description || t("unknown-error")
|
||||
);
|
||||
setIsMsg(true);
|
||||
return;
|
||||
}
|
||||
showApprovalAnimation(3);
|
||||
setMsgStatus("success");
|
||||
setMsgTitle(t("success"));
|
||||
setMsgDescription(t("loan-success"));
|
||||
setIsMsg(true);
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -215,6 +196,5 @@ export const HomePage = () => {
|
||||
)}
|
||||
</Stack>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,47 +4,26 @@ import { Button, Card, Field, Input, Stack } from "@chakra-ui/react";
|
||||
import { setIsLoggedInAtom, triggerLogoutAtom } from "@/states/Atoms";
|
||||
import { useAtom } from "jotai";
|
||||
import Cookies from "js-cookie";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { Navigate, useNavigate, useLocation } from "react-router-dom";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { API_BASE } from "@/config/api.config";
|
||||
import { unlockAnimation } from "@/components/dotLottie";
|
||||
import { logoutAnimation } from "@/components/dotLottie";
|
||||
|
||||
export const LoginPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isLoggedIn, setIsLoggedIn] = useAtom(setIsLoggedInAtom);
|
||||
const [triggerLogout, setTriggerLogout] = useAtom(triggerLogoutAtom);
|
||||
const [showAnimation, setShowAnimation] = useState(false);
|
||||
const [showLogout, setShowLogout] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const from = location.state?.from?.pathname || "/";
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerLogout) {
|
||||
setShowLogout(true);
|
||||
window.setTimeout(() => {
|
||||
setShowLogout(false);
|
||||
}, 4500);
|
||||
}
|
||||
|
||||
if (!isLoggedIn) return;
|
||||
|
||||
// Existing sessions should redirect immediately, fresh logins wait for animation.
|
||||
if (!showAnimation) {
|
||||
if (isLoggedIn) {
|
||||
navigate(from, { replace: true });
|
||||
return;
|
||||
window.location.reload(); // if deleted, the user context is not updated in time
|
||||
}
|
||||
|
||||
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]);
|
||||
}, [isLoggedIn, navigate, from]);
|
||||
|
||||
const loginFnc = async (username: string, password: string) => {
|
||||
const response = await fetch(`${API_BASE}/api/users/login`, {
|
||||
@@ -63,8 +42,6 @@ export const LoginPage = () => {
|
||||
};
|
||||
}
|
||||
|
||||
setShowAnimation(true);
|
||||
|
||||
Cookies.set("token", data.token);
|
||||
setIsLoggedIn(true);
|
||||
return { success: true };
|
||||
@@ -85,22 +62,14 @@ export const LoginPage = () => {
|
||||
return;
|
||||
}
|
||||
setTriggerLogout(false);
|
||||
navigate(from, { replace: true });
|
||||
};
|
||||
|
||||
if (isLoggedIn) {
|
||||
return <Navigate to={from} replace />;
|
||||
}
|
||||
|
||||
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">
|
||||
<form onSubmit={(e) => e.preventDefault()}>
|
||||
<Card.Root maxW="sm">
|
||||
@@ -128,17 +97,9 @@ export const LoginPage = () => {
|
||||
</Card.Body>
|
||||
<Card.Footer justifyContent="flex-end">
|
||||
{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
|
||||
</Button>
|
||||
</Card.Footer>
|
||||
@@ -154,6 +115,5 @@ export const LoginPage = () => {
|
||||
</Card.Root>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import path from "node:path";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react(), svgr(), tailwindcss(), tsconfigPaths()],
|
||||
plugins: [tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "src"),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
port: 8001,
|
||||
watch: {
|
||||
usePolling: true,
|
||||
allowedHosts: ["insta.the1s.de"],
|
||||
port: 8101,
|
||||
watch: { usePolling: true },
|
||||
hmr: {
|
||||
host: "insta.the1s.de",
|
||||
port: 8101,
|
||||
protocol: "wss",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
Copyright (c) 2026 Theis Gaedigk
|
||||
|
||||
All rights reserved.
|
||||
|
||||
This source code is not to be copied, modified, or distributed in any form
|
||||
without explicit written permission from the author.
|
||||
@@ -14,7 +14,7 @@ server {
|
||||
}
|
||||
|
||||
location /backend/ {
|
||||
proxy_pass http://borrow_system-backend_v2:8004/;
|
||||
proxy_pass http://borrow_system-backend_v2:8102/;
|
||||
}
|
||||
|
||||
location ~* \.(?:js|mjs|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "admin",
|
||||
"private": true,
|
||||
"version": "v1.3.2 (dev)",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -8,9 +8,13 @@ export default defineConfig({
|
||||
plugins: [react(), svgr(), tailwindcss(), tsconfigPaths()],
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
port: 8003,
|
||||
watch: {
|
||||
usePolling: true,
|
||||
allowedHosts: ["admin.insta.the1s.de"],
|
||||
port: 8103,
|
||||
watch: { usePolling: true },
|
||||
hmr: {
|
||||
host: "admin.insta.the1s.de",
|
||||
port: 8103,
|
||||
protocol: "wss",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"backend-info": {
|
||||
"version": "v2.1.1 (dev)"
|
||||
"version": "v2.1.1"
|
||||
},
|
||||
"frontend-info": {
|
||||
"version": "v2.1.2 (dev)"
|
||||
"version": "v2.1.2"
|
||||
},
|
||||
"admin-panel-info": {
|
||||
"version": "v1.3.2 (dev)"
|
||||
"version": "v1.3.2"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "backendv2",
|
||||
"version": "v2.1.1 (dev)",
|
||||
"version": "1.0.0",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
|
||||
@@ -20,7 +20,7 @@ import apiRouter from "./routes/api/api.route.js";
|
||||
|
||||
env.config();
|
||||
const app = express();
|
||||
const port = 8004;
|
||||
const port = 8102;
|
||||
|
||||
app.use(cors());
|
||||
// Body-Parser VOR den Routen registrieren
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
services:
|
||||
# usr-frontend_v2:
|
||||
# container_name: borrow_system-usr-frontend
|
||||
# build: ./FrontendV2
|
||||
# ports:
|
||||
# - "8001:80"
|
||||
# restart: unless-stopped
|
||||
usr-frontend_v2:
|
||||
container_name: borrow_system-usr-frontend
|
||||
networks:
|
||||
- proxynet
|
||||
build: ./FrontendV2
|
||||
restart: unless-stopped
|
||||
|
||||
# admin-frontend:
|
||||
# container_name: borrow_system-admin-frontend
|
||||
# build: ./admin
|
||||
# ports:
|
||||
# - "8003:80"
|
||||
# restart: unless-stopped
|
||||
admin-frontend:
|
||||
container_name: borrow_system-admin-frontend
|
||||
networks:
|
||||
- proxynet
|
||||
build: ./admin
|
||||
restart: unless-stopped
|
||||
|
||||
backend_v2:
|
||||
container_name: borrow_system-backend_v2
|
||||
networks:
|
||||
- proxynet
|
||||
build: ./backendV2
|
||||
ports:
|
||||
- "8004:8004"
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
DB_HOST: mysql_v2
|
||||
@@ -30,6 +30,8 @@ services:
|
||||
|
||||
mysql_v2:
|
||||
container_name: borrow_system-mysql-v2
|
||||
networks:
|
||||
- proxynet
|
||||
image: mysql:8.0
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@@ -39,9 +41,11 @@ services:
|
||||
volumes:
|
||||
- mysql-v2-data:/var/lib/mysql
|
||||
- ./mysql-timezone.cnf:/etc/mysql/conf.d/timezone.cnf:ro
|
||||
ports:
|
||||
- "3310:3306"
|
||||
|
||||
volumes:
|
||||
mysql-data:
|
||||
mysql-v2-data:
|
||||
|
||||
networks:
|
||||
proxynet:
|
||||
external: true
|
||||
|
||||