Files
igny8/frontend/src/pages/account/AccountSettingsPage.tsx
IGNY8 VPS (Salman) 3ea7d4f933 final polish 3
2025-12-27 15:55:54 +00:00

938 lines
39 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 { usePageContext } from '../../context/PageContext';
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();
const { setPageInfo } = usePageContext();
// 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<string>('');
const [success, setSuccess] = useState<string>('');
// Set page context for AppHeader - no selectors for account pages per audit Section 1
useEffect(() => {
setPageInfo({
title: 'Account Settings',
badge: { icon: <Settings className="w-4 h-4" />, color: 'indigo' },
selectorVisibility: 'none',
});
return () => setPageInfo(null);
}, [setPageInfo]);
// Account settings state
const [settings, setSettings] = useState<AccountSettings | null>(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<TeamMember[]>([]);
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<HTMLInputElement>) => {
setAccountForm(prev => ({
...prev,
[e.target.name]: e.target.value
}));
};
const tabs = [
{ id: 'account' as TabType, label: 'Account', icon: <Settings className="w-4 h-4" /> },
{ id: 'profile' as TabType, label: 'Profile', icon: <User className="w-4 h-4" /> },
{ id: 'team' as TabType, label: 'Team', icon: <Users className="w-4 h-4" /> },
];
if (loading) {
return (
<div className="p-6">
<PageMeta title="Account Settings" description="Manage your account, profile, and team" />
<div className="flex items-center justify-center h-64">
<div className="flex flex-col items-center gap-3">
<Loader2 className="w-8 h-8 animate-spin text-[var(--color-brand-500)]" />
<div className="text-gray-500 dark:text-gray-400">Loading settings...</div>
</div>
</div>
</div>
);
}
return (
<div className="p-6">
<PageMeta title="Account Settings" description="Manage your account, profile, and team" />
{/* Page Header with Breadcrumb */}
<div className="mb-6">
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-2">
<span>Account Settings</span>
<span></span>
<span className="text-gray-900 dark:text-white font-medium">
{activeTab === 'account' && 'Account'}
{activeTab === 'profile' && 'Profile'}
{activeTab === 'team' && 'Team'}
</span>
</div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
{activeTab === 'account' && 'Account Information'}
{activeTab === 'profile' && 'Profile Settings'}
{activeTab === 'team' && 'Team Management'}
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
{activeTab === 'account' && 'Manage your organization and billing information'}
{activeTab === 'profile' && 'Update your personal information and preferences'}
{activeTab === 'team' && 'Invite and manage team members'}
</p>
</div>
{/* Tab Content */}
<div className="mt-6">
{/* Account Tab */}
{activeTab === 'account' && (
<div className="space-y-6 max-w-4xl">
{error && (
<div className="p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
<p className="text-red-800 dark:text-red-200">{error}</p>
</div>
)}
{success && (
<div className="p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
<p className="text-green-800 dark:text-green-200">{success}</p>
</div>
)}
<form onSubmit={handleAccountSubmit} className="space-y-6">
{/* Account Information */}
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Account Information</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Account Name
</label>
<input
type="text"
name="name"
value={accountForm.name}
onChange={handleAccountChange}
className="w-full px-3 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"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Account Slug
</label>
<input
type="text"
value={settings?.slug || ''}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-gray-100 dark:bg-gray-700"
disabled
/>
</div>
</div>
<div className="mt-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Billing Email
</label>
<input
type="email"
name="billing_email"
value={accountForm.billing_email}
onChange={handleAccountChange}
className="w-full px-3 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"
/>
</div>
</Card>
{/* Billing Address */}
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Billing Address</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Address Line 1
</label>
<input
type="text"
name="billing_address_line1"
value={accountForm.billing_address_line1}
onChange={handleAccountChange}
className="w-full px-3 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Address Line 2
</label>
<input
type="text"
name="billing_address_line2"
value={accountForm.billing_address_line2}
onChange={handleAccountChange}
className="w-full px-3 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"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
City
</label>
<input
type="text"
name="billing_city"
value={accountForm.billing_city}
onChange={handleAccountChange}
className="w-full px-3 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
State/Province
</label>
<input
type="text"
name="billing_state"
value={accountForm.billing_state}
onChange={handleAccountChange}
className="w-full px-3 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Postal Code
</label>
<input
type="text"
name="billing_postal_code"
value={accountForm.billing_postal_code}
onChange={handleAccountChange}
className="w-full px-3 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"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Country
</label>
<input
type="text"
name="billing_country"
value={accountForm.billing_country}
onChange={handleAccountChange}
className="w-full px-3 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"
placeholder="US, GB, IN, etc."
/>
</div>
</div>
</Card>
{/* Tax Information */}
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Tax Information</h2>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Tax ID / VAT Number
</label>
<input
type="text"
name="tax_id"
value={accountForm.tax_id}
onChange={handleAccountChange}
className="w-full px-3 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"
placeholder="Optional"
/>
</div>
</Card>
{/* Submit Button */}
<div className="flex justify-end">
<Button
type="submit"
variant="primary"
tone="brand"
disabled={saving}
startIcon={saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
>
{saving ? 'Saving...' : 'Save Changes'}
</Button>
</div>
</form>
</div>
)}
{/* Profile Tab */}
{activeTab === 'profile' && (
<div className="space-y-6 max-w-4xl">
<form onSubmit={handleProfileSubmit} className="space-y-6">
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">About You</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
First Name
</label>
<input
type="text"
value={profileForm.firstName}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Last Name
</label>
<input
type="text"
value={profileForm.lastName}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Email
</label>
<input
type="email"
value={profileForm.email}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Phone Number (optional)
</label>
<input
type="tel"
value={profileForm.phone}
onChange={(e) => 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"
/>
</div>
</div>
</Card>
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Preferences</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Your Timezone
</label>
<select
value={profileForm.timezone}
onChange={(e) => setProfileForm({ ...profileForm, timezone: 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"
>
<option value="America/New_York">Eastern Time</option>
<option value="America/Chicago">Central Time</option>
<option value="America/Denver">Mountain Time</option>
<option value="America/Los_Angeles">Pacific Time</option>
<option value="UTC">UTC</option>
<option value="Europe/London">London</option>
<option value="Asia/Kolkata">India</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Language
</label>
<select
value={profileForm.language}
onChange={(e) => setProfileForm({ ...profileForm, language: 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"
>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
</select>
</div>
</div>
</Card>
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">Notifications</h2>
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
Choose what emails you want to receive:
</p>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-gray-900 dark:text-white">Important Updates</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
Get notified about important changes to your account
</div>
</div>
<input
type="checkbox"
checked={profileForm.emailNotifications}
onChange={(e) => setProfileForm({ ...profileForm, emailNotifications: e.target.checked })}
className="w-5 h-5 text-[var(--color-brand-500)] rounded focus:ring-[var(--color-brand-500)]"
/>
</div>
<div className="flex items-center justify-between">
<div>
<div className="font-medium text-gray-900 dark:text-white">Tips & Product Updates</div>
<div className="text-sm text-gray-600 dark:text-gray-400">
Hear about new features and content tips
</div>
</div>
<input
type="checkbox"
checked={profileForm.marketingEmails}
onChange={(e) => setProfileForm({ ...profileForm, marketingEmails: e.target.checked })}
className="w-5 h-5 text-[var(--color-brand-500)] rounded focus:ring-[var(--color-brand-500)]"
/>
</div>
</div>
</Card>
<Card className="p-6">
<h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white flex items-center gap-2">
<Lock className="w-5 h-5" />
Security
</h2>
<Button
variant="outline"
tone="neutral"
onClick={() => setShowPasswordModal(true)}
>
Change Password
</Button>
</Card>
{/* Submit Button */}
<div className="flex justify-end">
<Button
type="submit"
variant="primary"
tone="brand"
disabled={saving}
startIcon={saving ? <Loader2 className="w-4 h-4 animate-spin" /> : <Save className="w-4 h-4" />}
>
{saving ? 'Saving...' : 'Save Profile'}
</Button>
</div>
</form>
</div>
)}
{/* Team Tab */}
{activeTab === 'team' && (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">Team Members</h2>
<p className="text-sm text-gray-600 dark:text-gray-400">
Manage who can access your account
</p>
</div>
<Button
variant="primary"
tone="brand"
startIcon={<UserPlus className="w-4 h-4" />}
onClick={() => setShowInviteModal(true)}
>
Invite Someone
</Button>
</div>
{teamLoading ? (
<div className="flex items-center justify-center h-32">
<Loader2 className="w-6 h-6 animate-spin text-[var(--color-brand-500)]" />
</div>
) : (
<Card className="overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<tr>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Name</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Email</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Status</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Role</th>
<th className="text-left py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Joined</th>
<th className="text-right py-3 px-4 text-sm font-medium text-gray-700 dark:text-gray-300">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
{members.map((member) => (
<tr key={member.id} className="hover:bg-gray-50 dark:hover:bg-gray-800">
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">
{member.first_name || member.last_name
? `${member.first_name} ${member.last_name}`.trim()
: '-'}
</td>
<td className="py-3 px-4 text-sm text-gray-900 dark:text-white">
{member.email}
</td>
<td className="py-3 px-4">
<Badge
variant="light"
color={member.is_active ? 'success' : 'error'}
>
{member.is_active ? 'Active' : 'Inactive'}
</Badge>
</td>
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">
{member.is_staff ? 'Admin' : 'Member'}
</td>
<td className="py-3 px-4 text-sm text-gray-600 dark:text-gray-400">
{member.date_joined ? new Date(member.date_joined).toLocaleDateString() : 'N/A'}
</td>
<td className="py-3 px-4 text-right">
<Button
variant="outline"
tone="neutral"
size="sm"
onClick={() => handleRemoveMember(member.id, member.email)}
>
Remove
</Button>
</td>
</tr>
))}
{members.length === 0 && (
<tr>
<td colSpan={6} className="py-12 text-center text-gray-500 dark:text-gray-400">
<Users className="w-12 h-12 mx-auto mb-3 text-gray-300 dark:text-gray-600" />
No team members yet. Invite your first team member!
</td>
</tr>
)}
</tbody>
</table>
</div>
</Card>
)}
{/* Role Permissions Info */}
<Card className="p-6">
<h3 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white flex items-center gap-2">
<Shield className="w-5 h-5" />
Role Permissions
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
<div className="flex items-center justify-between mb-2">
<h4 className="font-semibold text-gray-900 dark:text-white">Admin</h4>
<Badge variant="light" color="primary">High Access</Badge>
</div>
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<li> Manage all sites and content</li>
<li> Invite team members</li>
<li> Cannot manage billing</li>
</ul>
</div>
<div className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
<div className="flex items-center justify-between mb-2">
<h4 className="font-semibold text-gray-900 dark:text-white">Member</h4>
<Badge variant="light" color="info">Standard Access</Badge>
</div>
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1">
<li> Create and edit content</li>
<li> View analytics</li>
<li> Cannot invite users</li>
</ul>
</div>
</div>
</Card>
</div>
)}
</div>
{/* Invite Modal */}
{showInviteModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<Card className="p-6 w-full max-w-md mx-4">
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
Invite Team Member
</h2>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Email *
</label>
<input
type="email"
value={inviteForm.email}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
First Name
</label>
<input
type="text"
value={inviteForm.first_name}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Last Name
</label>
<input
type="text"
value={inviteForm.last_name}
onChange={(e) => 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"
/>
</div>
</div>
<div className="mt-6 flex justify-end gap-3">
<Button
variant="outline"
tone="neutral"
onClick={() => {
setShowInviteModal(false);
setInviteForm({ email: '', first_name: '', last_name: '' });
}}
disabled={inviting}
>
Cancel
</Button>
<Button
variant="primary"
tone="brand"
onClick={handleInvite}
disabled={inviting}
>
{inviting ? 'Inviting...' : 'Send Invitation'}
</Button>
</div>
</Card>
</div>
)}
{/* Password Change Modal */}
{showPasswordModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div
className="fixed inset-0 bg-black/50"
onClick={() => setShowPasswordModal(false)}
/>
<Card className="relative z-10 w-full max-w-md p-6 m-4">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white flex items-center gap-2">
<Lock className="w-5 h-5" />
Change Password
</h2>
<button
onClick={() => setShowPasswordModal(false)}
className="p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
<X className="w-5 h-5" />
</button>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Current Password
</label>
<input
type="password"
value={passwordForm.currentPassword}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
New Password
</label>
<input
type="password"
value={passwordForm.newPassword}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
Confirm New Password
</label>
<input
type="password"
value={passwordForm.confirmPassword}
onChange={(e) => 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"
/>
</div>
</div>
<div className="mt-6 flex justify-end gap-3">
<Button
variant="outline"
tone="neutral"
onClick={() => {
setShowPasswordModal(false);
setPasswordForm({ currentPassword: '', newPassword: '', confirmPassword: '' });
}}
disabled={changingPassword}
>
Cancel
</Button>
<Button
variant="primary"
tone="brand"
onClick={handlePasswordChange}
disabled={changingPassword}
>
{changingPassword ? 'Changing...' : 'Change Password'}
</Button>
</div>
</Card>
</div>
)}
</div>
);
}