import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useQuery, useConvex } from 'convex/react'; import { api } from '../../../../convex/_generated/api'; import Avatar from './Avatar'; import AvatarCropModal from './AvatarCropModal'; import { useTheme, THEMES, THEME_LABELS } from '../contexts/ThemeContext'; const THEME_PREVIEWS = { [THEMES.LIGHT]: { bg: '#ffffff', sidebar: '#f2f3f5', tertiary: '#e3e5e8', text: '#313338' }, [THEMES.DARK]: { bg: '#313338', sidebar: '#2b2d31', tertiary: '#1e1f22', text: '#f2f3f5' }, [THEMES.ASH]: { bg: '#202225', sidebar: '#1a1b1e', tertiary: '#111214', text: '#f0f1f3' }, [THEMES.ONYX]: { bg: '#0c0c14', sidebar: '#080810', tertiary: '#000000', text: '#e0def0' }, }; const TABS = [ { id: 'account', label: 'My Account', section: 'USER SETTINGS' }, { id: 'appearance', label: 'Appearance', section: 'USER SETTINGS' }, { id: 'voice', label: 'Voice & Video', section: 'APP SETTINGS' }, { id: 'keybinds', label: 'Keybinds', section: 'APP SETTINGS' }, ]; const UserSettings = ({ onClose, userId, username, onLogout }) => { const [activeTab, setActiveTab] = useState('account'); useEffect(() => { const handleKey = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [onClose]); const renderSidebar = () => { let lastSection = null; const items = []; TABS.forEach((tab, i) => { if (tab.section !== lastSection) { if (lastSection !== null) { items.push(
); } items.push(
{tab.section}
); lastSection = tab.section; } items.push(
setActiveTab(tab.id)} style={{ padding: '6px 10px', borderRadius: '4px', cursor: 'pointer', marginBottom: '2px', fontSize: '15px', backgroundColor: activeTab === tab.id ? 'var(--background-modifier-selected)' : 'transparent', color: activeTab === tab.id ? 'var(--header-primary)' : 'var(--header-secondary)', }} > {tab.label}
); }); items.push(
); items.push(
Log Out
); return items; }; return (
{/* Sidebar */}
{renderSidebar()}
{/* Content */}
{activeTab === 'account' && } {activeTab === 'appearance' && } {activeTab === 'voice' && } {activeTab === 'keybinds' && }
{/* Right spacer with close button */}
ESC
); }; /* ========================================= MY ACCOUNT TAB ========================================= */ const MyAccountTab = ({ userId, username }) => { const allUsers = useQuery(api.auth.getPublicKeys); const convex = useConvex(); const currentUser = allUsers?.find(u => u.id === userId); const [displayName, setDisplayName] = useState(''); const [aboutMe, setAboutMe] = useState(''); const [customStatus, setCustomStatus] = useState(''); const [avatarFile, setAvatarFile] = useState(null); const [avatarPreview, setAvatarPreview] = useState(null); const [saving, setSaving] = useState(false); const [hasChanges, setHasChanges] = useState(false); const [showCropModal, setShowCropModal] = useState(false); const [rawImageUrl, setRawImageUrl] = useState(null); const fileInputRef = useRef(null); useEffect(() => { if (currentUser) { setDisplayName(currentUser.displayName || ''); setAboutMe(currentUser.aboutMe || ''); setCustomStatus(currentUser.customStatus || ''); } }, [currentUser]); useEffect(() => { if (!currentUser) return; const changed = displayName !== (currentUser.displayName || '') || aboutMe !== (currentUser.aboutMe || '') || customStatus !== (currentUser.customStatus || '') || avatarFile !== null; setHasChanges(changed); }, [displayName, aboutMe, customStatus, avatarFile, currentUser]); const handleAvatarChange = (e) => { const file = e.target.files?.[0]; if (!file) return; const url = URL.createObjectURL(file); setRawImageUrl(url); setShowCropModal(true); e.target.value = ''; }; const handleCropApply = (blob) => { const file = new File([blob], 'avatar.png', { type: 'image/png' }); setAvatarFile(file); const previewUrl = URL.createObjectURL(blob); setAvatarPreview(previewUrl); if (rawImageUrl) URL.revokeObjectURL(rawImageUrl); setRawImageUrl(null); setShowCropModal(false); }; const handleCropCancel = () => { if (rawImageUrl) URL.revokeObjectURL(rawImageUrl); setRawImageUrl(null); setShowCropModal(false); }; const handleSave = async () => { if (!userId || saving) return; setSaving(true); try { let avatarStorageId; if (avatarFile) { const uploadUrl = await convex.mutation(api.files.generateUploadUrl); const res = await fetch(uploadUrl, { method: 'POST', headers: { 'Content-Type': avatarFile.type }, body: avatarFile, }); const { storageId } = await res.json(); avatarStorageId = storageId; } const args = { userId, displayName, aboutMe, customStatus }; if (avatarStorageId) args.avatarStorageId = avatarStorageId; await convex.mutation(api.auth.updateProfile, args); setAvatarFile(null); if (avatarPreview) { URL.revokeObjectURL(avatarPreview); setAvatarPreview(null); } } catch (err) { console.error('Failed to save profile:', err); alert('Failed to save profile: ' + err.message); } finally { setSaving(false); } }; const handleReset = () => { if (currentUser) { setDisplayName(currentUser.displayName || ''); setAboutMe(currentUser.aboutMe || ''); setCustomStatus(currentUser.customStatus || ''); } setAvatarFile(null); if (avatarPreview) { URL.revokeObjectURL(avatarPreview); setAvatarPreview(null); } if (rawImageUrl) { URL.revokeObjectURL(rawImageUrl); setRawImageUrl(null); } setShowCropModal(false); }; const avatarUrl = avatarPreview || currentUser?.avatarUrl; return (

My Account

{/* Profile card */}
{/* Banner */}
{/* Profile body */}
{/* Avatar */}
fileInputRef.current?.click()} style={{ marginTop: '-40px', marginBottom: '12px', width: 'fit-content', cursor: 'pointer', position: 'relative' }} >
CHANGE
AVATAR
{/* Fields */}
{/* Username (read-only) */}
{username}
{/* Display Name */}
setDisplayName(e.target.value)} placeholder="How others see you in chat" style={{ width: '100%', backgroundColor: 'var(--bg-tertiary)', border: 'none', borderRadius: '4px', padding: '10px', color: 'var(--text-normal)', fontSize: '16px', outline: 'none', boxSizing: 'border-box', }} />
{/* About Me */}