first commit

This commit is contained in:
lendry
2026-06-24 14:37:15 +03:00
commit 995adeedd4
188 changed files with 28810 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
'use client';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import {
ChevronRight,
FileText,
Heart,
LockKeyhole,
LogOut,
ShieldCheck,
Smartphone,
UserRound
} from 'lucide-react';
import { AdminBadge } from '@/components/id/admin-badge';
import { useAuth } from '@/components/id/auth-provider';
import { useAvatarUrl } from '@/hooks/use-avatar-url';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { cn } from '@/lib/utils';
function maskPhone(phone?: string | null) {
if (!phone) return null;
const digits = phone.replace(/\D/g, '');
if (digits.length < 10) return phone;
return `+${digits.slice(0, 1)} ${digits.slice(1, 4)} ***-**-${digits.slice(-2)}`;
}
const menuItems = [
{ href: '/data', label: 'Личные данные', subtitle: 'ФИО, день рождения, пол', icon: UserRound },
{ href: '/security', label: 'Телефон и безопасность', subtitle: 'PIN, пароль, устройства', icon: Smartphone },
{ href: '/documents', label: 'Документы', subtitle: 'Паспорт, права, загран', icon: FileText },
{ href: '/family', label: 'Семья', subtitle: 'Участники и доступ', icon: Heart },
{ href: '/security', label: 'Защита аккаунта', subtitle: 'Способы входа и сессии', icon: LockKeyhole }
];
export function UserMenu({ className }: { className?: string }) {
const router = useRouter();
const { user, token, logout } = useAuth();
const { avatarUrl } = useAvatarUrl(user?.id, user?.hasAvatar, token);
if (!user) return null;
const contactLine = [maskPhone(user.phone), user.username ?? user.email].filter(Boolean).join(' · ');
const initials = user.displayName
.split(' ')
.map((part) => part[0])
.join('')
.slice(0, 2)
.toUpperCase();
return (
<Popover>
<PopoverTrigger asChild>
<button
type="button"
className={cn('flex items-center gap-2 rounded-full border border-[#eceef4] bg-white py-1 pl-1 pr-3 shadow-sm transition hover:bg-[#fafbfd]', className)}
aria-label="Меню пользователя"
>
<Avatar className="h-9 w-9">
{avatarUrl ? <AvatarImage src={avatarUrl} alt={user.displayName} /> : null}
<AvatarFallback className="text-xs font-semibold">{initials}</AvatarFallback>
</Avatar>
<span className="hidden max-w-[120px] truncate text-sm font-medium md:inline">{user.displayName}</span>
</button>
</PopoverTrigger>
<PopoverContent align="end" className="w-[min(92vw,360px)] rounded-[28px] border-[#eceef4] p-0 shadow-2xl">
<div className="border-b border-[#eceef4] px-5 pb-5 pt-5 text-center">
<Avatar className="mx-auto h-20 w-20">
{avatarUrl ? <AvatarImage src={avatarUrl} alt={user.displayName} /> : null}
<AvatarFallback className="text-lg font-semibold">{initials}</AvatarFallback>
</Avatar>
<div className="mt-3 flex items-center justify-center gap-2">
<p className="text-lg font-semibold">{user.displayName}</p>
<AdminBadge user={user} />
</div>
<p className="mt-1 text-sm text-[#667085]">{contactLine || 'Контакты не указаны'}</p>
</div>
<div className="p-2">
{menuItems.map((item) => (
<Link
key={`${item.href}-${item.label}`}
href={item.href}
className="flex items-center gap-3 rounded-[18px] px-3 py-3 transition hover:bg-[#f4f5f8]"
>
<item.icon className="h-5 w-5 shrink-0 text-[#667085]" />
<div className="min-w-0 flex-1 text-left">
<div className="font-medium">{item.label}</div>
<div className="truncate text-xs text-[#667085]">{item.subtitle}</div>
</div>
<ChevronRight className="h-4 w-4 text-[#a8adbc]" />
</Link>
))}
{user.canAccessAdmin ? (
<Link href="/admin/users" className="mt-1 flex items-center gap-3 rounded-[18px] px-3 py-3 transition hover:bg-[#f4f5f8]">
<ShieldCheck className="h-5 w-5 shrink-0 text-[#667085]" />
<div className="min-w-0 flex-1 text-left">
<div className="font-medium">Админ-панель</div>
<div className="text-xs text-[#667085]">Пользователи, OAuth и настройки</div>
</div>
<ChevronRight className="h-4 w-4 text-[#a8adbc]" />
</Link>
) : null}
<button
type="button"
onClick={() => {
logout();
router.push('/auth/login');
}}
className="mt-1 flex w-full items-center gap-3 rounded-[18px] px-3 py-3 text-left transition hover:bg-[#f4f5f8]"
>
<LogOut className="h-5 w-5 shrink-0 text-[#667085]" />
<div>
<div className="font-medium">Выйти</div>
<div className="text-xs text-[#667085]">Завершить текущую сессию</div>
</div>
</button>
</div>
</PopoverContent>
</Popover>
);
}