import React, { useState, useEffect, useCallback } from 'react'; import { UserDTO, MaterialDTO } from '../types'; import { DataService } from '../services/dataService'; import { X, Save, Shield, User, Calendar, LogOut, Grid, Heart, Package, Code, Video } from 'lucide-react'; import { MaterialCard } from './MaterialCard'; import Image from 'next/image'; import { useToast } from './ToastProvider'; interface ProfileModalProps { user: UserDTO; onClose: () => void; onUpdate: () => void; } export const ProfileModal: React.FC = ({ user, onClose, onUpdate }) => { const [formData, setFormData] = useState({ username: user.username, avatarUrl: user.avatarUrl, }); const [loading, setLoading] = useState(false); const [activeTab, setActiveTab] = useState<'info' | 'created' | 'favorites'>('info'); const [userMaterials, setUserMaterials] = useState([]); const [favoriteMaterials, setFavoriteMaterials] = useState([]); const [loadingMaterials, setLoadingMaterials] = useState(false); const [createdPage, setCreatedPage] = useState(1); const [favoritesPage, setFavoritesPage] = useState(1); const pageSize = 20; const [createdTotal, setCreatedTotal] = useState(0); const [createdHasNext, setCreatedHasNext] = useState(false); const [favoritesTotal, setFavoritesTotal] = useState(0); const [favoritesHasNext, setFavoritesHasNext] = useState(false); const toast = useToast(); const siteIconSvg = encodeURIComponent("NM"); const siteIcon = `data:image/svg+xml;utf8,${siteIconSvg}`; const loadFavorites = useCallback(async () => { try { setLoadingMaterials(true); const result = await DataService.getUserFavorites(favoritesPage, pageSize); setFavoriteMaterials(result.items); setFavoritesTotal(result.total); setFavoritesHasNext(result.hasNext); } catch (error) { toast.error('Failed to load favorites'); } finally { setLoadingMaterials(false); } }, [toast, favoritesPage]); const loadUserMaterials = useCallback(async () => { try { setLoadingMaterials(true); const result = await DataService.getUserMaterials(createdPage, pageSize); setUserMaterials(result.items); setCreatedTotal(result.total); setCreatedHasNext(result.hasNext); } catch (error) { toast.error('Failed to load materials'); } finally { setLoadingMaterials(false); } }, [toast, createdPage]); useEffect(() => { if (activeTab === 'created') { loadUserMaterials(); } else if (activeTab === 'favorites') { loadFavorites(); } }, [activeTab, loadFavorites, loadUserMaterials]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { setLoading(true); await DataService.updateUserProfile(user.id, formData); toast.success('Profile updated'); onUpdate(); onClose(); } catch (error) { toast.error('Failed to update profile'); } finally { setLoading(false); } }; const handleLogout = async () => { try { await fetch('/api/v1/auth/logout', { method: 'POST' }); localStorage.removeItem('NEXUS_DATA_MODE'); window.location.href = '/auth/login'; } catch (error) { toast.error('Logout failed'); } }; return (
{/* Background Grids */}
{/* Close Button */} {/* Header with Tabs */}
Profile

{formData.username}

{user.role} • {user.status}

{/* Tabs */}
{/* Content Area */}
{activeTab === 'info' && (
STATUS {user.status}
JOINED {new Date(user.createdAt).toLocaleDateString()}
setFormData({ ...formData, username: e.target.value })} className="w-full bg-black/40 border border-cyber-panel p-3 text-white focus:border-cyber-pink focus:outline-none rounded-sm font-mono text-sm" />
setFormData({ ...formData, avatarUrl: e.target.value })} className="w-full bg-black/40 border border-cyber-panel p-3 text-white focus:border-cyber-pink focus:outline-none rounded-sm font-mono text-sm" />
)} {activeTab === 'created' && (

MY CREATIONS

loadUserMaterials()} /> {(user.role === 'MANAGER' || user.role === 'ADMIN') && ( loadUserMaterials()} userRole={user.role} /> )}
{loadingMaterials ? (
LOADING...
) : userMaterials.length > 0 ? ( <>
{userMaterials.map(m => ( { }} currentUser={user} /> ))}
PAGE {createdPage} / {Math.max(1, Math.ceil(createdTotal / pageSize))}
) : (

NO CREATIONS YET

)}
)} {activeTab === 'favorites' && (

MY FAVORITES

{loadingMaterials ? (
LOADING...
) : favoriteMaterials.length > 0 ? (
{favoriteMaterials.map(m => ( { }} currentUser={user} /> ))}
) : (

NO FAVORITES YET

)}
PAGE {favoritesPage} / {Math.max(1, Math.ceil(favoritesTotal / pageSize))}
)}
); }; const UploadZipButton: React.FC<{ onUploaded: () => void }> = ({ onUploaded }) => { const toast = useToast(); const inputRef = React.useRef(null); const [loading, setLoading] = useState(false); const onClick = () => inputRef.current?.click(); const onChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (file.size > 3 * 1024 * 1024) { toast.error('ZIP size must be ≤ 3MB'); return; } setLoading(true); try { await DataService.uploadZip(file, { title: file.name }); toast.success('ZIP uploaded'); onUploaded(); } catch (err: any) { toast.error(err.message || 'Upload failed'); } finally { setLoading(false); if (inputRef.current) inputRef.current.value = ''; } }; return (
); }; const UploadVideoButton: React.FC<{ onUploaded: () => void; userRole: UserDTO['role'] }> = ({ onUploaded, userRole }) => { const toast = useToast(); const inputRef = React.useRef(null); const [loading, setLoading] = useState(false); const disabled = !(userRole === 'MANAGER' || userRole === 'ADMIN'); const onClick = () => { if (disabled) { toast.error('Manager role required'); return; } inputRef.current?.click(); }; const onChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setLoading(true); try { await DataService.uploadVideo(file, { title: file.name }); toast.success('Video uploaded'); onUploaded(); } catch (err: any) { toast.error(err.message || 'Upload failed'); } finally { setLoading(false); if (inputRef.current) inputRef.current.value = ''; } }; return (
); };