/** * Account Settings Page - Consolidated Settings * Tabs: Account, Profile, Team * Tab selection driven by URL path for sidebar navigation */ import { useState, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { Save, Loader2, Settings, User, Users, UserPlus, Shield, Lock, X } from 'lucide-react'; import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; import Badge from '../../components/ui/badge/Badge'; import PageMeta from '../../components/common/PageMeta'; import PageHeader from '../../components/common/PageHeader'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { useAuthStore } from '../../store/authStore'; import { getAccountSettings, updateAccountSettings, getTeamMembers, inviteTeamMember, removeTeamMember, getUserProfile, changePassword, type AccountSettings, type TeamMember, } from '../../services/billing.api'; type TabType = 'account' | 'profile' | 'team'; // Map URL paths to tab types function getTabFromPath(pathname: string): TabType { if (pathname.includes('/profile')) return 'profile'; if (pathname.includes('/team')) return 'team'; return 'account'; } export default function AccountSettingsPage() { const toast = useToast(); const location = useLocation(); const { user, refreshUser } = useAuthStore(); // Derive active tab from URL path const activeTab = getTabFromPath(location.pathname); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(''); // Account settings state const [settings, setSettings] = useState(null); const [accountForm, setAccountForm] = useState({ name: '', billing_address_line1: '', billing_address_line2: '', billing_city: '', billing_state: '', billing_postal_code: '', billing_country: '', tax_id: '', billing_email: '', }); // Profile settings state const [profileForm, setProfileForm] = useState({ firstName: '', lastName: '', email: '', phone: '', timezone: 'America/New_York', language: 'en', emailNotifications: true, marketingEmails: false, }); // Password change state const [showPasswordModal, setShowPasswordModal] = useState(false); const [changingPassword, setChangingPassword] = useState(false); const [passwordForm, setPasswordForm] = useState({ currentPassword: '', newPassword: '', confirmPassword: '', }); // Team state const [members, setMembers] = useState([]); const [teamLoading, setTeamLoading] = useState(false); const [showInviteModal, setShowInviteModal] = useState(false); const [inviting, setInviting] = useState(false); const [inviteForm, setInviteForm] = useState({ email: '', first_name: '', last_name: '', }); useEffect(() => { loadData(); }, []); // Load profile from auth store user data useEffect(() => { if (user) { const [firstName = '', lastName = ''] = (user.username || '').split(' '); setProfileForm({ firstName: firstName, lastName: lastName, email: user.email || '', phone: '', timezone: 'America/New_York', language: 'en', emailNotifications: true, marketingEmails: false, }); } }, [user]); const loadData = async () => { try { setLoading(true); const accountData = await getAccountSettings(); setSettings(accountData); setAccountForm({ name: accountData.name || '', billing_address_line1: accountData.billing_address_line1 || '', billing_address_line2: accountData.billing_address_line2 || '', billing_city: accountData.billing_city || '', billing_state: accountData.billing_state || '', billing_postal_code: accountData.billing_postal_code || '', billing_country: accountData.billing_country || '', tax_id: accountData.tax_id || '', billing_email: accountData.billing_email || '', }); } catch (err: any) { setError(err.message || 'Failed to load settings'); } finally { setLoading(false); } }; const loadTeamMembers = async () => { try { setTeamLoading(true); const data = await getTeamMembers(); setMembers(data.results || []); } catch (error: any) { toast.error(`Failed to load team members: ${error.message}`); } finally { setTeamLoading(false); } }; // Load team members when team tab is selected useEffect(() => { if (activeTab === 'team' && members.length === 0) { loadTeamMembers(); } }, [activeTab]); const handleAccountSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { setSaving(true); setError(''); setSuccess(''); await updateAccountSettings(accountForm); setSuccess('Account settings updated successfully'); toast.success('Account settings saved'); await loadData(); } catch (err: any) { setError(err.message || 'Failed to update account settings'); toast.error(err.message || 'Failed to save settings'); } finally { setSaving(false); } }; const handleProfileSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { setSaving(true); // Profile data is stored in auth user - refresh after save // Note: Full profile API would go here when backend supports it toast.success('Profile settings saved'); await refreshUser(); } catch (err: any) { toast.error(err.message || 'Failed to save profile'); } finally { setSaving(false); } }; const handlePasswordChange = async () => { if (!passwordForm.currentPassword || !passwordForm.newPassword) { toast.error('Please fill in all password fields'); return; } if (passwordForm.newPassword !== passwordForm.confirmPassword) { toast.error('New passwords do not match'); return; } if (passwordForm.newPassword.length < 8) { toast.error('Password must be at least 8 characters'); return; } try { setChangingPassword(true); await changePassword(passwordForm.currentPassword, passwordForm.newPassword); toast.success('Password changed successfully'); setShowPasswordModal(false); setPasswordForm({ currentPassword: '', newPassword: '', confirmPassword: '' }); } catch (err: any) { toast.error(err.message || 'Failed to change password'); } finally { setChangingPassword(false); } }; const handleInvite = async () => { if (!inviteForm.email) { toast.error('Email is required'); return; } try { setInviting(true); const result = await inviteTeamMember(inviteForm); toast.success(result.message || 'Team member invited successfully'); setShowInviteModal(false); setInviteForm({ email: '', first_name: '', last_name: '' }); await loadTeamMembers(); } catch (error: any) { toast.error(`Failed to invite team member: ${error.message}`); } finally { setInviting(false); } }; const handleRemoveMember = async (userId: number, email: string) => { if (!confirm(`Are you sure you want to remove ${email} from the team?`)) { return; } try { const result = await removeTeamMember(userId); toast.success(result.message || 'Team member removed successfully'); await loadTeamMembers(); } catch (error: any) { toast.error(`Failed to remove team member: ${error.message}`); } }; const handleAccountChange = (e: React.ChangeEvent) => { setAccountForm(prev => ({ ...prev, [e.target.name]: e.target.value })); }; const tabs = [ { id: 'account' as TabType, label: 'Account', icon: }, { id: 'profile' as TabType, label: 'Profile', icon: }, { id: 'team' as TabType, label: 'Team', icon: }, ]; if (loading) { return ( <> , color: 'blue' }} />
Loading settings...
); } // Page titles based on active tab const pageTitles = { account: { title: 'Account Information', description: 'Manage your organization and billing information' }, profile: { title: 'Profile Settings', description: 'Update your personal information and preferences' }, team: { title: 'Team Management', description: 'Invite and manage team members' }, }; return ( <> , color: 'blue' }} parent="Account Settings" />
{/* Tab Content */} {/* Account Tab */} {activeTab === 'account' && (
{error && (

{error}

)} {success && (

{success}

)}
{/* Account Information */}

Account Information

{/* Billing Address */}

Billing Address

{/* Tax Information */}

Tax Information

{/* Submit Button */}
)} {/* Profile Tab */} {activeTab === 'profile' && (

About You

setProfileForm({ ...profileForm, firstName: e.target.value })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-[var(--color-brand-500)] dark:bg-gray-800" />
setProfileForm({ ...profileForm, lastName: e.target.value })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-[var(--color-brand-500)] dark:bg-gray-800" />
setProfileForm({ ...profileForm, email: e.target.value })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-[var(--color-brand-500)] dark:bg-gray-800" />
setProfileForm({ ...profileForm, phone: e.target.value })} className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-[var(--color-brand-500)] dark:bg-gray-800" />

Preferences

Notifications

Choose what emails you want to receive:

Important Updates
Get notified about important changes to your account
setProfileForm({ ...profileForm, emailNotifications: e.target.checked })} className="w-5 h-5 text-[var(--color-brand-500)] rounded focus:ring-[var(--color-brand-500)]" />
Tips & Product Updates
Hear about new features and content tips
setProfileForm({ ...profileForm, marketingEmails: e.target.checked })} className="w-5 h-5 text-[var(--color-brand-500)] rounded focus:ring-[var(--color-brand-500)]" />

Security

{/* Submit Button */}
)} {/* Team Tab */} {activeTab === 'team' && (

Team Members

Manage who can access your account

{teamLoading ? (
) : (
{members.map((member) => ( ))} {members.length === 0 && ( )}
Name Email Status Role Joined Actions
{member.first_name || member.last_name ? `${member.first_name} ${member.last_name}`.trim() : '-'} {member.email} {member.is_active ? 'Active' : 'Inactive'} {member.is_staff ? 'Admin' : 'Member'} {member.date_joined ? new Date(member.date_joined).toLocaleDateString() : 'N/A'}
No team members yet. Invite your first team member!
)} {/* Role Permissions Info */}

Role Permissions

Admin

High Access
  • ✓ Manage all sites and content
  • ✓ Invite team members
  • ✗ Cannot manage billing

Member

Standard Access
  • ✓ Create and edit content
  • ✓ View analytics
  • ✗ Cannot invite users
)} {/* Invite Modal */} {showInviteModal && (

Invite Team Member

setInviteForm(prev => ({ ...prev, email: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white" placeholder="user@example.com" />
setInviteForm(prev => ({ ...prev, first_name: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white" />
setInviteForm(prev => ({ ...prev, last_name: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white" />
)} {/* Password Change Modal */} {showPasswordModal && (
setShowPasswordModal(false)} />

Change Password

setPasswordForm(prev => ({ ...prev, currentPassword: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white" placeholder="Enter current password" />
setPasswordForm(prev => ({ ...prev, newPassword: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white" placeholder="Enter new password" />
setPasswordForm(prev => ({ ...prev, confirmPassword: e.target.value }))} className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-white" placeholder="Confirm new password" />
)}
); }