From ab35f73158e638b2b1e314548719b65ebd189e13 Mon Sep 17 00:00:00 2001 From: zsola03 Date: Sun, 26 Oct 2025 19:46:13 +0100 Subject: [PATCH] =?UTF-8?q?userdetails,resetpass=20m=C3=BCk=C3=B6d=C3=B6k?= =?UTF-8?q?=C3=A9pes=20lett?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SerpentRace_Frontend/src/App.jsx | 4 + SerpentRace_Frontend/src/api/userApi.js | 49 ++ .../src/components/Navbar/Navbar.jsx | 2 + .../components/Userdetails/Userdetails.jsx | 600 ++++++++++++++---- .../src/pages/Auth/LoginForm.jsx | 108 +++- .../src/pages/Auth/ResetPassword.jsx | 117 +++- .../src/pages/Auth/ResetPasswordRedirect.jsx | 30 + 7 files changed, 773 insertions(+), 137 deletions(-) create mode 100644 SerpentRace_Frontend/src/pages/Auth/ResetPasswordRedirect.jsx diff --git a/SerpentRace_Frontend/src/App.jsx b/SerpentRace_Frontend/src/App.jsx index 71e6aa74..27e876f4 100644 --- a/SerpentRace_Frontend/src/App.jsx +++ b/SerpentRace_Frontend/src/App.jsx @@ -6,6 +6,7 @@ import EmailVerification from "./pages/Auth/EmailVerification" import Test from "./pages/Testing/Test" import ForgotPassword from "./pages/Auth/ForgotPassword" import ResetPassword from "./pages/Auth/ResetPassword" +import ResetPasswordRedirect from "./pages/Auth/ResetPasswordRedirect" import Landingpage from "./pages/Landing/Landingpage" import Home from "./pages/Landing/Home" import DeckManagerPage from "./pages/Decks/DeckManagerPage" @@ -16,6 +17,7 @@ import ScrollToTop from "./components/ScrollToTop" import GameScreen from "./pages/Game/GameScreen" import Reports from "./pages/Report/Reports" import Lobby from "./pages/Lobby/Lobby" +import ProfileCard from "./components/Userdetails/Userdetails" import { ToastConfig } from "./components/Toastify/toastifyServices" // ✅ fontos: named import, nem default! import VerifyEmailPage from "./pages/Auth/VerifyEmailPage" @@ -51,6 +53,7 @@ function App() { <> + } /> } /> } /> } /> @@ -59,6 +62,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/SerpentRace_Frontend/src/api/userApi.js b/SerpentRace_Frontend/src/api/userApi.js index ae8cf15c..af231c48 100644 --- a/SerpentRace_Frontend/src/api/userApi.js +++ b/SerpentRace_Frontend/src/api/userApi.js @@ -57,4 +57,53 @@ export const verifyEmail = async (token) => { } }; +// Get current user profile +export const getUserProfile = async () => { + try { + const response = await apiClient.get("/users/profile"); + return response.data; + } catch (error) { + throw error; + } +}; + +// Update current user profile +export const updateUserProfile = async (data) => { + try { + const response = await apiClient.patch("/users/profile", data); + return response.data; + } catch (error) { + throw error; + } +}; + +// Delete current user profile +export const deleteUserProfile = async () => { + try { + const response = await apiClient.delete("/users/profile"); + return response.data; + } catch (error) { + throw error; + } +}; + +// Request password reset +export const forgotPassword = async (email) => { + try { + const response = await apiClient.post("/users/forgot-password", { email }); + return response.data; + } catch (error) { + throw error; + } +}; + +// Reset password with token +export const resetPassword = async (token, newPassword) => { + try { + const response = await apiClient.post("/users/reset-password", { token, newPassword }); + return response.data; + } catch (error) { + throw error; + } +}; diff --git a/SerpentRace_Frontend/src/components/Navbar/Navbar.jsx b/SerpentRace_Frontend/src/components/Navbar/Navbar.jsx index 68d471aa..67610804 100644 --- a/SerpentRace_Frontend/src/components/Navbar/Navbar.jsx +++ b/SerpentRace_Frontend/src/components/Navbar/Navbar.jsx @@ -43,6 +43,7 @@ const Navbar = () => { <> Paklik Statisztikák + Profil )} @@ -128,6 +129,7 @@ const Navbar = () => { <> setMenuOpen(false)} className={navLinkClass}>Paklik setMenuOpen(false)} className={navLinkClass}>Statisztikák + setMenuOpen(false)} className={navLinkClass}>Profil )} setMenuOpen(false)} className={navLinkClassPlay}>Játék diff --git a/SerpentRace_Frontend/src/components/Userdetails/Userdetails.jsx b/SerpentRace_Frontend/src/components/Userdetails/Userdetails.jsx index 3fcd1134..676cfcc6 100644 --- a/SerpentRace_Frontend/src/components/Userdetails/Userdetails.jsx +++ b/SerpentRace_Frontend/src/components/Userdetails/Userdetails.jsx @@ -1,54 +1,210 @@ -import React, { useState } from "react" +import React, { useState, useEffect } from "react" +import { useNavigate } from "react-router-dom" import { FaCommentDots, FaUserFriends, FaBriefcase, - FaFacebookF, - FaTwitter, - FaDribbble, - FaSun, - FaMoon, - FaMedal + FaMedal, + FaEdit, + FaSave, + FaTimes, + FaTrash, + FaEye, + FaEyeSlash } from "react-icons/fa" +import Navbar from "../Navbar/Navbar" +import Footer from "../Footer/Footer" +import Background from "../../assets/backgrounds/Background" +import { getUserProfile, updateUserProfile, deleteUserProfile } from "../../api/userApi" +import { notifySuccess, notifyError, notifyWarning } from "../Toastify/toastifyServices" const ProfileCard = () => { - const [darkMode, setDarkMode] = useState(false) - const activityLevel = 87 - const isPremium = true + const navigate = useNavigate() + + // State + const [user, setUser] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const [isEditing, setIsEditing] = useState(false) + const [showPassword, setShowPassword] = useState(false) + const [showDeleteModal, setShowDeleteModal] = useState(false) + + // Edit form state + const [editForm, setEditForm] = useState({ + username: "", + email: "", + fname: "", + lname: "", + phone: "", + password: "" + }) + // Load user profile on mount + useEffect(() => { + loadUserProfile() + }, []) + + const loadUserProfile = async () => { + setIsLoading(true) + try { + const data = await getUserProfile() + setUser(data) + setEditForm({ + username: data.username || "", + email: data.email || "", + fname: data.fname || "", + lname: data.lname || "", + phone: data.phone || "", + password: "" + }) + } catch (error) { + console.error('Failed to load user profile:', error) + notifyError('Hiba történt a profil betöltése során') + } finally { + setIsLoading(false) + } + } + + const handleEditToggle = () => { + if (isEditing) { + setEditForm({ + username: user.username || "", + email: user.email || "", + fname: user.fname || "", + lname: user.lname || "", + phone: user.phone || "", + password: "" + }) + } + setIsEditing(!isEditing) + } + + const handleInputChange = (e) => { + const { name, value } = e.target + setEditForm(prev => ({ ...prev, [name]: value })) + } + + const handleSaveProfile = async () => { + try { + const updates = {} + if (editForm.username !== user.username) updates.username = editForm.username + if (editForm.email !== user.email) updates.email = editForm.email + if (editForm.fname !== user.fname) updates.fname = editForm.fname + if (editForm.lname !== user.lname) updates.lname = editForm.lname + if (editForm.phone !== user.phone) updates.phone = editForm.phone + if (editForm.password && editForm.password.trim() !== "") updates.password = editForm.password + + if (Object.keys(updates).length === 0) { + notifyWarning('Nincs változtatás') + return + } + + const updatedUser = await updateUserProfile(updates) + setUser(updatedUser) + setIsEditing(false) + setEditForm(prev => ({ ...prev, password: "" })) + notifySuccess('Profil sikeresen frissítve!') + } catch (error) { + console.error('Failed to update profile:', error) + const errorMessage = error?.response?.data?.error || error.message || 'Ismeretlen hiba' + notifyError('Hiba történt a mentés során: ' + errorMessage) + } + } + + const handleDeleteProfile = () => { + setShowDeleteModal(true) + } + + const handleConfirmDelete = async () => { + try { + await deleteUserProfile() + notifySuccess("Profil sikeresen törölve!") + localStorage.removeItem("authLevel") + localStorage.removeItem("username") + navigate("/") + } catch (err) { + console.error("Profil törlési hiba:", err) + notifyError(err.response?.data?.message || "Hiba a profil törlésekor!") + } finally { + setShowDeleteModal(false) + } + } + + const handleCancelDelete = () => { + setShowDeleteModal(false) + } + + if (isLoading) { + return ( +
+
+ +
+
+ +
+
+
+
+

Betöltés...

+
+
+
+ ) + } + + if (!user) { + return ( +
+
+ +
+
+ +
+
+
+
😢
+

Nem sikerült betölteni a profilt

+
+
+
+ ) + } + + const isPremium = user.state === 2 + const activityLevel = user.activity_level || 65 + let activityColor = "" let activityEmoji = "" let blocksToColor = 1 - let celebrationEmoji = "" if (activityLevel <= 24) { - activityColor = "red-600" + activityColor = "error" activityEmoji = "😞" blocksToColor = 1 } else if (activityLevel <= 49) { - activityColor = "orange-500" + activityColor = "warning" activityEmoji = "😐" blocksToColor = 2 } else if (activityLevel <= 74) { - activityColor = "yellow-400" + activityColor = "secondary" activityEmoji = "🙂" blocksToColor = 3 } else { - activityColor = "emerald-500" + activityColor = "success" activityEmoji = "😄" blocksToColor = 4 - celebrationEmoji = "🎉" } const colorMap = { - "red-600": "#dc2626", - "orange-500": "#f97316", - "yellow-400": "#facc15", - "emerald-500": "#10b981" + success: "#5fa985", + warning: "#e6c04f", + error: "#e15b64", + secondary: "#8d8e83" } const getBlockStyle = (index) => ({ - backgroundColor: index < blocksToColor ? colorMap[activityColor] : (darkMode ? "#4b5563" : "#d1d5db") + backgroundColor: index < blocksToColor ? colorMap[activityColor] : "#314045" }) const stats = [ @@ -61,101 +217,333 @@ const ProfileCard = () => { const badges = ["🏆", "🔥", "🎯", "🧠", "💎", "🚀"] return ( -
-
- - - -
- Avatar - -
-

- BÉKAAAAA -

-
-
- {isPremium ? "Premium Account" : "Free Account"} -
-
-

- Active | Male | 23.05.1992 -

-
- -
-

- Activity Level: {activityLevel}% - {activityEmoji} - {celebrationEmoji && {celebrationEmoji}} -

-
- -
- {[0, 1, 2, 3].map(i => ( -
- ))} -
- - {/* Badge szekció */} -
-

Badge-ek

-
- {badges.map((badge, i) => ( - - {badge} - - ))} +
+
+ +
+
+ +
+
+
+ + {/* Hero Section - Cover Photo */} +
+
+
- {/* Statisztikák */} -
- {stats.map((s, i) => ( -
-
{s.icon}
-

{s.value}

-

{s.label}

+ {/* Main Profile Card */} +
+ + {/* Avatar & Name Section */} +
+ {/* Avatar */} +
+
+ + {user.lname?.[0]?.toUpperCase() || ''}.{user.fname?.[0]?.toUpperCase() || ''} + +
- ))} -
-

- Gyere és játsz velünk! -

+ {/* Name & Status */} +
+
+

+ {user.username} +

+
+ {isPremium ? "👑 Premium" : user.state === 1 ? "✓ Verified" : "Free"} +
+
+

+ {user.fname} {user.lname} +

+
-
- - - + {/* Action Buttons */} + {!isEditing && ( +
+ + +
+ )} +
+ + {/* Content Grid */} +
+ + {!isEditing ? ( + <> + {/* Contact Info & Activity Row */} +
+ + {/* Contact Information */} +
+

+ 📋 Kapcsolati adatok +

+ +
+
+ 📧 +
+

Email

+

{user.email}

+
+
+ +
+ 📱 +
+

Telefon

+

{user.phone || "Nincs megadva"}

+
+
+
+
+ + {/* Activity Level */} +
+

+ ⚡ Aktivitás +

+
+
+ Szint + + {activityLevel}% {activityEmoji} + +
+
+ {[0, 1, 2, 3].map((i) => ( +
+ ))} +
+
+
+
+ + {/* Stats Grid */} +
+

+ 📊 Statisztikák +

+
+ {stats.map((s, i) => ( +
+
+
{s.icon}
+

{s.value}

+

{s.label}

+
+
+
+ ))} +
+
+ + {/* Badges */} +
+

+ 🏆 Badge-ek +

+
+ {badges.map((badge, i) => ( +
+ {badge} +
+ ))} +
+
+ + ) : ( + <> + {/* Edit Mode */} +
+

+ ✏️ Profil szerkesztése +

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+
+
+ + {/* Save/Cancel buttons */} +
+ + +
+
+ + )} +
-
+
+ + {/* Delete Confirmation Modal */} + {showDeleteModal && ( +
+
+

+ Biztosan törölni szeretnéd a profilodat? +

+

+ Ez a művelet nem visszavonható! +

+
+ + +
+
+
+ )} + +
) } export default ProfileCard - - -import UserProfile from "../../components/Userdetails/Userdetails.jsx" - diff --git a/SerpentRace_Frontend/src/pages/Auth/LoginForm.jsx b/SerpentRace_Frontend/src/pages/Auth/LoginForm.jsx index 308f76a0..c0a7dac2 100644 --- a/SerpentRace_Frontend/src/pages/Auth/LoginForm.jsx +++ b/SerpentRace_Frontend/src/pages/Auth/LoginForm.jsx @@ -4,7 +4,7 @@ import Button from "../../components/Buttons/Button" import { motion } from "framer-motion" import { useState, useEffect } from "react" import { useLocation, useNavigate } from "react-router-dom" -import { login } from "../../api/userApi" +import { login, forgotPassword } from "../../api/userApi" import { FaArrowLeft } from "react-icons/fa" export default function LoginForm() { @@ -15,11 +15,21 @@ export default function LoginForm() { const navigate = useNavigate() const [showSuccess, setShowSuccess] = useState(false) const [showErrorPopup, setShowErrorPopup] = useState(false) + const [showForgotPasswordModal, setShowForgotPasswordModal] = useState(false) + const [forgotEmail, setForgotEmail] = useState("") + const [forgotPasswordMessage, setForgotPasswordMessage] = useState("") + const [successMessage, setSuccessMessage] = useState("") + const [isSendingEmail, setIsSendingEmail] = useState(false) useEffect(() => { if (location.state && location.state.success) { + const message = location.state.message || "Sikeres regisztráció! Az email ellenőrzése után be tudsz lépni." + setSuccessMessage(message) setShowSuccess(true) - setTimeout(() => setShowSuccess(false), 4000) + setTimeout(() => { + setShowSuccess(false) + setSuccessMessage("") + }, 4000) } }, [location.state]) @@ -67,6 +77,32 @@ export default function LoginForm() { }) } + const handleForgotPassword = async (e) => { + e.preventDefault() + setForgotPasswordMessage("") + + if (!forgotEmail || !validateEmail(forgotEmail)) { + setForgotPasswordMessage("Kérlek adj meg egy érvényes email címet!") + return + } + + setIsSendingEmail(true) + + try { + await forgotPassword(forgotEmail) + setForgotPasswordMessage("Jelszó visszaállító email elküldve! Ellenőrizd a postaládád.") + setTimeout(() => { + setShowForgotPasswordModal(false) + setForgotEmail("") + setForgotPasswordMessage("") + setIsSendingEmail(false) + }, 3000) + } catch (error) { + setForgotPasswordMessage("Hiba történt. Próbáld újra később!") + setIsSendingEmail(false) + } + } + return ( - Sikeres regisztráció! Az email ellenőrzése után be tudsz lépni. + {successMessage || "Sikeres művelet!"}
)} @@ -103,7 +139,7 @@ export default function LoginForm() {
)} -
+ setPassword(e.target.value)} /> + + {/* Elfelejtett jelszó link */} +
+ setShowForgotPasswordModal(true)} + className="text-sm text-green-600 hover:text-green-700 hover:underline cursor-pointer font-medium" + > + Elfelejtetted a jelszavad? + +
+ + +
+ +
+
+ )} ) } diff --git a/SerpentRace_Frontend/src/pages/Auth/ResetPassword.jsx b/SerpentRace_Frontend/src/pages/Auth/ResetPassword.jsx index 1dccb19b..a7180915 100644 --- a/SerpentRace_Frontend/src/pages/Auth/ResetPassword.jsx +++ b/SerpentRace_Frontend/src/pages/Auth/ResetPassword.jsx @@ -1,26 +1,63 @@ // src/pages/Auth/ResetPassword.jsx // Új jelszó megadása -import { useState } from "react"; +import { useState, useEffect } from "react"; +import { useSearchParams, useNavigate } from "react-router-dom"; import Background from "../../assets/backgrounds/Background"; import { motion } from "framer-motion"; import Button from "../../components/Buttons/Button"; import InputBox from "../../components/Inputs/InputBox"; +import { resetPassword } from "../../api/userApi"; +import { FaArrowLeft } from "react-icons/fa"; export default function ResetPassword() { const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [error, setError] = useState(""); + const [success, setSuccess] = useState(false); + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const token = searchParams.get("token"); - const handleSubmit = (e) => { + useEffect(() => { + if (!token) { + setError("Érvénytelen vagy hiányzó token!"); + } + }, [token]); + + const handleSubmit = async (e) => { e.preventDefault(); - if (password !== confirmPassword) { - setError("A jelszavak nem egyeznek."); + setError(""); + + if (!password || !confirmPassword) { + setError("Minden mező kitöltése kötelező!"); return; } - setError(""); - // Backend API - console.log("Új jelszó:", password); + + if (password.length < 6) { + setError("A jelszónak legalább 6 karakter hosszúnak kell lennie!"); + return; + } + + if (password !== confirmPassword) { + setError("A jelszavak nem egyeznek!"); + return; + } + + if (!token) { + setError("Érvénytelen token!"); + return; + } + + try { + await resetPassword(token, password); + setSuccess(true); + setTimeout(() => { + navigate("/login", { state: { success: true, message: "Jelszó sikeresen megváltoztatva! Jelentkezz be az új jelszóval." } }); + }, 2000); + } catch (err) { + setError(err.response?.data?.message || "Hiba történt a jelszó visszaállítása során!"); + } }; return ( @@ -28,32 +65,58 @@ export default function ResetPassword() {
-

+ {/* Vissza gomb */} +
navigate("/login")} + > + + + Vissza a bejelentkezéshez + +
+ +

Új jelszó megadása

-
- setPassword(e.target.value)} - /> - setConfirmPassword(e.target.value)} - /> - {error && ( -
{error}
- )} -
diff --git a/SerpentRace_Frontend/src/pages/Auth/ResetPasswordRedirect.jsx b/SerpentRace_Frontend/src/pages/Auth/ResetPasswordRedirect.jsx new file mode 100644 index 00000000..1f8ecff4 --- /dev/null +++ b/SerpentRace_Frontend/src/pages/Auth/ResetPasswordRedirect.jsx @@ -0,0 +1,30 @@ +// src/pages/Auth/ResetPasswordRedirect.jsx +// Redirect component for /api/auth/reset-password links from emails + +import { useEffect } from "react"; +import { useSearchParams, useNavigate } from "react-router-dom"; + +export default function ResetPasswordRedirect() { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + + useEffect(() => { + const token = searchParams.get("token"); + if (token) { + // Redirect to the actual reset password page + navigate(`/reset-password?token=${token}`, { replace: true }); + } else { + // No token, redirect to login + navigate("/login", { replace: true }); + } + }, [searchParams, navigate]); + + return ( +
+
+
+

Átirányítás...

+
+
+ ); +}