('/articles/my'); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при получении моих статей', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -151,9 +147,7 @@ export const fetchArticleById = createAsyncThunk( const response = await axios.get(`/articles/${articleId}`); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при получении статьи', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -177,9 +171,7 @@ export const createArticle = createAsyncThunk( }); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при создании статьи', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -207,9 +199,7 @@ export const updateArticle = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при обновлении статьи', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -222,9 +212,7 @@ export const deleteArticle = createAsyncThunk( await axios.delete(`/articles/${articleId}`); return articleId; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при удалении статьи', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -263,7 +251,12 @@ const articlesSlice = createSlice({ ); builder.addCase(fetchArticles.rejected, (state, action: any) => { state.fetchArticles.status = 'failed'; - state.fetchArticles.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // fetchMyArticles @@ -280,7 +273,12 @@ const articlesSlice = createSlice({ ); builder.addCase(fetchMyArticles.rejected, (state, action: any) => { state.fetchMyArticles.status = 'failed'; - state.fetchMyArticles.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // fetchArticleById @@ -297,7 +295,12 @@ const articlesSlice = createSlice({ ); builder.addCase(fetchArticleById.rejected, (state, action: any) => { state.fetchArticleById.status = 'failed'; - state.fetchArticleById.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // createArticle @@ -314,7 +317,14 @@ const articlesSlice = createSlice({ ); builder.addCase(createArticle.rejected, (state, action: any) => { state.createArticle.status = 'failed'; - state.createArticle.error = action.payload; + state.createArticle.error = action.payload.title; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // updateArticle @@ -331,7 +341,14 @@ const articlesSlice = createSlice({ ); builder.addCase(updateArticle.rejected, (state, action: any) => { state.updateArticle.status = 'failed'; - state.updateArticle.error = action.payload; + state.createArticle.error = action.payload.title; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // deleteArticle @@ -355,7 +372,12 @@ const articlesSlice = createSlice({ ); builder.addCase(deleteArticle.rejected, (state, action: any) => { state.deleteArticle.status = 'failed'; - state.deleteArticle.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); }, }); diff --git a/src/redux/slices/contests.ts b/src/redux/slices/contests.ts index 4074135..1c0f749 100644 --- a/src/redux/slices/contests.ts +++ b/src/redux/slices/contests.ts @@ -1,5 +1,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import axios from '../../axios'; +import { toastError } from '../../lib/toastNotification'; // ===================== // Типы @@ -280,10 +281,7 @@ export const fetchParticipatingContests = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to fetch participating contests', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -297,9 +295,7 @@ export const fetchMySubmissions = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch my submissions', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -321,9 +317,7 @@ export const fetchContests = createAsyncThunk( }); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch contests', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -335,9 +329,7 @@ export const fetchContestById = createAsyncThunk( const response = await axios.get(`/contests/${id}`); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch contest', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -352,9 +344,7 @@ export const createContest = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to create contest', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -375,9 +365,7 @@ export const updateContest = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to update contest', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -389,9 +377,7 @@ export const deleteContest = createAsyncThunk( await axios.delete(`/contests/${contestId}`); return contestId; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to delete contest', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -403,9 +389,7 @@ export const fetchMyContests = createAsyncThunk( const response = await axios.get('/contests/my'); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch my contests', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -424,10 +408,7 @@ export const fetchRegisteredContests = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to fetch registered contests', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -451,10 +432,7 @@ export const addOrUpdateContestMember = createAsyncThunk( ); return { contestId, members: response.data }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to add or update contest member', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -470,10 +448,7 @@ export const deleteContestMember = createAsyncThunk( await axios.delete(`/contests/${contestId}/members/${memberId}`); return { contestId, memberId }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to delete contest member', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -488,10 +463,7 @@ export const startContestAttempt = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to start contest attempt', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -506,9 +478,7 @@ export const fetchMyAttemptsInContest = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch my attempts', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -531,10 +501,7 @@ export const fetchContestMembers = createAsyncThunk( ); return { contestId, ...response.data }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to fetch contest members', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -549,9 +516,7 @@ export const checkContestRegistration = createAsyncThunk( ); return { contestId, registered: response.data.registered }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to check registration', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -566,10 +531,7 @@ export const fetchUpcomingEligibleContests = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to fetch upcoming eligible contests', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -584,9 +546,7 @@ export const fetchMyAllAttempts = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch my attempts', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -601,9 +561,7 @@ export const fetchMyActiveAttempt = createAsyncThunk( ); return { contestId, attempt: response.data }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch active attempt', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -642,7 +600,13 @@ const contestsSlice = createSlice({ ); builder.addCase(fetchMySubmissions.rejected, (state, action: any) => { state.fetchMySubmissions.status = 'failed'; - state.fetchMySubmissions.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchContests.pending, (state) => { @@ -658,7 +622,13 @@ const contestsSlice = createSlice({ ); builder.addCase(fetchContests.rejected, (state, action: any) => { state.fetchContests.status = 'failed'; - state.fetchContests.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchContestById.pending, (state) => { @@ -673,7 +643,13 @@ const contestsSlice = createSlice({ ); builder.addCase(fetchContestById.rejected, (state, action: any) => { state.fetchContestById.status = 'failed'; - state.fetchContestById.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(createContest.pending, (state) => { @@ -688,7 +664,13 @@ const contestsSlice = createSlice({ ); builder.addCase(createContest.rejected, (state, action: any) => { state.createContest.status = 'failed'; - state.createContest.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(updateContest.pending, (state) => { @@ -703,7 +685,13 @@ const contestsSlice = createSlice({ ); builder.addCase(updateContest.rejected, (state, action: any) => { state.updateContest.status = 'failed'; - state.updateContest.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(deleteContest.pending, (state) => { @@ -725,7 +713,13 @@ const contestsSlice = createSlice({ ); builder.addCase(deleteContest.rejected, (state, action: any) => { state.deleteContest.status = 'failed'; - state.deleteContest.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchMyContests.pending, (state) => { @@ -740,7 +734,13 @@ const contestsSlice = createSlice({ ); builder.addCase(fetchMyContests.rejected, (state, action: any) => { state.fetchMyContests.status = 'failed'; - state.fetchMyContests.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchRegisteredContests.pending, (state) => { @@ -760,7 +760,15 @@ const contestsSlice = createSlice({ fetchRegisteredContests.rejected, (state, action: any) => { 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) => { state.fetchContestMembers.status = 'failed'; - state.fetchContestMembers.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(addOrUpdateContestMember.pending, (state) => { @@ -800,7 +814,15 @@ const contestsSlice = createSlice({ addOrUpdateContestMember.rejected, (state, action: any) => { 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) => { state.deleteContestMember.status = 'failed'; - state.deleteContestMember.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(startContestAttempt.pending, (state) => { @@ -827,7 +855,13 @@ const contestsSlice = createSlice({ ); builder.addCase(startContestAttempt.rejected, (state, action: any) => { state.startAttempt.status = 'failed'; - state.startAttempt.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchMyAttemptsInContest.pending, (state) => { @@ -844,7 +878,15 @@ const contestsSlice = createSlice({ fetchMyAttemptsInContest.rejected, (state, action: any) => { 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) => { state.fetchMyAllAttempts.status = 'failed'; - state.fetchMyAllAttempts.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchMyActiveAttempt.pending, (state) => { @@ -881,7 +929,13 @@ const contestsSlice = createSlice({ ); builder.addCase(fetchMyActiveAttempt.rejected, (state, action: any) => { state.fetchMyActiveAttempt.status = 'failed'; - state.fetchMyActiveAttempt.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(checkContestRegistration.pending, (state) => { @@ -904,7 +958,15 @@ const contestsSlice = createSlice({ checkContestRegistration.rejected, (state, action: any) => { 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, (state, action: any) => { 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, (state, action: any) => { 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); + }); + }); }, ); }, diff --git a/src/redux/slices/groupChat.ts b/src/redux/slices/groupChat.ts index 9233a9d..a37d91d 100644 --- a/src/redux/slices/groupChat.ts +++ b/src/redux/slices/groupChat.ts @@ -1,5 +1,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import axios from '../../axios'; +import { toastError } from '../../lib/toastNotification'; // ========================================= // Типы @@ -81,10 +82,7 @@ export const fetchGroupMessages = createAsyncThunk( messages: response.data as ChatMessage[], }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при получении сообщений группы', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -99,9 +97,7 @@ export const sendGroupMessage = createAsyncThunk( }); return response.data as ChatMessage; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при отправке сообщения', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -167,7 +163,12 @@ const groupChatSlice = createSlice({ builder.addCase(fetchGroupMessages.rejected, (state, action: any) => { state.fetchMessages.status = 'failed'; - state.fetchMessages.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // send message @@ -188,7 +189,12 @@ const groupChatSlice = createSlice({ builder.addCase(sendGroupMessage.rejected, (state, action: any) => { state.sendMessage.status = 'failed'; - state.sendMessage.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); }, }); diff --git a/src/redux/slices/groupfeed.ts b/src/redux/slices/groupfeed.ts index f322fc2..9c65cf0 100644 --- a/src/redux/slices/groupfeed.ts +++ b/src/redux/slices/groupfeed.ts @@ -1,5 +1,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import axios from '../../axios'; +import { toastError } from '../../lib/toastNotification'; // ===================== // Типы @@ -104,9 +105,7 @@ export const fetchGroupPosts = createAsyncThunk( ); return { page, data: response.data as PostsPage }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка загрузки постов', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -124,9 +123,7 @@ export const fetchPostById = createAsyncThunk( ); return response.data as Post; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка загрузки поста', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -149,9 +146,7 @@ export const createPost = createAsyncThunk( }); return response.data as Post; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка создания поста', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -183,9 +178,7 @@ export const updatePost = createAsyncThunk( ); return response.data as Post; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка обновления поста', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -201,9 +194,7 @@ export const deletePost = createAsyncThunk( await axios.delete(`/groups/${groupId}/feed/${postId}`); return postId; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка удаления поста', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -244,7 +235,13 @@ const postsSlice = createSlice({ ); builder.addCase(fetchGroupPosts.rejected, (state, action: any) => { state.fetchPosts.status = 'failed'; - state.fetchPosts.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // fetchPostById @@ -260,7 +257,13 @@ const postsSlice = createSlice({ ); builder.addCase(fetchPostById.rejected, (state, action: any) => { state.fetchPostById.status = 'failed'; - state.fetchPostById.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // createPost @@ -281,7 +284,13 @@ const postsSlice = createSlice({ ); builder.addCase(createPost.rejected, (state, action: any) => { state.createPost.status = 'failed'; - state.createPost.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // updatePost @@ -310,7 +319,13 @@ const postsSlice = createSlice({ ); builder.addCase(updatePost.rejected, (state, action: any) => { state.updatePost.status = 'failed'; - state.updatePost.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // deletePost @@ -338,7 +353,13 @@ const postsSlice = createSlice({ ); builder.addCase(deletePost.rejected, (state, action: any) => { state.deletePost.status = 'failed'; - state.deletePost.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); }, }); diff --git a/src/redux/slices/groups.ts b/src/redux/slices/groups.ts index 38350bb..58898a2 100644 --- a/src/redux/slices/groups.ts +++ b/src/redux/slices/groups.ts @@ -1,5 +1,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; 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 }); return response.data as Group; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при создании группы', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -155,9 +154,7 @@ export const updateGroup = createAsyncThunk( }); return response.data as Group; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при обновлении группы', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -169,9 +166,7 @@ export const deleteGroup = createAsyncThunk( await axios.delete(`/groups/${groupId}`); return groupId; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при удалении группы', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -183,9 +178,7 @@ export const fetchMyGroups = createAsyncThunk( const response = await axios.get('/groups/my'); return response.data.groups as Group[]; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при получении групп', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -197,9 +190,7 @@ export const fetchGroupById = createAsyncThunk( const response = await axios.get(`/groups/${groupId}`); return response.data as Group; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при получении группы', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -221,10 +212,7 @@ export const addGroupMember = createAsyncThunk( }); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при добавлении участника', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -239,9 +227,7 @@ export const removeGroupMember = createAsyncThunk( await axios.delete(`/groups/${groupId}/members/${memberId}`); return { groupId, memberId }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при удалении участника', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -258,10 +244,7 @@ export const fetchGroupJoinLink = createAsyncThunk( const response = await axios.get(`/groups/${groupId}/join-link`); return response.data as { token: string; expiresAt: string }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при получении ссылки для присоединения', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -274,10 +257,7 @@ export const joinGroupByToken = createAsyncThunk( const response = await axios.post(`/groups/join/${token}`); return response.data as Group; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при присоединении к группе по ссылке', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -314,7 +294,13 @@ const groupsSlice = createSlice({ ); builder.addCase(fetchMyGroups.rejected, (state, action: any) => { state.fetchMyGroups.status = 'failed'; - state.fetchMyGroups.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // fetchGroupById @@ -330,7 +316,13 @@ const groupsSlice = createSlice({ ); builder.addCase(fetchGroupById.rejected, (state, action: any) => { state.fetchGroupById.status = 'failed'; - state.fetchGroupById.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // createGroup @@ -347,7 +339,13 @@ const groupsSlice = createSlice({ ); builder.addCase(createGroup.rejected, (state, action: any) => { state.createGroup.status = 'failed'; - state.createGroup.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // updateGroup @@ -370,7 +368,13 @@ const groupsSlice = createSlice({ ); builder.addCase(updateGroup.rejected, (state, action: any) => { state.updateGroup.status = 'failed'; - state.updateGroup.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // deleteGroup @@ -391,7 +395,13 @@ const groupsSlice = createSlice({ ); builder.addCase(deleteGroup.rejected, (state, action: any) => { state.deleteGroup.status = 'failed'; - state.deleteGroup.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // addGroupMember @@ -403,7 +413,13 @@ const groupsSlice = createSlice({ }); builder.addCase(addGroupMember.rejected, (state, action: any) => { state.addGroupMember.status = 'failed'; - state.addGroupMember.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // removeGroupMember @@ -430,7 +446,13 @@ const groupsSlice = createSlice({ ); builder.addCase(removeGroupMember.rejected, (state, action: any) => { state.removeGroupMember.status = 'failed'; - state.removeGroupMember.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // fetchGroupJoinLink @@ -449,7 +471,13 @@ const groupsSlice = createSlice({ ); builder.addCase(fetchGroupJoinLink.rejected, (state, action: any) => { state.fetchGroupJoinLink.status = 'failed'; - state.fetchGroupJoinLink.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // joinGroupByToken @@ -466,7 +494,13 @@ const groupsSlice = createSlice({ ); builder.addCase(joinGroupByToken.rejected, (state, action: any) => { state.joinGroupByToken.status = 'failed'; - state.joinGroupByToken.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); }, }); diff --git a/src/redux/slices/missions.ts b/src/redux/slices/missions.ts index 1d6919f..0a4d7f3 100644 --- a/src/redux/slices/missions.ts +++ b/src/redux/slices/missions.ts @@ -1,5 +1,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import axios from '../../axios'; +import { toastError } from '../../lib/toastNotification'; // ─── Типы ──────────────────────────────────────────── @@ -29,6 +30,9 @@ interface MissionsState { missions: Mission[]; currentMission: Mission | null; hasNextPage: boolean; + create: { + errors?: Record; + }; statuses: { fetchList: Status; fetchById: Status; @@ -45,6 +49,7 @@ const initialState: MissionsState = { missions: [], currentMission: null, hasNextPage: false, + create: {}, statuses: { fetchList: 'idle', fetchById: 'idle', @@ -79,9 +84,7 @@ export const fetchMissions = createAsyncThunk( }); return response.data; // { missions, hasNextPage } } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при получении миссий', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -94,9 +97,7 @@ export const fetchMissionById = createAsyncThunk( const response = await axios.get(`/missions/${id}`); return response.data; // Mission } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при получении миссии', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -109,10 +110,7 @@ export const fetchMyMissions = createAsyncThunk( const response = await axios.get('/missions/my'); return response.data as Mission[]; // массив миссий пользователя } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при получении моих миссий', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -141,9 +139,7 @@ export const uploadMission = createAsyncThunk( }); return response.data; // Mission } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при загрузке миссии', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -156,9 +152,7 @@ export const deleteMission = createAsyncThunk( await axios.delete(`/missions/${id}`); return id; // возвращаем id удалённой миссии } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при удалении миссии', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -204,7 +198,16 @@ const missionsSlice = createSlice({ fetchMissions.rejected, (state, action: PayloadAction) => { 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, (state, action: PayloadAction) => { 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, (state, action: PayloadAction) => { 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, (state, action: PayloadAction) => { 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, (state, action: PayloadAction) => { 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); + }); + }); }, ); }, diff --git a/src/views/home/account/Account.tsx b/src/views/home/account/Account.tsx index 3c932c8..d1c2d0a 100644 --- a/src/views/home/account/Account.tsx +++ b/src/views/home/account/Account.tsx @@ -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, diff --git a/src/views/home/account/RightPanel.tsx b/src/views/home/account/RightPanel.tsx index 952bc23..819e202 100644 --- a/src/views/home/account/RightPanel.tsx +++ b/src/views/home/account/RightPanel.tsx @@ -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 = () => { )}`}
(`/articles/${articleId}`); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при получении статьи', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -177,9 +171,7 @@ export const createArticle = createAsyncThunk( }); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при создании статьи', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -207,9 +199,7 @@ export const updateArticle = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при обновлении статьи', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -222,9 +212,7 @@ export const deleteArticle = createAsyncThunk( await axios.delete(`/articles/${articleId}`); return articleId; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при удалении статьи', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -263,7 +251,12 @@ const articlesSlice = createSlice({ ); builder.addCase(fetchArticles.rejected, (state, action: any) => { state.fetchArticles.status = 'failed'; - state.fetchArticles.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // fetchMyArticles @@ -280,7 +273,12 @@ const articlesSlice = createSlice({ ); builder.addCase(fetchMyArticles.rejected, (state, action: any) => { state.fetchMyArticles.status = 'failed'; - state.fetchMyArticles.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // fetchArticleById @@ -297,7 +295,12 @@ const articlesSlice = createSlice({ ); builder.addCase(fetchArticleById.rejected, (state, action: any) => { state.fetchArticleById.status = 'failed'; - state.fetchArticleById.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // createArticle @@ -314,7 +317,14 @@ const articlesSlice = createSlice({ ); builder.addCase(createArticle.rejected, (state, action: any) => { state.createArticle.status = 'failed'; - state.createArticle.error = action.payload; + state.createArticle.error = action.payload.title; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // updateArticle @@ -331,7 +341,14 @@ const articlesSlice = createSlice({ ); builder.addCase(updateArticle.rejected, (state, action: any) => { state.updateArticle.status = 'failed'; - state.updateArticle.error = action.payload; + state.createArticle.error = action.payload.title; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // deleteArticle @@ -355,7 +372,12 @@ const articlesSlice = createSlice({ ); builder.addCase(deleteArticle.rejected, (state, action: any) => { state.deleteArticle.status = 'failed'; - state.deleteArticle.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); }, }); diff --git a/src/redux/slices/contests.ts b/src/redux/slices/contests.ts index 4074135..1c0f749 100644 --- a/src/redux/slices/contests.ts +++ b/src/redux/slices/contests.ts @@ -1,5 +1,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import axios from '../../axios'; +import { toastError } from '../../lib/toastNotification'; // ===================== // Типы @@ -280,10 +281,7 @@ export const fetchParticipatingContests = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to fetch participating contests', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -297,9 +295,7 @@ export const fetchMySubmissions = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch my submissions', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -321,9 +317,7 @@ export const fetchContests = createAsyncThunk( }); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch contests', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -335,9 +329,7 @@ export const fetchContestById = createAsyncThunk( const response = await axios.get(`/contests/${id}`); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch contest', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -352,9 +344,7 @@ export const createContest = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to create contest', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -375,9 +365,7 @@ export const updateContest = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to update contest', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -389,9 +377,7 @@ export const deleteContest = createAsyncThunk( await axios.delete(`/contests/${contestId}`); return contestId; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to delete contest', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -403,9 +389,7 @@ export const fetchMyContests = createAsyncThunk( const response = await axios.get('/contests/my'); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch my contests', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -424,10 +408,7 @@ export const fetchRegisteredContests = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to fetch registered contests', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -451,10 +432,7 @@ export const addOrUpdateContestMember = createAsyncThunk( ); return { contestId, members: response.data }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to add or update contest member', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -470,10 +448,7 @@ export const deleteContestMember = createAsyncThunk( await axios.delete(`/contests/${contestId}/members/${memberId}`); return { contestId, memberId }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to delete contest member', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -488,10 +463,7 @@ export const startContestAttempt = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to start contest attempt', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -506,9 +478,7 @@ export const fetchMyAttemptsInContest = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch my attempts', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -531,10 +501,7 @@ export const fetchContestMembers = createAsyncThunk( ); return { contestId, ...response.data }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to fetch contest members', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -549,9 +516,7 @@ export const checkContestRegistration = createAsyncThunk( ); return { contestId, registered: response.data.registered }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to check registration', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -566,10 +531,7 @@ export const fetchUpcomingEligibleContests = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Failed to fetch upcoming eligible contests', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -584,9 +546,7 @@ export const fetchMyAllAttempts = createAsyncThunk( ); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch my attempts', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -601,9 +561,7 @@ export const fetchMyActiveAttempt = createAsyncThunk( ); return { contestId, attempt: response.data }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Failed to fetch active attempt', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -642,7 +600,13 @@ const contestsSlice = createSlice({ ); builder.addCase(fetchMySubmissions.rejected, (state, action: any) => { state.fetchMySubmissions.status = 'failed'; - state.fetchMySubmissions.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchContests.pending, (state) => { @@ -658,7 +622,13 @@ const contestsSlice = createSlice({ ); builder.addCase(fetchContests.rejected, (state, action: any) => { state.fetchContests.status = 'failed'; - state.fetchContests.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchContestById.pending, (state) => { @@ -673,7 +643,13 @@ const contestsSlice = createSlice({ ); builder.addCase(fetchContestById.rejected, (state, action: any) => { state.fetchContestById.status = 'failed'; - state.fetchContestById.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(createContest.pending, (state) => { @@ -688,7 +664,13 @@ const contestsSlice = createSlice({ ); builder.addCase(createContest.rejected, (state, action: any) => { state.createContest.status = 'failed'; - state.createContest.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(updateContest.pending, (state) => { @@ -703,7 +685,13 @@ const contestsSlice = createSlice({ ); builder.addCase(updateContest.rejected, (state, action: any) => { state.updateContest.status = 'failed'; - state.updateContest.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(deleteContest.pending, (state) => { @@ -725,7 +713,13 @@ const contestsSlice = createSlice({ ); builder.addCase(deleteContest.rejected, (state, action: any) => { state.deleteContest.status = 'failed'; - state.deleteContest.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchMyContests.pending, (state) => { @@ -740,7 +734,13 @@ const contestsSlice = createSlice({ ); builder.addCase(fetchMyContests.rejected, (state, action: any) => { state.fetchMyContests.status = 'failed'; - state.fetchMyContests.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchRegisteredContests.pending, (state) => { @@ -760,7 +760,15 @@ const contestsSlice = createSlice({ fetchRegisteredContests.rejected, (state, action: any) => { 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) => { state.fetchContestMembers.status = 'failed'; - state.fetchContestMembers.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(addOrUpdateContestMember.pending, (state) => { @@ -800,7 +814,15 @@ const contestsSlice = createSlice({ addOrUpdateContestMember.rejected, (state, action: any) => { 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) => { state.deleteContestMember.status = 'failed'; - state.deleteContestMember.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(startContestAttempt.pending, (state) => { @@ -827,7 +855,13 @@ const contestsSlice = createSlice({ ); builder.addCase(startContestAttempt.rejected, (state, action: any) => { state.startAttempt.status = 'failed'; - state.startAttempt.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchMyAttemptsInContest.pending, (state) => { @@ -844,7 +878,15 @@ const contestsSlice = createSlice({ fetchMyAttemptsInContest.rejected, (state, action: any) => { 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) => { state.fetchMyAllAttempts.status = 'failed'; - state.fetchMyAllAttempts.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(fetchMyActiveAttempt.pending, (state) => { @@ -881,7 +929,13 @@ const contestsSlice = createSlice({ ); builder.addCase(fetchMyActiveAttempt.rejected, (state, action: any) => { state.fetchMyActiveAttempt.status = 'failed'; - state.fetchMyActiveAttempt.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); builder.addCase(checkContestRegistration.pending, (state) => { @@ -904,7 +958,15 @@ const contestsSlice = createSlice({ checkContestRegistration.rejected, (state, action: any) => { 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, (state, action: any) => { 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, (state, action: any) => { 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); + }); + }); }, ); }, diff --git a/src/redux/slices/groupChat.ts b/src/redux/slices/groupChat.ts index 9233a9d..a37d91d 100644 --- a/src/redux/slices/groupChat.ts +++ b/src/redux/slices/groupChat.ts @@ -1,5 +1,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import axios from '../../axios'; +import { toastError } from '../../lib/toastNotification'; // ========================================= // Типы @@ -81,10 +82,7 @@ export const fetchGroupMessages = createAsyncThunk( messages: response.data as ChatMessage[], }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при получении сообщений группы', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -99,9 +97,7 @@ export const sendGroupMessage = createAsyncThunk( }); return response.data as ChatMessage; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при отправке сообщения', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -167,7 +163,12 @@ const groupChatSlice = createSlice({ builder.addCase(fetchGroupMessages.rejected, (state, action: any) => { state.fetchMessages.status = 'failed'; - state.fetchMessages.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // send message @@ -188,7 +189,12 @@ const groupChatSlice = createSlice({ builder.addCase(sendGroupMessage.rejected, (state, action: any) => { state.sendMessage.status = 'failed'; - state.sendMessage.error = action.payload; + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); }, }); diff --git a/src/redux/slices/groupfeed.ts b/src/redux/slices/groupfeed.ts index f322fc2..9c65cf0 100644 --- a/src/redux/slices/groupfeed.ts +++ b/src/redux/slices/groupfeed.ts @@ -1,5 +1,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import axios from '../../axios'; +import { toastError } from '../../lib/toastNotification'; // ===================== // Типы @@ -104,9 +105,7 @@ export const fetchGroupPosts = createAsyncThunk( ); return { page, data: response.data as PostsPage }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка загрузки постов', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -124,9 +123,7 @@ export const fetchPostById = createAsyncThunk( ); return response.data as Post; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка загрузки поста', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -149,9 +146,7 @@ export const createPost = createAsyncThunk( }); return response.data as Post; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка создания поста', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -183,9 +178,7 @@ export const updatePost = createAsyncThunk( ); return response.data as Post; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка обновления поста', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -201,9 +194,7 @@ export const deletePost = createAsyncThunk( await axios.delete(`/groups/${groupId}/feed/${postId}`); return postId; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка удаления поста', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -244,7 +235,13 @@ const postsSlice = createSlice({ ); builder.addCase(fetchGroupPosts.rejected, (state, action: any) => { state.fetchPosts.status = 'failed'; - state.fetchPosts.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // fetchPostById @@ -260,7 +257,13 @@ const postsSlice = createSlice({ ); builder.addCase(fetchPostById.rejected, (state, action: any) => { state.fetchPostById.status = 'failed'; - state.fetchPostById.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // createPost @@ -281,7 +284,13 @@ const postsSlice = createSlice({ ); builder.addCase(createPost.rejected, (state, action: any) => { state.createPost.status = 'failed'; - state.createPost.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // updatePost @@ -310,7 +319,13 @@ const postsSlice = createSlice({ ); builder.addCase(updatePost.rejected, (state, action: any) => { state.updatePost.status = 'failed'; - state.updatePost.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // deletePost @@ -338,7 +353,13 @@ const postsSlice = createSlice({ ); builder.addCase(deletePost.rejected, (state, action: any) => { state.deletePost.status = 'failed'; - state.deletePost.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); }, }); diff --git a/src/redux/slices/groups.ts b/src/redux/slices/groups.ts index 38350bb..58898a2 100644 --- a/src/redux/slices/groups.ts +++ b/src/redux/slices/groups.ts @@ -1,5 +1,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; 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 }); return response.data as Group; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при создании группы', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -155,9 +154,7 @@ export const updateGroup = createAsyncThunk( }); return response.data as Group; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при обновлении группы', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -169,9 +166,7 @@ export const deleteGroup = createAsyncThunk( await axios.delete(`/groups/${groupId}`); return groupId; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при удалении группы', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -183,9 +178,7 @@ export const fetchMyGroups = createAsyncThunk( const response = await axios.get('/groups/my'); return response.data.groups as Group[]; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при получении групп', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -197,9 +190,7 @@ export const fetchGroupById = createAsyncThunk( const response = await axios.get(`/groups/${groupId}`); return response.data as Group; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при получении группы', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -221,10 +212,7 @@ export const addGroupMember = createAsyncThunk( }); return response.data; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при добавлении участника', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -239,9 +227,7 @@ export const removeGroupMember = createAsyncThunk( await axios.delete(`/groups/${groupId}/members/${memberId}`); return { groupId, memberId }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при удалении участника', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -258,10 +244,7 @@ export const fetchGroupJoinLink = createAsyncThunk( const response = await axios.get(`/groups/${groupId}/join-link`); return response.data as { token: string; expiresAt: string }; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при получении ссылки для присоединения', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -274,10 +257,7 @@ export const joinGroupByToken = createAsyncThunk( const response = await axios.post(`/groups/join/${token}`); return response.data as Group; } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при присоединении к группе по ссылке', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -314,7 +294,13 @@ const groupsSlice = createSlice({ ); builder.addCase(fetchMyGroups.rejected, (state, action: any) => { state.fetchMyGroups.status = 'failed'; - state.fetchMyGroups.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // fetchGroupById @@ -330,7 +316,13 @@ const groupsSlice = createSlice({ ); builder.addCase(fetchGroupById.rejected, (state, action: any) => { state.fetchGroupById.status = 'failed'; - state.fetchGroupById.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // createGroup @@ -347,7 +339,13 @@ const groupsSlice = createSlice({ ); builder.addCase(createGroup.rejected, (state, action: any) => { state.createGroup.status = 'failed'; - state.createGroup.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // updateGroup @@ -370,7 +368,13 @@ const groupsSlice = createSlice({ ); builder.addCase(updateGroup.rejected, (state, action: any) => { state.updateGroup.status = 'failed'; - state.updateGroup.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // deleteGroup @@ -391,7 +395,13 @@ const groupsSlice = createSlice({ ); builder.addCase(deleteGroup.rejected, (state, action: any) => { state.deleteGroup.status = 'failed'; - state.deleteGroup.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // addGroupMember @@ -403,7 +413,13 @@ const groupsSlice = createSlice({ }); builder.addCase(addGroupMember.rejected, (state, action: any) => { state.addGroupMember.status = 'failed'; - state.addGroupMember.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // removeGroupMember @@ -430,7 +446,13 @@ const groupsSlice = createSlice({ ); builder.addCase(removeGroupMember.rejected, (state, action: any) => { state.removeGroupMember.status = 'failed'; - state.removeGroupMember.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // fetchGroupJoinLink @@ -449,7 +471,13 @@ const groupsSlice = createSlice({ ); builder.addCase(fetchGroupJoinLink.rejected, (state, action: any) => { state.fetchGroupJoinLink.status = 'failed'; - state.fetchGroupJoinLink.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); // joinGroupByToken @@ -466,7 +494,13 @@ const groupsSlice = createSlice({ ); builder.addCase(joinGroupByToken.rejected, (state, action: any) => { state.joinGroupByToken.status = 'failed'; - state.joinGroupByToken.error = action.payload; + + const errors = action.payload.errors as Record; + Object.values(errors).forEach((messages) => { + messages.forEach((msg) => { + toastError(msg); + }); + }); }); }, }); diff --git a/src/redux/slices/missions.ts b/src/redux/slices/missions.ts index 1d6919f..0a4d7f3 100644 --- a/src/redux/slices/missions.ts +++ b/src/redux/slices/missions.ts @@ -1,5 +1,6 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import axios from '../../axios'; +import { toastError } from '../../lib/toastNotification'; // ─── Типы ──────────────────────────────────────────── @@ -29,6 +30,9 @@ interface MissionsState { missions: Mission[]; currentMission: Mission | null; hasNextPage: boolean; + create: { + errors?: Record; + }; statuses: { fetchList: Status; fetchById: Status; @@ -45,6 +49,7 @@ const initialState: MissionsState = { missions: [], currentMission: null, hasNextPage: false, + create: {}, statuses: { fetchList: 'idle', fetchById: 'idle', @@ -79,9 +84,7 @@ export const fetchMissions = createAsyncThunk( }); return response.data; // { missions, hasNextPage } } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при получении миссий', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -94,9 +97,7 @@ export const fetchMissionById = createAsyncThunk( const response = await axios.get(`/missions/${id}`); return response.data; // Mission } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при получении миссии', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -109,10 +110,7 @@ export const fetchMyMissions = createAsyncThunk( const response = await axios.get('/missions/my'); return response.data as Mission[]; // массив миссий пользователя } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || - 'Ошибка при получении моих миссий', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -141,9 +139,7 @@ export const uploadMission = createAsyncThunk( }); return response.data; // Mission } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при загрузке миссии', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -156,9 +152,7 @@ export const deleteMission = createAsyncThunk( await axios.delete(`/missions/${id}`); return id; // возвращаем id удалённой миссии } catch (err: any) { - return rejectWithValue( - err.response?.data?.message || 'Ошибка при удалении миссии', - ); + return rejectWithValue(err.response?.data); } }, ); @@ -204,7 +198,16 @@ const missionsSlice = createSlice({ fetchMissions.rejected, (state, action: PayloadAction) => { 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, (state, action: PayloadAction) => { 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, (state, action: PayloadAction) => { 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, (state, action: PayloadAction) => { 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, (state, action: PayloadAction) => { 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); + }); + }); }, ); }, diff --git a/src/views/home/account/Account.tsx b/src/views/home/account/Account.tsx index 3c932c8..d1c2d0a 100644 --- a/src/views/home/account/Account.tsx +++ b/src/views/home/account/Account.tsx @@ -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, diff --git a/src/views/home/account/RightPanel.tsx b/src/views/home/account/RightPanel.tsx index 952bc23..819e202 100644 --- a/src/views/home/account/RightPanel.tsx +++ b/src/views/home/account/RightPanel.tsx @@ -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 = () => { )}`}