Files
IdP/apps/frontend/app/page.tsx
2026-06-25 16:01:41 +03:00

141 lines
6.4 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.
'use client';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useRouter } from 'next/navigation';
import { BadgeCheck, Bell, Car, FileText, Fingerprint, Heart, KeyRound, PawPrint, ShieldCheck, Smartphone, UserRound } from 'lucide-react';
import { AddressQuickSection } from '@/components/addresses/address-quick-section';
import { DocumentFormDialog } from '@/components/documents/document-form-dialog';
import { ActionTile, SectionTitle } from '@/components/id/action-tile';
import { AdminBadge } from '@/components/id/admin-badge';
import { AvatarDisplay } from '@/components/id/avatar-upload';
import { useAuth } from '@/components/id/auth-provider';
import { IdShell } from '@/components/id/shell';
import { Button } from '@/components/ui/button';
import { useRequireAuth } from '@/hooks/use-require-auth';
import { type DocumentTypeCode, indexDocumentsByType } from '@/lib/document-catalog';
import { apiFetch, UserDocument } from '@/lib/api';
export default function HomePage() {
const router = useRouter();
const { user, token, isPinLocked } = useAuth();
const { isReady } = useRequireAuth();
const contactLine = [user?.phone, user?.username ?? user?.email].filter(Boolean).join(' · ');
const [documents, setDocuments] = useState<UserDocument[]>([]);
const [activeDocumentType, setActiveDocumentType] = useState<DocumentTypeCode | null>(null);
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 {
setDocuments([]);
}
}, [isPinLocked, token, user]);
useEffect(() => {
if (isReady && user && !isPinLocked) void loadDocuments();
}, [isPinLocked, isReady, loadDocuments, user]);
const documentsByType = useMemo(() => indexDocumentsByType(documents), [documents]);
function openDocument(type: DocumentTypeCode) {
setActiveDocumentType(type);
}
if (!isReady) {
return (
<IdShell active="/" wide>
<div className="py-20 text-center text-[#667085]">Загрузка профиля...</div>
</IdShell>
);
}
return (
<IdShell active="/" wide>
<div className="flex flex-col items-center">
{user ? (
<AvatarDisplay
userId={user.id}
displayName={user.displayName}
hasAvatar={user.hasAvatar}
token={token}
isVerified={user.isVerified}
verificationIcon={user.verificationIcon}
className="h-24 w-24 border-4 border-white shadow-xl"
badgeSize="md"
/>
) : (
<div className="h-24 w-24 rounded-full bg-[#f4f5f8]" />
)}
<h1 className="mt-4 text-2xl font-semibold">{user?.displayName ?? 'Загрузка профиля...'}</h1>
{user ? <AdminBadge user={user} className="mt-2" /> : null}
<p className="text-sm text-[#667085]">{contactLine || 'Данные профиля загружаются'}</p>
<div className="mt-4 flex gap-3">
<Button variant="secondary" size="sm" onClick={() => router.push('/family')}>
<Heart className="h-4 w-4 text-red-500" />
Семья
</Button>
<Button variant="secondary" size="sm" onClick={() => router.push('/data')}>
<UserRound className="h-4 w-4" />
Профиль
</Button>
</div>
<div className="mt-8 flex w-full max-w-[450px] items-center justify-between rounded-[24px] bg-[#f0f2f7] p-5">
<div>
<h2 className="font-semibold">Защитите ваш аккаунт</h2>
<p className="mt-1 text-sm text-[#667085]">Настройте PIN-код и способы восстановления</p>
<Button className="mt-4 rounded-xl" size="sm" onClick={() => router.push('/security')}>
Перейти к защите
</Button>
</div>
<BadgeCheck className="h-20 w-20 text-[#68b8ff]" />
</div>
<div className="w-full max-w-[720px]">
<SectionTitle>Защита аккаунта</SectionTitle>
<div className="grid grid-cols-3 gap-4 md:grid-cols-5">
<ActionTile icon={ShieldCheck} label="Способы входа" onClick={() => router.push('/security')} />
<ActionTile icon={Smartphone} label="Проверить устройства" onClick={() => router.push('/security')} />
<ActionTile icon={Bell} label="Активность" onClick={() => router.push('/security')} />
<ActionTile icon={KeyRound} label="Добавить запасную почту" onClick={() => router.push('/security')} />
<ActionTile icon={Fingerprint} label="Привязать лицо или отпечаток" onClick={() => router.push('/security')} />
</div>
<SectionTitle>Документы</SectionTitle>
<div className="grid grid-cols-3 gap-4 md:grid-cols-6">
<ActionTile icon={FileText} label="Все" onClick={() => router.push('/documents')} />
<ActionTile icon={FileText} label="Паспорт" onClick={() => openDocument('PASSPORT_RF')} />
<ActionTile icon={FileText} label="Загран" onClick={() => openDocument('FOREIGN_PASSPORT')} />
<ActionTile icon={Car} label="ВУ" onClick={() => openDocument('DRIVER_LICENSE')} />
</div>
<AddressQuickSection layout="home" isPinLocked={isPinLocked} isReady={isReady} />
<SectionTitle>Семья</SectionTitle>
<div className="grid grid-cols-3 gap-4 md:grid-cols-6">
<ActionTile icon={UserRound} label="Вы" onClick={() => router.push('/family')} />
<ActionTile icon={Heart} label="Добавить" onClick={() => router.push('/family')} />
<ActionTile icon={PawPrint} label="Питомцы" onClick={() => router.push('/family')} />
</div>
</div>
</div>
{activeDocumentType && user ? (
<DocumentFormDialog
open={Boolean(activeDocumentType)}
onOpenChange={(open) => {
if (!open) setActiveDocumentType(null);
}}
documentType={activeDocumentType}
userId={user.id}
token={token}
existing={documentsByType.get(activeDocumentType) ?? null}
onSaved={loadDocuments}
/>
) : null}
</IdShell>
);
}