add error toasts

This commit is contained in:
Виталий Лавшонок
2025-12-10 01:33:16 +03:00
parent 02de330034
commit d1a46435c4
17 changed files with 508 additions and 278 deletions

View File

@@ -23,7 +23,11 @@ const Account = () => {
const username = query.get('username') ?? myname ?? '';
useEffect(() => {
if (username == myname) dispatch(setMenuActivePage('account'));
if (username == myname) {
dispatch(setMenuActivePage('account'));
} else {
dispatch(setMenuActivePage(''));
}
dispatch(
fetchProfileMissions({
username: username,

View File

@@ -1,4 +1,3 @@
import { PrimaryButton } from '../../../components/button/PrimaryButton';
import { ReverseButton } from '../../../components/button/ReverseButton';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { logout } from '../../../redux/slices/auth';
@@ -77,13 +76,13 @@ const RightPanel = () => {
)}`}
</div>
{username == myname && (
{/* {username == myname && (
<PrimaryButton
onClick={() => {}}
text="Редактировать"
className="w-full"
/>
)}
)} */}
<div className="h-[1px] w-full bg-liquid-lighter"></div>

View File

@@ -8,6 +8,8 @@ import {
setMissionsStatus,
} from '../../../../redux/slices/missions';
import ConfirmModal from '../../../../components/modal/ConfirmModal';
import { fetchProfileMissions } from '../../../../redux/slices/profile';
import { useQuery } from '../../../../hooks/useQuery';
interface ItemProps {
count: number;
@@ -50,6 +52,10 @@ const Missions = () => {
(state) => state.profile.missions,
);
const myname = useAppSelector((state) => state.auth.username);
const query = useQuery();
const username = query.get('username') ?? myname ?? '';
useEffect(() => {
dispatch(setMenuActiveProfilePage('missions'));
}, []);
@@ -115,7 +121,17 @@ const Missions = () => {
confirmColor="error"
confirmText="Удалить"
onConfirmClick={() => {
dispatch(deleteMission(taskdeleteId));
dispatch(deleteMission(taskdeleteId))
.unwrap()
.then(() => {
dispatch(
fetchProfileMissions({
username: username,
recentPageSize: 1,
authoredPageSize: 100,
}),
);
});
}}
/>
</div>

View File

@@ -7,6 +7,7 @@ import GroupMenu from './GroupMenu';
import { Posts } from './posts/Posts';
import { Chat } from './chat/Chat';
import { Contests } from './contests/Contests';
import { setMenuActivePage } from '../../../redux/slices/store';
interface GroupsBlockProps {}
@@ -20,6 +21,7 @@ const Group: FC<GroupsBlockProps> = () => {
const group = useAppSelector((state) => state.groups.fetchGroupById.group);
useEffect(() => {
dispatch(setMenuActivePage('groups'));
dispatch(fetchGroupById(groupId));
}, [groupId]);

View File

@@ -2,7 +2,6 @@ import { FC, useEffect, useState } from 'react';
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
import { fetchGroupPosts } from '../../../../redux/slices/groupfeed';
import { SearchInput } from '../../../../components/input/SearchInput';
import { setMenuActiveGroupPage } from '../../../../redux/slices/store';
import { fetchGroupById } from '../../../../redux/slices/groups';
import { SecondaryButton } from '../../../../components/button/SecondaryButton';
@@ -57,13 +56,6 @@ export const Posts: FC<PostsProps> = ({ groupId }) => {
<div className="h-full relative">
<div className="grid grid-rows-[40px,1fr,40px] h-full relative min-h-0 gap-[20px]">
<div className="h-[40px] mb-[20px] relative">
<SearchInput
className="w-[216px]"
onChange={(v) => {
v;
}}
placeholder="Поиск сообщений"
/>
{isAdmin && (
<div className=" h-[40px] w-[180px] absolute top-0 right-0 flex items-center">
<SecondaryButton

View File

@@ -3,7 +3,6 @@ import {
Account,
Clipboard,
Cup,
Home,
Openbook,
Users,
} from '../../../assets/icons/menu';
@@ -12,7 +11,6 @@ import { useAppSelector } from '../../../redux/hooks';
const Menu = () => {
const menuItems = [
{ text: 'Главная', href: '/home', icon: Home, page: 'home' },
{
text: 'Задачи',
href: '/home/missions',

View File

@@ -8,6 +8,10 @@ import {
setMissionsStatus,
uploadMission,
} from '../../../redux/slices/missions';
import { toastSuccess } from '../../../lib/toastNotification';
import { cn } from '../../../lib/cn';
import { Link } from 'react-router-dom';
import { NumberInput } from '../../../components/input/NumberInput';
interface ModalCreateProps {
active: boolean;
@@ -24,6 +28,8 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
const status = useAppSelector((state) => state.missions.statuses.upload);
const dispatch = useAppDispatch();
const [clickSubmit, setClickSubmit] = useState<boolean>(false);
const addTag = () => {
const newTag = tagInput.trim();
if (newTag && !tags.includes(newTag)) {
@@ -43,13 +49,14 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
};
const handleSubmit = async () => {
if (!file) return alert('Выберите файл миссии!');
setClickSubmit(true);
if (!file) return;
dispatch(uploadMission({ file, name, difficulty, tags }));
};
useEffect(() => {
if (status === 'successful') {
alert('Миссия успешно загружена!');
toastSuccess('Миссия создана!');
setName('');
setDifficulty(1);
setTags([]);
@@ -60,9 +67,18 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
}, [status]);
useEffect(() => {
if (active == true) {
setClickSubmit(false);
}
dispatch(setMissionsStatus({ key: 'upload', status: 'idle' }));
}, [active]);
const getNameErrorMessage = (): string => {
if (!clickSubmit) return '';
if (name == '') return 'Поле не может быть пустым';
return '';
};
return (
<Modal
className="bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white"
@@ -82,16 +98,17 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
defaultState={name}
onChange={setName}
placeholder="В яблочко"
error={getNameErrorMessage()}
/>
<Input
<NumberInput
name="difficulty"
autocomplete="difficulty"
className="mt-[10px]"
type="number"
label="Сложность"
defaultState={'' + difficulty}
onChange={(v) => setDifficulty(Number(v))}
defaultState={difficulty}
minValue={1}
maxValue={3500}
onChange={(v) => setDifficulty(v)}
placeholder="1"
/>
@@ -106,6 +123,16 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
className="hidden"
/>
</label>
{
<div
className={cn(
'text-liquid-red text-[14px] h-auto text-left mt-[5px] whitespace-pre-line overflow-hidden ',
(!clickSubmit || file) && 'h-0 mt-0',
)}
>
Необходимо выбрать файл задачи
</div>
}
</div>
{/* Теги */}
@@ -148,6 +175,17 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
</div>
</div>
<div>
Создать пакет задачи можно на платформе{' '}
<Link
to={'https://polygon.codeforces.com'}
target="_blank"
className="text-[#7489ff] hover:text-[#8c9dfd] transition-color duration-300"
>
polygon
</Link>
</div>
<div className="flex flex-row w-full items-center justify-end mt-[20px] gap-[20px]">
<PrimaryButton
onClick={handleSubmit}
@@ -159,8 +197,6 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
text="Отмена"
/>
</div>
{status == 'failed' && <div>error</div>}
</div>
</Modal>
);

View File

@@ -1,5 +1,5 @@
import { FC, Fragment, useEffect, useState } from 'react';
import { Navigate, useParams } from 'react-router-dom';
import { Navigate, useNavigate, useParams } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
import { fetchGroupById, GroupMember } from '../../../../redux/slices/groups';
import { Edit } from '../../../../assets/icons/input';
@@ -13,6 +13,7 @@ export const GroupRightPanel: FC = () => {
return <Navigate to="/home/groups" replace />;
}
const navigate = useNavigate();
const dispatch = useAppDispatch();
const [user, setUser] = useState<GroupMember | undefined>();
@@ -56,7 +57,14 @@ export const GroupRightPanel: FC = () => {
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="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"
onClick={() => {
navigate(
`/home/account/missions?username=${v.username}`,
);
}}
>
<div className="h-[40px] w-[40px] rounded-[10px] bg-[#D9D9D9]"></div>
<div className="flex flex-col">
<div className="text-liquid-white font-bold text-[16px] leading-5">
@@ -73,7 +81,8 @@ export const GroupRightPanel: FC = () => {
!v.role.includes('Creator') && (
<div
className="h-[34px] w-[34px] absolute right-[34px] opacity-0 group-hover:opacity-100 transition-all duration-300 hover:bg-liquid-light rounded-[10px] p-[5px] active:scale-90"
onClick={() => {
onClick={(e) => {
e.stopPropagation();
if (
Number(userId) == v.userId
) {