diff --git a/apps/api-gateway/src/controllers/family.controller.ts b/apps/api-gateway/src/controllers/family.controller.ts index 686d311..efc91a0 100644 --- a/apps/api-gateway/src/controllers/family.controller.ts +++ b/apps/api-gateway/src/controllers/family.controller.ts @@ -5,7 +5,7 @@ import { firstValueFrom } from 'rxjs'; import { map } from 'rxjs/operators'; import { CoreGrpcService } from '../core-grpc.service'; import { getAuthorizedUserId } from '../document-access'; -import { AddFamilyMemberDto, CreateFamilyGroupDto, LeaveFamilyGroupDto, RespondFamilyInviteDto, SendFamilyInviteDto, UpdateFamilyGroupDto } from '../dto/identity.dto'; +import { AddFamilyMemberDto, CreateFamilyGroupDto, LeaveFamilyGroupDto, RespondFamilyInviteDto, SendFamilyInviteDto, TransferFamilyOwnershipDto, UpdateFamilyGroupDto } from '../dto/identity.dto'; @ApiTags('Семья') @ApiBearerAuth() @@ -108,6 +108,23 @@ export class FamilyController { ); } + @Post('groups/:groupId/transfer-ownership') + @ApiOperation({ summary: 'Передать управление семейной группой' }) + async transferOwnership( + @Headers('authorization') authorization: string | undefined, + @Param('groupId') groupId: string, + @Body() dto: TransferFamilyOwnershipDto + ) { + const requesterId = await this.auth(authorization); + return firstValueFrom( + this.core.family.TransferFamilyOwnership({ + requesterId, + groupId, + newOwnerUserId: dto.newOwnerUserId + }) + ); + } + @Get('groups/:groupId/invite-search') @ApiOperation({ summary: 'Поиск пользователей для приглашения в семью' }) async searchInviteUsers( diff --git a/apps/api-gateway/src/dto/identity.dto.ts b/apps/api-gateway/src/dto/identity.dto.ts index 0bcb37f..e073f96 100644 --- a/apps/api-gateway/src/dto/identity.dto.ts +++ b/apps/api-gateway/src/dto/identity.dto.ts @@ -161,3 +161,10 @@ export class LeaveFamilyGroupDto { newOwnerUserId?: string; } +export class TransferFamilyOwnershipDto { + @ApiProperty({ description: 'ID пользователя, которому передаётся управление семьёй' }) + @IsString({ message: 'ID нового главы семьи должен быть строкой' }) + @IsNotEmpty({ message: 'Укажите нового главу семьи' }) + newOwnerUserId!: string; +} + diff --git a/apps/frontend/app/admin/users/page.tsx b/apps/frontend/app/admin/users/page.tsx index b12a875..85046c5 100644 --- a/apps/frontend/app/admin/users/page.tsx +++ b/apps/frontend/app/admin/users/page.tsx @@ -324,7 +324,7 @@ export default function AdminUsersPage() { -
+
{loading ? (
diff --git a/apps/frontend/app/documents/page.tsx b/apps/frontend/app/documents/page.tsx index d8b3030..aa64619 100644 --- a/apps/frontend/app/documents/page.tsx +++ b/apps/frontend/app/documents/page.tsx @@ -52,7 +52,7 @@ export default function DocumentsPage() { return ( -

Документы

+

Документы

Храните документы и заполняйте формы — ID подставит данные там, где вы разрешите.

diff --git a/apps/frontend/app/family/[groupId]/page.tsx b/apps/frontend/app/family/[groupId]/page.tsx index 0c6d9b5..2744a47 100644 --- a/apps/frontend/app/family/[groupId]/page.tsx +++ b/apps/frontend/app/family/[groupId]/page.tsx @@ -19,7 +19,7 @@ export default function FamilyGroupPage({ params }: { params: Promise<{ groupId: const { groupId } = use(params); return ( - + ); diff --git a/apps/frontend/app/family/page.tsx b/apps/frontend/app/family/page.tsx index a8d6a47..fe20408 100644 --- a/apps/frontend/app/family/page.tsx +++ b/apps/frontend/app/family/page.tsx @@ -65,12 +65,12 @@ export default function FamilyPage() { return (

Семья

-

Семейный доступ

-

Приглашайте близких, общайтесь в чатах и управляйте семейной группой.

+

Семейный доступ

+

Приглашайте близких, общайтесь в чатах и управляйте семейной группой.

-
- setName(event.target.value)} placeholder="Название семьи" /> -
diff --git a/apps/frontend/app/globals.css b/apps/frontend/app/globals.css index 743cefd..020e43b 100644 --- a/apps/frontend/app/globals.css +++ b/apps/frontend/app/globals.css @@ -22,6 +22,11 @@ body { background: var(--background); color: var(--foreground); font-family: Arial, Helvetica, sans-serif; + overflow-x: hidden; +} + +html { + overflow-x: hidden; } .rdp-root { diff --git a/apps/frontend/app/security/page.tsx b/apps/frontend/app/security/page.tsx index 2175ee1..ef27619 100644 --- a/apps/frontend/app/security/page.tsx +++ b/apps/frontend/app/security/page.tsx @@ -335,7 +335,7 @@ export default function SecurityPage() { return (

Безопасность

-

Устройства

+

Устройства

На них вы вошли в ID и получаете уведомления безопасности от сервисов

diff --git a/apps/frontend/components/family/family-group-view.tsx b/apps/frontend/components/family/family-group-view.tsx index 28a1f2f..809d047 100644 --- a/apps/frontend/components/family/family-group-view.tsx +++ b/apps/frontend/components/family/family-group-view.tsx @@ -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([]); 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(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 ( -
-