diff --git a/backend/routes/app/users.route.js b/backend/routes/app/users.route.js
index c85f339..019f85b 100644
--- a/backend/routes/app/users.route.js
+++ b/backend/routes/app/users.route.js
@@ -6,7 +6,7 @@ dotenv.config();
const router = express.Router();
router.post("/verify-token", authenticate, async (req, res) => {
- res.status(200);
+ res.sendStatus(200);
});
router.post("/login", async (req, res) => {
diff --git a/frontend/src/components/LandingPage.tsx b/frontend/src/components/LandingPage.tsx
deleted file mode 100644
index a8155b9..0000000
--- a/frontend/src/components/LandingPage.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export const LandingPage = () => {
- return (
- <>
-
Landing Page
- >
- );
-};
diff --git a/frontend/src/components/LoginCard.tsx b/frontend/src/components/LoginCard.tsx
new file mode 100644
index 0000000..9e79513
--- /dev/null
+++ b/frontend/src/components/LoginCard.tsx
@@ -0,0 +1,70 @@
+import { useForm } from "@tanstack/react-form";
+import { Input, Button } from "@mui/joy";
+import { useMutation } from "@tanstack/react-query";
+import { signInUser } from "../utils/auth";
+import { useTranslation } from "react-i18next";
+import { useNavigate } from "@tanstack/react-router";
+
+export const LoginCard = () => {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+
+ const form = useForm({
+ defaultValues: {
+ username: "",
+ password: "",
+ },
+ onSubmit: async ({ value }) => {
+ mutate({ username: value.username, password: value.password });
+ },
+ });
+
+ const { mutate, isPending } = useMutation({
+ mutationFn: ({
+ username,
+ password,
+ }: {
+ username: string;
+ password: string;
+ }) => signInUser(username, password, t),
+ onSuccess: (result) => {
+ if (result.ok) {
+ navigate({ to: "/app/inventory" });
+ }
+ },
+ });
+
+ return (
+ <>
+
+ {(field) => (
+ field.handleChange(e.target.value)}
+ placeholder={t("username")}
+ />
+ )}
+
+
+ {(field) => (
+ field.handleChange(e.target.value)}
+ placeholder={t("password")}
+ />
+ )}
+
+
+
+ >
+ );
+};
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index bef5202..820ab0f 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,10 +1,16 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-import './index.css'
-import App from './App.tsx'
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import "./index.css";
+import "./utils/i18n";
+import App from "./App.tsx";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-createRoot(document.getElementById('root')!).render(
+const queryClient = new QueryClient();
+
+createRoot(document.getElementById("root")!).render(
-
+
+
+
,
-)
+);
diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts
index dfe1a17..716996f 100644
--- a/frontend/src/routeTree.gen.ts
+++ b/frontend/src/routeTree.gen.ts
@@ -11,9 +11,10 @@
import { Route as rootRouteImport } from './routes/__root'
import { Route as LoginRouteImport } from './routes/login'
import { Route as IndexRouteImport } from './routes/index'
-import { Route as AppViewProductRouteImport } from './routes/app/view-product'
-import { Route as AppInventoryRouteImport } from './routes/app/inventory'
-import { Route as AppAddProductRouteImport } from './routes/app/add-product'
+import { Route as AppHiddenLayoutRouteImport } from './routes/app/_hiddenLayout'
+import { Route as AppHiddenLayoutViewProductRouteImport } from './routes/app/_hiddenLayout/view-product'
+import { Route as AppHiddenLayoutInventoryRouteImport } from './routes/app/_hiddenLayout/inventory'
+import { Route as AppHiddenLayoutAddProductRouteImport } from './routes/app/_hiddenLayout/add-product'
const LoginRoute = LoginRouteImport.update({
id: '/login',
@@ -25,49 +26,61 @@ const IndexRoute = IndexRouteImport.update({
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
-const AppViewProductRoute = AppViewProductRouteImport.update({
- id: '/app/view-product',
- path: '/app/view-product',
- getParentRoute: () => rootRouteImport,
-} as any)
-const AppInventoryRoute = AppInventoryRouteImport.update({
- id: '/app/inventory',
- path: '/app/inventory',
- getParentRoute: () => rootRouteImport,
-} as any)
-const AppAddProductRoute = AppAddProductRouteImport.update({
- id: '/app/add-product',
- path: '/app/add-product',
+const AppHiddenLayoutRoute = AppHiddenLayoutRouteImport.update({
+ id: '/app/_hiddenLayout',
+ path: '/app',
getParentRoute: () => rootRouteImport,
} as any)
+const AppHiddenLayoutViewProductRoute =
+ AppHiddenLayoutViewProductRouteImport.update({
+ id: '/view-product',
+ path: '/view-product',
+ getParentRoute: () => AppHiddenLayoutRoute,
+ } as any)
+const AppHiddenLayoutInventoryRoute =
+ AppHiddenLayoutInventoryRouteImport.update({
+ id: '/inventory',
+ path: '/inventory',
+ getParentRoute: () => AppHiddenLayoutRoute,
+ } as any)
+const AppHiddenLayoutAddProductRoute =
+ AppHiddenLayoutAddProductRouteImport.update({
+ id: '/add-product',
+ path: '/add-product',
+ getParentRoute: () => AppHiddenLayoutRoute,
+ } as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/login': typeof LoginRoute
- '/app/add-product': typeof AppAddProductRoute
- '/app/inventory': typeof AppInventoryRoute
- '/app/view-product': typeof AppViewProductRoute
+ '/app': typeof AppHiddenLayoutRouteWithChildren
+ '/app/add-product': typeof AppHiddenLayoutAddProductRoute
+ '/app/inventory': typeof AppHiddenLayoutInventoryRoute
+ '/app/view-product': typeof AppHiddenLayoutViewProductRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/login': typeof LoginRoute
- '/app/add-product': typeof AppAddProductRoute
- '/app/inventory': typeof AppInventoryRoute
- '/app/view-product': typeof AppViewProductRoute
+ '/app': typeof AppHiddenLayoutRouteWithChildren
+ '/app/add-product': typeof AppHiddenLayoutAddProductRoute
+ '/app/inventory': typeof AppHiddenLayoutInventoryRoute
+ '/app/view-product': typeof AppHiddenLayoutViewProductRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/login': typeof LoginRoute
- '/app/add-product': typeof AppAddProductRoute
- '/app/inventory': typeof AppInventoryRoute
- '/app/view-product': typeof AppViewProductRoute
+ '/app/_hiddenLayout': typeof AppHiddenLayoutRouteWithChildren
+ '/app/_hiddenLayout/add-product': typeof AppHiddenLayoutAddProductRoute
+ '/app/_hiddenLayout/inventory': typeof AppHiddenLayoutInventoryRoute
+ '/app/_hiddenLayout/view-product': typeof AppHiddenLayoutViewProductRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/login'
+ | '/app'
| '/app/add-product'
| '/app/inventory'
| '/app/view-product'
@@ -75,6 +88,7 @@ export interface FileRouteTypes {
to:
| '/'
| '/login'
+ | '/app'
| '/app/add-product'
| '/app/inventory'
| '/app/view-product'
@@ -82,17 +96,16 @@ export interface FileRouteTypes {
| '__root__'
| '/'
| '/login'
- | '/app/add-product'
- | '/app/inventory'
- | '/app/view-product'
+ | '/app/_hiddenLayout'
+ | '/app/_hiddenLayout/add-product'
+ | '/app/_hiddenLayout/inventory'
+ | '/app/_hiddenLayout/view-product'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
LoginRoute: typeof LoginRoute
- AppAddProductRoute: typeof AppAddProductRoute
- AppInventoryRoute: typeof AppInventoryRoute
- AppViewProductRoute: typeof AppViewProductRoute
+ AppHiddenLayoutRoute: typeof AppHiddenLayoutRouteWithChildren
}
declare module '@tanstack/react-router' {
@@ -111,36 +124,57 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
- '/app/view-product': {
- id: '/app/view-product'
- path: '/app/view-product'
+ '/app/_hiddenLayout': {
+ id: '/app/_hiddenLayout'
+ path: '/app'
+ fullPath: '/app'
+ preLoaderRoute: typeof AppHiddenLayoutRouteImport
+ parentRoute: typeof rootRouteImport
+ }
+ '/app/_hiddenLayout/view-product': {
+ id: '/app/_hiddenLayout/view-product'
+ path: '/view-product'
fullPath: '/app/view-product'
- preLoaderRoute: typeof AppViewProductRouteImport
- parentRoute: typeof rootRouteImport
+ preLoaderRoute: typeof AppHiddenLayoutViewProductRouteImport
+ parentRoute: typeof AppHiddenLayoutRoute
}
- '/app/inventory': {
- id: '/app/inventory'
- path: '/app/inventory'
+ '/app/_hiddenLayout/inventory': {
+ id: '/app/_hiddenLayout/inventory'
+ path: '/inventory'
fullPath: '/app/inventory'
- preLoaderRoute: typeof AppInventoryRouteImport
- parentRoute: typeof rootRouteImport
+ preLoaderRoute: typeof AppHiddenLayoutInventoryRouteImport
+ parentRoute: typeof AppHiddenLayoutRoute
}
- '/app/add-product': {
- id: '/app/add-product'
- path: '/app/add-product'
+ '/app/_hiddenLayout/add-product': {
+ id: '/app/_hiddenLayout/add-product'
+ path: '/add-product'
fullPath: '/app/add-product'
- preLoaderRoute: typeof AppAddProductRouteImport
- parentRoute: typeof rootRouteImport
+ preLoaderRoute: typeof AppHiddenLayoutAddProductRouteImport
+ parentRoute: typeof AppHiddenLayoutRoute
}
}
}
+interface AppHiddenLayoutRouteChildren {
+ AppHiddenLayoutAddProductRoute: typeof AppHiddenLayoutAddProductRoute
+ AppHiddenLayoutInventoryRoute: typeof AppHiddenLayoutInventoryRoute
+ AppHiddenLayoutViewProductRoute: typeof AppHiddenLayoutViewProductRoute
+}
+
+const AppHiddenLayoutRouteChildren: AppHiddenLayoutRouteChildren = {
+ AppHiddenLayoutAddProductRoute: AppHiddenLayoutAddProductRoute,
+ AppHiddenLayoutInventoryRoute: AppHiddenLayoutInventoryRoute,
+ AppHiddenLayoutViewProductRoute: AppHiddenLayoutViewProductRoute,
+}
+
+const AppHiddenLayoutRouteWithChildren = AppHiddenLayoutRoute._addFileChildren(
+ AppHiddenLayoutRouteChildren,
+)
+
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
LoginRoute: LoginRoute,
- AppAddProductRoute: AppAddProductRoute,
- AppInventoryRoute: AppInventoryRoute,
- AppViewProductRoute: AppViewProductRoute,
+ AppHiddenLayoutRoute: AppHiddenLayoutRouteWithChildren,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/frontend/src/routes/app/_hiddenLayout.tsx b/frontend/src/routes/app/_hiddenLayout.tsx
new file mode 100644
index 0000000..636a903
--- /dev/null
+++ b/frontend/src/routes/app/_hiddenLayout.tsx
@@ -0,0 +1,15 @@
+// routes/app/_layout.tsx (oder app.tsx als Parent)
+import { Outlet, createFileRoute } from "@tanstack/react-router";
+
+export const Route = createFileRoute("/app/_hiddenLayout")({
+ component: AppLayout,
+});
+
+function AppLayout() {
+ return (
+
+
Layout
+
+
+ );
+}
diff --git a/frontend/src/routes/app/_hiddenLayout/add-product.tsx b/frontend/src/routes/app/_hiddenLayout/add-product.tsx
new file mode 100644
index 0000000..f2307b9
--- /dev/null
+++ b/frontend/src/routes/app/_hiddenLayout/add-product.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute, redirect } from "@tanstack/react-router";
+import { isAuthenticated } from "../../../utils/auth";
+
+export const Route = createFileRoute("/app/_hiddenLayout/add-product")({
+ beforeLoad: async () => {
+ if (!(await isAuthenticated())) {
+ throw redirect({
+ to: "/login",
+ });
+ }
+ },
+ component: RouteComponent,
+});
+
+function RouteComponent() {
+ return Hello "/app/add-product"!
;
+}
diff --git a/frontend/src/routes/app/_hiddenLayout/inventory.tsx b/frontend/src/routes/app/_hiddenLayout/inventory.tsx
new file mode 100644
index 0000000..afeab4b
--- /dev/null
+++ b/frontend/src/routes/app/_hiddenLayout/inventory.tsx
@@ -0,0 +1,21 @@
+import { createFileRoute, redirect } from "@tanstack/react-router";
+import { isAuthenticated } from "../../../utils/auth";
+
+export const Route = createFileRoute("/app/_hiddenLayout/inventory")({
+ beforeLoad: async () => {
+ if (!(await isAuthenticated())) {
+ throw redirect({
+ to: "/login",
+ });
+ }
+ },
+ component: RouteComponent,
+});
+
+function RouteComponent() {
+ return (
+ <>
+ Inventar
+ >
+ );
+}
diff --git a/frontend/src/routes/app/_hiddenLayout/view-product.tsx b/frontend/src/routes/app/_hiddenLayout/view-product.tsx
new file mode 100644
index 0000000..2cbef25
--- /dev/null
+++ b/frontend/src/routes/app/_hiddenLayout/view-product.tsx
@@ -0,0 +1,17 @@
+import { createFileRoute, redirect } from "@tanstack/react-router";
+import { isAuthenticated } from "../../../utils/auth";
+
+export const Route = createFileRoute("/app/_hiddenLayout/view-product")({
+ beforeLoad: async () => {
+ if (!(await isAuthenticated())) {
+ throw redirect({
+ to: "/login",
+ });
+ }
+ },
+ component: RouteComponent,
+});
+
+function RouteComponent() {
+ return Hello "/app/view-product"!
;
+}
diff --git a/frontend/src/routes/app/add-product.tsx b/frontend/src/routes/app/add-product.tsx
deleted file mode 100644
index c218e62..0000000
--- a/frontend/src/routes/app/add-product.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { createFileRoute } from '@tanstack/react-router'
-
-export const Route = createFileRoute('/app/add-product')({
- component: RouteComponent,
-})
-
-function RouteComponent() {
- return Hello "/app/add-product"!
-}
diff --git a/frontend/src/routes/app/inventory.tsx b/frontend/src/routes/app/inventory.tsx
deleted file mode 100644
index da772c9..0000000
--- a/frontend/src/routes/app/inventory.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { createFileRoute, redirect } from "@tanstack/react-router";
-import { isAuthenticated } from "../../utils/auth";
-
-export const Route = createFileRoute("/app/inventory")({
- beforeLoad: () => {
- if (!isAuthenticated()) {
- throw redirect({
- to: "/login",
- });
- }
- },
- component: RouteComponent,
-});
-
-function RouteComponent() {
- return Hello "/app/inventory"!
;
-}
diff --git a/frontend/src/routes/app/view-product.tsx b/frontend/src/routes/app/view-product.tsx
deleted file mode 100644
index 1bc3a33..0000000
--- a/frontend/src/routes/app/view-product.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { createFileRoute } from '@tanstack/react-router'
-
-export const Route = createFileRoute('/app/view-product')({
- component: RouteComponent,
-})
-
-function RouteComponent() {
- return Hello "/app/view-product"!
-}
diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx
index f7b477e..9940f41 100644
--- a/frontend/src/routes/login.tsx
+++ b/frontend/src/routes/login.tsx
@@ -1,9 +1,14 @@
-import { createFileRoute } from '@tanstack/react-router'
+import { createFileRoute } from "@tanstack/react-router";
+import { LoginCard } from "../components/LoginCard";
-export const Route = createFileRoute('/login')({
+export const Route = createFileRoute("/login")({
component: RouteComponent,
-})
+});
function RouteComponent() {
- return Hello "/login"!
+ return (
+ <>
+
+ >
+ );
}
diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts
index b329cd1..6ed64c5 100644
--- a/frontend/src/utils/auth.ts
+++ b/frontend/src/utils/auth.ts
@@ -1,29 +1,33 @@
import { API_BASE } from "../config/api.config";
import Cookies from "js-cookie";
-import { useTranslation } from "react-i18next";
+import type { TFunction } from "i18next";
import { toast } from "react-toastify";
-import { redirect } from "@tanstack/react-router";
-
-const { t } = useTranslation();
export async function isAuthenticated() {
- const result = await fetch(`${API_BASE}/users/verify-token`, {
- method: "POST",
- headers: {
- Authorization: `Bearer ${Cookies.get("token") || ""}`,
- "Content-Type": "application/json",
- Accept: "application/json",
- },
- });
+ if (Cookies.get("token")) {
+ const result = await fetch(`${API_BASE}/users/verify-token`, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${Cookies.get("token") || ""}`,
+ "Content-Type": "application/json",
+ Accept: "application/json",
+ },
+ });
- if (result.status === 200) {
- return true;
+ if (result.status === 200) {
+ return true;
+ }
}
+ Cookies.remove("token");
return false;
}
-export async function signInUser(username: string, password: string) {
+export async function signInUser(
+ username: string,
+ password: string,
+ t: TFunction,
+) {
const result = await fetch(`${API_BASE}/users/login`, {
method: "POST",
headers: {
@@ -35,21 +39,19 @@ export async function signInUser(username: string, password: string) {
});
const response = await result.json();
+ console.log(response);
if (result.status === 202) {
- Cookies.set("token", response.token);
- return true;
+ Cookies.set("token", response.data.token);
+ return { ok: true as const };
}
- if (result.status !== 202) {
- Cookies.remove("token");
- toast.error(t(response.code));
- }
+ Cookies.remove("token");
+ toast.error(t(response.code));
+ return { ok: false as const };
}
export function signOutUser() {
Cookies.remove("token");
- throw redirect({
- to: "/login",
- });
+ return { ok: true as const };
}
diff --git a/frontend/src/utils/i18n/locales/de/de.json b/frontend/src/utils/i18n/locales/de/de.json
index e69de29..6876475 100644
--- a/frontend/src/utils/i18n/locales/de/de.json
+++ b/frontend/src/utils/i18n/locales/de/de.json
@@ -0,0 +1,6 @@
+{
+ "username": "Benutzername",
+ "password": "Passwort",
+ "eu001": "Falscher Benutzername oder Passwort!",
+ "login": "Login"
+}
\ No newline at end of file
diff --git a/frontend/src/utils/i18n/locales/en/en.json b/frontend/src/utils/i18n/locales/en/en.json
index e69de29..4ea442b 100644
--- a/frontend/src/utils/i18n/locales/en/en.json
+++ b/frontend/src/utils/i18n/locales/en/en.json
@@ -0,0 +1,6 @@
+{
+ "username": "Username",
+ "password": "Password",
+ "eu001": "Wrong username or password!",
+ "login": "Login"
+}
\ No newline at end of file