// SettingsView - Impostazioni profilo utente function SettingsView() { const { user, updateProfile, changePassword, logout, refreshUser } = useAuth(); const toast = useToast(); const [activeTab, setActiveTab] = React.useState('profile'); // Profile form state const [profileData, setProfileData] = React.useState({ full_name: user?.full_name || '', email: user?.email || '', }); const [profileLoading, setProfileLoading] = React.useState(false); // Avatar state const [avatarFile, setAvatarFile] = React.useState(null); const [avatarPreview, setAvatarPreview] = React.useState( user?.avatar_path ? ApiUsers.getAvatarUrl(user.avatar_path) : null ); const [avatarLoading, setAvatarLoading] = React.useState(false); const [avatarDeleted, setAvatarDeleted] = React.useState(false); const fileInputRef = React.useRef(null); // Update avatar preview when user changes React.useEffect(() => { if (user?.avatar_path && !avatarFile && !avatarDeleted) { setAvatarPreview(ApiUsers.getAvatarUrl(user.avatar_path)); } }, [user?.avatar_path]); const handleAvatarChange = (e) => { const file = e.target.files[0]; if (file) { // Validate file type const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; if (!allowedTypes.includes(file.type)) { toast('Tipo file non supportato. Usa JPG, PNG, GIF o WEBP.', 'error'); return; } // Validate file size (5MB max) if (file.size > 5 * 1024 * 1024) { toast('File troppo grande. Massimo 5MB.', 'error'); return; } setAvatarFile(file); setAvatarPreview(URL.createObjectURL(file)); setAvatarDeleted(false); } }; const handleRemoveAvatar = () => { setAvatarFile(null); if (avatarPreview && !avatarPreview.startsWith('/api/')) { URL.revokeObjectURL(avatarPreview); } setAvatarPreview(null); setAvatarDeleted(true); if (fileInputRef.current) { fileInputRef.current.value = ''; } }; const handleAvatarSave = async () => { setAvatarLoading(true); try { if (avatarFile) { await ApiUsers.uploadMyAvatar(avatarFile); toast('Avatar aggiornato con successo', 'success'); } else if (avatarDeleted && user?.avatar_path) { await ApiUsers.deleteMyAvatar(); toast('Avatar rimosso con successo', 'success'); } // Refresh user data in context if (refreshUser) { await refreshUser(); } setAvatarFile(null); setAvatarDeleted(false); } catch (error) { toast(error.message || 'Errore durante il salvataggio dell\'avatar', 'error'); } finally { setAvatarLoading(false); } }; // Cleanup preview URL on unmount React.useEffect(() => { return () => { if (avatarPreview && !avatarPreview.startsWith('/api/')) { URL.revokeObjectURL(avatarPreview); } }; }, [avatarPreview]); // Password form state const [passwordData, setPasswordData] = React.useState({ current_password: '', new_password: '', confirm_password: '', }); const [passwordLoading, setPasswordLoading] = React.useState(false); const [showPasswords, setShowPasswords] = React.useState({ current: false, new: true, confirm: true, }); // Calendar integration state (admin only) const [calendarStatus, setCalendarStatus] = React.useState({ connected: false, connected_at: null, shared: false }); const [calendarLoading, setCalendarLoading] = React.useState(false); const [sharingLoading, setSharingLoading] = React.useState(false); const isAdmin = user?.role === 'admin'; // Fetch calendar status on mount (admin only) React.useEffect(() => { if (isAdmin) { fetchCalendarStatus(); } }, [isAdmin]); // Handle OAuth callback React.useEffect(() => { if (isAdmin) { const hash = window.location.hash; const params = new URLSearchParams(hash.split('?')[1] || ''); if (params.get('calendar_success') === 'true') { // Tokens are now saved directly by backend callback toast('Google Calendar connesso con successo', 'success'); fetchCalendarStatus(); // Clean URL window.history.replaceState(null, '', '/#/settings'); setActiveTab('integrations'); } else if (params.get('calendar_error')) { const error = decodeURIComponent(params.get('calendar_error')); toast(`Errore: ${error}`, 'error'); // Clean URL window.history.replaceState(null, '', '/#/settings'); setActiveTab('integrations'); } } }, [isAdmin]); const fetchCalendarStatus = async () => { try { const status = await ApiUtils.getCalendarStatus(); setCalendarStatus(status); } catch (error) { console.error('Error fetching calendar status:', error); } }; const handleDisconnectCalendar = async () => { setCalendarLoading(true); try { await ApiUtils.disconnectCalendar(); setCalendarStatus({ connected: false, connected_at: null, shared: false }); toast('Google Calendar disconnesso', 'success'); } catch (error) { toast('Errore durante la disconnessione', 'error'); console.error('Error disconnecting calendar:', error); } finally { setCalendarLoading(false); } }; const handleToggleSharing = async () => { const newShared = !calendarStatus.shared; setSharingLoading(true); try { await ApiUtils.setCalendarSharing(newShared); setCalendarStatus(prev => ({ ...prev, shared: newShared })); toast(newShared ? 'Calendario condiviso con il team' : 'Condivisione calendario disattivata', 'success'); } catch (error) { toast('Errore durante l\'aggiornamento della condivisione', 'error'); console.error('Error toggling calendar sharing:', error); } finally { setSharingLoading(false); } }; const handleProfileUpdate = async (e) => { e.preventDefault(); setProfileLoading(true); const result = await updateProfile(profileData); if (result.success) { toast('Profilo aggiornato con successo', 'success'); } else { toast(result.error, 'error'); } setProfileLoading(false); }; const handlePasswordChange = async (e) => { e.preventDefault(); if (passwordData.new_password !== passwordData.confirm_password) { toast('Le password non coincidono', 'error'); return; } if (passwordData.new_password.length < 8) { toast('La password deve essere di almeno 8 caratteri', 'error'); return; } setPasswordLoading(true); const result = await changePassword(passwordData.current_password, passwordData.new_password); if (result.success) { toast('Password modificata con successo', 'success'); setPasswordData({ current_password: '', new_password: '', confirm_password: '' }); } else { toast(result.error, 'error'); } setPasswordLoading(false); }; const tabs = [ { id: 'profile', label: 'Profilo', icon: '👤' }, { id: 'security', label: 'Sicurezza', icon: '🔒' }, { id: 'notifications', label: 'Notifiche Email', icon: '📧' }, ...(isAdmin ? [{ id: 'integrations', label: 'Integrazioni', icon: '🔗' }] : []), ]; return (
Gestisci il tuo account e le preferenze
JPG, PNG, GIF o WEBP. Massimo 5MB.
Disconnettiti dal tuo account su questo dispositivo.
Sincronizza il tuo calendario Google per visualizzare gli eventi nel calendario scadenze. Gli eventi del calendario verranno mostrati insieme alle scadenze delle pratiche.
{calendarStatus.connected && calendarStatus.connected_at && (Connesso il {new Date(calendarStatus.connected_at).toLocaleDateString('it-IT', { day: '2-digit', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' })}
)} {/* Action Buttons */}Permetti agli altri utenti di vedere gli eventi del tuo calendario Google nella dashboard.