diff --git a/components/settings/unified/UnifiedSettingsModal.tsx b/components/settings/unified/UnifiedSettingsModal.tsx index 89029e8..b529e14 100644 --- a/components/settings/unified/UnifiedSettingsModal.tsx +++ b/components/settings/unified/UnifiedSettingsModal.tsx @@ -210,7 +210,7 @@ function TabContent({ // Account tabs if (context === 'account') { switch (activeTab) { - case 'profile': return + case 'profile': return case 'security': return case 'devices': return } diff --git a/components/settings/unified/tabs/AccountProfileTab.tsx b/components/settings/unified/tabs/AccountProfileTab.tsx index 5eecee1..b7ff11d 100644 --- a/components/settings/unified/tabs/AccountProfileTab.tsx +++ b/components/settings/unified/tabs/AccountProfileTab.tsx @@ -1,17 +1,141 @@ 'use client' -import ProfileSettings from '@/components/settings/ProfileSettings' -import TrustedDevicesCard from '@/components/settings/TrustedDevicesCard' +import { useState, useEffect, useRef, useCallback } from 'react' +import { Input, Button, toast, Spinner } from '@ciphera-net/ui' +import { useAuth } from '@/lib/auth/context' +import { updateDisplayName } from '@/lib/api/user' +import { deleteAccount } from '@/lib/api/user' +import { getAuthErrorMessage } from '@ciphera-net/ui' + +export default function AccountProfileTab({ onDirtyChange, onRegisterSave }: { onDirtyChange?: (dirty: boolean) => void; onRegisterSave?: (fn: () => Promise) => void }) { + const { user, refresh, logout } = useAuth() + const [displayName, setDisplayName] = useState('') + const initialRef = useRef('') + const hasInitialized = useRef(false) + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) + const [deleteText, setDeleteText] = useState('') + const [deleting, setDeleting] = useState(false) + + useEffect(() => { + if (!user || hasInitialized.current) return + setDisplayName(user.display_name || '') + initialRef.current = user.display_name || '' + hasInitialized.current = true + }, [user]) + + // Track dirty state + useEffect(() => { + if (!hasInitialized.current) return + onDirtyChange?.(displayName !== initialRef.current) + }, [displayName, onDirtyChange]) + + const handleSave = useCallback(async () => { + try { + await updateDisplayName(displayName.trim()) + await refresh() + initialRef.current = displayName.trim() + onDirtyChange?.(false) + toast.success('Profile updated') + } catch (err) { + toast.error(getAuthErrorMessage(err as Error) || 'Failed to update profile') + } + }, [displayName, refresh, onDirtyChange]) + + useEffect(() => { + onRegisterSave?.(handleSave) + }, [handleSave, onRegisterSave]) + + const handleDelete = async () => { + if (deleteText !== 'DELETE') return + setDeleting(true) + try { + await deleteAccount() + logout() + } catch (err) { + toast.error(getAuthErrorMessage(err as Error) || 'Failed to delete account') + setDeleting(false) + } + } + + if (!user) return
-export default function AccountProfileTab() { return ( -
+

Profile

Manage your personal account settings.

- + {/* Display Name */} +
+
+ + setDisplayName(e.target.value)} + placeholder="Your name" + /> +
+ + {/* Email — read-only display */} +
+ + +

Email changes require password verification. Use Ciphera Auth to change your email.

+
+
+ + {/* Danger Zone */} +
+

Danger Zone

+
+
+
+

Delete Account

+

Permanently delete your account and all associated data.

+
+ +
+ {showDeleteConfirm && ( +
+

This will permanently delete:

+
    +
  • Your account and all personal data
  • +
  • All sessions and trusted devices
  • +
  • You will be removed from all organizations
  • +
+
+ + setDeleteText(e.target.value)} + className="w-full px-3 py-2 border border-neutral-700 rounded-lg bg-neutral-900 text-white text-sm" + placeholder="DELETE" + /> +
+
+ + +
+
+ )} +
+
) }