Files
SerpentRace/SerpentRace_Frontend/src/components/Userdetails/Userdetails.jsx
T
GitG0r0 6d25a499b2 feat: Centralized navigation system with HandleNavigate hook
BREAKING CHANGE: Replaced all direct useNavigate() usage with HandleNavigate hook

## Summary
- Complete frontend navigation refactoring
- Centralized route management with routes.js
- Converted 18+ components to use HandleNavigate
- Enhanced navigation with 20+ type-safe functions

## New Files
- src/utils/routes.js - Central route constants and helpers
- Documentations/FRONTEND_CODING_GUIDELINES.md - Frontend best practices (300+ lines)
- Documentations/NAVIGATION_REFACTORING_REPORT.md - Detailed refactoring report (400+ lines)

## Modified Components (18+)
### Pages
- Home.jsx, LoginForm.jsx, RegisterForm.jsx
- ResetPassword.jsx, VerifyEmailPage.jsx
- DeckCreator.jsx, Card_display.jsx
- Lobby.jsx, GameTest.jsx, ChooseDeck.jsx, PlayerSetup.jsx
- Landingpage.jsx

### Components
- Userdetails.jsx, DeckInfoPopUp.jsx
- PlayMenu.jsx, LandingPage.jsx, DeckManager.jsx

### Hooks
- useRequireAuth.jsx

### Core
- App.jsx - Route constants integration
- HandleNavigate.jsx - Enhanced with 20+ navigation functions

## Key Improvements
 Type-safe navigation (goDeckDetails(id) vs navigate('/deck/'+id))
 Automatic scroll management
 Centralized state passing
 Single source of truth for routes
 Backwards compatibility aliases
 Zero compile errors
 Production ready

## Validation
- useNavigate: Only in HandleNavigate.jsx
- navigate() calls: 0 direct usage
- Compile errors: 0
- Documentation: Complete
2025-11-17 09:07:05 +01:00

550 lines
24 KiB
React
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from "react"
import HandleNavigate from "../../utils/HandleNavigate/HandleNavigate"
import {
FaCommentDots,
FaUserFriends,
FaBriefcase,
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 { goLanding } = HandleNavigate()
// 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")
goLanding()
} 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 (
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">
<div className="fixed inset-0 -z-10 pointer-events-none">
<Background />
</div>
<div className="fixed top-0 left-0 right-0 z-30">
<Navbar />
</div>
<main className="flex-1 min-h-[calc(100vh-64px)] flex mt-[64px] items-center justify-center">
<div className="text-center">
<div className="text-6xl mb-4"></div>
<p className="text-xl text-[color:var(--color-text)]">Betöltés...</p>
</div>
</main>
</div>
)
}
if (!user) {
return (
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">
<div className="fixed inset-0 -z-10 pointer-events-none">
<Background />
</div>
<div className="fixed top-0 left-0 right-0 z-30">
<Navbar />
</div>
<main className="flex-1 min-h-[calc(100vh-64px)] flex mt-[64px] items-center justify-center">
<div className="text-center">
<div className="text-6xl mb-4">😢</div>
<p className="text-xl text-[color:var(--color-text)]">Nem sikerült betölteni a profilt</p>
</div>
</main>
</div>
)
}
const isPremium = user.state === 2
const activityLevel = user.activity_level || 65
let activityColor = ""
let activityEmoji = ""
let blocksToColor = 1
if (activityLevel <= 24) {
activityColor = "error"
activityEmoji = "😞"
blocksToColor = 1
} else if (activityLevel <= 49) {
activityColor = "warning"
activityEmoji = "😐"
blocksToColor = 2
} else if (activityLevel <= 74) {
activityColor = "secondary"
activityEmoji = "🙂"
blocksToColor = 3
} else {
activityColor = "success"
activityEmoji = "😄"
blocksToColor = 4
}
const colorMap = {
success: "#5fa985",
warning: "#e6c04f",
error: "#e15b64",
secondary: "#8d8e83"
}
const getBlockStyle = (index) => ({
backgroundColor: index < blocksToColor ? colorMap[activityColor] : "#314045"
})
const stats = [
{ label: "Játékok", value: 1256, icon: <FaCommentDots /> },
{ label: "Barátok", value: 8562, icon: <FaUserFriends /> },
{ label: "Győzelmek", value: 189, icon: <FaBriefcase /> },
{ label: "Badge-ek", value: 6, icon: <FaMedal /> }
]
const badges = ["🏆", "🔥", "🎯", "🧠", "💎", "🚀"]
return (
<div className="w-full min-h-screen flex flex-col relative overflow-x-hidden">
<div className="fixed inset-0 -z-10 pointer-events-none">
<Background />
</div>
<div className="fixed top-0 left-0 right-0 z-30">
<Navbar />
</div>
<main className="flex-1 min-h-[calc(100vh-64px)] flex mt-[64px] flex-col items-center justify-center py-8 px-4">
<div className="relative max-w-5xl w-full animate-fadeInUp">
{/* Hero Section - Cover Photo */}
<div className="relative rounded-t-2xl overflow-hidden bg-gradient-to-r from-[color:var(--color-mint)] via-[color:var(--color-success)] to-[color:var(--color-mint)] h-32 shadow-lg">
<div className="absolute inset-0 opacity-10">
<div className="absolute inset-0" style={{
backgroundImage: 'radial-gradient(circle, white 1px, transparent 1px)',
backgroundSize: '20px 20px'
}}></div>
</div>
</div>
{/* Main Profile Card */}
<div className="relative bg-[color:var(--color-card)] rounded-b-2xl shadow-2xl border-2 border-[color:var(--color-surface-selected)] -mt-16">
{/* Avatar & Name Section */}
<div className="flex flex-col sm:flex-row items-center sm:items-end gap-6 px-8 pb-6">
{/* Avatar */}
<div className="relative -mt-12 flex-shrink-0">
<div className="w-32 h-32 rounded-2xl bg-gradient-to-br from-[color:var(--color-mint)] to-[color:var(--color-success)] flex items-center justify-center shadow-2xl ring-4 ring-[color:var(--color-card)] transform hover:scale-105 transition-transform">
<span className="text-white text-4xl font-bold">
{user.lname?.[0]?.toUpperCase() || ''}.{user.fname?.[0]?.toUpperCase() || ''}
</span>
</div>
</div>
{/* Name & Status */}
<div className="flex-1 text-center sm:text-left">
<div className="flex flex-col sm:flex-row sm:items-center gap-3 mb-2">
<h1 className="text-3xl font-bold text-[color:var(--color-text)]">
{user.username}
</h1>
<div
className={`inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold shadow-md ${
isPremium
? "bg-gradient-to-r from-[color:var(--color-mint)] to-[color:var(--color-success)] text-white"
: "bg-[color:var(--color-surface)] text-[color:var(--color-text-muted)] border border-[color:var(--color-surface-selected)]"
}`}
>
{isPremium ? "👑 Premium" : user.state === 1 ? "✓ Verified" : "Free"}
</div>
</div>
<p className="text-lg text-[color:var(--color-text)] font-medium">
{user.fname} {user.lname}
</p>
</div>
{/* Action Buttons */}
{!isEditing && (
<div className="flex gap-2 flex-shrink-0">
<button
onClick={handleEditToggle}
className="px-4 py-2 rounded-lg bg-[color:var(--color-mint)] text-white font-semibold hover:opacity-90 transition-all flex items-center gap-2 shadow-md"
>
<FaEdit /> Szerkesztés
</button>
<button
onClick={handleDeleteProfile}
className="p-2 rounded-lg bg-[color:var(--color-error)] text-white hover:opacity-90 transition-all shadow-md"
title="Profil törlése"
>
<FaTrash />
</button>
</div>
)}
</div>
{/* Content Grid */}
<div className="px-8 pb-8 space-y-6">
{!isEditing ? (
<>
{/* Contact Info & Activity Row */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Contact Information */}
<div className="lg:col-span-2 space-y-4">
<h3 className="text-lg font-bold text-[color:var(--color-text)] border-b-2 border-[color:var(--color-mint)] pb-2">
📋 Kapcsolati adatok
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="flex items-center gap-3 p-3 rounded-lg bg-[color:var(--color-surface)] border border-[color:var(--color-surface-selected)]">
<span className="text-2xl">📧</span>
<div>
<p className="text-xs text-[color:var(--color-text-muted)] font-medium">Email</p>
<p className="text-sm text-[color:var(--color-text)] font-semibold">{user.email}</p>
</div>
</div>
<div className="flex items-center gap-3 p-3 rounded-lg bg-[color:var(--color-surface)] border border-[color:var(--color-surface-selected)]">
<span className="text-2xl">📱</span>
<div>
<p className="text-xs text-[color:var(--color-text-muted)] font-medium">Telefon</p>
<p className="text-sm text-[color:var(--color-text)] font-semibold">{user.phone || "Nincs megadva"}</p>
</div>
</div>
</div>
</div>
{/* Activity Level */}
<div className="space-y-4">
<h3 className="text-lg font-bold text-[color:var(--color-text)] border-b-2 border-[color:var(--color-mint)] pb-2">
Aktivitás
</h3>
<div className="p-4 rounded-lg bg-gradient-to-br from-[color:var(--color-surface)] to-[color:var(--color-surface-selected)] border border-[color:var(--color-surface-selected)]">
<div className="flex justify-between items-center mb-3">
<span className="text-sm font-medium text-[color:var(--color-text)]">Szint</span>
<span
className="text-sm font-bold px-3 py-1 rounded-full text-white"
style={{ backgroundColor: colorMap[activityColor] }}
>
{activityLevel}% {activityEmoji}
</span>
</div>
<div className="flex gap-1.5">
{[0, 1, 2, 3].map((i) => (
<div
key={i}
className="flex-1 h-2.5 rounded-full transition-all duration-500"
style={getBlockStyle(i)}
/>
))}
</div>
</div>
</div>
</div>
{/* Stats Grid */}
<div>
<h3 className="text-lg font-bold text-[color:var(--color-text)] border-b-2 border-[color:var(--color-mint)] pb-2 mb-4">
📊 Statisztikák
</h3>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
{stats.map((s, i) => (
<div
key={i}
className="relative group overflow-hidden p-4 rounded-xl bg-gradient-to-br from-[color:var(--color-mint)] to-[color:var(--color-success)] text-white shadow-lg hover:shadow-xl transition-all"
>
<div className="relative z-10">
<div className="text-3xl mb-2 opacity-80">{s.icon}</div>
<p className="text-2xl font-bold mb-1">{s.value}</p>
<p className="text-xs opacity-90">{s.label}</p>
</div>
<div className="absolute inset-0 bg-white opacity-0 group-hover:opacity-10 transition-opacity"></div>
</div>
))}
</div>
</div>
{/* Badges */}
<div>
<h3 className="text-lg font-bold text-[color:var(--color-text)] border-b-2 border-[color:var(--color-mint)] pb-2 mb-4">
🏆 Badge-ek
</h3>
<div className="flex flex-wrap gap-3 p-4 rounded-lg bg-[color:var(--color-surface)] border border-[color:var(--color-surface-selected)]">
{badges.map((badge, i) => (
<div
key={i}
className="w-14 h-14 flex items-center justify-center bg-gradient-to-br from-[color:var(--color-mint)]/20 to-[color:var(--color-success)]/20 rounded-lg hover:scale-110 transition-transform cursor-pointer border border-[color:var(--color-mint)]/30"
>
<span className="text-2xl">{badge}</span>
</div>
))}
</div>
</div>
</>
) : (
<>
{/* Edit Mode */}
<div className="max-w-2xl mx-auto space-y-6">
<h3 className="text-xl font-bold text-[color:var(--color-text)] text-center mb-6">
Profil szerkesztése
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="sm:col-span-2">
<label className="block text-sm font-bold text-[color:var(--color-text)] mb-2">
Felhasználónév
</label>
<input
type="text"
name="username"
value={editForm.username}
onChange={handleInputChange}
className="w-full px-4 py-2.5 rounded-lg bg-[color:var(--color-surface)] border-2 border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-mint)] focus:border-[color:var(--color-mint)] outline-none transition-all"
/>
</div>
<div>
<label className="block text-sm font-bold text-[color:var(--color-text)] mb-2">
Keresztnév
</label>
<input
type="text"
name="fname"
value={editForm.fname}
onChange={handleInputChange}
className="w-full px-4 py-2.5 rounded-lg bg-[color:var(--color-surface)] border-2 border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-mint)] focus:border-[color:var(--color-mint)] outline-none transition-all"
/>
</div>
<div>
<label className="block text-sm font-bold text-[color:var(--color-text)] mb-2">
Vezetéknév
</label>
<input
type="text"
name="lname"
value={editForm.lname}
onChange={handleInputChange}
className="w-full px-4 py-2.5 rounded-lg bg-[color:var(--color-surface)] border-2 border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-mint)] focus:border-[color:var(--color-mint)] outline-none transition-all"
/>
</div>
<div className="sm:col-span-2">
<label className="block text-sm font-bold text-[color:var(--color-text)] mb-2">
Email
</label>
<input
type="email"
name="email"
value={editForm.email}
disabled
className="w-full px-4 py-2.5 rounded-lg bg-[color:var(--color-surface)] border-2 border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] outline-none opacity-50 cursor-not-allowed"
/>
</div>
<div className="sm:col-span-2">
<label className="block text-sm font-bold text-[color:var(--color-text)] mb-2">
Telefonszám
</label>
<input
type="tel"
name="phone"
value={editForm.phone}
onChange={handleInputChange}
className="w-full px-4 py-2.5 rounded-lg bg-[color:var(--color-surface)] border-2 border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-mint)] focus:border-[color:var(--color-mint)] outline-none transition-all"
/>
</div>
<div className="sm:col-span-2">
<label className="block text-sm font-bold text-[color:var(--color-text)] mb-2">
Új jelszó (opcionális)
</label>
<div className="relative">
<input
type={showPassword ? "text" : "password"}
name="password"
value={editForm.password}
onChange={handleInputChange}
placeholder="Hagyd üresen ha nem változtatod"
className="w-full px-4 py-2.5 rounded-lg bg-[color:var(--color-surface)] border-2 border-[color:var(--color-surface-selected)] text-[color:var(--color-text)] focus:ring-2 focus:ring-[color:var(--color-mint)] focus:border-[color:var(--color-mint)] outline-none pr-12 transition-all"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-[color:var(--color-text-muted)] hover:text-[color:var(--color-mint)] transition-colors"
>
{showPassword ? <FaEyeSlash size={18} /> : <FaEye size={18} />}
</button>
</div>
</div>
</div>
{/* Save/Cancel buttons */}
<div className="flex gap-3 pt-4">
<button
onClick={handleSaveProfile}
className="flex-1 px-6 py-3 rounded-lg bg-gradient-to-r from-[color:var(--color-success)] to-[color:var(--color-mint)] text-white font-bold hover:shadow-lg transition-all flex items-center justify-center gap-2"
>
<FaSave /> Mentés
</button>
<button
onClick={handleEditToggle}
className="flex-1 px-6 py-3 rounded-lg bg-[color:var(--color-surface-selected)] text-[color:var(--color-text)] font-bold hover:bg-[color:var(--color-surface)] transition-all flex items-center justify-center gap-2"
>
<FaTimes /> Mégse
</button>
</div>
</div>
</>
)}
</div>
</div>
</div>
</main>
{/* Delete Confirmation Modal */}
{showDeleteModal && (
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
<div className="bg-white rounded-xl shadow-xl p-6 w-80 text-center animate-fadeIn">
<h3 className="text-lg font-semibold mb-4 text-gray-800">
Biztosan törölni szeretnéd a profilodat?
</h3>
<p className="text-sm text-gray-600 mb-6">
Ez a művelet nem visszavonható!
</p>
<div className="flex justify-center gap-4">
<button
onClick={handleConfirmDelete}
className="bg-[color:var(--color-error)] text-white px-4 py-2 rounded-lg hover:opacity-80 transition"
>
Igen, törlöm
</button>
<button
onClick={handleCancelDelete}
className="bg-gray-200 px-4 py-2 rounded-lg hover:bg-gray-300 transition"
>
Mégse
</button>
</div>
</div>
</div>
)}
<Footer />
</div>
)
}
export default ProfileCard