import React from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import axios from 'axios'; import Sidebar from '../components/Sidebar'; import { useAuth } from '../context/AuthContext'; import { useProfileStats } from '../features/profile/hooks/useProfileStats'; import { useAchievementNotification } from '../context/AchievementNotificationContext'; import { Trophy, Star, Flame, Target, ShieldCheck, Zap, Map, Settings, Mail, Lock, Camera, BookOpen, X } from 'lucide-react'; const iconMap = { Map, Zap, Flame, ShieldCheck, Trophy, Target, Camera, Settings }; const AchievementBadge = ({ iconName, title, desc, unlocked, color, currentValue, targetValue }) => { const { t } = useTranslation(); const Icon = iconMap[iconName] || Star; const showProgress = !unlocked && targetValue > 1 && currentValue !== undefined; const cleanTitleKey = title.toLowerCase().replace(/\s+/g, '_'); const localizedTitle = t(`achievements.${cleanTitleKey}.title`, title); const localizedDesc = t(`achievements.${cleanTitleKey}.description`, desc); return (
{unlocked && (
)}

{localizedTitle}

{localizedDesc}

{showProgress && (
Progress {currentValue} / {targetValue}
)} ); }; const ProfilePage = () => { const { user, updateUser } = useAuth(); const navigate = useNavigate(); const { triggerNotification } = useAchievementNotification(); const { t } = useTranslation(); const fileInputRef = React.useRef(null); const [uploading, setUploading] = React.useState(false); const [isAllAchievementsOpen, setIsAllAchievementsOpen] = React.useState(false); const [activeFilter, setActiveFilter] = React.useState('all'); const [password, setPassword] = React.useState(''); const handleSaveChanges = async () => { if (!user) return; try { const payload = {}; if (password && password.trim() !== '') { payload.password = password; } else { triggerNotification({ id: 'account-update-info-' + Date.now(), title: t('profile.no_changes', 'No Changes'), desc: t('profile.no_changes_desc', 'No changes were made to your account details.'), icon: 'ShieldCheck', color: 'bg-blue-500/20', unlocked: true }); return; } const API_URL = import.meta.env.VITE_API_URL; const res = await axios.put(`${API_URL}/users/${user.id}`, payload); if (res.data) { updateUser(res.data); setPassword(''); triggerNotification({ id: 'account-update-success-' + Date.now(), title: t('profile.changes_saved', 'Changes Saved!'), desc: t('profile.changes_saved_desc', 'Your account details have been successfully updated.'), icon: 'ShieldCheck', color: 'bg-green-500/20', unlocked: true }); } } catch (err) { console.error('[ProfilePage] Failed to save changes:', err); triggerNotification({ id: 'account-update-error-' + Date.now(), title: t('profile.update_failed', 'Update Failed'), desc: err.response?.data?.message || err.message, icon: 'X', color: 'bg-red-500/20', unlocked: true }); } }; React.useEffect(() => { if (!user) { navigate('/auth'); } }, [user, navigate]); const { goalsMapped, nodesCreated, dayStreak, points, xp, maxXp, level, achievements, loading } = useProfileStats(user); const statsList = [ { label: t('profile.completed_goals', "Goals Mapped"), value: goalsMapped, icon: Map, color: "text-blue-500" }, { label: t('profile.nodes_created', "Nodes Created"), value: nodesCreated, icon: Zap, color: "text-yellow-500" }, { label: t('habits.active_streak', "Day Streak"), value: dayStreak, icon: Flame, color: "text-orange-500" }, { label: t('profile.total_points', "Points"), value: points, icon: Star, color: "text-secondary" }, ]; const unlockedCount = achievements ? achievements.filter(a => a.unlocked).length : 0; const handleCameraClick = () => { fileInputRef.current?.click(); }; const handleFileChange = (e) => { const file = e.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = (event) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); const MAX_WIDTH = 256; const MAX_HEIGHT = 256; let width = img.width; let height = img.height; if (width > height) { if (width > MAX_WIDTH) { height *= MAX_WIDTH / width; width = MAX_WIDTH; } } else { if (height > MAX_HEIGHT) { width *= MAX_HEIGHT / height; width = MAX_HEIGHT; } } canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, width, height); const dataUrl = canvas.toDataURL('image/jpeg', 0.85); uploadAvatar(dataUrl); }; img.src = event.target.result; }; reader.readAsDataURL(file); }; const uploadAvatar = async (base64Image) => { if (!user) return; setUploading(true); try { const API_URL = import.meta.env.VITE_API_URL; const response = await axios.put(`${API_URL}/users/${user.id}`, { avatarUrl: base64Image }); updateUser(response.data); triggerNotification({ title: t('profile.avatar_updated', "Avatar Updated!"), desc: t('profile.avatar_updated_desc', "Looking sharp, visionary!"), icon: "ShieldCheck", color: "bg-green-500/10" }); } catch (err) { console.error("Failed to upload avatar", err); alert("Failed to update profile picture. Please try again."); } finally { setUploading(false); } }; return (
{/* Profile Header */}
Avatar {uploading && (
)}

{user?.displayName || 'New User'}

{user?.isPremium ? 'Premium' : t('sidebar.free_plan')} {t('profile.user_level')} {level}
{t('profile.level_progress')} {xp} / {maxXp} XP
{t('profile.next_level_prefix', 'NEXT LEVEL:')} {level >= 5 ? t('profile.master', 'MASTER') : t('profile.visionary', 'VISIONARY')}
{false && ( )}
{/* Content Tabs */}
{/* Stats Grid */}
{statsList.map((stat, i) => (
{loading ? '-' : stat.value}
{stat.label}
))}
{/* Achievements Section */}

{t('profile.achievements')}

{t('profile.slogan', 'Your journey, documented in ink.')}

{unlockedCount} / {achievements?.length || 6} {t('profile.unlocked')}
{!loading && achievements?.map((ach, i) => ( ))}
{/* Account Settings / Privacy */}

{t('profile.account_details', 'Account Details')}

setPassword(e.target.value)} placeholder="••••••••" className="w-full p-3 bg-surface-container-low border-2 border-primary/10 rounded-lg focus:border-primary outline-none transition-all" />
{false && (

{t('profile.privacy_display', 'Privacy & Display')}

{t('profile.public_profile', 'Public Profile')}
{t('profile.public_profile_desc', 'Allow others to see your achievements')}
{t('profile.focus_mode', 'Focus Mode')}
{t('profile.focus_mode_desc', 'Hide statistics during deep work sessions')}
"{t('profile.privacy_quote', "Your data is stored locally first. You're in control.")}"
)}
{/* Interactive Achievements Notebook Modal */} {isAllAchievementsOpen && (
{/* Backdrop */} setIsAllAchievementsOpen(false)} className="absolute inset-0 bg-primary/20 backdrop-blur-sm" /> {/* Modal Body */} {/* Close button */} {/* Title & Notebook Aesthetic */}
{t('profile.personal_archive', 'Personal Archive')}

{t('profile.achievements_notebook', 'Achievements Notebook')}

{t('profile.notebook_desc', "Every milestone you've mapped, sketched, and achieved.")}

{/* Filtering tabs */}
{['all', 'unlocked', 'locked'].map((filter) => ( ))}
{/* Achievements Grid List (Scrollable) */}
{achievements ?.filter(ach => { if (activeFilter === 'unlocked') return ach.unlocked; if (activeFilter === 'locked') return !ach.unlocked; return true; }) .map((ach, i) => { const BadgeIcon = iconMap[ach.icon] || Star; const progressPercent = Math.min((ach.currentValue / ach.targetValue) * 100, 100); const cleanTitleKey = ach.title.toLowerCase().replace(/\s+/g, '_'); const localizedTitle = t(`achievements.${cleanTitleKey}.title`, ach.title); const localizedDesc = t(`achievements.${cleanTitleKey}.description`, ach.desc); return ( {/* Left Side: Badge Icon */}
{ach.unlocked && (
)}
{/* Right Side: Details & Progress Bar */}

{localizedTitle}

{ach.unlocked ? t('profile.unlocked') : t('profile.locked')}

{localizedDesc}

{/* Progress Tracker bar */}
Progress {ach.currentValue} / {ach.targetValue}
); })}
{/* Footer Quote */}
"{t('profile.success_quote', "Success is a series of small wins, mapped in ink and achieved with heart.")}"
)}
); }; export default ProfilePage;