style login page
This commit is contained in:
parent
575eecfc69
commit
97b0e5bdd3
6 changed files with 128 additions and 60 deletions
17
frontend/package-lock.json
generated
17
frontend/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
39
frontend/src/components/basics/PasswordField.tsx
Normal file
39
frontend/src/components/basics/PasswordField.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue