diff --git a/growbros-frontend/package-lock.json b/growbros-frontend/package-lock.json index a9a56dcbd6dcd5a93908da1c9a56fa9f4665b360..a1a72755876def8d514674d4c32587c117461d30 100644 --- a/growbros-frontend/package-lock.json +++ b/growbros-frontend/package-lock.json @@ -12,11 +12,13 @@ "jwt-decode": "^4.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-modal": "^3.16.1", "react-router-dom": "^6.18.0" }, "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", + "@types/react-modal": "^3.16.3", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react-swc": "^3.3.2", @@ -773,6 +775,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.5", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", @@ -1424,6 +1435,11 @@ "node": ">=0.10.0" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1917,6 +1933,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2076,6 +2100,16 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -2128,6 +2162,34 @@ "react": "^18.2.0" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-modal": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", + "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" + } + }, "node_modules/react-router": { "version": "6.20.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.20.1.tgz", @@ -2468,6 +2530,14 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/growbros-frontend/package.json b/growbros-frontend/package.json index bfd61e675aa1f83015be9acd07421a50610b4893..12710de1d735108819a3d939ab2205e29a287e80 100644 --- a/growbros-frontend/package.json +++ b/growbros-frontend/package.json @@ -14,11 +14,13 @@ "jwt-decode": "^4.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-modal": "^3.16.1", "react-router-dom": "^6.18.0" }, "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", + "@types/react-modal": "^3.16.3", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react-swc": "^3.3.2", diff --git a/growbros-frontend/src/jwt/Cookies.ts b/growbros-frontend/src/jwt/Cookies.ts index 54ff1341cc9011536bdf7259559541ef920e4d62..fbc57dc5ae2fa7ea09fff72a0c2a941842692486 100644 --- a/growbros-frontend/src/jwt/Cookies.ts +++ b/growbros-frontend/src/jwt/Cookies.ts @@ -22,12 +22,12 @@ export function getCookie(name: string | any[]) { } export function deleteJwtCookie() { - document.cookie = `Jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`; console.log("deleted Cookie ") } export function checkJwtStatus(setIsLoggedIn: { (value: SetStateAction<boolean>): void; (arg0: boolean): void; }) { + const location = useLocation(); return useEffect(() => { @@ -45,6 +45,7 @@ export function checkJwtStatus(setIsLoggedIn: { (value: SetStateAction<boolean>) // Der JWT-Cookie ist vorhanden - Überprüfe die Gültigkeit // @ts-ignore const tokenParts = jwtCookie.split('.'); + if (tokenParts.length !== 3) { // Ungültiges JWT-Format (nicht genau 3 Teile) setIsLoggedIn(false); @@ -53,7 +54,7 @@ export function checkJwtStatus(setIsLoggedIn: { (value: SetStateAction<boolean>) } const payload = JSON.parse(atob(tokenParts[1])); - const expirationTime = payload.exp * 1000; //Umwandlung in Millisekunden + const expirationTime = payload.exp*1000; //Umwandlung in Millisekunden console.log(expirationTime) console.log(Date.now()) diff --git a/growbros-frontend/src/pages/Login.tsx b/growbros-frontend/src/pages/Login.tsx index bca3ce7cf6ed933c688b308de205ce4931bad1b3..21c4eed299ff37a0abf020ea54bfdd09f3603792 100644 --- a/growbros-frontend/src/pages/Login.tsx +++ b/growbros-frontend/src/pages/Login.tsx @@ -1,3 +1,4 @@ +import "../stylesheets/RegisterAndLogin.css"; import {useState} from "react"; import {deleteJwtCookie, setCookie} from "../jwt/Cookies.ts"; import {jwtDecode} from "jwt-decode"; @@ -50,26 +51,26 @@ function Login() { return ( <main> <div> - <h2>Login</h2> + <h2 className="h2">Login</h2> <form onSubmit={handleSubmit}> - <div> - <label>E-Mail:</label> - <input + <div className="form-group"> + <input className="input" type="text" value={email} onChange={(e) => setEmail(e.target.value)} + placeholder="Email" /> </div> - <div> - <label>Passwort:</label> - <input + <div className="form-group"> + <input className="input" type="password" value={password} onChange={(e) => setPassword(e.target.value)} + placeholder="Passwort" /> </div> <div> - <button type="submit">Anmelden</button> + <button className="submitButton" type="submit">Anmelden</button> </div> </form> </div> diff --git a/growbros-frontend/src/pages/Register.tsx b/growbros-frontend/src/pages/Register.tsx index 2ee6d9062bdf4c8529e46b84342c57ef9f8f8600..7f56bc005e2128523104bd9df77fa5a4b6ee5049 100644 --- a/growbros-frontend/src/pages/Register.tsx +++ b/growbros-frontend/src/pages/Register.tsx @@ -1,8 +1,9 @@ -import "../stylesheets/Register.css"; +import "../stylesheets/RegisterAndLogin.css"; import {FormEvent, useState} from "react"; import {setCookie} from "../jwt/Cookies.ts"; import { useNavigate } from 'react-router-dom'; import { jwtDecode } from 'jwt-decode'; +import Modal from 'react-modal'; function Register() { @@ -13,8 +14,49 @@ function Register() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); + const [errorModalIsOpen, setErrorModalIsOpen] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + + const isValidEmail = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + + const isValidName = (name: string): boolean => { + return name.trim() !== ''; // Prüfe, ob der Name nicht leer ist + }; + + const isValidPassword = (password: string): boolean => { + return password.length >= 4; // Prüfe, ob das Passwort mindestens 4 Zeichen lang ist + }; + + const openErrorModal = (message: string) => { + setErrorMessage(message); + setErrorModalIsOpen(true); + }; + + const closeErrorModal = () => { + setErrorModalIsOpen(false); + setErrorMessage(''); + }; + const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); //verhindert, dass die Seite neu geladen wird, wenn das Formular abgeschickt wird + + if (!isValidEmail(email)) { + openErrorModal('Ungültige E-Mail-Adresse'); + return; + } + + if (!isValidName(firstname) || !isValidName(lastname)) { + openErrorModal('Vorname und Nachname dürfen nicht leer sein'); + return; + } + + if (!isValidPassword(password)) { + openErrorModal('Das Passwort muss mindestens 4 Zeichen lang sein'); + return; + } await handleRegister(); }; @@ -53,46 +95,56 @@ function Register() { return ( <main> <div className="register"> - <h2>Registrierung</h2> + <h2 className="h2">REGISTRIERUNG</h2> <form onSubmit={handleSubmit}> - <div> - <label>Vorname:</label> - <input + <div className="form-group"> + <input className="input" type="text" value={firstname} onChange={(e) => setFirstname(e.target.value)} + placeholder="Name" /> </div> - <div> - <label>Nachname:</label> - <input + <div className="form-group"> + <input className="input" type="text" value={lastname} onChange={(e) => setLastname(e.target.value)} + placeholder="Nachname" /> </div> - <div > - <label>Mailadresse:</label> - <input + <div className="form-group"> + <input className="input" type="text" value={email} onChange={(e) => setEmail(e.target.value)} + placeholder="Mailadresse" /> </div> - <div> - <label>Passwort:</label> - <input + <div className="form-group"> + <input className="input" type="password" value={password} onChange={(e) => setPassword(e.target.value)} + placeholder="Passwort (mindestens 4 Zeichen)" /> </div> - <div className="submitButton"> - <button type="submit">Anmelden</button> + <div> + <button className="submitButton" type="submit">Anmelden</button> </div> </form> </div> - + <Modal + isOpen={errorModalIsOpen} + onRequestClose={closeErrorModal} + contentLabel="Fehlermeldung" + className="errorField" + > + <div className="error-message"> + <p>{errorMessage}</p> + <button onClick={closeErrorModal}>Schließen</button> + </div> + </Modal> </main> ); } diff --git a/growbros-frontend/src/stylesheets/Register.css b/growbros-frontend/src/stylesheets/Register.css deleted file mode 100644 index 9e2d18bd5da8b40be809912a1982b5eff654628c..0000000000000000000000000000000000000000 --- a/growbros-frontend/src/stylesheets/Register.css +++ /dev/null @@ -1,38 +0,0 @@ -.register { - display: flex; - justify-content: center; - flex-direction: column; - padding-left: 40px; - /*height: 100vh;*/ -} - -form { - display: flex; - flex-direction: column; - width: 600px; - border-radius: 5px; - background-color: dimgrey; - margin-top: 10px; -} - -label { - margin:20px; -} - -input { - padding: 8px; - margin-bottom: 16px; - background-color: #e0e0e0; - border: 1px solid #ccc; - border-radius: 3px; -} - -.submitButton { - padding: 10px; - border-radius: 3px; - cursor: pointer; -} -h2 { - padding-top: 50px; - padding-bottom: 10px; -} diff --git a/growbros-frontend/src/stylesheets/RegisterAndLogin.css b/growbros-frontend/src/stylesheets/RegisterAndLogin.css new file mode 100644 index 0000000000000000000000000000000000000000..5193956190532d2886831298e9b8c0be67469d2d --- /dev/null +++ b/growbros-frontend/src/stylesheets/RegisterAndLogin.css @@ -0,0 +1,68 @@ +.register { + display: flex; + flex-direction: column; + align-items: stretch; +} + +form { + display: flex; + flex-direction: column; + padding: 25px; +} + +.h2 { + background-color: rgb(120, 147, 81); + padding: 15px; +} + +.form-group { + display: flex; + flex-direction: row; + align-content: stretch; +} + +label { + margin:20px; +} + +.input { + background-color: #ffffff; + border-radius: 5px; + color: #1a1a1a; + transition: none; +} + +.input:hover{ + border-color: darkgrey; +} + +.submitButton { + border-radius: 5px; + cursor: pointer; + background-color: rgb(120, 147, 81); + color: #fcfdf7; + border: none; + font-size: 1em; + font-weight: 500; + line-height: unset; + transition: none; +} + +.submitButton:hover { + border-color: #f2f2f2; + background-color: rgb(252, 253, 247); + color: rgb(120, 147, 81); +} + +.error-message { + color: #ac3838; + text-align: center; +} + +.errorField { + margin: auto; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} diff --git a/requests.http b/requests.http index d51fde8dc0b81d562777227dd8523f0330788ad3..b9ac6a0fff7cab4522e19d7bc0270e4d124a2dcf 100644 --- a/requests.http +++ b/requests.http @@ -21,3 +21,15 @@ content-type: application/json "email": "lukas.karsch@gmx.de", "password": "12345678" } + +### Create account +POST http://localhost:8080/api/v1/auth/register +content-type: application/json + +{ + "email": "hannah.zing@icloud.com", + "firstname": "Hännah", + "lastname": "Zink", + "password": "hhh" +} +