import { useState, useEffect } from 'react'; import { useAuth } from '../contexts/AuthContext.jsx'; import { useToast } from '../contexts/ToastContext.jsx'; import { api } from '../utils/api.js'; import Avatar from './Avatar.jsx'; const LS_FONT_KEY = 'rosterchirp_font_scale'; const MIN_SCALE = 0.8; const MAX_SCALE = 2.0; export default function ProfileModal({ onClose }) { const { user, updateUser } = useAuth(); const toast = useToast(); const [isMobile, setIsMobile] = useState(() => window.innerWidth < 768); const [displayName, setDisplayName] = useState(user?.display_name || ''); const [savedDisplayName, setSavedDisplayName] = useState(user?.display_name || ''); const [displayNameWarning, setDisplayNameWarning] = useState(''); const [aboutMe, setAboutMe] = useState(user?.about_me || ''); const [currentPw, setCurrentPw] = useState(''); const [newPw, setNewPw] = useState(''); const [confirmPw, setConfirmPw] = useState(''); const [loading, setLoading] = useState(false); const [tab, setTab] = useState('profile'); // 'profile' | 'password' | 'notifications' | 'appearance' const [pushTesting, setPushTesting] = useState(false); const [pushResult, setPushResult] = useState(null); const [notifPermission, setNotifPermission] = useState( typeof Notification !== 'undefined' ? Notification.permission : 'unsupported' ); const isIOS = /iphone|ipad/i.test(navigator.userAgent); const isStandalone = window.navigator.standalone === true; const [hideAdminTag, setHideAdminTag] = useState(!!user?.hide_admin_tag); const [allowDm, setAllowDm] = useState(user?.allow_dm !== 0); const savedScale = parseFloat(localStorage.getItem(LS_FONT_KEY)); const [fontScale, setFontScale] = useState( (savedScale >= MIN_SCALE && savedScale <= MAX_SCALE) ? savedScale : 1.0 ); useEffect(() => { const onResize = () => setIsMobile(window.innerWidth < 768); window.addEventListener('resize', onResize); return () => window.removeEventListener('resize', onResize); }, []); const applyFontScale = (val) => { setFontScale(val); document.documentElement.style.setProperty('--font-scale', val); localStorage.setItem(LS_FONT_KEY, val); }; const handleSaveProfile = async () => { if (displayNameWarning) return toast('Display name is already in use', 'error'); setLoading(true); try { const { user: updated } = await api.updateProfile({ displayName, aboutMe, hideAdminTag, allowDm }); updateUser(updated); setSavedDisplayName(displayName); toast('Profile updated', 'success'); } catch (e) { toast(e.message, 'error'); } finally { setLoading(false); } }; const handleAvatarUpload = async (e) => { const file = e.target.files?.[0]; if (!file) return; try { const { avatarUrl } = await api.uploadAvatar(file); updateUser({ avatar: avatarUrl }); toast('Avatar updated', 'success'); } catch (e) { toast(e.message, 'error'); } }; const handleChangePassword = async () => { if (newPw !== confirmPw) return toast('Passwords do not match', 'error'); if (newPw.length < 8) return toast('Password too short (min 8)', 'error'); setLoading(true); try { await api.changePassword({ currentPassword: currentPw, newPassword: newPw }); toast('Password changed', 'success'); setCurrentPw(''); setNewPw(''); setConfirmPw(''); } catch (e) { toast(e.message, 'error'); } finally { setLoading(false); } }; return (
e.target === e.currentTarget && onClose()}>

My Profile

{/* Avatar */}
{!user?.is_default_admin && ( )}
{user?.display_name || user?.name}
{user?.email}
{user?.role}
{/* Tabs — select on mobile, buttons on desktop */} {isMobile ? ( ) : (
)} {tab === 'profile' && (
{ const val = e.target.value; setDisplayName(val); setDisplayNameWarning(''); if (val && val !== user?.display_name) { try { const { taken } = await api.checkDisplayName(val); if (taken) setDisplayNameWarning('Display name is already in use'); } catch {} } }} placeholder={user?.name} autoComplete="new-password" autoCorrect="off" autoCapitalize="words" spellCheck={false} style={{ borderColor: displayNameWarning ? '#e53935' : undefined }} /> {displayName !== savedDisplayName ? null : savedDisplayName ? ( ) : null}
{displayNameWarning && {displayNameWarning}} {savedDisplayName && Username: {user?.name}}