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 (
{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 */}
{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')}
{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 */}
{/* 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;