feat: restructure routing and authentication for hidden layout and user login

This commit is contained in:
2026-05-26 17:06:31 +02:00
parent d6e29a74af
commit ed9fb0d1ce
16 changed files with 284 additions and 127 deletions
+1 -1
View File
@@ -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) => {
-7
View File
@@ -1,7 +0,0 @@
export const LandingPage = () => {
return (
<>
<p>Landing Page</p>
</>
);
};
+70
View File
@@ -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 (
<>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.Field name="username">
{(field) => (
<Input
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
placeholder={t("username")}
/>
)}
</form.Field>
<form.Field name="password">
{(field) => (
<Input
value={field.state.value}
type="password"
onChange={(e) => field.handleChange(e.target.value)}
placeholder={t("password")}
/>
)}
</form.Field>
<Button type="submit" loading={isPending}>
{t("login")}
</Button>
</form>
</>
);
};
+12 -6
View File
@@ -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(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>,
)
);
+83 -49
View File
@@ -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)
+15
View File
@@ -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 (
<div>
<h1>Layout</h1>
<Outlet />
</div>
);
}
@@ -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 <div>Hello "/app/add-product"!</div>;
}
@@ -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 (
<>
<p>Inventar</p>
</>
);
}
@@ -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 <div>Hello "/app/view-product"!</div>;
}
-9
View File
@@ -1,9 +0,0 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/app/add-product')({
component: RouteComponent,
})
function RouteComponent() {
return <div>Hello "/app/add-product"!</div>
}
-17
View File
@@ -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 <div>Hello "/app/inventory"!</div>;
}
-9
View File
@@ -1,9 +0,0 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/app/view-product')({
component: RouteComponent,
})
function RouteComponent() {
return <div>Hello "/app/view-product"!</div>
}
+9 -4
View File
@@ -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 <div>Hello "/login"!</div>
return (
<>
<LoginCard />
</>
);
}
+14 -12
View File
@@ -1,12 +1,10 @@
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() {
if (Cookies.get("token")) {
const result = await fetch(`${API_BASE}/users/verify-token`, {
method: "POST",
headers: {
@@ -19,11 +17,17 @@ export async function isAuthenticated() {
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));
}
return { ok: false as const };
}
export function signOutUser() {
Cookies.remove("token");
throw redirect({
to: "/login",
});
return { ok: true as const };
}
@@ -0,0 +1,6 @@
{
"username": "Benutzername",
"password": "Passwort",
"eu001": "Falscher Benutzername oder Passwort!",
"login": "Login"
}
@@ -0,0 +1,6 @@
{
"username": "Username",
"password": "Password",
"eu001": "Wrong username or password!",
"login": "Login"
}