import { useState, useRef, useEffect } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { useAuthStore } from '../stores/auth.ts'; import { usePushStore } from '../stores/push.ts'; import { ThemeToggle } from './ThemeToggle.tsx'; export function UserSettings() { const { user, updateProfile, changePassword, isLoading, error, clearError } = useAuthStore(); const { isSupported, isSubscribed, subscribe, unsubscribe, checkSubscription } = usePushStore(); const navigate = useNavigate(); const [displayName, setDisplayName] = useState(''); const [bio, setBio] = useState(''); const [statusText, setStatusText] = useState(''); const [accentColor, setAccentColor] = useState('#fe8019'); const [saved, setSaved] = useState(false); const [uploading, setUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const fileInputRef = useRef(null); // Password change state const [currentPassword, setCurrentPassword] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmNewPassword, setConfirmNewPassword] = useState(''); const [passwordLoading, setPasswordLoading] = useState(false); const [passwordError, setPasswordError] = useState(null); const [passwordSuccess, setPasswordSuccess] = useState(false); useEffect(() => { if (user) { setDisplayName(user.display_name || ''); setBio(user.bio || ''); setStatusText(user.status_text || ''); setAccentColor(user.accent_color || '#fe8019'); } checkSubscription(); }, [user, checkSubscription]); if (!user) { navigate('/login'); return null; } const handleSave = async (e: React.FormEvent) => { e.preventDefault(); clearError(); setSaved(false); try { await updateProfile({ display_name: displayName, bio, status_text: statusText, accent_color: accentColor, }); setSaved(true); setTimeout(() => setSaved(false), 3000); } catch { // error is set in store } }; const handleAvatarUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setUploading(true); setUploadError(null); try { const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/v1/upload', { method: 'POST', credentials: 'include', body: formData, }); if (!response.ok) { const data = await response.json().catch(() => null); throw new Error(data?.message || data?.error || `Upload failed: ${response.status}`); } const uploadData = await response.json(); await updateProfile({ avatar_url: uploadData.url }); } catch (err) { setUploadError(err instanceof Error ? err.message : 'Upload failed'); } finally { setUploading(false); if (fileInputRef.current) { fileInputRef.current.value = ''; } } }; const hexValid = /^#[0-9a-fA-F]{6}$/.test(accentColor); const handleChangePassword = async () => { setPasswordError(null); setPasswordSuccess(false); if (newPassword !== confirmNewPassword) { setPasswordError("new passwords do not match"); return; } if (newPassword.length < 8) { setPasswordError("password must be at least 8 characters"); return; } setPasswordLoading(true); try { await changePassword(currentPassword, newPassword); setPasswordSuccess(true); setCurrentPassword(''); setNewPassword(''); setConfirmNewPassword(''); setTimeout(() => setPasswordSuccess(false), 3000); } catch { // error is set in store } finally { setPasswordLoading(false); } }; return (
{/* Box-drawn frame */}
{/* Header */}
            {'┌──────────────────────────────────┐\n'}
            {'│   === PROFILE SETTINGS ===       │\n'}
            {'└──────────────────────────────────┘'}
          
{/* Avatar Section */}
{user.avatar ? ( avatar ) : ( {user.username.charAt(0).toUpperCase()} )}
{user.avatar || 'no avatar set'}
{uploadError && (

ERR: {uploadError}

)}
{/* Display Name */}
setDisplayName(e.target.value.slice(0, 32))} maxLength={32} className="terminal-input w-full" placeholder="your display name" /> {displayName.length}/32
{/* Bio */}