From e06753fb24d2f675c9fae388ed0d6cdbfe6c3982 Mon Sep 17 00:00:00 2001 From: Jean Jacques Avril Date: Tue, 31 Dec 2024 15:30:28 +0000 Subject: [PATCH] nextjs improvements, auth dummies, auth guard --- .gitignore | 6 - .vscode/settings.json | 7 +- backend-dart/run.sh | 2 + frontend-react/deno.json | 3 + frontend-react/import_map.json | 5 + frontend-react/messages/de.json | 7 ++ .../src/app/[locale]/(auth)/login/page.tsx | 15 ++- .../app/[locale]/(auth)/register/layout.tsx | 9 ++ .../src/app/[locale]/(auth)/register/page.tsx | 108 ++++++++++++++++++ .../src/app/[locale]/(info)/about/layout.tsx | 16 +++ .../src/app/[locale]/(info)/about/page.tsx | 48 ++++++++ .../src/app/[locale]/(landing)/page.tsx | 11 +- .../src/app/[locale]/dashboard/layout.tsx | 19 +++ .../src/app/[locale]/dashboard/page.tsx | 97 ++++++++++++++++ frontend-react/src/app/layout.tsx | 13 ++- .../src/components/Buttons/BackButton.tsx | 26 +++++ .../components/Buttons/StartpageButton.tsx | 16 +++ .../src/components/guards/AuthGuard.tsx | 23 ++++ frontend-react/src/context/UserContext.tsx | 33 ++++++ 19 files changed, 448 insertions(+), 16 deletions(-) create mode 100644 backend-dart/run.sh create mode 100644 frontend-react/deno.json create mode 100644 frontend-react/import_map.json create mode 100755 frontend-react/src/app/[locale]/(auth)/register/layout.tsx create mode 100755 frontend-react/src/app/[locale]/(auth)/register/page.tsx create mode 100755 frontend-react/src/app/[locale]/(info)/about/layout.tsx create mode 100755 frontend-react/src/app/[locale]/(info)/about/page.tsx create mode 100755 frontend-react/src/app/[locale]/dashboard/layout.tsx create mode 100755 frontend-react/src/app/[locale]/dashboard/page.tsx create mode 100644 frontend-react/src/components/Buttons/BackButton.tsx create mode 100644 frontend-react/src/components/Buttons/StartpageButton.tsx create mode 100644 frontend-react/src/components/guards/AuthGuard.tsx create mode 100644 frontend-react/src/context/UserContext.tsx diff --git a/.gitignore b/.gitignore index 829a367..4e4545b 100755 --- a/.gitignore +++ b/.gitignore @@ -13,12 +13,6 @@ node_modules/ .DS_Store Thumbs.db -# IDE spezifische Ignorierungen -.idea/ -.vscode/ -*.swp -*~ - # Weitere Build-Verzeichnisse **/dist/ **/build/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 768ae7d..74609b3 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,9 @@ { "editor.formatOnSave": true, - "cSpell.language": "en,de-DE" + "cSpell.language": "en,de-DE", + "files.autoSave": "afterDelay", + "editor.formatOnPaste": false, + "deno.disablePaths": [ + "frontend-react" + ] } diff --git a/backend-dart/run.sh b/backend-dart/run.sh new file mode 100644 index 0000000..d740187 --- /dev/null +++ b/backend-dart/run.sh @@ -0,0 +1,2 @@ +#!/bin/sh +dart run bin/backend_dart.dart # Backend on port 8080 diff --git a/frontend-react/deno.json b/frontend-react/deno.json new file mode 100644 index 0000000..dc438fe --- /dev/null +++ b/frontend-react/deno.json @@ -0,0 +1,3 @@ +{ + "unstable": ["unsafe-proto"] +} \ No newline at end of file diff --git a/frontend-react/import_map.json b/frontend-react/import_map.json new file mode 100644 index 0000000..33b1426 --- /dev/null +++ b/frontend-react/import_map.json @@ -0,0 +1,5 @@ +{ + "imports": { + "@/components/": "./components/" + } +} \ No newline at end of file diff --git a/frontend-react/messages/de.json b/frontend-react/messages/de.json index 75f8b73..4b95c9f 100755 --- a/frontend-react/messages/de.json +++ b/frontend-react/messages/de.json @@ -19,5 +19,12 @@ "NotFoundPage": { "description": "Bitte überprüfe die Addressleiste deines Browsers oder verwende die Navigation um zu einer bekannten Seite zu wechseln.", "title": "Seite nicht gefunden" + }, + "Auth": { + "login": "Anmelden", + "logout": "Abmelden", + "profile": "Profil", + "register": "Registrieren", + "resetPassword": "Passwort zurücksetzen" } } \ No newline at end of file diff --git a/frontend-react/src/app/[locale]/(auth)/login/page.tsx b/frontend-react/src/app/[locale]/(auth)/login/page.tsx index 2d9d247..8f5d993 100755 --- a/frontend-react/src/app/[locale]/(auth)/login/page.tsx +++ b/frontend-react/src/app/[locale]/(auth)/login/page.tsx @@ -1,4 +1,6 @@ "use client"; +import BackButton from "@/components/Buttons/BackButton"; +import StartpageButton from "@/components/Buttons/StartpageButton"; import Link from "next/link"; import React from "react"; import { useState } from "react"; @@ -20,7 +22,8 @@ const LoginPage = () => {
{
{
Create an account +
+ + +
); diff --git a/frontend-react/src/app/[locale]/(auth)/register/layout.tsx b/frontend-react/src/app/[locale]/(auth)/register/layout.tsx new file mode 100755 index 0000000..ec30286 --- /dev/null +++ b/frontend-react/src/app/[locale]/(auth)/register/layout.tsx @@ -0,0 +1,9 @@ +const LoginLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { + return ( +
+ {children} +
+ ); +}; + +export default LoginLayout; \ No newline at end of file diff --git a/frontend-react/src/app/[locale]/(auth)/register/page.tsx b/frontend-react/src/app/[locale]/(auth)/register/page.tsx new file mode 100755 index 0000000..65f12b0 --- /dev/null +++ b/frontend-react/src/app/[locale]/(auth)/register/page.tsx @@ -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 ( +
+
+

Registrieren

+
+
+ + 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 + /> +
+
+ + 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 + /> +
+
+ + 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 + /> +
+ + + Bereits ein Konto? Anmelden + +
+ + +
+
+
+
+ ); +}; + +export default RegisterPage; diff --git a/frontend-react/src/app/[locale]/(info)/about/layout.tsx b/frontend-react/src/app/[locale]/(info)/about/layout.tsx new file mode 100755 index 0000000..02539f3 --- /dev/null +++ b/frontend-react/src/app/[locale]/(info)/about/layout.tsx @@ -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 ( + + {children} + + ); +} diff --git a/frontend-react/src/app/[locale]/(info)/about/page.tsx b/frontend-react/src/app/[locale]/(info)/about/page.tsx new file mode 100755 index 0000000..9b2bd93 --- /dev/null +++ b/frontend-react/src/app/[locale]/(info)/about/page.tsx @@ -0,0 +1,48 @@ +"use client"; + +import React from "react"; + +export default function About() { + return ( +
+
+
+

+ Über das Projekt +

+

+ Entwickler: Jean Jacques Avril +

+

+ Webseite:{" "} + + jeanavril.com + +

+

+ Technologien: Next.js, Go, Docker, Deno, Dart +

+

+ 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. +

+
+ + Registrieren + + + Mehr erfahren + +
+
+
+
+ ); +} diff --git a/frontend-react/src/app/[locale]/(landing)/page.tsx b/frontend-react/src/app/[locale]/(landing)/page.tsx index b1425dd..8257b2c 100755 --- a/frontend-react/src/app/[locale]/(landing)/page.tsx +++ b/frontend-react/src/app/[locale]/(landing)/page.tsx @@ -1,4 +1,7 @@ "use client"; + +import Link from "next/link"; + export default function Home() { return (
@@ -14,13 +17,17 @@ export default function Home() {

+ 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" + > Register Learn more + + Go to Dashboard +
diff --git a/frontend-react/src/app/[locale]/dashboard/layout.tsx b/frontend-react/src/app/[locale]/dashboard/layout.tsx new file mode 100755 index 0000000..188a85c --- /dev/null +++ b/frontend-react/src/app/[locale]/dashboard/layout.tsx @@ -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 ( + + + {children} + + + ); +} diff --git a/frontend-react/src/app/[locale]/dashboard/page.tsx b/frontend-react/src/app/[locale]/dashboard/page.tsx new file mode 100755 index 0000000..04406d0 --- /dev/null +++ b/frontend-react/src/app/[locale]/dashboard/page.tsx @@ -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 ( +
+
+ {/* Timer Section */} +
+

New Entry

+
+ + + +
+
+ + {/* Past Entries Section */} +
+

+ Past Entries +

+
+ {entries.length + ? ( +
    + {entries.map((entry) => ( +
  • +
    +

    + {entry.task} +

    +

    + {entry.date} - {entry.duration} +

    +
    +
  • + ))} +
+ ) + : ( +

+ No entries yet. Start adding some! +

+ )} +
+
+
+
+ ); +}; + +export default Dashboard; diff --git a/frontend-react/src/app/layout.tsx b/frontend-react/src/app/layout.tsx index 0a7f803..c1eb2c8 100755 --- a/frontend-react/src/app/layout.tsx +++ b/frontend-react/src/app/layout.tsx @@ -1,4 +1,5 @@ -import {ReactNode} from 'react'; +import { UserProvider } from "@/context/UserContext"; +import { ReactNode } from "react"; type Props = { children: ReactNode; @@ -6,6 +7,10 @@ type Props = { // Since we have a `not-found.tsx` page on the root, a layout file // is required, even if it's just passing children through. -export default function RootLayout({children}: Props) { - return children; -} \ No newline at end of file +export default function RootLayout({ children }: Props) { + return ( + + {children} + + ); +} diff --git a/frontend-react/src/components/Buttons/BackButton.tsx b/frontend-react/src/components/Buttons/BackButton.tsx new file mode 100644 index 0000000..37382da --- /dev/null +++ b/frontend-react/src/components/Buttons/BackButton.tsx @@ -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 ( + + ); +}; + +export default BackButton; diff --git a/frontend-react/src/components/Buttons/StartpageButton.tsx b/frontend-react/src/components/Buttons/StartpageButton.tsx new file mode 100644 index 0000000..0184067 --- /dev/null +++ b/frontend-react/src/components/Buttons/StartpageButton.tsx @@ -0,0 +1,16 @@ +import { HomeIcon } from "lucide-react"; +import Link from "next/link"; + +const StartpageButton = () => { + return ( + + + Startseite + + ); +}; + +export default StartpageButton; diff --git a/frontend-react/src/components/guards/AuthGuard.tsx b/frontend-react/src/components/guards/AuthGuard.tsx new file mode 100644 index 0000000..0ff5352 --- /dev/null +++ b/frontend-react/src/components/guards/AuthGuard.tsx @@ -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

Lade...

; // Anzeige während des Ladens oder Weiterleitens + } + + return <>{children}; +}; + +export default AuthGuard; diff --git a/frontend-react/src/context/UserContext.tsx b/frontend-react/src/context/UserContext.tsx new file mode 100644 index 0000000..59bdbb8 --- /dev/null +++ b/frontend-react/src/context/UserContext.tsx @@ -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(undefined); + +export const UserProvider = ({ children }: { children: ReactNode }) => { + const [user, setUser] = useState(null); + + return ( + + {children} + + ); +}; + +export const useUser = (): UserContextType => { + const context = useContext(UserContext); + if (!context) { + throw new Error("useUser must be used within a UserProvider"); + } + return context; +};