SEction 4 completeed

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-27 02:59:27 +00:00
parent 178b7c23ce
commit 74a3441ee4
6 changed files with 326 additions and 446 deletions

View File

@@ -6,19 +6,22 @@
import { useState, useEffect } from 'react';
import {
Save, Loader2, Settings, User, Users, UserPlus, Shield, Lock
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 { 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';
@@ -27,6 +30,7 @@ type TabType = 'account' | 'profile' | 'team';
export default function AccountSettingsPage() {
const toast = useToast();
const { user, refreshUser } = useAuthStore();
const [activeTab, setActiveTab] = useState<TabType>('account');
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
@@ -59,6 +63,15 @@ export default function AccountSettingsPage() {
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);
@@ -74,6 +87,23 @@ export default function AccountSettingsPage() {
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);
@@ -138,9 +168,10 @@ export default function AccountSettingsPage() {
e.preventDefault();
try {
setSaving(true);
// TODO: Connect to profile API when available
await new Promise(resolve => setTimeout(resolve, 500));
// 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 {
@@ -148,6 +179,33 @@ export default function AccountSettingsPage() {
}
};
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');
@@ -555,7 +613,11 @@ export default function AccountSettingsPage() {
<Lock className="w-5 h-5" />
Security
</h2>
<Button variant="outline" tone="neutral">
<Button
variant="outline"
tone="neutral"
onClick={() => setShowPasswordModal(true)}
>
Change Password
</Button>
</Card>
@@ -771,6 +833,91 @@ export default function AccountSettingsPage() {
</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>
);
}