nextjs improvements, auth dummies, auth guard
This commit is contained in:
parent
872ad76f59
commit
e06753fb24
6
.gitignore
vendored
6
.gitignore
vendored
@ -13,12 +13,6 @@ node_modules/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# IDE spezifische Ignorierungen
|
|
||||||
.idea/
|
|
||||||
.vscode/
|
|
||||||
*.swp
|
|
||||||
*~
|
|
||||||
|
|
||||||
# Weitere Build-Verzeichnisse
|
# Weitere Build-Verzeichnisse
|
||||||
**/dist/
|
**/dist/
|
||||||
**/build/
|
**/build/
|
||||||
|
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@ -1,4 +1,9 @@
|
|||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"cSpell.language": "en,de-DE"
|
"cSpell.language": "en,de-DE",
|
||||||
|
"files.autoSave": "afterDelay",
|
||||||
|
"editor.formatOnPaste": false,
|
||||||
|
"deno.disablePaths": [
|
||||||
|
"frontend-react"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
2
backend-dart/run.sh
Normal file
2
backend-dart/run.sh
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
dart run bin/backend_dart.dart # Backend on port 8080
|
3
frontend-react/deno.json
Normal file
3
frontend-react/deno.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"unstable": ["unsafe-proto"]
|
||||||
|
}
|
5
frontend-react/import_map.json
Normal file
5
frontend-react/import_map.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"@/components/": "./components/"
|
||||||
|
}
|
||||||
|
}
|
@ -19,5 +19,12 @@
|
|||||||
"NotFoundPage": {
|
"NotFoundPage": {
|
||||||
"description": "Bitte überprüfe die Addressleiste deines Browsers oder verwende die Navigation um zu einer bekannten Seite zu wechseln.",
|
"description": "Bitte überprüfe die Addressleiste deines Browsers oder verwende die Navigation um zu einer bekannten Seite zu wechseln.",
|
||||||
"title": "Seite nicht gefunden"
|
"title": "Seite nicht gefunden"
|
||||||
|
},
|
||||||
|
"Auth": {
|
||||||
|
"login": "Anmelden",
|
||||||
|
"logout": "Abmelden",
|
||||||
|
"profile": "Profil",
|
||||||
|
"register": "Registrieren",
|
||||||
|
"resetPassword": "Passwort zurücksetzen"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
import BackButton from "@/components/Buttons/BackButton";
|
||||||
|
import StartpageButton from "@/components/Buttons/StartpageButton";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@ -20,7 +22,8 @@ const LoginPage = () => {
|
|||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label
|
<label
|
||||||
htmlFor="email"
|
htmlFor="email"
|
||||||
className="block text-gray-700 font-medium mb-2">
|
className="block text-gray-700 font-medium mb-2"
|
||||||
|
>
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -36,7 +39,8 @@ const LoginPage = () => {
|
|||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<label
|
<label
|
||||||
htmlFor="password"
|
htmlFor="password"
|
||||||
className="block text-gray-700 font-medium mb-2">
|
className="block text-gray-700 font-medium mb-2"
|
||||||
|
>
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -51,12 +55,17 @@ const LoginPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 transition duration-200">
|
className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 transition duration-200"
|
||||||
|
>
|
||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
<Link className="block text-center mt-4 text-blue-500" href="register">
|
<Link className="block text-center mt-4 text-blue-500" href="register">
|
||||||
Create an account
|
Create an account
|
||||||
</Link>
|
</Link>
|
||||||
|
<div className="flex justify-between mt-8">
|
||||||
|
<BackButton />
|
||||||
|
<StartpageButton />
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
9
frontend-react/src/app/[locale]/(auth)/register/layout.tsx
Executable file
9
frontend-react/src/app/[locale]/(auth)/register/layout.tsx
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
const LoginLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginLayout;
|
108
frontend-react/src/app/[locale]/(auth)/register/page.tsx
Executable file
108
frontend-react/src/app/[locale]/(auth)/register/page.tsx
Executable file
@ -0,0 +1,108 @@
|
|||||||
|
"use client";
|
||||||
|
import Link from "next/link";
|
||||||
|
import React, { FormEvent, useState } from "react";
|
||||||
|
import BackButton from "@/components/Buttons/BackButton";
|
||||||
|
import StartpageButton from "@/components/Buttons/StartpageButton";
|
||||||
|
|
||||||
|
const RegisterPage = () => {
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
|
|
||||||
|
interface RegisterFormState {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
confirmPassword: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
alert("Passwords do not match");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const formData: RegisterFormState = {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
confirmPassword,
|
||||||
|
};
|
||||||
|
// Registrierungshandhabung hier hinzufügen
|
||||||
|
console.log("Registering user with data", formData);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center min-h-screen bg-gray-100">
|
||||||
|
<div className="w-full max-w-md bg-white rounded-lg shadow-md p-8">
|
||||||
|
<h2 className="text-2xl font-bold mb-6 text-center">Registrieren</h2>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label
|
||||||
|
htmlFor="email"
|
||||||
|
className="block text-gray-700 font-medium mb-2"
|
||||||
|
>
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
placeholder="Geben Sie Ihre Email ein"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label
|
||||||
|
htmlFor="password"
|
||||||
|
className="block text-gray-700 font-medium mb-2"
|
||||||
|
>
|
||||||
|
Passwort
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
placeholder="Geben Sie Ihr Passwort ein"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label
|
||||||
|
htmlFor="confirmPassword"
|
||||||
|
className="block text-gray-700 font-medium mb-2"
|
||||||
|
>
|
||||||
|
Passwort bestätigen
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="confirmPassword"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
|
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
|
placeholder="Bestätigen Sie Ihr Passwort"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 transition duration-200"
|
||||||
|
>
|
||||||
|
Registrieren
|
||||||
|
</button>
|
||||||
|
<Link className="block text-center mt-4 text-blue-500" href="/login">
|
||||||
|
Bereits ein Konto? Anmelden
|
||||||
|
</Link>
|
||||||
|
<div className="flex justify-between mt-8">
|
||||||
|
<BackButton />
|
||||||
|
<StartpageButton />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RegisterPage;
|
16
frontend-react/src/app/[locale]/(info)/about/layout.tsx
Executable file
16
frontend-react/src/app/[locale]/(info)/about/layout.tsx
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import BaseLayout from "@/components/Layout/BaseLayout";
|
||||||
|
import { routing } from "@/i18n/routing";
|
||||||
|
import UnauthenticatedLayout from "@/components/Layout/UnauthenticatedLayout";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: Props) {
|
||||||
|
return (
|
||||||
|
<BaseLayout locale={routing.defaultLocale}>
|
||||||
|
<UnauthenticatedLayout>{children}</UnauthenticatedLayout>
|
||||||
|
</BaseLayout>
|
||||||
|
);
|
||||||
|
}
|
48
frontend-react/src/app/[locale]/(info)/about/page.tsx
Executable file
48
frontend-react/src/app/[locale]/(info)/about/page.tsx
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
return (
|
||||||
|
<main className="flex items-center justify-center">
|
||||||
|
<div className="max-w-2xl pt-5 sm:pt-10 lg:pt-20">
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-balance text-5xl font-semibold tracking-tight text-gray-900 sm:text-7xl">
|
||||||
|
Über das Projekt
|
||||||
|
</h1>
|
||||||
|
<p className="mt-8 text-pretty text-lg font-medium text-gray-500 sm:text-xl/8">
|
||||||
|
Entwickler: Jean Jacques Avril
|
||||||
|
</p>
|
||||||
|
<p className="mt-2 text-pretty text-lg font-medium text-gray-500 sm:text-xl/8">
|
||||||
|
Webseite:{" "}
|
||||||
|
<a
|
||||||
|
href="https://jeanavril.com"
|
||||||
|
className="text-indigo-600 hover:underline"
|
||||||
|
>
|
||||||
|
jeanavril.com
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className="mt-2 text-pretty text-lg font-medium text-gray-500 sm:text-xl/8">
|
||||||
|
Technologien: Next.js, Go, Docker, Deno, Dart
|
||||||
|
</p>
|
||||||
|
<p className="mt-8 text-pretty text-lg font-medium text-gray-500 sm:text-xl/8">
|
||||||
|
Mit ActaTempus können Sie Ihre Arbeitszeiten mühelos erfassen und
|
||||||
|
Einblicke in Ihre Arbeitsgewohnheiten gewinnen. Beginnen Sie noch
|
||||||
|
heute und steigern Sie Ihre Produktivität.
|
||||||
|
</p>
|
||||||
|
<div className="mt-10 flex items-center justify-center gap-x-6">
|
||||||
|
<a
|
||||||
|
href="/register"
|
||||||
|
className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||||
|
>
|
||||||
|
Registrieren
|
||||||
|
</a>
|
||||||
|
<a href="#" className="text-sm/6 font-semibold text-gray-900">
|
||||||
|
Mehr erfahren <span aria-hidden="true">→</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<main className="flex items-center justify-center">
|
<main className="flex items-center justify-center">
|
||||||
@ -14,13 +17,17 @@ export default function Home() {
|
|||||||
</p>
|
</p>
|
||||||
<div className="mt-10 flex items-center justify-center gap-x-6">
|
<div className="mt-10 flex items-center justify-center gap-x-6">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="/register"
|
||||||
className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
|
className="rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
|
||||||
|
>
|
||||||
Register
|
Register
|
||||||
</a>
|
</a>
|
||||||
<a href="#" className="text-sm/6 font-semibold text-gray-900">
|
<a href="#" className="text-sm/6 font-semibold text-gray-900">
|
||||||
Learn more <span aria-hidden="true">→</span>
|
Learn more <span aria-hidden="true">→</span>
|
||||||
</a>
|
</a>
|
||||||
|
<Link href={"/dashboard"}>
|
||||||
|
Go to Dashboard
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
19
frontend-react/src/app/[locale]/dashboard/layout.tsx
Executable file
19
frontend-react/src/app/[locale]/dashboard/layout.tsx
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import BaseLayout from "@/components/Layout/BaseLayout";
|
||||||
|
import { routing } from "@/i18n/routing";
|
||||||
|
import UnauthenticatedLayout from "@/components/Layout/UnauthenticatedLayout";
|
||||||
|
import AuthGuard from "@/components/guards/AuthGuard";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: Props) {
|
||||||
|
return (
|
||||||
|
<BaseLayout locale={routing.defaultLocale}>
|
||||||
|
<AuthGuard>
|
||||||
|
<UnauthenticatedLayout>{children}</UnauthenticatedLayout>
|
||||||
|
</AuthGuard>
|
||||||
|
</BaseLayout>
|
||||||
|
);
|
||||||
|
}
|
97
frontend-react/src/app/[locale]/dashboard/page.tsx
Executable file
97
frontend-react/src/app/[locale]/dashboard/page.tsx
Executable file
@ -0,0 +1,97 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { FormEvent, useState } from "react";
|
||||||
|
|
||||||
|
const Dashboard = () => {
|
||||||
|
const [newEntry, setNewEntry] = useState({ task: "", duration: "" });
|
||||||
|
const [entries, setEntries] = useState([
|
||||||
|
{ id: 1, task: "Meeting", duration: "1h 30m", date: "2024-12-30" },
|
||||||
|
{ id: 2, task: "Development", duration: "3h 45m", date: "2024-12-29" },
|
||||||
|
{ id: 3, task: "Code Review", duration: "2h", date: "2024-12-28" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const handleInputChange = (e: FormEvent) => {
|
||||||
|
const { name, value } = e.target as HTMLInputElement;
|
||||||
|
setNewEntry({ ...newEntry, [name]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddEntry = () => {
|
||||||
|
if (!newEntry.task || !newEntry.duration) return;
|
||||||
|
const newId = entries.length ? entries[entries.length - 1].id + 1 : 1;
|
||||||
|
setEntries([
|
||||||
|
...entries,
|
||||||
|
{ id: newId, ...newEntry, date: new Date().toISOString().split("T")[0] },
|
||||||
|
]);
|
||||||
|
setNewEntry({ task: "", duration: "" });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="min-h-screen bg-gray-100 py-10 px-4">
|
||||||
|
<div className="max-w-4xl mx-auto bg-white rounded-lg shadow-lg p-6">
|
||||||
|
{/* Timer Section */}
|
||||||
|
<section className="mb-8">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800 mb-4">New Entry</h2>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="task"
|
||||||
|
value={newEntry.task}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="Task Name"
|
||||||
|
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="duration"
|
||||||
|
value={newEntry.duration}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="Duration (e.g., 1h 30m)"
|
||||||
|
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500 focus:outline-none"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleAddEntry}
|
||||||
|
className="w-full bg-indigo-600 text-white py-2 rounded-lg hover:bg-indigo-500 transition duration-200"
|
||||||
|
>
|
||||||
|
Add Entry
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Past Entries Section */}
|
||||||
|
<section>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800 mb-4">
|
||||||
|
Past Entries
|
||||||
|
</h2>
|
||||||
|
<div className="bg-gray-50 p-4 rounded-lg shadow-inner">
|
||||||
|
{entries.length
|
||||||
|
? (
|
||||||
|
<ul className="divide-y divide-gray-300">
|
||||||
|
{entries.map((entry) => (
|
||||||
|
<li
|
||||||
|
key={entry.id}
|
||||||
|
className="py-3 flex justify-between items-center"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<p className="text-lg font-medium text-gray-700">
|
||||||
|
{entry.task}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{entry.date} - {entry.duration}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<p className="text-gray-500">
|
||||||
|
No entries yet. Start adding some!
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dashboard;
|
@ -1,4 +1,5 @@
|
|||||||
import {ReactNode} from 'react';
|
import { UserProvider } from "@/context/UserContext";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -6,6 +7,10 @@ type Props = {
|
|||||||
|
|
||||||
// Since we have a `not-found.tsx` page on the root, a layout file
|
// Since we have a `not-found.tsx` page on the root, a layout file
|
||||||
// is required, even if it's just passing children through.
|
// is required, even if it's just passing children through.
|
||||||
export default function RootLayout({children}: Props) {
|
export default function RootLayout({ children }: Props) {
|
||||||
return children;
|
return (
|
||||||
}
|
<UserProvider>
|
||||||
|
{children}
|
||||||
|
</UserProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
26
frontend-react/src/components/Buttons/BackButton.tsx
Normal file
26
frontend-react/src/components/Buttons/BackButton.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { ChevronLeft } from "lucide-react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
const BackButton = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
if (window.history.length > 1) {
|
||||||
|
router.back();
|
||||||
|
} else {
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={handleBack}
|
||||||
|
className="text-blue-500 hover:underline focus:outline-none flex items-center"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="inline-block w-4 h-4 mr-2" />
|
||||||
|
Zurück
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BackButton;
|
16
frontend-react/src/components/Buttons/StartpageButton.tsx
Normal file
16
frontend-react/src/components/Buttons/StartpageButton.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { HomeIcon } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
const StartpageButton = () => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
className="text-blue-500 hover:underline focus:outline-none flex items-center"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
<HomeIcon className="inline-block w-4 h-4 mr-2" />
|
||||||
|
Startseite
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StartpageButton;
|
23
frontend-react/src/components/guards/AuthGuard.tsx
Normal file
23
frontend-react/src/components/guards/AuthGuard.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"use client";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useUser } from "@/context/UserContext";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
|
const AuthGuard = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { user } = useUser(); // Benutzer aus dem globalen Zustand abrufen
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!user) {
|
||||||
|
router.push("/login"); // Weiterleitung zur Login-Seite
|
||||||
|
}
|
||||||
|
}, [user, router]);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return <p>Lade...</p>; // Anzeige während des Ladens oder Weiterleitens
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthGuard;
|
33
frontend-react/src/context/UserContext.tsx
Normal file
33
frontend-react/src/context/UserContext.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
"use client";
|
||||||
|
import React, { createContext, ReactNode, useContext, useState } from "react";
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UserContextType = {
|
||||||
|
user: User | null;
|
||||||
|
setUser: (user: User | null) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserContext = createContext<UserContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export const UserProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={{ user, setUser }}>
|
||||||
|
{children}
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUser = (): UserContextType => {
|
||||||
|
const context = useContext(UserContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useUser must be used within a UserProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user