131 lines
4.9 KiB
TypeScript
131 lines
4.9 KiB
TypeScript
'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}
|
||
</>
|
||
);
|
||
}
|