add error toasts
This commit is contained in:
@@ -58,7 +58,7 @@ export const Input: React.FC<inputProps> = ({
|
|||||||
)}
|
)}
|
||||||
value={value}
|
value={value}
|
||||||
name={name}
|
name={name}
|
||||||
autoComplete={autocomplete}
|
autoComplete={autocomplete || undefined}
|
||||||
type={
|
type={
|
||||||
type == 'password'
|
type == 'password'
|
||||||
? visible
|
? visible
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from '../redux/slices/articles';
|
} from '../redux/slices/articles';
|
||||||
import { useQuery } from '../hooks/useQuery';
|
import { useQuery } from '../hooks/useQuery';
|
||||||
import { ReverseButton } from '../components/button/ReverseButton';
|
import { ReverseButton } from '../components/button/ReverseButton';
|
||||||
|
import { cn } from '../lib/cn';
|
||||||
|
|
||||||
const ArticleEditor = () => {
|
const ArticleEditor = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -24,6 +25,7 @@ const ArticleEditor = () => {
|
|||||||
const back = query.get('back') ?? undefined;
|
const back = query.get('back') ?? undefined;
|
||||||
const articleId = Number(query.get('articleId') ?? undefined);
|
const articleId = Number(query.get('articleId') ?? undefined);
|
||||||
const refactor = articleId && !isNaN(articleId);
|
const refactor = articleId && !isNaN(articleId);
|
||||||
|
const [clickSubmit, setClickSubmit] = useState<boolean>(false);
|
||||||
|
|
||||||
// Достаём данные из redux
|
// Достаём данные из redux
|
||||||
const article = useAppSelector(
|
const article = useAppSelector(
|
||||||
@@ -61,7 +63,6 @@ const ArticleEditor = () => {
|
|||||||
const removeTag = (tagToRemove: string) => {
|
const removeTag = (tagToRemove: string) => {
|
||||||
setTags(tags.filter((tag) => tag !== tagToRemove));
|
setTags(tags.filter((tag) => tag !== tagToRemove));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ==========================
|
// ==========================
|
||||||
// Эффекты по статусам
|
// Эффекты по статусам
|
||||||
// ==========================
|
// ==========================
|
||||||
@@ -96,6 +97,7 @@ const ArticleEditor = () => {
|
|||||||
// Получение статьи
|
// Получение статьи
|
||||||
// ==========================
|
// ==========================
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setClickSubmit(false);
|
||||||
if (articleId) {
|
if (articleId) {
|
||||||
dispatch(fetchArticleById(articleId));
|
dispatch(fetchArticleById(articleId));
|
||||||
}
|
}
|
||||||
@@ -110,6 +112,18 @@ const ArticleEditor = () => {
|
|||||||
}
|
}
|
||||||
}, [article]);
|
}, [article]);
|
||||||
|
|
||||||
|
const getNameErrorMessage = (): string => {
|
||||||
|
if (!clickSubmit) return '';
|
||||||
|
if (name == '') return 'Поле не может быть пустым';
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getContentErrorMessage = (): string => {
|
||||||
|
if (!clickSubmit) return '';
|
||||||
|
if (code == '') return 'Поле не может быть пустым';
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
// ==========================
|
// ==========================
|
||||||
// Рендер
|
// Рендер
|
||||||
// ==========================
|
// ==========================
|
||||||
@@ -137,6 +151,7 @@ const ArticleEditor = () => {
|
|||||||
<div className="flex gap-[20px]">
|
<div className="flex gap-[20px]">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setClickSubmit(true);
|
||||||
dispatch(
|
dispatch(
|
||||||
updateArticle({
|
updateArticle({
|
||||||
articleId,
|
articleId,
|
||||||
@@ -163,6 +178,7 @@ const ArticleEditor = () => {
|
|||||||
) : (
|
) : (
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setClickSubmit(true);
|
||||||
dispatch(
|
dispatch(
|
||||||
createArticle({
|
createArticle({
|
||||||
name,
|
name,
|
||||||
@@ -188,6 +204,7 @@ const ArticleEditor = () => {
|
|||||||
label="Название"
|
label="Название"
|
||||||
onChange={setName}
|
onChange={setName}
|
||||||
placeholder="Новая статья"
|
placeholder="Новая статья"
|
||||||
|
error={getNameErrorMessage()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Теги */}
|
{/* Теги */}
|
||||||
@@ -236,6 +253,14 @@ const ArticleEditor = () => {
|
|||||||
text="Редактировать текст"
|
text="Редактировать текст"
|
||||||
className="mt-[20px]"
|
className="mt-[20px]"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'text-liquid-red text-[14px] h-auto mt-[5px] whitespace-pre-line ',
|
||||||
|
getContentErrorMessage() == '' && 'h-0 mt-0',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{getContentErrorMessage()}
|
||||||
|
</div>
|
||||||
<MarkdownPreview
|
<MarkdownPreview
|
||||||
content={code}
|
content={code}
|
||||||
className="bg-transparent border-liquid-lighter border-[3px] rounded-[20px] mt-[20px]"
|
className="bg-transparent border-liquid-lighter border-[3px] rounded-[20px] mt-[20px]"
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
// src/pages/Home.tsx
|
// src/pages/Home.tsx
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
import Login from '../views/home/auth/Login';
|
import Login from '../views/home/auth/Login';
|
||||||
import Register from '../views/home/auth/Register';
|
import Register from '../views/home/auth/Register';
|
||||||
import Menu from '../views/home/menu/Menu';
|
import Menu from '../views/home/menu/Menu';
|
||||||
import { useAppDispatch, useAppSelector } from '../redux/hooks';
|
import { useAppDispatch, useAppSelector } from '../redux/hooks';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { fetchWhoAmI, logout } from '../redux/slices/auth';
|
import { fetchWhoAmI } from '../redux/slices/auth';
|
||||||
import Missions from '../views/home/missions/Missions';
|
import Missions from '../views/home/missions/Missions';
|
||||||
import Articles from '../views/home/articles/Articles';
|
import Articles from '../views/home/articles/Articles';
|
||||||
import Groups from '../views/home/groups/Groups';
|
import Groups from '../views/home/groups/Groups';
|
||||||
import Contests from '../views/home/contests/Contests';
|
import Contests from '../views/home/contests/Contests';
|
||||||
import { PrimaryButton } from '../components/button/PrimaryButton';
|
|
||||||
import Group from '../views/home/group/Group';
|
import Group from '../views/home/group/Group';
|
||||||
import Contest from '../views/home/contest/Contest';
|
import Contest from '../views/home/contest/Contest';
|
||||||
import Account from '../views/home/account/Account';
|
import Account from '../views/home/account/Account';
|
||||||
@@ -19,14 +18,8 @@ import { MissionsRightPanel } from '../views/home/rightpanel/Missions';
|
|||||||
import { ArticlesRightPanel } from '../views/home/rightpanel/Articles';
|
import { ArticlesRightPanel } from '../views/home/rightpanel/Articles';
|
||||||
import { GroupRightPanel } from '../views/home/rightpanel/group/Group';
|
import { GroupRightPanel } from '../views/home/rightpanel/group/Group';
|
||||||
import GroupInvite from '../views/home/groupinviter/GroupInvite';
|
import GroupInvite from '../views/home/groupinviter/GroupInvite';
|
||||||
import {
|
|
||||||
toastError,
|
|
||||||
toastSuccess,
|
|
||||||
toastWarning,
|
|
||||||
} from '../lib/toastNotification';
|
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const name = useAppSelector((state) => state.auth.username);
|
|
||||||
const jwt = useAppSelector((state) => state.auth.jwt);
|
const jwt = useAppSelector((state) => state.auth.jwt);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
@@ -59,53 +52,7 @@ const Home = () => {
|
|||||||
<Route path="contest/:contestId/*" element={<Contest />} />
|
<Route path="contest/:contestId/*" element={<Contest />} />
|
||||||
<Route
|
<Route
|
||||||
path="*"
|
path="*"
|
||||||
element={
|
element={<Navigate to="/home/account" replace />}
|
||||||
<>
|
|
||||||
<p>{jwt}</p>
|
|
||||||
<PrimaryButton
|
|
||||||
onClick={() => {
|
|
||||||
if (jwt) {
|
|
||||||
navigator.clipboard.writeText(jwt);
|
|
||||||
alert(jwt);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
text="скопировать токен"
|
|
||||||
className="pt-[20px]"
|
|
||||||
/>
|
|
||||||
<p className="py-[20px]">{name}</p>
|
|
||||||
<PrimaryButton
|
|
||||||
onClick={() => {
|
|
||||||
dispatch(logout());
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
выйти
|
|
||||||
</PrimaryButton>
|
|
||||||
|
|
||||||
<div className="flex mt-[20px] gap-[20px]">
|
|
||||||
<PrimaryButton
|
|
||||||
color="success"
|
|
||||||
text="Toast"
|
|
||||||
onClick={() => {
|
|
||||||
toastSuccess('Success');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<PrimaryButton
|
|
||||||
color="warning"
|
|
||||||
text="Toast"
|
|
||||||
onClick={() => {
|
|
||||||
toastWarning('Warning');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<PrimaryButton
|
|
||||||
color="error"
|
|
||||||
text="Toast"
|
|
||||||
onClick={() => {
|
|
||||||
toastError('Error');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import axios from '../../axios';
|
import axios from '../../axios';
|
||||||
|
import { toastError } from '../../lib/toastNotification';
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// Типы
|
// Типы
|
||||||
@@ -120,9 +121,7 @@ export const fetchArticles = createAsyncThunk(
|
|||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при получении статей',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -135,10 +134,7 @@ export const fetchMyArticles = createAsyncThunk(
|
|||||||
const response = await axios.get<Article[]>('/articles/my');
|
const response = await axios.get<Article[]>('/articles/my');
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Ошибка при получении моих статей',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -151,9 +147,7 @@ export const fetchArticleById = createAsyncThunk(
|
|||||||
const response = await axios.get<Article>(`/articles/${articleId}`);
|
const response = await axios.get<Article>(`/articles/${articleId}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при получении статьи',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -177,9 +171,7 @@ export const createArticle = createAsyncThunk(
|
|||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при создании статьи',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -207,9 +199,7 @@ export const updateArticle = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при обновлении статьи',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -222,9 +212,7 @@ export const deleteArticle = createAsyncThunk(
|
|||||||
await axios.delete(`/articles/${articleId}`);
|
await axios.delete(`/articles/${articleId}`);
|
||||||
return articleId;
|
return articleId;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при удалении статьи',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -263,7 +251,12 @@ const articlesSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchArticles.rejected, (state, action: any) => {
|
builder.addCase(fetchArticles.rejected, (state, action: any) => {
|
||||||
state.fetchArticles.status = 'failed';
|
state.fetchArticles.status = 'failed';
|
||||||
state.fetchArticles.error = action.payload;
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// fetchMyArticles
|
// fetchMyArticles
|
||||||
@@ -280,7 +273,12 @@ const articlesSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchMyArticles.rejected, (state, action: any) => {
|
builder.addCase(fetchMyArticles.rejected, (state, action: any) => {
|
||||||
state.fetchMyArticles.status = 'failed';
|
state.fetchMyArticles.status = 'failed';
|
||||||
state.fetchMyArticles.error = action.payload;
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// fetchArticleById
|
// fetchArticleById
|
||||||
@@ -297,7 +295,12 @@ const articlesSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchArticleById.rejected, (state, action: any) => {
|
builder.addCase(fetchArticleById.rejected, (state, action: any) => {
|
||||||
state.fetchArticleById.status = 'failed';
|
state.fetchArticleById.status = 'failed';
|
||||||
state.fetchArticleById.error = action.payload;
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// createArticle
|
// createArticle
|
||||||
@@ -314,7 +317,14 @@ const articlesSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(createArticle.rejected, (state, action: any) => {
|
builder.addCase(createArticle.rejected, (state, action: any) => {
|
||||||
state.createArticle.status = 'failed';
|
state.createArticle.status = 'failed';
|
||||||
state.createArticle.error = action.payload;
|
state.createArticle.error = action.payload.title;
|
||||||
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// updateArticle
|
// updateArticle
|
||||||
@@ -331,7 +341,14 @@ const articlesSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(updateArticle.rejected, (state, action: any) => {
|
builder.addCase(updateArticle.rejected, (state, action: any) => {
|
||||||
state.updateArticle.status = 'failed';
|
state.updateArticle.status = 'failed';
|
||||||
state.updateArticle.error = action.payload;
|
state.createArticle.error = action.payload.title;
|
||||||
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// deleteArticle
|
// deleteArticle
|
||||||
@@ -355,7 +372,12 @@ const articlesSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(deleteArticle.rejected, (state, action: any) => {
|
builder.addCase(deleteArticle.rejected, (state, action: any) => {
|
||||||
state.deleteArticle.status = 'failed';
|
state.deleteArticle.status = 'failed';
|
||||||
state.deleteArticle.error = action.payload;
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import axios from '../../axios';
|
import axios from '../../axios';
|
||||||
|
import { toastError } from '../../lib/toastNotification';
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// Типы
|
// Типы
|
||||||
@@ -280,10 +281,7 @@ export const fetchParticipatingContests = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Failed to fetch participating contests',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -297,9 +295,7 @@ export const fetchMySubmissions = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to fetch my submissions',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -321,9 +317,7 @@ export const fetchContests = createAsyncThunk(
|
|||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to fetch contests',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -335,9 +329,7 @@ export const fetchContestById = createAsyncThunk(
|
|||||||
const response = await axios.get<Contest>(`/contests/${id}`);
|
const response = await axios.get<Contest>(`/contests/${id}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to fetch contest',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -352,9 +344,7 @@ export const createContest = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to create contest',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -375,9 +365,7 @@ export const updateContest = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to update contest',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -389,9 +377,7 @@ export const deleteContest = createAsyncThunk(
|
|||||||
await axios.delete(`/contests/${contestId}`);
|
await axios.delete(`/contests/${contestId}`);
|
||||||
return contestId;
|
return contestId;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to delete contest',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -403,9 +389,7 @@ export const fetchMyContests = createAsyncThunk(
|
|||||||
const response = await axios.get<Contest[]>('/contests/my');
|
const response = await axios.get<Contest[]>('/contests/my');
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to fetch my contests',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -424,10 +408,7 @@ export const fetchRegisteredContests = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Failed to fetch registered contests',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -451,10 +432,7 @@ export const addOrUpdateContestMember = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return { contestId, members: response.data };
|
return { contestId, members: response.data };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Failed to add or update contest member',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -470,10 +448,7 @@ export const deleteContestMember = createAsyncThunk(
|
|||||||
await axios.delete(`/contests/${contestId}/members/${memberId}`);
|
await axios.delete(`/contests/${contestId}/members/${memberId}`);
|
||||||
return { contestId, memberId };
|
return { contestId, memberId };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Failed to delete contest member',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -488,10 +463,7 @@ export const startContestAttempt = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Failed to start contest attempt',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -506,9 +478,7 @@ export const fetchMyAttemptsInContest = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to fetch my attempts',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -531,10 +501,7 @@ export const fetchContestMembers = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return { contestId, ...response.data };
|
return { contestId, ...response.data };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Failed to fetch contest members',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -549,9 +516,7 @@ export const checkContestRegistration = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return { contestId, registered: response.data.registered };
|
return { contestId, registered: response.data.registered };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to check registration',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -566,10 +531,7 @@ export const fetchUpcomingEligibleContests = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Failed to fetch upcoming eligible contests',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -584,9 +546,7 @@ export const fetchMyAllAttempts = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to fetch my attempts',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -601,9 +561,7 @@ export const fetchMyActiveAttempt = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return { contestId, attempt: response.data };
|
return { contestId, attempt: response.data };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Failed to fetch active attempt',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -642,7 +600,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchMySubmissions.rejected, (state, action: any) => {
|
builder.addCase(fetchMySubmissions.rejected, (state, action: any) => {
|
||||||
state.fetchMySubmissions.status = 'failed';
|
state.fetchMySubmissions.status = 'failed';
|
||||||
state.fetchMySubmissions.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(fetchContests.pending, (state) => {
|
builder.addCase(fetchContests.pending, (state) => {
|
||||||
@@ -658,7 +622,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchContests.rejected, (state, action: any) => {
|
builder.addCase(fetchContests.rejected, (state, action: any) => {
|
||||||
state.fetchContests.status = 'failed';
|
state.fetchContests.status = 'failed';
|
||||||
state.fetchContests.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(fetchContestById.pending, (state) => {
|
builder.addCase(fetchContestById.pending, (state) => {
|
||||||
@@ -673,7 +643,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchContestById.rejected, (state, action: any) => {
|
builder.addCase(fetchContestById.rejected, (state, action: any) => {
|
||||||
state.fetchContestById.status = 'failed';
|
state.fetchContestById.status = 'failed';
|
||||||
state.fetchContestById.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(createContest.pending, (state) => {
|
builder.addCase(createContest.pending, (state) => {
|
||||||
@@ -688,7 +664,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(createContest.rejected, (state, action: any) => {
|
builder.addCase(createContest.rejected, (state, action: any) => {
|
||||||
state.createContest.status = 'failed';
|
state.createContest.status = 'failed';
|
||||||
state.createContest.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(updateContest.pending, (state) => {
|
builder.addCase(updateContest.pending, (state) => {
|
||||||
@@ -703,7 +685,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(updateContest.rejected, (state, action: any) => {
|
builder.addCase(updateContest.rejected, (state, action: any) => {
|
||||||
state.updateContest.status = 'failed';
|
state.updateContest.status = 'failed';
|
||||||
state.updateContest.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(deleteContest.pending, (state) => {
|
builder.addCase(deleteContest.pending, (state) => {
|
||||||
@@ -725,7 +713,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(deleteContest.rejected, (state, action: any) => {
|
builder.addCase(deleteContest.rejected, (state, action: any) => {
|
||||||
state.deleteContest.status = 'failed';
|
state.deleteContest.status = 'failed';
|
||||||
state.deleteContest.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(fetchMyContests.pending, (state) => {
|
builder.addCase(fetchMyContests.pending, (state) => {
|
||||||
@@ -740,7 +734,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchMyContests.rejected, (state, action: any) => {
|
builder.addCase(fetchMyContests.rejected, (state, action: any) => {
|
||||||
state.fetchMyContests.status = 'failed';
|
state.fetchMyContests.status = 'failed';
|
||||||
state.fetchMyContests.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(fetchRegisteredContests.pending, (state) => {
|
builder.addCase(fetchRegisteredContests.pending, (state) => {
|
||||||
@@ -760,7 +760,15 @@ const contestsSlice = createSlice({
|
|||||||
fetchRegisteredContests.rejected,
|
fetchRegisteredContests.rejected,
|
||||||
(state, action: any) => {
|
(state, action: any) => {
|
||||||
state.fetchRegisteredContests.status = 'failed';
|
state.fetchRegisteredContests.status = 'failed';
|
||||||
state.fetchRegisteredContests.error = action.payload;
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -787,7 +795,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchContestMembers.rejected, (state, action: any) => {
|
builder.addCase(fetchContestMembers.rejected, (state, action: any) => {
|
||||||
state.fetchContestMembers.status = 'failed';
|
state.fetchContestMembers.status = 'failed';
|
||||||
state.fetchContestMembers.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(addOrUpdateContestMember.pending, (state) => {
|
builder.addCase(addOrUpdateContestMember.pending, (state) => {
|
||||||
@@ -800,7 +814,15 @@ const contestsSlice = createSlice({
|
|||||||
addOrUpdateContestMember.rejected,
|
addOrUpdateContestMember.rejected,
|
||||||
(state, action: any) => {
|
(state, action: any) => {
|
||||||
state.addOrUpdateMember.status = 'failed';
|
state.addOrUpdateMember.status = 'failed';
|
||||||
state.addOrUpdateMember.error = action.payload;
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -812,7 +834,13 @@ const contestsSlice = createSlice({
|
|||||||
});
|
});
|
||||||
builder.addCase(deleteContestMember.rejected, (state, action: any) => {
|
builder.addCase(deleteContestMember.rejected, (state, action: any) => {
|
||||||
state.deleteContestMember.status = 'failed';
|
state.deleteContestMember.status = 'failed';
|
||||||
state.deleteContestMember.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(startContestAttempt.pending, (state) => {
|
builder.addCase(startContestAttempt.pending, (state) => {
|
||||||
@@ -827,7 +855,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(startContestAttempt.rejected, (state, action: any) => {
|
builder.addCase(startContestAttempt.rejected, (state, action: any) => {
|
||||||
state.startAttempt.status = 'failed';
|
state.startAttempt.status = 'failed';
|
||||||
state.startAttempt.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(fetchMyAttemptsInContest.pending, (state) => {
|
builder.addCase(fetchMyAttemptsInContest.pending, (state) => {
|
||||||
@@ -844,7 +878,15 @@ const contestsSlice = createSlice({
|
|||||||
fetchMyAttemptsInContest.rejected,
|
fetchMyAttemptsInContest.rejected,
|
||||||
(state, action: any) => {
|
(state, action: any) => {
|
||||||
state.fetchMyAttemptsInContest.status = 'failed';
|
state.fetchMyAttemptsInContest.status = 'failed';
|
||||||
state.fetchMyAttemptsInContest.error = action.payload;
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -860,7 +902,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchMyAllAttempts.rejected, (state, action: any) => {
|
builder.addCase(fetchMyAllAttempts.rejected, (state, action: any) => {
|
||||||
state.fetchMyAllAttempts.status = 'failed';
|
state.fetchMyAllAttempts.status = 'failed';
|
||||||
state.fetchMyAllAttempts.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(fetchMyActiveAttempt.pending, (state) => {
|
builder.addCase(fetchMyActiveAttempt.pending, (state) => {
|
||||||
@@ -881,7 +929,13 @@ const contestsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchMyActiveAttempt.rejected, (state, action: any) => {
|
builder.addCase(fetchMyActiveAttempt.rejected, (state, action: any) => {
|
||||||
state.fetchMyActiveAttempt.status = 'failed';
|
state.fetchMyActiveAttempt.status = 'failed';
|
||||||
state.fetchMyActiveAttempt.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(checkContestRegistration.pending, (state) => {
|
builder.addCase(checkContestRegistration.pending, (state) => {
|
||||||
@@ -904,7 +958,15 @@ const contestsSlice = createSlice({
|
|||||||
checkContestRegistration.rejected,
|
checkContestRegistration.rejected,
|
||||||
(state, action: any) => {
|
(state, action: any) => {
|
||||||
state.checkRegistration.status = 'failed';
|
state.checkRegistration.status = 'failed';
|
||||||
state.checkRegistration.error = action.payload;
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -922,7 +984,15 @@ const contestsSlice = createSlice({
|
|||||||
fetchUpcomingEligibleContests.rejected,
|
fetchUpcomingEligibleContests.rejected,
|
||||||
(state, action: any) => {
|
(state, action: any) => {
|
||||||
state.fetchUpcomingEligible.status = 'failed';
|
state.fetchUpcomingEligible.status = 'failed';
|
||||||
state.fetchUpcomingEligible.error = action.payload;
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -944,7 +1014,15 @@ const contestsSlice = createSlice({
|
|||||||
fetchParticipatingContests.rejected,
|
fetchParticipatingContests.rejected,
|
||||||
(state, action: any) => {
|
(state, action: any) => {
|
||||||
state.fetchParticipating.status = 'failed';
|
state.fetchParticipating.status = 'failed';
|
||||||
state.fetchParticipating.error = action.payload;
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import axios from '../../axios';
|
import axios from '../../axios';
|
||||||
|
import { toastError } from '../../lib/toastNotification';
|
||||||
|
|
||||||
// =========================================
|
// =========================================
|
||||||
// Типы
|
// Типы
|
||||||
@@ -81,10 +82,7 @@ export const fetchGroupMessages = createAsyncThunk(
|
|||||||
messages: response.data as ChatMessage[],
|
messages: response.data as ChatMessage[],
|
||||||
};
|
};
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Ошибка при получении сообщений группы',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -99,9 +97,7 @@ export const sendGroupMessage = createAsyncThunk(
|
|||||||
});
|
});
|
||||||
return response.data as ChatMessage;
|
return response.data as ChatMessage;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при отправке сообщения',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -167,7 +163,12 @@ const groupChatSlice = createSlice({
|
|||||||
|
|
||||||
builder.addCase(fetchGroupMessages.rejected, (state, action: any) => {
|
builder.addCase(fetchGroupMessages.rejected, (state, action: any) => {
|
||||||
state.fetchMessages.status = 'failed';
|
state.fetchMessages.status = 'failed';
|
||||||
state.fetchMessages.error = action.payload;
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// send message
|
// send message
|
||||||
@@ -188,7 +189,12 @@ const groupChatSlice = createSlice({
|
|||||||
|
|
||||||
builder.addCase(sendGroupMessage.rejected, (state, action: any) => {
|
builder.addCase(sendGroupMessage.rejected, (state, action: any) => {
|
||||||
state.sendMessage.status = 'failed';
|
state.sendMessage.status = 'failed';
|
||||||
state.sendMessage.error = action.payload;
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import axios from '../../axios';
|
import axios from '../../axios';
|
||||||
|
import { toastError } from '../../lib/toastNotification';
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// Типы
|
// Типы
|
||||||
@@ -104,9 +105,7 @@ export const fetchGroupPosts = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return { page, data: response.data as PostsPage };
|
return { page, data: response.data as PostsPage };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка загрузки постов',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -124,9 +123,7 @@ export const fetchPostById = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data as Post;
|
return response.data as Post;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка загрузки поста',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -149,9 +146,7 @@ export const createPost = createAsyncThunk(
|
|||||||
});
|
});
|
||||||
return response.data as Post;
|
return response.data as Post;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка создания поста',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -183,9 +178,7 @@ export const updatePost = createAsyncThunk(
|
|||||||
);
|
);
|
||||||
return response.data as Post;
|
return response.data as Post;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка обновления поста',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -201,9 +194,7 @@ export const deletePost = createAsyncThunk(
|
|||||||
await axios.delete(`/groups/${groupId}/feed/${postId}`);
|
await axios.delete(`/groups/${groupId}/feed/${postId}`);
|
||||||
return postId;
|
return postId;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка удаления поста',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -244,7 +235,13 @@ const postsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchGroupPosts.rejected, (state, action: any) => {
|
builder.addCase(fetchGroupPosts.rejected, (state, action: any) => {
|
||||||
state.fetchPosts.status = 'failed';
|
state.fetchPosts.status = 'failed';
|
||||||
state.fetchPosts.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// fetchPostById
|
// fetchPostById
|
||||||
@@ -260,7 +257,13 @@ const postsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchPostById.rejected, (state, action: any) => {
|
builder.addCase(fetchPostById.rejected, (state, action: any) => {
|
||||||
state.fetchPostById.status = 'failed';
|
state.fetchPostById.status = 'failed';
|
||||||
state.fetchPostById.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// createPost
|
// createPost
|
||||||
@@ -281,7 +284,13 @@ const postsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(createPost.rejected, (state, action: any) => {
|
builder.addCase(createPost.rejected, (state, action: any) => {
|
||||||
state.createPost.status = 'failed';
|
state.createPost.status = 'failed';
|
||||||
state.createPost.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// updatePost
|
// updatePost
|
||||||
@@ -310,7 +319,13 @@ const postsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(updatePost.rejected, (state, action: any) => {
|
builder.addCase(updatePost.rejected, (state, action: any) => {
|
||||||
state.updatePost.status = 'failed';
|
state.updatePost.status = 'failed';
|
||||||
state.updatePost.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// deletePost
|
// deletePost
|
||||||
@@ -338,7 +353,13 @@ const postsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(deletePost.rejected, (state, action: any) => {
|
builder.addCase(deletePost.rejected, (state, action: any) => {
|
||||||
state.deletePost.status = 'failed';
|
state.deletePost.status = 'failed';
|
||||||
state.deletePost.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import axios from '../../axios';
|
import axios from '../../axios';
|
||||||
|
import { toastError } from '../../lib/toastNotification';
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
// Типы
|
// Типы
|
||||||
@@ -131,9 +132,7 @@ export const createGroup = createAsyncThunk(
|
|||||||
const response = await axios.post('/groups', { name, description });
|
const response = await axios.post('/groups', { name, description });
|
||||||
return response.data as Group;
|
return response.data as Group;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при создании группы',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -155,9 +154,7 @@ export const updateGroup = createAsyncThunk(
|
|||||||
});
|
});
|
||||||
return response.data as Group;
|
return response.data as Group;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при обновлении группы',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -169,9 +166,7 @@ export const deleteGroup = createAsyncThunk(
|
|||||||
await axios.delete(`/groups/${groupId}`);
|
await axios.delete(`/groups/${groupId}`);
|
||||||
return groupId;
|
return groupId;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при удалении группы',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -183,9 +178,7 @@ export const fetchMyGroups = createAsyncThunk(
|
|||||||
const response = await axios.get('/groups/my');
|
const response = await axios.get('/groups/my');
|
||||||
return response.data.groups as Group[];
|
return response.data.groups as Group[];
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при получении групп',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -197,9 +190,7 @@ export const fetchGroupById = createAsyncThunk(
|
|||||||
const response = await axios.get(`/groups/${groupId}`);
|
const response = await axios.get(`/groups/${groupId}`);
|
||||||
return response.data as Group;
|
return response.data as Group;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при получении группы',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -221,10 +212,7 @@ export const addGroupMember = createAsyncThunk(
|
|||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Ошибка при добавлении участника',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -239,9 +227,7 @@ export const removeGroupMember = createAsyncThunk(
|
|||||||
await axios.delete(`/groups/${groupId}/members/${memberId}`);
|
await axios.delete(`/groups/${groupId}/members/${memberId}`);
|
||||||
return { groupId, memberId };
|
return { groupId, memberId };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при удалении участника',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -258,10 +244,7 @@ export const fetchGroupJoinLink = createAsyncThunk(
|
|||||||
const response = await axios.get(`/groups/${groupId}/join-link`);
|
const response = await axios.get(`/groups/${groupId}/join-link`);
|
||||||
return response.data as { token: string; expiresAt: string };
|
return response.data as { token: string; expiresAt: string };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Ошибка при получении ссылки для присоединения',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -274,10 +257,7 @@ export const joinGroupByToken = createAsyncThunk(
|
|||||||
const response = await axios.post(`/groups/join/${token}`);
|
const response = await axios.post(`/groups/join/${token}`);
|
||||||
return response.data as Group;
|
return response.data as Group;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Ошибка при присоединении к группе по ссылке',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -314,7 +294,13 @@ const groupsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchMyGroups.rejected, (state, action: any) => {
|
builder.addCase(fetchMyGroups.rejected, (state, action: any) => {
|
||||||
state.fetchMyGroups.status = 'failed';
|
state.fetchMyGroups.status = 'failed';
|
||||||
state.fetchMyGroups.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// fetchGroupById
|
// fetchGroupById
|
||||||
@@ -330,7 +316,13 @@ const groupsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchGroupById.rejected, (state, action: any) => {
|
builder.addCase(fetchGroupById.rejected, (state, action: any) => {
|
||||||
state.fetchGroupById.status = 'failed';
|
state.fetchGroupById.status = 'failed';
|
||||||
state.fetchGroupById.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// createGroup
|
// createGroup
|
||||||
@@ -347,7 +339,13 @@ const groupsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(createGroup.rejected, (state, action: any) => {
|
builder.addCase(createGroup.rejected, (state, action: any) => {
|
||||||
state.createGroup.status = 'failed';
|
state.createGroup.status = 'failed';
|
||||||
state.createGroup.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// updateGroup
|
// updateGroup
|
||||||
@@ -370,7 +368,13 @@ const groupsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(updateGroup.rejected, (state, action: any) => {
|
builder.addCase(updateGroup.rejected, (state, action: any) => {
|
||||||
state.updateGroup.status = 'failed';
|
state.updateGroup.status = 'failed';
|
||||||
state.updateGroup.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// deleteGroup
|
// deleteGroup
|
||||||
@@ -391,7 +395,13 @@ const groupsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(deleteGroup.rejected, (state, action: any) => {
|
builder.addCase(deleteGroup.rejected, (state, action: any) => {
|
||||||
state.deleteGroup.status = 'failed';
|
state.deleteGroup.status = 'failed';
|
||||||
state.deleteGroup.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// addGroupMember
|
// addGroupMember
|
||||||
@@ -403,7 +413,13 @@ const groupsSlice = createSlice({
|
|||||||
});
|
});
|
||||||
builder.addCase(addGroupMember.rejected, (state, action: any) => {
|
builder.addCase(addGroupMember.rejected, (state, action: any) => {
|
||||||
state.addGroupMember.status = 'failed';
|
state.addGroupMember.status = 'failed';
|
||||||
state.addGroupMember.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// removeGroupMember
|
// removeGroupMember
|
||||||
@@ -430,7 +446,13 @@ const groupsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(removeGroupMember.rejected, (state, action: any) => {
|
builder.addCase(removeGroupMember.rejected, (state, action: any) => {
|
||||||
state.removeGroupMember.status = 'failed';
|
state.removeGroupMember.status = 'failed';
|
||||||
state.removeGroupMember.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// fetchGroupJoinLink
|
// fetchGroupJoinLink
|
||||||
@@ -449,7 +471,13 @@ const groupsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(fetchGroupJoinLink.rejected, (state, action: any) => {
|
builder.addCase(fetchGroupJoinLink.rejected, (state, action: any) => {
|
||||||
state.fetchGroupJoinLink.status = 'failed';
|
state.fetchGroupJoinLink.status = 'failed';
|
||||||
state.fetchGroupJoinLink.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// joinGroupByToken
|
// joinGroupByToken
|
||||||
@@ -466,7 +494,13 @@ const groupsSlice = createSlice({
|
|||||||
);
|
);
|
||||||
builder.addCase(joinGroupByToken.rejected, (state, action: any) => {
|
builder.addCase(joinGroupByToken.rejected, (state, action: any) => {
|
||||||
state.joinGroupByToken.status = 'failed';
|
state.joinGroupByToken.status = 'failed';
|
||||||
state.joinGroupByToken.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<string, string[]>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import axios from '../../axios';
|
import axios from '../../axios';
|
||||||
|
import { toastError } from '../../lib/toastNotification';
|
||||||
|
|
||||||
// ─── Типы ────────────────────────────────────────────
|
// ─── Типы ────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -29,6 +30,9 @@ interface MissionsState {
|
|||||||
missions: Mission[];
|
missions: Mission[];
|
||||||
currentMission: Mission | null;
|
currentMission: Mission | null;
|
||||||
hasNextPage: boolean;
|
hasNextPage: boolean;
|
||||||
|
create: {
|
||||||
|
errors?: Record<string, string[]>;
|
||||||
|
};
|
||||||
statuses: {
|
statuses: {
|
||||||
fetchList: Status;
|
fetchList: Status;
|
||||||
fetchById: Status;
|
fetchById: Status;
|
||||||
@@ -45,6 +49,7 @@ const initialState: MissionsState = {
|
|||||||
missions: [],
|
missions: [],
|
||||||
currentMission: null,
|
currentMission: null,
|
||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
|
create: {},
|
||||||
statuses: {
|
statuses: {
|
||||||
fetchList: 'idle',
|
fetchList: 'idle',
|
||||||
fetchById: 'idle',
|
fetchById: 'idle',
|
||||||
@@ -79,9 +84,7 @@ export const fetchMissions = createAsyncThunk(
|
|||||||
});
|
});
|
||||||
return response.data; // { missions, hasNextPage }
|
return response.data; // { missions, hasNextPage }
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при получении миссий',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -94,9 +97,7 @@ export const fetchMissionById = createAsyncThunk(
|
|||||||
const response = await axios.get(`/missions/${id}`);
|
const response = await axios.get(`/missions/${id}`);
|
||||||
return response.data; // Mission
|
return response.data; // Mission
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при получении миссии',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -109,10 +110,7 @@ export const fetchMyMissions = createAsyncThunk(
|
|||||||
const response = await axios.get('/missions/my');
|
const response = await axios.get('/missions/my');
|
||||||
return response.data as Mission[]; // массив миссий пользователя
|
return response.data as Mission[]; // массив миссий пользователя
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message ||
|
|
||||||
'Ошибка при получении моих миссий',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -141,9 +139,7 @@ export const uploadMission = createAsyncThunk(
|
|||||||
});
|
});
|
||||||
return response.data; // Mission
|
return response.data; // Mission
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при загрузке миссии',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -156,9 +152,7 @@ export const deleteMission = createAsyncThunk(
|
|||||||
await axios.delete(`/missions/${id}`);
|
await axios.delete(`/missions/${id}`);
|
||||||
return id; // возвращаем id удалённой миссии
|
return id; // возвращаем id удалённой миссии
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(err.response?.data);
|
||||||
err.response?.data?.message || 'Ошибка при удалении миссии',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -204,7 +198,16 @@ const missionsSlice = createSlice({
|
|||||||
fetchMissions.rejected,
|
fetchMissions.rejected,
|
||||||
(state, action: PayloadAction<any>) => {
|
(state, action: PayloadAction<any>) => {
|
||||||
state.statuses.fetchList = 'failed';
|
state.statuses.fetchList = 'failed';
|
||||||
state.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -224,7 +227,16 @@ const missionsSlice = createSlice({
|
|||||||
fetchMissionById.rejected,
|
fetchMissionById.rejected,
|
||||||
(state, action: PayloadAction<any>) => {
|
(state, action: PayloadAction<any>) => {
|
||||||
state.statuses.fetchById = 'failed';
|
state.statuses.fetchById = 'failed';
|
||||||
state.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -244,7 +256,16 @@ const missionsSlice = createSlice({
|
|||||||
fetchMyMissions.rejected,
|
fetchMyMissions.rejected,
|
||||||
(state, action: PayloadAction<any>) => {
|
(state, action: PayloadAction<any>) => {
|
||||||
state.statuses.fetchMy = 'failed';
|
state.statuses.fetchMy = 'failed';
|
||||||
state.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -264,7 +285,18 @@ const missionsSlice = createSlice({
|
|||||||
uploadMission.rejected,
|
uploadMission.rejected,
|
||||||
(state, action: PayloadAction<any>) => {
|
(state, action: PayloadAction<any>) => {
|
||||||
state.statuses.upload = 'failed';
|
state.statuses.upload = 'failed';
|
||||||
state.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
state.create.errors = errors;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -290,7 +322,16 @@ const missionsSlice = createSlice({
|
|||||||
deleteMission.rejected,
|
deleteMission.rejected,
|
||||||
(state, action: PayloadAction<any>) => {
|
(state, action: PayloadAction<any>) => {
|
||||||
state.statuses.delete = 'failed';
|
state.statuses.delete = 'failed';
|
||||||
state.error = action.payload;
|
|
||||||
|
const errors = action.payload.errors as Record<
|
||||||
|
string,
|
||||||
|
string[]
|
||||||
|
>;
|
||||||
|
Object.values(errors).forEach((messages) => {
|
||||||
|
messages.forEach((msg) => {
|
||||||
|
toastError(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ const Account = () => {
|
|||||||
const username = query.get('username') ?? myname ?? '';
|
const username = query.get('username') ?? myname ?? '';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (username == myname) dispatch(setMenuActivePage('account'));
|
if (username == myname) {
|
||||||
|
dispatch(setMenuActivePage('account'));
|
||||||
|
} else {
|
||||||
|
dispatch(setMenuActivePage(''));
|
||||||
|
}
|
||||||
dispatch(
|
dispatch(
|
||||||
fetchProfileMissions({
|
fetchProfileMissions({
|
||||||
username: username,
|
username: username,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { PrimaryButton } from '../../../components/button/PrimaryButton';
|
|
||||||
import { ReverseButton } from '../../../components/button/ReverseButton';
|
import { ReverseButton } from '../../../components/button/ReverseButton';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||||
import { logout } from '../../../redux/slices/auth';
|
import { logout } from '../../../redux/slices/auth';
|
||||||
@@ -77,13 +76,13 @@ const RightPanel = () => {
|
|||||||
)}`}
|
)}`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{username == myname && (
|
{/* {username == myname && (
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
text="Редактировать"
|
text="Редактировать"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
<div className="h-[1px] w-full bg-liquid-lighter"></div>
|
<div className="h-[1px] w-full bg-liquid-lighter"></div>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
setMissionsStatus,
|
setMissionsStatus,
|
||||||
} from '../../../../redux/slices/missions';
|
} from '../../../../redux/slices/missions';
|
||||||
import ConfirmModal from '../../../../components/modal/ConfirmModal';
|
import ConfirmModal from '../../../../components/modal/ConfirmModal';
|
||||||
|
import { fetchProfileMissions } from '../../../../redux/slices/profile';
|
||||||
|
import { useQuery } from '../../../../hooks/useQuery';
|
||||||
|
|
||||||
interface ItemProps {
|
interface ItemProps {
|
||||||
count: number;
|
count: number;
|
||||||
@@ -50,6 +52,10 @@ const Missions = () => {
|
|||||||
(state) => state.profile.missions,
|
(state) => state.profile.missions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const myname = useAppSelector((state) => state.auth.username);
|
||||||
|
const query = useQuery();
|
||||||
|
const username = query.get('username') ?? myname ?? '';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(setMenuActiveProfilePage('missions'));
|
dispatch(setMenuActiveProfilePage('missions'));
|
||||||
}, []);
|
}, []);
|
||||||
@@ -115,7 +121,17 @@ const Missions = () => {
|
|||||||
confirmColor="error"
|
confirmColor="error"
|
||||||
confirmText="Удалить"
|
confirmText="Удалить"
|
||||||
onConfirmClick={() => {
|
onConfirmClick={() => {
|
||||||
dispatch(deleteMission(taskdeleteId));
|
dispatch(deleteMission(taskdeleteId))
|
||||||
|
.unwrap()
|
||||||
|
.then(() => {
|
||||||
|
dispatch(
|
||||||
|
fetchProfileMissions({
|
||||||
|
username: username,
|
||||||
|
recentPageSize: 1,
|
||||||
|
authoredPageSize: 100,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import GroupMenu from './GroupMenu';
|
|||||||
import { Posts } from './posts/Posts';
|
import { Posts } from './posts/Posts';
|
||||||
import { Chat } from './chat/Chat';
|
import { Chat } from './chat/Chat';
|
||||||
import { Contests } from './contests/Contests';
|
import { Contests } from './contests/Contests';
|
||||||
|
import { setMenuActivePage } from '../../../redux/slices/store';
|
||||||
|
|
||||||
interface GroupsBlockProps {}
|
interface GroupsBlockProps {}
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ const Group: FC<GroupsBlockProps> = () => {
|
|||||||
const group = useAppSelector((state) => state.groups.fetchGroupById.group);
|
const group = useAppSelector((state) => state.groups.fetchGroupById.group);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
dispatch(setMenuActivePage('groups'));
|
||||||
dispatch(fetchGroupById(groupId));
|
dispatch(fetchGroupById(groupId));
|
||||||
}, [groupId]);
|
}, [groupId]);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { FC, useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
|
||||||
import { fetchGroupPosts } from '../../../../redux/slices/groupfeed';
|
import { fetchGroupPosts } from '../../../../redux/slices/groupfeed';
|
||||||
import { SearchInput } from '../../../../components/input/SearchInput';
|
|
||||||
import { setMenuActiveGroupPage } from '../../../../redux/slices/store';
|
import { setMenuActiveGroupPage } from '../../../../redux/slices/store';
|
||||||
import { fetchGroupById } from '../../../../redux/slices/groups';
|
import { fetchGroupById } from '../../../../redux/slices/groups';
|
||||||
import { SecondaryButton } from '../../../../components/button/SecondaryButton';
|
import { SecondaryButton } from '../../../../components/button/SecondaryButton';
|
||||||
@@ -57,13 +56,6 @@ export const Posts: FC<PostsProps> = ({ groupId }) => {
|
|||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<div className="grid grid-rows-[40px,1fr,40px] 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="h-[40px] mb-[20px] relative">
|
<div className="h-[40px] mb-[20px] relative">
|
||||||
<SearchInput
|
|
||||||
className="w-[216px]"
|
|
||||||
onChange={(v) => {
|
|
||||||
v;
|
|
||||||
}}
|
|
||||||
placeholder="Поиск сообщений"
|
|
||||||
/>
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<div className=" h-[40px] w-[180px] absolute top-0 right-0 flex items-center">
|
<div className=" h-[40px] w-[180px] absolute top-0 right-0 flex items-center">
|
||||||
<SecondaryButton
|
<SecondaryButton
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
Account,
|
Account,
|
||||||
Clipboard,
|
Clipboard,
|
||||||
Cup,
|
Cup,
|
||||||
Home,
|
|
||||||
Openbook,
|
Openbook,
|
||||||
Users,
|
Users,
|
||||||
} from '../../../assets/icons/menu';
|
} from '../../../assets/icons/menu';
|
||||||
@@ -12,7 +11,6 @@ import { useAppSelector } from '../../../redux/hooks';
|
|||||||
|
|
||||||
const Menu = () => {
|
const Menu = () => {
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ text: 'Главная', href: '/home', icon: Home, page: 'home' },
|
|
||||||
{
|
{
|
||||||
text: 'Задачи',
|
text: 'Задачи',
|
||||||
href: '/home/missions',
|
href: '/home/missions',
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import {
|
|||||||
setMissionsStatus,
|
setMissionsStatus,
|
||||||
uploadMission,
|
uploadMission,
|
||||||
} from '../../../redux/slices/missions';
|
} 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 {
|
interface ModalCreateProps {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
@@ -24,6 +28,8 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
|
|||||||
const status = useAppSelector((state) => state.missions.statuses.upload);
|
const status = useAppSelector((state) => state.missions.statuses.upload);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const [clickSubmit, setClickSubmit] = useState<boolean>(false);
|
||||||
|
|
||||||
const addTag = () => {
|
const addTag = () => {
|
||||||
const newTag = tagInput.trim();
|
const newTag = tagInput.trim();
|
||||||
if (newTag && !tags.includes(newTag)) {
|
if (newTag && !tags.includes(newTag)) {
|
||||||
@@ -43,13 +49,14 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!file) return alert('Выберите файл миссии!');
|
setClickSubmit(true);
|
||||||
|
if (!file) return;
|
||||||
dispatch(uploadMission({ file, name, difficulty, tags }));
|
dispatch(uploadMission({ file, name, difficulty, tags }));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'successful') {
|
if (status === 'successful') {
|
||||||
alert('Миссия успешно загружена!');
|
toastSuccess('Миссия создана!');
|
||||||
setName('');
|
setName('');
|
||||||
setDifficulty(1);
|
setDifficulty(1);
|
||||||
setTags([]);
|
setTags([]);
|
||||||
@@ -60,9 +67,18 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
|
|||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (active == true) {
|
||||||
|
setClickSubmit(false);
|
||||||
|
}
|
||||||
dispatch(setMissionsStatus({ key: 'upload', status: 'idle' }));
|
dispatch(setMissionsStatus({ key: 'upload', status: 'idle' }));
|
||||||
}, [active]);
|
}, [active]);
|
||||||
|
|
||||||
|
const getNameErrorMessage = (): string => {
|
||||||
|
if (!clickSubmit) return '';
|
||||||
|
if (name == '') return 'Поле не может быть пустым';
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
className="bg-liquid-background border-liquid-lighter border-[2px] p-[25px] rounded-[20px] text-liquid-white"
|
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}
|
defaultState={name}
|
||||||
onChange={setName}
|
onChange={setName}
|
||||||
placeholder="В яблочко"
|
placeholder="В яблочко"
|
||||||
|
error={getNameErrorMessage()}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<NumberInput
|
||||||
name="difficulty"
|
name="difficulty"
|
||||||
autocomplete="difficulty"
|
|
||||||
className="mt-[10px]"
|
className="mt-[10px]"
|
||||||
type="number"
|
|
||||||
label="Сложность"
|
label="Сложность"
|
||||||
defaultState={'' + difficulty}
|
defaultState={difficulty}
|
||||||
onChange={(v) => setDifficulty(Number(v))}
|
minValue={1}
|
||||||
|
maxValue={3500}
|
||||||
|
onChange={(v) => setDifficulty(v)}
|
||||||
placeholder="1"
|
placeholder="1"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -106,6 +123,16 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
|
|||||||
className="hidden"
|
className="hidden"
|
||||||
/>
|
/>
|
||||||
</label>
|
</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>
|
</div>
|
||||||
|
|
||||||
{/* Теги */}
|
{/* Теги */}
|
||||||
@@ -148,6 +175,17 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
|
|||||||
</div>
|
</div>
|
||||||
</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]">
|
<div className="flex flex-row w-full items-center justify-end mt-[20px] gap-[20px]">
|
||||||
<PrimaryButton
|
<PrimaryButton
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
@@ -159,8 +197,6 @@ const ModalCreate: FC<ModalCreateProps> = ({ active, setActive }) => {
|
|||||||
text="Отмена"
|
text="Отмена"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{status == 'failed' && <div>error</div>}
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FC, Fragment, useEffect, useState } from 'react';
|
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 { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
|
||||||
import { fetchGroupById, GroupMember } from '../../../../redux/slices/groups';
|
import { fetchGroupById, GroupMember } from '../../../../redux/slices/groups';
|
||||||
import { Edit } from '../../../../assets/icons/input';
|
import { Edit } from '../../../../assets/icons/input';
|
||||||
@@ -13,6 +13,7 @@ export const GroupRightPanel: FC = () => {
|
|||||||
return <Navigate to="/home/groups" replace />;
|
return <Navigate to="/home/groups" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const [user, setUser] = useState<GroupMember | undefined>();
|
const [user, setUser] = useState<GroupMember | undefined>();
|
||||||
@@ -56,7 +57,14 @@ export const GroupRightPanel: FC = () => {
|
|||||||
return (
|
return (
|
||||||
<Fragment key={i}>
|
<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="h-[40px] w-[40px] rounded-[10px] bg-[#D9D9D9]"></div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="text-liquid-white font-bold text-[16px] leading-5">
|
<div className="text-liquid-white font-bold text-[16px] leading-5">
|
||||||
@@ -73,7 +81,8 @@ export const GroupRightPanel: FC = () => {
|
|||||||
!v.role.includes('Creator') && (
|
!v.role.includes('Creator') && (
|
||||||
<div
|
<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"
|
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 (
|
if (
|
||||||
Number(userId) == v.userId
|
Number(userId) == v.userId
|
||||||
) {
|
) {
|
||||||
|
|||||||
Reference in New Issue
Block a user