From 97b0e5bdd36abe1c5aeb92b2c5078afd9ca34a8d Mon Sep 17 00:00:00 2001 From: Anika Raemer Date: Sun, 12 Oct 2025 08:36:41 +0200 Subject: [PATCH] style login page --- frontend/package-lock.json | 17 +++ frontend/package.json | 2 + frontend/src/App.css | 23 ++-- frontend/src/components/LoginPage.tsx | 103 ++++++++++-------- .../src/components/basics/PasswordField.tsx | 39 +++++++ .../src/components/recipes/RecipeListPage.tsx | 4 +- 6 files changed, 128 insertions(+), 60 deletions(-) create mode 100644 frontend/src/components/basics/PasswordField.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7c7cea3..a008f21 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,6 +11,8 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "lucide": "^0.545.0", + "lucide-react": "^0.545.0", "react": "^19.1.1", "react-dom": "^19.1.1", "react-router-dom": "^7.8.2" @@ -3371,6 +3373,21 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide": { + "version": "0.545.0", + "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.545.0.tgz", + "integrity": "sha512-mrBH0upkb1TH8ZLkf0XERQAKMdTJ1C+Offr7eSvanmdQ7JrV8jkrFw5jgRAS8fygiZgG4X9mfEg/BXCL+mNMRw==", + "license": "ISC" + }, + "node_modules/lucide-react": { + "version": "0.545.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.545.0.tgz", + "integrity": "sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.18", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", diff --git a/frontend/package.json b/frontend/package.json index 05a2c30..6a0c46a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,8 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", + "lucide": "^0.545.0", + "lucide-react": "^0.545.0", "react": "^19.1.1", "react-dom": "^19.1.1", "react-router-dom": "^7.8.2" diff --git a/frontend/src/App.css b/frontend/src/App.css index cc584c0..1b3fd1b 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -4,20 +4,15 @@ /* Custom recipe app styles */ @layer components{ - .app-container { - @apply p-4 flex; + + /* background */ + .app-bg { + @apply flex items-center w-screen justify-center min-h-screen bg-gray-50 } + /* headings */ .content-title{ - @apply text-3xl font-black mb-8 text-blue-400; - } - - .main-view { - @apply w-2/3 pl-4; - } - - .recipe-image { - @apply my-4 w-64; + @apply text-3xl font-black mb-8 text-blue-900; } .section-heading { @@ -28,10 +23,16 @@ @apply font-semibold mb-2 mt-4; } + /* labels */ .label { @apply text-gray-600 } + /* errors */ + .error-text { + @apply text-sm text-red-600 + } + /* buttons */ .basic-button{ @apply px-4 py-2 shadow-md rounded-lg whitespace-nowrap diff --git a/frontend/src/components/LoginPage.tsx b/frontend/src/components/LoginPage.tsx index eb23cdd..ea80442 100644 --- a/frontend/src/components/LoginPage.tsx +++ b/frontend/src/components/LoginPage.tsx @@ -1,55 +1,64 @@ import { useState } from "react"; -import Button from "./basics/Button"; +import Button, { ButtonType } from "./basics/Button"; import type { LoginRequestDto } from "../api/dtos/LoginRequestDto"; import type { LoginResponseDto } from "../api/dtos/LoginResponseDto"; import { login } from "../api/points/AuthPoint"; import { getRecipeListUrl } from "../routes"; import { useNavigate } from "react-router-dom"; +import PasswordField from "./basics/PasswordField"; -export default function LoginPage(){ - const [userName, setUserName] = useState(""); - const [password, setPassword] = useState(""); - const navigate = useNavigate(); - const executeLogin = async () =>{ - const dto : LoginRequestDto = { - userName: userName, - password: password - } - try{ - // @todo move to auth handler - console.log("trying to log in with " + dto.userName) - const loginResponse : LoginResponseDto = await login(dto); - localStorage.setItem("session", JSON.stringify(loginResponse)); - console.log("Successfully logged on with user " + loginResponse.userData?.userName) - navigate(getRecipeListUrl()); - } catch(err){ - // @todo show error in GUI - console.error(err); - } +export default function LoginPage() { + const [userName, setUserName] = useState(""); + const [password, setPassword] = useState(""); + const [errorMessage, setErrorMessage] = useState(null); + + const navigate = useNavigate(); + + const executeLogin = async () => { + const dto: LoginRequestDto = { + userName, + password, + }; + + try { + console.log("Trying to log in with " + dto.userName); + const loginResponse: LoginResponseDto = await login(dto); + localStorage.setItem("session", JSON.stringify(loginResponse)); + console.log("Successfully logged in as " + loginResponse.userData?.userName); + setErrorMessage(null); + navigate(getRecipeListUrl()); + } catch (err: any) { + console.error("Login failed:", err); + setErrorMessage("Login fehlgeschlagen! Bitte überprüfe Benutzername und Passwort."); } - return( -
- { - setUserName(e.target.value) - }} - /> - {/* @todo Password mode!!! */} - { - setPassword(e.target.value) - }} - /> -
- ); -} \ No newline at end of file + }; + + return ( +
+
+

Anmeldung

+ + setUserName(e.target.value)} + /> + + + + {errorMessage && ( +

{errorMessage}

+ )} + +
+
+ ); +} diff --git a/frontend/src/components/basics/PasswordField.tsx b/frontend/src/components/basics/PasswordField.tsx new file mode 100644 index 0000000..1c0175e --- /dev/null +++ b/frontend/src/components/basics/PasswordField.tsx @@ -0,0 +1,39 @@ +import { Eye, EyeOff } from "lucide-react"; +import { useState } from "react"; + +type PasswordFieldProps = { + onPasswordChanged: (password : string) => void +} + +/** + * Password field component + */ +export default function PasswordField({onPasswordChanged} : PasswordFieldProps){ + const [showPassword, setShowPassword] = useState(false); + const [password, setPassword] = useState(""); + const iconSize = 20; + + const changePassword = (password : string) => { + setPassword(password); + onPasswordChanged(password) + } + return ( +
+ changePassword(e.target.value)} + /> + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/recipes/RecipeListPage.tsx b/frontend/src/components/recipes/RecipeListPage.tsx index 591b57a..4a17300 100644 --- a/frontend/src/components/recipes/RecipeListPage.tsx +++ b/frontend/src/components/recipes/RecipeListPage.tsx @@ -39,12 +39,12 @@ export default function RecipeListPage() { if(!recipeList) { return
Loading!
} return ( /*Container spanning entire screen used to center content horizontally */ -
+
{/* Container defining the maximum width of the content */}
{/* Header - remains in position when scrolling */}
-

Recipes

+

Recipes