style login page

This commit is contained in:
Anika Raemer 2025-10-12 08:36:41 +02:00
parent 575eecfc69
commit 97b0e5bdd3
6 changed files with 128 additions and 60 deletions

View file

@ -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",

View file

@ -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"

View file

@ -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

View file

@ -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(){
export default function LoginPage() {
const [userName, setUserName] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [errorMessage, setErrorMessage] = useState<string | null>(null);
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);
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 on with user " + loginResponse.userData?.userName)
console.log("Successfully logged in as " + loginResponse.userData?.userName);
setErrorMessage(null);
navigate(getRecipeListUrl());
} catch(err){
// @todo show error in GUI
console.error(err);
} catch (err: any) {
console.error("Login failed:", err);
setErrorMessage("Login fehlgeschlagen! Bitte überprüfe Benutzername und Passwort.");
}
}
return(
<div className="p-6 max-w-2xl mx-auto">
};
return (
<div className="app-bg">
<div className="flex flex-col gap-3 max-w-sm w-full mx-auto p-6 bg-white rounded-2xl shadow-md">
<h2 className="content-title text-center mb-2">Anmeldung</h2>
<input
className="input-field"
placeholder="Benutzername"
value = {userName}
onChange={e => {
setUserName(e.target.value)
}}
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
{/* @todo Password mode!!! */}
<input
className="input-field"
placeholder="Passwort"
value = {password}
onChange={e => {
setPassword(e.target.value)
}}
<PasswordField
onPasswordChanged = {setPassword}
/>
{errorMessage && (
<p className="error-text text-center">{errorMessage}</p>
)}
<Button
buttonType={ButtonType.PrimaryButton}
text="Login"
onClick= {executeLogin}
onClick={executeLogin}
/>
</div>
</div>
);
}

View file

@ -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<string>("");
const iconSize = 20;
const changePassword = (password : string) => {
setPassword(password);
onPasswordChanged(password)
}
return (
<div className="relative">
<input
className="input-field pr-10"
type={showPassword ? "text" : "password"}
placeholder="Passwort"
value={password}
onChange={(e) => changePassword(e.target.value)}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700"
aria-label={showPassword ? "Passwort ausblenden" : "Passwort anzeigen"}
>
{showPassword ? <EyeOff size={iconSize} /> : <Eye size={iconSize} />}
</button>
</div>
);
}

View file

@ -39,12 +39,12 @@ export default function RecipeListPage() {
if(!recipeList) { return <div>Loading!</div>}
return (
/*Container spanning entire screen used to center content horizontally */
<div className="w-screen min-h-screen flex justify-center">
<div className="app-bg">
{/* Container defining the maximum width of the content */}
<div className="bg-gray-100 w-full min-h-screen max-w-6xl shadow-xl p-8">
{/* Header - remains in position when scrolling */}
<div className="sticky bg-gray-100 top-0 left-0 right-0 pb-4 border-b-2 border-gray-300">
<h1 className="content-title text-blue-900">Recipes</h1>
<h1 className="content-title">Recipes</h1>
<RecipeListToolbar
onAddClicked={handleAdd}
onSearchStringChanged={setSearchString}