Add for leave family
This commit is contained in:
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import {
|
||||
ArrowLeft,
|
||||
ArrowRightLeft,
|
||||
BarChart3,
|
||||
CalendarDays,
|
||||
Camera,
|
||||
@@ -95,6 +96,7 @@ import {
|
||||
setChatRoomPinned,
|
||||
setChatRoomMuted,
|
||||
toggleChatMessageReaction,
|
||||
transferFamilyOwnership,
|
||||
updateFamilyGroup,
|
||||
uploadMediaObject,
|
||||
voteChatPoll
|
||||
@@ -281,7 +283,9 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
const [presenceMembers, setPresenceMembers] = useState<FamilyPresenceMember[]>([]);
|
||||
const [deleteFamilyDialogOpen, setDeleteFamilyDialogOpen] = useState(false);
|
||||
const [leaveFamilyDialogOpen, setLeaveFamilyDialogOpen] = useState(false);
|
||||
const [transferOwnershipDialogOpen, setTransferOwnershipDialogOpen] = useState(false);
|
||||
const [leavingFamily, setLeavingFamily] = useState(false);
|
||||
const [transferringOwnership, setTransferringOwnership] = useState(false);
|
||||
const [selectedNewOwnerUserId, setSelectedNewOwnerUserId] = useState<string | null>(null);
|
||||
const [deletingFamily, setDeletingFamily] = useState(false);
|
||||
const [deleteMessagesDialogOpen, setDeleteMessagesDialogOpen] = useState(false);
|
||||
@@ -396,6 +400,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
);
|
||||
const ownerMustTransfer = Boolean(isFamilyOwner && otherHumanMembers.length > 0);
|
||||
const ownerLeaveDeletesFamily = Boolean(isFamilyOwner && otherHumanMembers.length === 0);
|
||||
const mobileChatOpen = Boolean(activeRoomId);
|
||||
const isChatCreator = activeRoom?.createdById === user?.id;
|
||||
const canManageChatMembers = Boolean(isFamilyOwner || isChatCreator || activeRoomIsBot);
|
||||
const canManageActiveBot = Boolean(
|
||||
@@ -875,6 +880,30 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
setLeaveFamilyDialogOpen(true);
|
||||
}
|
||||
|
||||
function openTransferOwnershipDialog() {
|
||||
setSelectedNewOwnerUserId(otherHumanMembers[0]?.userId ?? null);
|
||||
setTransferOwnershipDialogOpen(true);
|
||||
}
|
||||
|
||||
async function confirmTransferOwnership() {
|
||||
if (!token || !selectedNewOwnerUserId) {
|
||||
showToast('Выберите нового главу семьи');
|
||||
return;
|
||||
}
|
||||
setTransferringOwnership(true);
|
||||
try {
|
||||
const updated = await transferFamilyOwnership(groupId, selectedNewOwnerUserId, token);
|
||||
setGroup(updated);
|
||||
setTransferOwnershipDialogOpen(false);
|
||||
setFamilyMembersOpen(false);
|
||||
showToast('Управление семьёй передано');
|
||||
} catch (error) {
|
||||
showToast(getApiErrorMessage(error, 'Не удалось передать управление семьёй') ?? 'Ошибка');
|
||||
} finally {
|
||||
setTransferringOwnership(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmLeaveFamily() {
|
||||
if (!token) return;
|
||||
if (ownerMustTransfer && !selectedNewOwnerUserId) {
|
||||
@@ -1447,8 +1476,20 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-120px)] min-h-[640px] overflow-hidden rounded-[28px] border border-[#eceef4] bg-[#eef2f8]">
|
||||
<aside className="flex w-[300px] shrink-0 flex-col border-r border-[#e4e8ef] bg-white">
|
||||
<div
|
||||
className={cn(
|
||||
'flex overflow-hidden bg-[#eef2f8]',
|
||||
'h-[calc(100dvh-3.5rem-env(safe-area-inset-bottom)-4.5rem)] min-h-0 lg:h-[calc(100vh-120px)] lg:min-h-[640px]',
|
||||
'rounded-none border-0 lg:rounded-[28px] lg:border lg:border-[#eceef4]'
|
||||
)}
|
||||
>
|
||||
<aside
|
||||
className={cn(
|
||||
'flex shrink-0 flex-col border-r border-[#e4e8ef] bg-white',
|
||||
'w-full lg:w-[300px]',
|
||||
mobileChatOpen ? 'hidden lg:flex' : 'flex'
|
||||
)}
|
||||
>
|
||||
<div className="border-b border-[#eceef4] p-4">
|
||||
<button type="button" className="mb-3 flex items-center gap-2 text-sm text-[#667085]" onClick={() => router.push('/family')}>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
@@ -1557,11 +1598,24 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section className="flex min-w-0 flex-1 flex-col bg-[#e6ebf2]">
|
||||
<section
|
||||
className={cn(
|
||||
'min-w-0 flex-1 flex-col bg-[#e6ebf2]',
|
||||
mobileChatOpen ? 'flex' : 'hidden lg:flex'
|
||||
)}
|
||||
>
|
||||
{activeRoom ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between border-b border-[#dce3ec] bg-white px-4 py-3">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<div className="flex items-center justify-between gap-2 border-b border-[#dce3ec] bg-white px-3 py-3 sm:px-4">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2 sm:gap-3">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Назад к списку чатов"
|
||||
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl text-[#667085] transition hover:bg-[#f4f5f8] lg:hidden"
|
||||
onClick={() => setActiveRoomId(null)}
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5" />
|
||||
</button>
|
||||
{(activeRoom.type === 'GROUP' || activeRoom.type === 'GENERAL') ? (
|
||||
<label className="group/room-avatar relative block shrink-0 cursor-pointer overflow-hidden rounded-full">
|
||||
<ChatRoomAvatarDisplay room={activeRoom} viewerUserId={user?.id ?? ''} token={token} />
|
||||
@@ -1623,13 +1677,13 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex shrink-0 items-center gap-1 sm:gap-2">
|
||||
{!activeRoomIsBot ? (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="rounded-xl"
|
||||
className="h-9 w-9 rounded-xl p-0 sm:h-auto sm:w-auto sm:px-3"
|
||||
aria-label="Поиск по сообщениям"
|
||||
onClick={() => {
|
||||
setChatToolsTab('search');
|
||||
@@ -1641,7 +1695,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="rounded-xl"
|
||||
className="hidden h-9 w-9 rounded-xl p-0 sm:flex sm:h-auto sm:w-auto sm:px-3"
|
||||
aria-label="Перейти к дате"
|
||||
onClick={() => {
|
||||
setChatToolsTab('calendar');
|
||||
@@ -2095,7 +2149,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
onDelete={() => void handleBulkDelete()}
|
||||
/>
|
||||
) : (
|
||||
<div className="border-t border-[#dce3ec] bg-white px-3 py-3">
|
||||
<div className="border-t border-[#dce3ec] bg-white px-2 py-2 pb-[max(0.5rem,env(safe-area-inset-bottom))] sm:px-3 sm:py-3">
|
||||
{editingMessageId ? (
|
||||
<ChatMessageEditBanner
|
||||
preview={draft.trim() || messages.find((item) => item.id === editingMessageId)?.content || undefined}
|
||||
@@ -2126,7 +2180,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
<Paperclip className="h-4 w-4" />
|
||||
</Button>
|
||||
{!activeRoomIsE2E ? (
|
||||
<Button size="sm" variant="ghost" className="h-10 w-10 rounded-xl p-0" onClick={() => setPollOpen(true)}>
|
||||
<Button size="sm" variant="ghost" className="hidden h-10 w-10 rounded-xl p-0 sm:flex" onClick={() => setPollOpen(true)}>
|
||||
<BarChart3 className="h-4 w-4" />
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -2373,6 +2427,19 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
<UserPlus className="mr-2 h-4 w-4" />
|
||||
Пригласить в семью
|
||||
</Button>
|
||||
{isFamilyOwner && otherHumanMembers.length > 0 ? (
|
||||
<Button
|
||||
className="mt-2 w-full rounded-xl"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setFamilyMembersOpen(false);
|
||||
openTransferOwnershipDialog();
|
||||
}}
|
||||
>
|
||||
<ArrowRightLeft className="mr-2 h-4 w-4" />
|
||||
Передать управление
|
||||
</Button>
|
||||
) : null}
|
||||
<Button
|
||||
className="mt-2 w-full rounded-xl"
|
||||
variant="secondary"
|
||||
@@ -2399,6 +2466,73 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={transferOwnershipDialogOpen} onOpenChange={setTransferOwnershipDialogOpen}>
|
||||
<DialogContent className="max-h-[90dvh] overflow-y-auto rounded-[28px] sm:max-w-[480px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Передать управление семьёй</DialogTitle>
|
||||
</DialogHeader>
|
||||
<p className="text-sm leading-relaxed text-[#667085]">
|
||||
Выберите участника, который станет новым главой семьи «{group?.name}». Вы останетесь участником и сможете
|
||||
покинуть семью в любой момент.
|
||||
</p>
|
||||
<div className="mt-4 max-h-[280px] space-y-2 overflow-y-auto">
|
||||
{otherHumanMembers.map((member) => {
|
||||
const selected = selectedNewOwnerUserId === member.userId;
|
||||
return (
|
||||
<button
|
||||
key={member.id}
|
||||
type="button"
|
||||
onClick={() => setSelectedNewOwnerUserId(member.userId)}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-3 rounded-2xl px-3 py-3 text-left transition',
|
||||
selected ? 'bg-[#3390ec]/10 ring-2 ring-[#3390ec]/40' : 'bg-[#f4f5f8] hover:bg-[#eceef4]'
|
||||
)}
|
||||
>
|
||||
<AvatarWithPresence
|
||||
userId={member.userId}
|
||||
displayName={member.displayName}
|
||||
hasAvatar={member.hasAvatar}
|
||||
token={token}
|
||||
isVerified={member.isVerified}
|
||||
verificationIcon={member.verificationIcon}
|
||||
className="h-10 w-10"
|
||||
/>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate font-medium">{member.displayName}</p>
|
||||
<p className="text-xs text-[#667085]">{familyMemberRoleLabel(member.role)}</p>
|
||||
</div>
|
||||
{selected ? <Check className="h-5 w-5 shrink-0 text-[#3390ec]" /> : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-6 flex flex-col-reverse gap-3 sm:flex-row">
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="flex-1"
|
||||
disabled={transferringOwnership}
|
||||
onClick={() => setTransferOwnershipDialogOpen(false)}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1"
|
||||
disabled={transferringOwnership || !selectedNewOwnerUserId}
|
||||
onClick={() => void confirmTransferOwnership()}
|
||||
>
|
||||
{transferringOwnership ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Передаём...
|
||||
</>
|
||||
) : (
|
||||
'Передать управление'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={leaveFamilyDialogOpen} onOpenChange={setLeaveFamilyDialogOpen}>
|
||||
<DialogContent className="rounded-[28px] sm:max-w-[480px]">
|
||||
<DialogHeader>
|
||||
|
||||
Reference in New Issue
Block a user