import React, { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { User, Mail, Phone, Lock, Globe, Clock, Camera, Save, X, Bell, Shield, Download, Trash2, AlertCircle, Cookie, ExternalLink, Check, ChevronDown, ChevronUp, Info, AlertTriangle, Star, Settings, CheckCircle, RefreshCw } from 'lucide-react'; import { Button, Card, Avatar, Input, Select, SettingSection, SettingRow, SettingsSearch, Badge } from '../../../../components/ui'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../../../components/ui/Tabs'; import { PageHeader } from '../../../../components/layout'; import { showToast } from '../../../../utils/toast'; import { useAuthUser, useAuthActions } from '../../../../stores/auth.store'; import { useAuthProfile, useUpdateProfile, useChangePassword } from '../../../../api/hooks/auth'; import { useCurrentTenant } from '../../../../stores'; // Import the communication preferences component import CommunicationPreferences, { type NotificationPreferences } from './CommunicationPreferences'; interface ProfileFormData { first_name: string; last_name: string; email: string; phone: string; language: string; timezone: string; avatar?: string; } interface PasswordData { currentPassword: string; newPassword: string; confirmPassword: string; } // Collapsible Section Component (similar to subscription page) const CollapsibleSection: React.FC<{ title: string; icon: React.ReactNode; isOpen: boolean; onToggle: () => void; children: React.ReactNode; showAlert?: boolean; className?: string; }> = ({ title, icon, isOpen, onToggle, children, showAlert = false, className = '' }) => { return ( {isOpen && (
{children}
)}
); }; // Profile Completion Status Component const ProfileCompletionStatus: React.FC<{ completionPercentage: number; missingFields: string[]; onEdit: () => void; }> = ({ completionPercentage, missingFields, onEdit }) => { const getStatusColor = () => { if (completionPercentage >= 90) return 'green'; if (completionPercentage >= 70) return 'yellow'; return 'red'; }; const statusColor = getStatusColor(); const colorClasses = { red: { bg: 'bg-red-50 dark:bg-red-900/20', border: 'border-red-200 dark:border-red-700', text: 'text-red-600 dark:text-red-400', icon: 'text-red-500' }, yellow: { bg: 'bg-yellow-50 dark:bg-yellow-900/20', border: 'border-yellow-200 dark:border-yellow-700', text: 'text-yellow-600 dark:text-yellow-400', icon: 'text-yellow-500' }, green: { bg: 'bg-green-50 dark:bg-green-900/20', border: 'border-green-200 dark:border-green-700', text: 'text-green-600 dark:text-green-400', icon: 'text-green-500' } }; const colors = colorClasses[statusColor]; return (

Perfil Completado

{Math.round(completionPercentage)}% Completado

Campos Completados

{Math.round(completionPercentage)}%

Campos Faltantes

{missingFields.length}

{missingFields.length > 0 && (

Campos faltantes: {missingFields.join(', ')}

)}
); }; // Enhanced Profile Field Component with inline editing const ProfileField: React.FC<{ label: string; value: string; onChange: (value: string) => void; error?: string; disabled?: boolean; type?: string; icon?: React.ReactNode; required?: boolean; isEditing: boolean; fieldType?: 'text' | 'email' | 'tel' | 'select'; options?: { value: string; label: string }[]; }> = ({ label, value, onChange, error, disabled = false, type = 'text', icon, required = false, isEditing, fieldType = 'text', options = [] }) => { const [isFocused, setIsFocused] = useState(false); const [localValue, setLocalValue] = useState(value); useEffect(() => { setLocalValue(value); }, [value]); const handleChange = (e: React.ChangeEvent) => { const newValue = e.target.value; setLocalValue(newValue); onChange(newValue); }; const handleBlur = () => { setIsFocused(false); if (localValue !== value) { // Auto-save logic could go here } }; if (fieldType === 'select') { return (
{icon &&
{icon}
}
{isEditing ? (
{error &&

{error}

}
) : (

{options.find(opt => opt.value === value)?.label || value}

)}
); } return (
{icon &&
{icon}
}
{isEditing ? (
setIsFocused(true)} onBlur={handleBlur} disabled={disabled} className={`w-full px-3 py-2 border rounded-lg bg-[var(--bg-primary)] text-[var(--text-primary)] focus:ring-2 focus:ring-[var(--color-primary)] focus:border-transparent transition-all text-sm ${ error ? 'border-red-500' : isFocused ? 'border-[var(--color-primary)]' : 'border-[var(--border-primary)]' }`} /> {error &&

{error}

}
) : (

{value || No establecido}

)}
); }; // Password Strength Meter Component const PasswordStrengthMeter: React.FC<{ password: string }> = ({ password }) => { const getPasswordStrength = (pwd: string) => { if (!pwd) return 0; let strength = 0; if (pwd.length >= 8) strength += 1; if (/[A-Z]/.test(pwd)) strength += 1; if (/[0-9]/.test(pwd)) strength += 1; if (/[^A-Za-z0-9]/.test(pwd)) strength += 1; if (pwd.length >= 12) strength += 1; return Math.min(5, strength); }; const strength = getPasswordStrength(password); const strengthLabels = ['Muy débil', 'Débil', 'Media', 'Fuerte', 'Muy fuerte']; const strengthColors = ['bg-red-500', 'bg-orange-500', 'bg-yellow-500', 'bg-green-500', 'bg-green-600']; return (
Débil Fuerte
{[1, 2, 3, 4, 5].map((level) => (
))}
{password && (

= 3 ? 'text-green-600' : strength >= 2 ? 'text-yellow-600' : 'text-red-600'}`}> {strengthLabels[strength - 1] || 'Muy débil'}

)}
); }; const NewProfileSettingsPage: React.FC = () => { const { t } = useTranslation('settings'); const navigate = useNavigate(); const user = useAuthUser(); const { logout } = useAuthActions(); const currentTenant = useCurrentTenant(); const { data: profile, isLoading: profileLoading } = useAuthProfile(); const updateProfileMutation = useUpdateProfile(); const changePasswordMutation = useChangePassword(); const [activeTab, setActiveTab] = useState('personal'); const [isEditing, setIsEditing] = useState(false); const [isLoading, setIsLoading] = useState(false); const [showPasswordForm, setShowPasswordForm] = useState(false); const [searchQuery, setSearchQuery] = useState(''); // Collapsible section states (similar to subscription page) const [showPersonalInfo, setShowPersonalInfo] = useState(true); const [showSecurity, setShowSecurity] = useState(false); const [showNotifications, setShowNotifications] = useState(false); const [showPrivacy, setShowPrivacy] = useState(false); // Export & Delete states const [isExporting, setIsExporting] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [deleteConfirmEmail, setDeleteConfirmEmail] = useState(''); const [deletePassword, setDeletePassword] = useState(''); const [deleteReason, setDeleteReason] = useState(''); const [isDeleting, setIsDeleting] = useState(false); // Profile completion tracking const [completionPercentage, setCompletionPercentage] = useState(0); const [missingFields, setMissingFields] = useState([]); // Notification preferences tracking const [notificationPreferences, setNotificationPreferences] = useState(null); const [notificationHasChanges, setNotificationHasChanges] = useState(false); const [profileData, setProfileData] = useState({ first_name: '', last_name: '', email: '', phone: '', language: 'es', timezone: 'Europe/Madrid' }); const [passwordData, setPasswordData] = useState({ currentPassword: '', newPassword: '', confirmPassword: '' }); const [errors, setErrors] = useState>({}); // Update profile data when profile is loaded React.useEffect(() => { if (profile) { setProfileData({ first_name: profile.first_name || '', last_name: profile.last_name || '', email: profile.email || '', phone: profile.phone || '', language: profile.language || 'es', timezone: profile.timezone || 'Europe/Madrid', avatar: profile.avatar || '' }); } }, [profile]); // Calculate profile completion percentage useEffect(() => { if (profile) { const requiredFields = ['first_name', 'last_name', 'email', 'phone', 'language', 'timezone']; const completedFields = requiredFields.filter(field => { const value = profile[field as keyof typeof profile]; return value && value.toString().trim() !== ''; }); const percentage = (completedFields.length / requiredFields.length) * 100; setCompletionPercentage(percentage); const missing = requiredFields.filter(field => { const value = profile[field as keyof typeof profile]; return !value || value.toString().trim() === ''; }); setMissingFields(missing.map(field => { const fieldNames: Record = { first_name: 'Nombre', last_name: 'Apellido', email: 'Email', phone: 'Teléfono', language: 'Idioma', timezone: 'Zona Horaria' }; return fieldNames[field] || field; })); } }, [profile]); // Track when notification preferences change const handleNotificationPreferencesChange = (preferences: NotificationPreferences) => { // Compare with stored preferences to detect changes if (notificationPreferences) { const hasChanges = JSON.stringify(preferences) !== JSON.stringify(notificationPreferences); setNotificationHasChanges(hasChanges); } else { // First time setting preferences setNotificationHasChanges(true); } }; // Initialize notification preferences from profile data useEffect(() => { if (profile?.notification_preferences) { setNotificationPreferences(profile.notification_preferences); } else { // Set default preferences if none exist const defaultPreferences: NotificationPreferences = { email_enabled: true, email_alerts: true, email_marketing: false, email_reports: true, whatsapp_enabled: false, whatsapp_alerts: false, whatsapp_reports: false, push_enabled: true, push_alerts: true, push_reports: false, quiet_hours_start: '22:00', quiet_hours_end: '08:00', timezone: profile?.timezone || 'Europe/Madrid', digest_frequency: 'daily', max_emails_per_day: 10, language: profile?.language || 'es' }; setNotificationPreferences(defaultPreferences); } }, [profile]); const languageOptions = [ { value: 'es', label: 'Español' }, { value: 'eu', label: 'Euskara' }, { value: 'en', label: 'English' } ]; const timezoneOptions = [ { value: 'Europe/Madrid', label: 'Madrid (CET/CEST)' }, { value: 'Atlantic/Canary', label: 'Canarias (WET/WEST)' }, { value: 'Europe/London', label: 'Londres (GMT/BST)' } ]; const validateProfile = (): boolean => { const newErrors: Record = {}; if (!profileData.first_name.trim()) { newErrors.first_name = t('profile.fields.first_name') + ' ' + t('common.required'); } if (!profileData.last_name.trim()) { newErrors.last_name = t('profile.fields.last_name') + ' ' + t('common.required'); } if (!profileData.email.trim()) { newErrors.email = t('profile.fields.email') + ' ' + t('common.required'); } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(profileData.email)) { newErrors.email = t('common.error'); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const validatePassword = (): boolean => { const newErrors: Record = {}; if (!passwordData.currentPassword) { newErrors.currentPassword = t('profile.password.current_password') + ' ' + t('common.required'); } if (!passwordData.newPassword) { newErrors.newPassword = t('profile.password.new_password') + ' ' + t('common.required'); } else if (passwordData.newPassword.length < 8) { newErrors.newPassword = t('profile.password.password_requirements'); } if (passwordData.newPassword !== passwordData.confirmPassword) { newErrors.confirmPassword = t('common.error'); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSaveProfile = async () => { if (!validateProfile()) return; setIsLoading(true); try { // Map the form fields to the expected API format // The API expects 'full_name' instead of separate 'first_name' and 'last_name' const profileUpdateData = { full_name: `${profileData.first_name} ${profileData.last_name}`.trim(), phone: profileData.phone, language: profileData.language, timezone: profileData.timezone }; await updateProfileMutation.mutateAsync(profileUpdateData); setIsEditing(false); showToast.success(t('profile.save_changes')); } catch (error) { showToast.error(t('common.error')); } finally { setIsLoading(false); } }; const handleChangePasswordSubmit = async () => { if (!validatePassword()) return; setIsLoading(true); try { await changePasswordMutation.mutateAsync({ current_password: passwordData.currentPassword, new_password: passwordData.newPassword, confirm_password: passwordData.confirmPassword }); setShowPasswordForm(false); setPasswordData({ currentPassword: '', newPassword: '', confirmPassword: '' }); showToast.success(t('profile.password.change_success')); } catch (error) { showToast.error(t('profile.password.change_error')); } finally { setIsLoading(false); } }; const handleInputChange = (field: keyof ProfileFormData) => (e: React.ChangeEvent) => { setProfileData(prev => ({ ...prev, [field]: e.target.value })); if (errors[field]) { setErrors(prev => ({ ...prev, [field]: '' })); } }; const handleSelectChange = (field: keyof ProfileFormData) => (value: string) => { setProfileData(prev => ({ ...prev, [field]: value })); }; const handlePasswordChange = (field: keyof PasswordData) => (e: React.ChangeEvent) => { setPasswordData(prev => ({ ...prev, [field]: e.target.value })); if (errors[field]) { setErrors(prev => ({ ...prev, [field]: '' })); } }; const handleSaveNotificationPreferences = async (preferences: NotificationPreferences) => { try { setIsLoading(true); await updateProfileMutation.mutateAsync({ notification_preferences: preferences }); // Update the stored preferences to track future changes setNotificationPreferences(preferences); setNotificationHasChanges(false); showToast.success(t('profile.notifications.save_success', 'Preferencias de notificación guardadas correctamente')); } catch (error) { showToast.error(t('profile.notifications.save_error', 'Error al guardar las preferencias de notificación')); } finally { setIsLoading(false); } }; const handleNotificationReset = () => { // Reset to initial state if (notificationPreferences) { // This would typically reset the form to the saved preferences // For now, we'll just reset the change tracking setNotificationHasChanges(false); } }; const handleDataExport = async () => { setIsExporting(true); try { const { authService } = await import('../../../../api'); const exportData = await authService.exportMyData(); // Convert to blob and download const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `my_data_export_${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); showToast.success(t('profile.privacy.export_success')); } catch (err) { showToast.error(t('profile.privacy.export_error')); } finally { setIsExporting(false); } }; const handleAccountDeletion = async () => { if (deleteConfirmEmail.toLowerCase() !== user?.email?.toLowerCase()) { showToast.error(t('common.error')); return; } if (!deletePassword) { showToast.error(t('common.error')); return; } setIsDeleting(true); try { const { authService } = await import('../../../../api'); await authService.deleteAccount(deleteConfirmEmail, deletePassword, deleteReason); showToast.success(t('common.success')); setTimeout(() => { logout(); navigate('/'); }, 2000); } catch (err: any) { showToast.error(err.message || t('common.error')); } finally { setIsDeleting(false); } }; if (profileLoading || !profile) { return (

{t('common.loading')}

); } return (
{/* NEW: Profile Completion Status Banner - Always Visible */} { setActiveTab('personal'); setShowPersonalInfo(true); setIsEditing(true); }} /> {/* Profile Header Card removed as requested */} {/* Search Bar removed as requested */} {/* NEW: Collapsible Sections (similar to subscription page) */}
{/* Personal Information Section */} } isOpen={showPersonalInfo} onToggle={() => setShowPersonalInfo(!showPersonalInfo)} showAlert={completionPercentage < 90} >
{/* Spacing between content blocks */}
{/* Enhanced Profile Form with Inline Editing */}

{t('profile.personal_info')}

{t('profile.personal_info_description') || 'Your personal information and account details'}

{!isEditing ? ( ) : ( <> )}
{/* Real-time validation feedback */} {isEditing && Object.keys(errors).length > 0 && (

Por favor, corrige los errores antes de guardar

)} {/* Enhanced Profile Fields with Inline Editing */}
handleInputChange('first_name')({ target: { value } } as any)} error={errors.first_name} disabled={isLoading} icon={} required isEditing={isEditing} /> handleInputChange('last_name')({ target: { value } } as any)} error={errors.last_name} disabled={isLoading} icon={} required isEditing={isEditing} /> handleInputChange('email')({ target: { value } } as any)} error={errors.email} disabled={isLoading} icon={} required isEditing={isEditing} type="email" /> handleInputChange('phone')({ target: { value } } as any)} error={errors.phone} disabled={isLoading} icon={} isEditing={isEditing} type="tel" /> handleSelectChange('language')(value)} error={errors.language} disabled={isLoading} icon={} isEditing={isEditing} fieldType="select" options={languageOptions} /> handleSelectChange('timezone')(value)} error={errors.timezone} disabled={isLoading} icon={} isEditing={isEditing} fieldType="select" options={timezoneOptions} />
{/* Save status indicator */} {isEditing && (
{isLoading ? ( <> Guardando cambios... ) : ( <> Los cambios se guardarán automáticamente al hacer clic en Guardar )}
)}
{/* Security Section */} } isOpen={showSecurity} onToggle={() => setShowSecurity(!showSecurity)} >
{/* Spacing between content blocks */}

Manage your password and security settings

Update your password to keep your account secure

{showPasswordForm && (
{/* Password Requirements Info */}

Requisitos de Contraseña

  • Mínimo 8 caracteres
  • Al menos una mayúscula
  • Al menos un número
  • Caracter especial recomendado
{/* Enhanced Password Fields */}
{errors.currentPassword && (

{errors.currentPassword}

)}
{errors.newPassword && (

{errors.newPassword}

)}
{/* Password Strength Meter */}
{errors.confirmPassword && (

{errors.confirmPassword}

)} {/* Password Match Indicator */} {passwordData.confirmPassword && passwordData.newPassword && (
{passwordData.newPassword === passwordData.confirmPassword ? ( <> Las contraseñas coinciden ) : ( <> Las contraseñas no coinciden )}
)}
{/* Enhanced Action Buttons */}
{/* Security Tip */}

Consejo de seguridad: Usa una contraseña única que no utilices en otros servicios. Considera usar un gestor de contraseñas para mayor seguridad.

)}
{/* Notifications Section */} } isOpen={showNotifications} onToggle={() => setShowNotifications(!showNotifications)} >
{/* Spacing between content blocks */}
{/* Privacy & Data Section */} } isOpen={showPrivacy} onToggle={() => setShowPrivacy(!showPrivacy)} >
{/* Spacing between content blocks */}
{/* GDPR Rights Information */}

{t('profile.privacy.gdpr_rights')}

{t('profile.privacy.gdpr_description')}

{/* Cookie Preferences */}

{t('profile.privacy.cookie_preferences')}

Gestiona tus preferencias de cookies

{/* Data Export */}

{t('profile.privacy.export_data')}

{t('profile.privacy.export_description')}

{/* Account Deletion */}

{t('profile.privacy.delete_account')}

{t('profile.privacy.delete_description')}

{t('profile.privacy.delete_warning')}

{/* Delete Account Modal (updated to match subscription page style) */} {showDeleteModal && (

{t('profile.privacy.delete_account')}?

{t('profile.privacy.delete_warning')}

setDeleteConfirmEmail(e.target.value)} required /> setDeletePassword(e.target.value)} required leftIcon={} />