'use client'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Car, ChevronRight, FileText, Loader2, Mail, Phone, Trash2, UserRound } from 'lucide-react'; import { AddressQuickSection } from '@/components/addresses/address-quick-section'; import { DocumentFormDialog } from '@/components/documents/document-form-dialog'; import { ActionTile } from '@/components/id/action-tile'; import { useAuth } from '@/components/id/auth-provider'; import { IdShell } from '@/components/id/shell'; import { useToast } from '@/components/id/toast-provider'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { AvatarDisplay, AvatarUpload } from '@/components/id/avatar-upload'; import { Input } from '@/components/ui/input'; import { useRequireAuth } from '@/hooks/use-require-auth'; import { getDocumentType, indexDocumentsByType, type DocumentTypeCode } from '@/lib/document-catalog'; import { apiFetch, getApiErrorMessage, softDeleteProfile, UserDocument } from '@/lib/api'; function Row({ icon: Icon, title, text, onClick, destructive }: { icon: typeof Mail; title: string; text?: string; onClick?: () => void; destructive?: boolean; }) { const content = ( <>
{title}
{text ?
{text}
: null}
{onClick ? : null} ); if (!onClick) { return
{content}
; } return ( ); } export default function DataPage() { const router = useRouter(); const { user, token, refreshProfile, logout } = useAuth(); const { isReady, isPinLocked } = useRequireAuth(); const { showToast } = useToast(); const [displayName, setDisplayName] = useState(''); const [email, setEmail] = useState(''); const [backupEmail, setBackupEmail] = useState(''); const [phone, setPhone] = useState(''); const [backupPhone, setBackupPhone] = useState(''); const [documents, setDocuments] = useState([]); const [isSaving, setIsSaving] = useState(false); const [activeDocumentType, setActiveDocumentType] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const loadDocuments = useCallback(async () => { if (!user || !token || isPinLocked) return; try { const response = await apiFetch<{ documents?: UserDocument[] }>(`/documents/users/${user.id}`, {}, token); setDocuments(response.documents ?? []); } catch (error) { const message = getApiErrorMessage(error, 'Не удалось загрузить документы'); if (message) showToast(message); } }, [isPinLocked, showToast, token, user]); useEffect(() => { if (!user) return; setDisplayName(user.displayName); setEmail(user.email ?? ''); setBackupEmail(user.backupEmail ?? ''); setPhone(user.phone ?? ''); setBackupPhone(user.backupPhone ?? ''); }, [user]); useEffect(() => { if (isReady && user && !isPinLocked) void loadDocuments(); }, [isPinLocked, isReady, loadDocuments, user]); const documentsByType = useMemo(() => indexDocumentsByType(documents), [documents]); function openDocument(type: DocumentTypeCode) { setActiveDocumentType(type); } async function updateProfile() { if (!user || !token) return; setIsSaving(true); const trimmedName = displayName.trim(); const [firstName, ...rest] = trimmedName.split(' '); try { await apiFetch(`/profile/users/${user.id}`, { method: 'PATCH', body: JSON.stringify({ firstName: firstName || undefined, lastName: rest.join(' ') || undefined }) }, token); const contacts: Record = {}; if (email.trim() && email.trim() !== (user.email ?? '')) contacts.email = email.trim(); if (phone.trim() && phone.trim() !== (user.phone ?? '')) contacts.phone = phone.trim(); if (backupEmail.trim() && backupEmail.trim() !== (user.backupEmail ?? '')) contacts.backupEmail = backupEmail.trim(); if (backupPhone.trim() && backupPhone.trim() !== (user.backupPhone ?? '')) contacts.backupPhone = backupPhone.trim(); if (Object.keys(contacts).length > 0) { await apiFetch(`/profile/users/${user.id}/contacts`, { method: 'PATCH', body: JSON.stringify(contacts) }, token); } await refreshProfile(); showToast('Профиль обновлён'); } catch (error) { const message = getApiErrorMessage(error, 'Не удалось обновить профиль'); if (message) showToast(message); } finally { setIsSaving(false); } } async function confirmDeleteProfile() { if (!user || !token) return; setIsDeleting(true); try { await softDeleteProfile(user.id, token); setDeleteDialogOpen(false); showToast('Профиль удалён'); logout(); } catch (error) { const message = getApiErrorMessage(error, 'Не удалось удалить профиль'); if (message) showToast(message); } finally { setIsDeleting(false); } } return (
{user ? ( ) : ( )}

{user?.displayName ?? 'Профиль'}

Логин: {user?.username ?? user?.email ?? user?.phone ?? 'не указан'}

Личные данные

Эти данные помогают быстрее входить в сервисы и восстанавливать доступ.

setDisplayName(event.target.value)} /> setEmail(event.target.value)} /> setBackupEmail(event.target.value)} /> setPhone(event.target.value)} /> setBackupPhone(event.target.value)} />

Документы

ID бережно хранит документы и подставляет их там, где вы разрешили.

openDocument('PASSPORT_RF')} /> openDocument('FOREIGN_PASSPORT')} /> openDocument('DRIVER_LICENSE')} /> openDocument('OMS')} />
{documents.length === 0 ? (

Документы пока не добавлены

) : ( documents.map((document) => { const config = getDocumentType(document.type); return ( openDocument(document.type as DocumentTypeCode)} /> ); }) )}

Контакты

Управление данными

setDeleteDialogOpen(true)} destructive />
{activeDocumentType && user ? ( { if (!open) setActiveDocumentType(null); }} documentType={activeDocumentType} userId={user.id} token={token} existing={documentsByType.get(activeDocumentType) ?? null} onSaved={loadDocuments} /> ) : null} Удалить профиль?

Ваш аккаунт будет помечен как удалённый. Вы сразу выйдете из системы и больше не сможете войти с текущими данными. Почта, телефон и логин будут освобождены для новой регистрации. Административные роли будут сняты. Запись в базе сохранится в архивном виде.

); }