add group chat

This commit is contained in:
Виталий Лавшонок
2025-11-23 10:30:31 +03:00
parent abb7301c16
commit 390f1f52c8
28 changed files with 414 additions and 217 deletions

View File

@@ -68,14 +68,14 @@ function greet(user: User) {
return \`Привет, \${user.name}! 👋 Роль: \${user.role}\`;
}
console.log(greet({ name: "Ты", role: "Разработчик" }));
consol.log(greet({ name: "Ты", role: "Разработчик" }));
\`\`\`
Пример **JavaScript**:
\`\`\`js
const sum = (a, b) => a + b;
console.log(sum(2, 3)); // 5
consol.log(sum(2, 3)); // 5
\`\`\`
Пример **Python**:
@@ -256,9 +256,7 @@ const MarkdownEditor: FC<MarkdownEditorProps> = ({
markdown.slice(cursorPos);
setMarkdown(newText);
} catch (err) {
console.error('Ошибка загрузки изображения:', err);
}
} catch (err) {}
}
}
};

View File

@@ -36,13 +36,17 @@ const Filters = () => {
text: 'ID',
},
]}
onChange={(v) => {}}
onChange={(v) => {
v;
}}
/>
<FilterDropDown
items={items}
defaultState={[]}
onChange={(values) => {}}
onChange={(values) => {
values;
}}
/>
</div>
);

View File

@@ -1,124 +1,126 @@
import { FC, useEffect } from "react";
import MissionItem from "./MissionItem";
import { FC, useEffect } from 'react';
import MissionItem from './MissionItem';
import {
Contest,
fetchMySubmissions,
setContestStatus,
} from "../../../redux/slices/contests";
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
import { PrimaryButton } from "../../../components/button/PrimaryButton";
import { useNavigate } from "react-router-dom";
import { arrowLeft } from "../../../assets/icons/header";
Contest,
fetchMySubmissions,
setContestStatus,
} from '../../../redux/slices/contests';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { PrimaryButton } from '../../../components/button/PrimaryButton';
import { useNavigate } from 'react-router-dom';
import { arrowLeft } from '../../../assets/icons/header';
export interface Article {
id: number;
name: string;
tags: string[];
id: number;
name: string;
tags: string[];
}
interface ContestMissionsProps {
contest?: Contest;
contest?: Contest;
}
const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
const navigate = useNavigate();
const dispatch = useAppDispatch();
const { submissions, status } = useAppSelector(
(state) => state.contests.fetchMySubmissions
);
const navigate = useNavigate();
const dispatch = useAppDispatch();
const { submissions, status } = useAppSelector(
(state) => state.contests.fetchMySubmissions,
);
useEffect(() => {
if (contest) dispatch(fetchMySubmissions(contest.id));
}, [contest]);
useEffect(() => {
if (contest) dispatch(fetchMySubmissions(contest.id));
}, [contest]);
useEffect(() => {
if (status == "successful") {
dispatch(setContestStatus({ key: "fetchMySubmissions", status: "idle" }));
useEffect(() => {
if (status == 'successful') {
dispatch(
setContestStatus({ key: 'fetchMySubmissions', status: 'idle' }),
);
}
}, [status]);
if (!contest) {
return <></>;
}
}, [status]);
if (!contest) {
return <></>;
}
const solvedCount = (contest.missions ?? []).filter((mission) =>
submissions.some(
(s) =>
s.solution.missionId === mission.id &&
s.solution.status === 'Accepted: All tests passed',
),
).length;
const solvedCount = (contest.missions ?? []).filter((mission) =>
submissions.some(
(s) =>
s.solution.missionId === mission.id &&
s.solution.status === "Accepted: All tests passed"
)
).length;
const totalCount = contest.missions?.length ?? 0;
const totalCount = contest.missions?.length ?? 0;
return (
<div className=" h-screen grid grid-rows-[74px,40px,1fr] p-[20px] gap-[20px]">
<div className="">
<div className="h-[50px] text-[40px] text-liquid-white font-bold">
{contest.name}
</div>
<div className="flex justify-between h-[24px] items-center gap-[10px]">
<div className="flex items-center">
<img
src={arrowLeft}
className="cursor-pointer"
onClick={() => {
navigate(`/home/contests`);
}}
/>
<span className="text-liquid-light font-bold text-[18px]">
Контест #{contest.id}
</span>
</div>
<div>{contest.attemptDurationMinutes ?? 0} минут</div>
</div>
</div>
<div className="flex justify-between items-center">
<div className="text-liquid-white text-[16px] font-bold">{`${solvedCount}/${totalCount} Решено`}</div>
<PrimaryButton
onClick={() => {
navigate(`/contest/${contest.id}/submissions`);
}}
text="Мои посылки"
/>
</div>
return (
<div className=" h-screen grid grid-rows-[74px,40px,1fr] p-[20px] gap-[20px]">
<div className="">
<div className="h-[50px] text-[40px] text-liquid-white font-bold">
{contest.name}
<div className="h-full min-h-0 overflow-y-scroll medium-scrollbar flex flex-col gap-[20px]">
<div className="w-full">
{(contest.missions ?? []).map((v, i) => {
const missionSubmissions = submissions.filter(
(s) => s.solution.missionId === v.id,
);
const hasSuccess = missionSubmissions.some(
(s) =>
s.solution.status ==
'Accepted: All tests passed',
);
const status = hasSuccess
? 'success'
: missionSubmissions.length > 0
? 'error'
: undefined;
return (
<MissionItem
contestId={contest.id}
key={i}
id={v.id}
name={v.name}
timeLimit={v.timeLimitMilliseconds}
memoryLimit={v.memoryLimitBytes}
status={status}
type={i % 2 ? 'second' : 'first'}
/>
);
})}
</div>
</div>
</div>
<div className="flex justify-between h-[24px] items-center gap-[10px]">
<div className="flex items-center">
<img
src={arrowLeft}
className="cursor-pointer"
onClick={() => {
navigate(`/home/contests`);
}}
/>
<span className="text-liquid-light font-bold text-[18px]">
Контест #{contest.id}
</span>
</div>
<div>{contest.attemptDurationMinutes ?? 0} минут</div>
</div>
</div>
<div className="flex justify-between items-center">
<div className="text-liquid-white text-[16px] font-bold">{`${solvedCount}/${totalCount} Решено`}</div>
<PrimaryButton
onClick={() => {
navigate(`/contest/${contest.id}/submissions`);
}}
text="Мои посылки"
/>
</div>
<div className="h-full min-h-0 overflow-y-scroll medium-scrollbar flex flex-col gap-[20px]">
<div className="w-full">
{(contest.missions ?? []).map((v, i) => {
const missionSubmissions = submissions.filter(
(s) => s.solution.missionId === v.id
);
const hasSuccess = missionSubmissions.some(
(s) => s.solution.status == "Accepted: All tests passed"
);
console.log(missionSubmissions);
const status = hasSuccess
? "success"
: missionSubmissions.length > 0
? "error"
: undefined;
return (
<MissionItem
contestId={contest.id}
key={i}
id={v.id}
name={v.name}
timeLimit={v.timeLimitMilliseconds}
memoryLimit={v.memoryLimitBytes}
status={status}
type={i % 2 ? "second" : "first"}
/>
);
})}
</div>
</div>
</div>
);
);
};
export default ContestMissions;

View File

@@ -36,13 +36,17 @@ const Filters = () => {
text: 'ID',
},
]}
onChange={(v) => console.log(v)}
onChange={(v) => {
v;
}}
/>
<FilterDropDown
items={items}
defaultState={[]}
onChange={(values) => console.log(values)}
onChange={(values) => {
values;
}}
/>
</div>
);

View File

@@ -5,7 +5,6 @@ import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { fetchGroupById } from '../../../redux/slices/groups';
import GroupMenu from './GroupMenu';
import { Posts } from './posts/Posts';
import { SearchInput } from '../../../components/input/SearchInput';
import { Chat } from './chat/Chat';
import { Contests } from './contests/Contests';

View File

@@ -1,8 +1,14 @@
import { FC, useEffect, useRef } from 'react';
import { FC, useEffect, useRef, useState } from 'react';
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
import { setMenuActiveGroupPage } from '../../../../redux/slices/store';
import { fetchGroupMessages } from '../../../../redux/slices/groupChat';
import {
fetchGroupMessages,
sendGroupMessage,
setGroupChatStatus,
} from '../../../../redux/slices/groupChat';
import { SearchInput } from '../../../../components/input/SearchInput';
import { MessageItem } from './MessageItem';
import { Send } from '../../../../assets/icons/input';
interface GroupChatProps {
groupId: number;
@@ -13,22 +19,27 @@ const CHUNK_SIZE = 10;
export const Chat: FC<GroupChatProps> = ({ groupId }) => {
const dispatch = useAppDispatch();
const messages = useAppSelector((s) => s.groupchat.messages[groupId] || []);
const hasMore = useAppSelector((s) => s.groupchat.hasMore[groupId]);
const isInitialLoaded = useAppSelector(
(s) => s.groupchat.isInitialLoaded[groupId],
const messagesState = useAppSelector(
(state) => state.groupchat.fetchMessages.status,
);
const lastMessageId = useAppSelector(
(state) => state.groupchat.lastMessage[groupId] || 0,
);
const user = useAppSelector((state) => state.auth);
const [text, setText] = useState<string>('');
const [firstMessagesFetch, setFirctMessagesFetch] = useState<boolean>(true);
const scrollRef = useRef<HTMLDivElement>(null);
// добавлено: ref для хранения предыдущей высоты
const prevHeightRef = useRef(0);
// активируем таб
useEffect(() => {
dispatch(setMenuActiveGroupPage('chat'));
}, []);
useEffect(() => {
console.log(messages);
}, [messages]);
// первичная загрузка
useEffect(() => {
dispatch(
@@ -39,23 +50,112 @@ export const Chat: FC<GroupChatProps> = ({ groupId }) => {
);
}, [groupId]);
// автоскролл вниз после начальной загрузки
// автоскролл вниз после начальной загрузки (но не при догрузке)
useEffect(() => {
if (!isInitialLoaded || !scrollRef.current) return;
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}, [isInitialLoaded, messages.length]);
const div = scrollRef.current;
if (!div) return;
// если prevHeightRef == 0 — значит это не догрузка, а обычная загрузка
if (prevHeightRef.current === 0) {
div.scrollTop = div.scrollHeight;
}
}, [messages.length]);
// добавлено: компенсирование скролла при догрузке
useEffect(() => {
const div = scrollRef.current;
if (!div) return;
if (prevHeightRef.current > 0) {
const diff = div.scrollHeight - prevHeightRef.current;
div.scrollTop = diff; // компенсируем смещение
prevHeightRef.current = 0; // сбрасываем
}
}, [messages]);
useEffect(() => {
if (messagesState == 'successful') {
dispatch(
setGroupChatStatus({ key: 'fetchMessages', status: 'idle' }),
);
}
if (messagesState == 'failed') {
dispatch(
setGroupChatStatus({ key: 'fetchMessages', status: 'idle' }),
);
}
}, [messagesState]);
const lastMessageIdRef = useRef<number | null>(null);
useEffect(() => {
lastMessageIdRef.current = lastMessageId;
if (firstMessagesFetch) {
setFirctMessagesFetch(false);
dispatch(
fetchGroupMessages({
groupId,
afterMessageId: lastMessageIdRef.current,
timeoutSeconds: 10,
}),
);
}
}, [messages]);
useEffect(() => {
const interval = setInterval(() => {
if (lastMessageIdRef.current === null) return;
dispatch(
fetchGroupMessages({
groupId,
afterMessageId: lastMessageIdRef.current,
timeoutSeconds: 10,
}),
);
}, 10000);
return () => clearInterval(interval);
}, [groupId]);
const handleSend = () => {
if (!text.trim()) return;
dispatch(
sendGroupMessage({
groupId,
content: text.trim(),
}),
).then(() => {
setText('');
setTimeout(() => {
const div = scrollRef.current;
if (div) div.scrollTop = div.scrollHeight;
}, 0);
});
};
const handleEnter = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
handleSend();
}
};
// догрузка старых сообщений при скролле вверх
const handleScroll = () => {
const div = scrollRef.current;
if (!div || !hasMore) return;
if (!div) return;
// если скролл в верхней точке
if (div.scrollTop === 0) {
prevHeightRef.current = div.scrollHeight; // запоминаем высоту до загрузки
if (div.scrollTop < 100) {
const first = messages[0];
if (!first) return;
if (!first || first.id == 1) return;
const beforeId = first.id - CHUNK_SIZE;
console.log(beforeId);
dispatch(
fetchGroupMessages({
@@ -69,7 +169,7 @@ export const Chat: FC<GroupChatProps> = ({ groupId }) => {
return (
<div className="h-full relative">
<div className="grid grid-rows-[40px,1fr,50px] h-full relative min-h-0 gap-[20px]">
<div className="grid grid-rows-[40px,1fr,40px] h-full relative min-h-0 gap-[20px]">
<div className="relative">
<SearchInput
className="w-[216px]"
@@ -83,25 +183,38 @@ export const Chat: FC<GroupChatProps> = ({ groupId }) => {
ref={scrollRef}
onScroll={handleScroll}
>
<div className="flex flex-col gap-[20px] min-h-0 h-0">
{messages.map((msg) => (
<div
key={msg.id}
className="bg-gray-800 text-white p-3 rounded-lg"
>
<div className="text-sm opacity-60">
{msg.authorUsername} {msg.id}
</div>
<div>{msg.content}</div>
<div className="text-[10px] opacity-40 mt-1">
{new Date(msg.createdAt).toLocaleString()}
</div>
</div>
<div className="flex flex-col gap-[20px] min-h-0 h-0 px-[16px]">
{messages.map((msg, i) => (
<MessageItem
key={i}
message={msg.content}
createdAt={msg.createdAt}
id={msg.id}
groupId={msg.groupId}
authorId={msg.authorId}
authorUsername={msg.authorUsername}
myMessage={msg.authorId == Number(user.id)}
/>
))}
</div>
</div>
<div className="bg-red-300">footer / input bar</div>
<label className="bg-liquid-lighter rounded-[10px] cursor-text flex items-center px-[16px]">
<input
className="w-[calc(100%-50px)] outline-none bg-transparent placeholder:text-[16px] placeholder:text-liquid-light placeholder:font-medium"
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={handleEnter}
placeholder="Введите сообщение"
/>
<img
src={Send}
className=" absolute cursor-pointer right-[16px] active:scale-90 transition-all duration-300"
onClick={() => {
handleSend();
}}
/>
</label>
</div>
</div>
);

View File

@@ -0,0 +1,81 @@
import { FC } from 'react';
import { useAppSelector } from '../../../../redux/hooks';
function convertDate(isoString: string) {
const date = new Date(isoString);
const dd = String(date.getUTCDate()).padStart(2, '0');
const mm = String(date.getUTCMonth() + 1).padStart(2, '0');
const yyyy = date.getUTCFullYear();
const hh = String(date.getUTCHours()).padStart(2, '0');
const min = String(date.getUTCMinutes()).padStart(2, '0');
return `${dd}.${mm}.${yyyy} ${hh}:${min}`;
}
interface MessageItemProps {
id: number;
groupId: number;
authorId: number;
authorUsername: string;
createdAt: string;
message: string;
myMessage: boolean;
}
export const MessageItem: FC<MessageItemProps> = ({
authorId,
authorUsername,
createdAt,
message,
myMessage,
}) => {
const members = useAppSelector(
(state) => state.groups.fetchGroupById.group?.members,
);
const member = members?.find((m) => m.userId === authorId);
return myMessage ? (
<div className="flex flex-col gap-[20px] items-end leading-[20px] text-[16px]">
<div className="w-[50%] flex flex-col gap-[10px]">
<div className="h-[20px] w-full flex gap-[10px] relative justify-end ">
<div className="font-bold text-liquid-light">
{convertDate(createdAt)}
</div>
</div>
<div className="flex justify-end">
<div className="bg-liquid-lighter w-fit max-w-full break-words px-[16px] py-[8px] rounded-[10px] ">
{message}
</div>
</div>
</div>
</div>
) : (
<div className="flex flex-col gap-[20px] ">
<div className="w-[50%] flex flex-col gap-[10px]">
<div className="h-[40px] w-full flex gap-[10px] relative ">
<div className="h-[40px] w-[40px] bg-[#D9D9D9] rounded-[10px]"></div>
<div className=" leading-[20px] font-bold text-[16px] ">
<div>{authorUsername} </div>
<div className="text-liquid-light">
{member ? member.role : 'роль не найдена'}
</div>
</div>
<div className=" leading-[20px] font-bold text-[16px] ">
<div className="text-liquid-light">
{convertDate(createdAt)}
</div>
</div>
</div>
<div className="flex">
<div className="bg-liquid-lighter w-fit max-w-full break-words px-[16px] py-[8px] rounded-[10px] ">
{message}
</div>
</div>
</div>
</div>
);
};

View File

@@ -2,9 +2,7 @@ import { FC, useEffect, useState } from 'react';
import { Modal } from '../../../../components/modal/Modal';
import { PrimaryButton } from '../../../../components/button/PrimaryButton';
import { SecondaryButton } from '../../../../components/button/SecondaryButton';
import { Input } from '../../../../components/input/Input';
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
import { createGroup } from '../../../../redux/slices/groups';
import MarkdownEditor from '../../../articleeditor/Editor';
import {
createPost,

View File

@@ -2,12 +2,9 @@ import { FC, useEffect, useState } from 'react';
import { Modal } from '../../../../components/modal/Modal';
import { PrimaryButton } from '../../../../components/button/PrimaryButton';
import { SecondaryButton } from '../../../../components/button/SecondaryButton';
import { Input } from '../../../../components/input/Input';
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
import { createGroup } from '../../../../redux/slices/groups';
import MarkdownEditor, { MarkDownPattern } from '../../../articleeditor/Editor';
import {
createPost,
deletePost,
fetchPostById,
setGroupFeedStatus,
@@ -55,7 +52,7 @@ const ModalUpdate: FC<ModalUpdateProps> = ({
}, [statusDelete]);
useEffect(() => {
dispatch(fetchPostById({ groupId, postId }));
if (postId) dispatch(fetchPostById({ groupId, postId }));
}, [postId]);
return (

View File

@@ -32,13 +32,10 @@ interface PostItemProps {
export const PostItem: FC<PostItemProps> = ({
id,
groupId,
authorId,
authorUsername,
name,
content,
createdAt,
updatedAt,
isAdmin,
setModalUpdateActive,
setUpdatePostId,

View File

@@ -58,7 +58,9 @@ export const Posts: FC<PostsProps> = ({ groupId }) => {
<div className="h-[40px] mb-[20px] relative">
<SearchInput
className="w-[216px]"
onChange={(v) => {}}
onChange={(v) => {
v;
}}
placeholder="Поиск сообщений"
/>
{isAdmin && (

View File

@@ -36,13 +36,17 @@ const Filters = () => {
text: 'ID',
},
]}
onChange={(v) => {}}
onChange={(v) => {
v;
}}
/>
<FilterDropDown
items={items}
defaultState={[]}
onChange={(values) => {}}
onChange={(values) => {
values;
}}
/>
</div>
);

View File

@@ -44,7 +44,6 @@ const GroupItem: React.FC<GroupItemProps> = ({
id,
name,
visible,
role,
description,
setUpdateGroup,
setUpdateActive,

View File

@@ -4,7 +4,6 @@ import { fetchGroupJoinLink } from '../../../redux/slices/groups';
import { Modal } from '../../../components/modal/Modal';
import { PrimaryButton } from '../../../components/button/PrimaryButton';
import { SecondaryButton } from '../../../components/button/SecondaryButton';
import { Input } from '../../../components/input/Input';
import { toastSuccess } from '../../../lib/toastNotification';
interface ModalInviteProps {
@@ -54,9 +53,7 @@ const ModalInvite: FC<ModalInviteProps> = ({
await navigator.clipboard.writeText(inviteLink);
toastSuccess('Приглашение скопировано в буфер обмена!');
setActive(false);
} catch (err) {
console.error('Не удалось скопировать ссылку:', err);
}
} catch (err) {}
};
return (

View File

@@ -36,13 +36,17 @@ const Filters = () => {
text: 'ID',
},
]}
onChange={(v) => console.log(v)}
onChange={(v) => {
v;
}}
/>
<FilterDropDown
items={items}
defaultState={[]}
onChange={(values) => console.log(values)}
onChange={(values) => {
values;
}}
/>
</div>
);

View File

@@ -1,4 +1,4 @@
import { FC } from 'react';
import { FC, Fragment } from 'react';
export const ArticlesRightPanel: FC = () => {
const items = [
@@ -23,7 +23,7 @@ export const ArticlesRightPanel: FC = () => {
{items.map((v, i) => {
return (
<>
<Fragment key={i}>
{
<div className="font-bold text-liquid-light text-[16px]">
{v.name}
@@ -32,7 +32,7 @@ export const ArticlesRightPanel: FC = () => {
{i + 1 != items.length && (
<div className="h-[1px] w-full bg-liquid-lighter"></div>
)}
</>
</Fragment>
);
})}
</div>

View File

@@ -1,4 +1,4 @@
import { FC } from 'react';
import { FC, Fragment } from 'react';
import { cn } from '../../../lib/cn';
export const MissionsRightPanel: FC = () => {
@@ -32,7 +32,7 @@ export const MissionsRightPanel: FC = () => {
{items.map((v, i) => {
return (
<>
<Fragment key={i}>
{
<div className="text-liquid-light text-[16px]">
<div className="font-bold ">{v.name}</div>
@@ -60,7 +60,7 @@ export const MissionsRightPanel: FC = () => {
{i + 1 != items.length && (
<div className="h-[1px] w-full bg-liquid-lighter"></div>
)}
</>
</Fragment>
);
})}
</div>

View File

@@ -1,4 +1,4 @@
import { FC, useEffect, useState } from 'react';
import { FC, Fragment, useEffect, useState } from 'react';
import { Navigate, useParams } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
import { fetchGroupById, GroupMember } from '../../../../redux/slices/groups';
@@ -54,7 +54,7 @@ export const GroupRightPanel: FC = () => {
{group?.members.map((v, i) => {
return (
<>
<Fragment key={i}>
{
<div className="text-liquid-light text-[16px] grid grid-cols-[40px,1fr] gap-[10px] items-center cursor-pointer hover:bg-liquid-lighter transition-all duration-300 rounded-[10px] p-[5px] group">
<div className="h-[40px] w-[40px] rounded-[10px] bg-[#D9D9D9]"></div>
@@ -101,7 +101,7 @@ export const GroupRightPanel: FC = () => {
{i + 1 != group?.members.length && (
<div className="h-[1px] w-full bg-liquid-lighter"></div>
)}
</>
</Fragment>
);
})}

View File

@@ -2,13 +2,10 @@ import { FC, useEffect, useState } from 'react';
import { Modal } from '../../../../components/modal/Modal';
import { PrimaryButton } from '../../../../components/button/PrimaryButton';
import { SecondaryButton } from '../../../../components/button/SecondaryButton';
import { Input } from '../../../../components/input/Input';
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
import {
deleteGroup,
removeGroupMember,
setGroupsStatus,
updateGroup,
} from '../../../../redux/slices/groups';
import ConfirmModal from '../../../../components/modal/ConfirmModal';
import { useNavigate } from 'react-router-dom';

View File

@@ -2,16 +2,13 @@ import { FC, useEffect, useState } from 'react';
import { Modal } from '../../../../components/modal/Modal';
import { PrimaryButton } from '../../../../components/button/PrimaryButton';
import { SecondaryButton } from '../../../../components/button/SecondaryButton';
import { Input } from '../../../../components/input/Input';
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
import {
addGroupMember,
deleteGroup,
fetchGroupById,
GroupMember,
removeGroupMember,
setGroupsStatus,
updateGroup,
} from '../../../../redux/slices/groups';
import ConfirmModal from '../../../../components/modal/ConfirmModal';
import { DropDownList } from '../../../../components/drop-down-list/DropDownList';
@@ -31,7 +28,6 @@ const ModalUpdate: FC<ModalUpdateProps> = ({
active,
setActive,
groupId,
userId,
user,
adminUser,
groupName,
@@ -76,7 +72,6 @@ const ModalUpdate: FC<ModalUpdateProps> = ({
}, [statusUpdate]);
useEffect(() => {
console.log(user);
if (user) {
setUserRole(
user?.role.includes('Creator') ? 'Creator' : user?.role,

View File

@@ -17,9 +17,7 @@ const CopyableDiv: FC<CopyableDivPropd> = ({ content }) => {
try {
await navigator.clipboard.writeText(content);
alert('Скопировано!');
} catch (err) {
console.error('Ошибка копирования:', err);
}
} catch (err) {}
};
return (