Files
IdP/apps/frontend/components/family/family-sidebar-panel.tsx
2026-06-25 16:01:41 +03:00

131 lines
4.9 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 Link from 'next/link';
import { useState } from 'react';
import { Loader2, Search, UsersRound } from 'lucide-react';
import { AvatarWithPresence } from '@/components/family/avatar-with-presence';
import { FamilyGroupSelector } from '@/components/family/family-group-selector';
import { FamilyInviteDialog } from '@/components/family/family-invite-dialog';
import { useFamilyOverlay } from '@/components/family/family-overlay-provider';
import { useAuth } from '@/components/id/auth-provider';
import { useSelectedFamily } from '@/hooks/use-primary-family';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
export function FamilySidebarPanel() {
const { user, token } = useAuth();
const {
group,
groupSummaries,
loading,
hasFamily,
selectedGroupId,
setSelectedGroupId,
presenceByUserId,
refresh
} = useSelectedFamily(Boolean(user && token));
const { openChatWithMember } = useFamilyOverlay();
const [inviteOpen, setInviteOpen] = useState(false);
if (!user || !token) return null;
return (
<>
<div className="mt-5 border-t border-[#eceef4] pt-4">
<div className="mb-2 flex items-center justify-between gap-1">
<div className="min-w-0">
<p className="truncate text-[11px] font-semibold uppercase tracking-wide text-[#667085]">Семья</p>
{hasFamily ? (
<FamilyGroupSelector
groups={groupSummaries}
selectedGroupId={selectedGroupId}
onSelect={setSelectedGroupId}
href={`/family/${group!.id}`}
nameClassName="text-xs font-medium text-[#1f2430]"
/>
) : (
<Link href="/family" className="text-xs font-medium text-[#3390ec] hover:underline">
Создать семью
</Link>
)}
</div>
{hasFamily ? (
<Button
type="button"
variant="ghost"
size="icon"
className="h-7 w-7 shrink-0 rounded-lg"
aria-label="Найти и пригласить"
onClick={() => setInviteOpen(true)}
>
<Search className="h-3.5 w-3.5" />
</Button>
) : null}
</div>
{loading ? (
<div className="flex items-center gap-2 py-2 text-[11px] text-[#667085]">
<Loader2 className="h-3.5 w-3.5 animate-spin" />
Загрузка...
</div>
) : hasFamily ? (
<div className="max-h-[220px] space-y-1 overflow-y-auto pr-1">
{(group?.members ?? []).map((member) => {
const presence = presenceByUserId.get(member.userId);
const isSelf = member.userId === user.id;
return (
<button
key={member.id}
type="button"
className={cn(
'flex w-full items-center gap-2 rounded-xl px-2 py-1.5 text-left transition hover:bg-[#f4f5f8]',
isSelf && 'bg-[#fafbfd]'
)}
onClick={() => openChatWithMember(member.userId, member.displayName)}
title={isSelf ? 'Вы' : `Написать ${member.displayName}`}
>
<AvatarWithPresence
userId={member.userId}
displayName={member.displayName}
hasAvatar={member.hasAvatar}
token={token}
isVerified={member.isVerified}
verificationIcon={member.verificationIcon}
online={isSelf ? undefined : presence?.online}
className="h-8 w-8"
/>
<div className="min-w-0 flex-1">
<p className="truncate text-[12px] font-medium leading-tight">
{member.displayName}
{isSelf ? ' (Вы)' : ''}
</p>
{!isSelf ? (
<p className={cn('truncate text-[10px]', presence?.online ? 'text-emerald-600' : 'text-[#a8adbc]')}>
{presence?.online ? 'В сети' : 'Не в сети'}
</p>
) : null}
</div>
</button>
);
})}
</div>
) : (
<div className="rounded-xl bg-[#f4f5f8] px-2 py-3 text-[11px] text-[#667085]">
<UsersRound className="mb-1 h-4 w-4" />
Приглашайте близких и общайтесь в чатах
</div>
)}
</div>
{hasFamily ? (
<FamilyInviteDialog
groupId={group!.id}
open={inviteOpen}
onOpenChange={setInviteOpen}
onInvited={() => void refresh()}
/>
) : null}
</>
);
}