feat: restructure routing and authentication for hidden layout and user login
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export const LandingPage = () => {
|
||||
return (
|
||||
<>
|
||||
<p>Landing Page</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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
@@ -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>,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
const AppHiddenLayoutRoute = AppHiddenLayoutRouteImport.update({
|
||||
id: '/app/_hiddenLayout',
|
||||
path: '/app',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const AppInventoryRoute = AppInventoryRouteImport.update({
|
||||
id: '/app/inventory',
|
||||
path: '/app/inventory',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
const AppHiddenLayoutViewProductRoute =
|
||||
AppHiddenLayoutViewProductRouteImport.update({
|
||||
id: '/view-product',
|
||||
path: '/view-product',
|
||||
getParentRoute: () => AppHiddenLayoutRoute,
|
||||
} as any)
|
||||
const AppAddProductRoute = AppAddProductRouteImport.update({
|
||||
id: '/app/add-product',
|
||||
path: '/app/add-product',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
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)
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user