group posts
This commit is contained in:
@@ -286,7 +286,6 @@ const authSlice = createSlice({
|
||||
state.error = action.payload as string;
|
||||
|
||||
// Если пользователь не авторизован (401), делаем logout и пытаемся refresh
|
||||
console.log(action);
|
||||
if (
|
||||
action.payload === 'Unauthorized' ||
|
||||
action.payload === 'Failed to fetch user info'
|
||||
|
||||
336
src/redux/slices/groupfeed.ts
Normal file
336
src/redux/slices/groupfeed.ts
Normal file
@@ -0,0 +1,336 @@
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||
import axios from '../../axios';
|
||||
|
||||
// =====================
|
||||
// Типы
|
||||
// =====================
|
||||
|
||||
type Status = 'idle' | 'loading' | 'successful' | 'failed';
|
||||
|
||||
export interface Post {
|
||||
id: number;
|
||||
groupId: number;
|
||||
authorId: number;
|
||||
authorUsername: string;
|
||||
name: string;
|
||||
content: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface PostsPage {
|
||||
items: Post[];
|
||||
hasNext: boolean;
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Состояние
|
||||
// =====================
|
||||
|
||||
interface PostsState {
|
||||
fetchPosts: {
|
||||
pages: Record<number, PostsPage>; // страница => данные
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
fetchPostById: {
|
||||
post?: Post;
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
createPost: {
|
||||
post?: Post;
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
updatePost: {
|
||||
post?: Post;
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
deletePost: {
|
||||
deletedId?: number;
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const initialState: PostsState = {
|
||||
fetchPosts: {
|
||||
pages: {},
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
fetchPostById: {
|
||||
post: undefined,
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
createPost: {
|
||||
post: undefined,
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
updatePost: {
|
||||
post: undefined,
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
deletePost: {
|
||||
deletedId: undefined,
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// =====================
|
||||
// Async Thunks
|
||||
// =====================
|
||||
|
||||
// Получить посты группы (пагинация)
|
||||
export const fetchGroupPosts = createAsyncThunk(
|
||||
'posts/fetchGroupPosts',
|
||||
async (
|
||||
{
|
||||
groupId,
|
||||
page = 0,
|
||||
pageSize = 20,
|
||||
}: { groupId: number; page?: number; pageSize?: number },
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/groups/${groupId}/feed?page=${page}&pageSize=${pageSize}`,
|
||||
);
|
||||
return { page, data: response.data as PostsPage };
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка загрузки постов',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Получить один пост
|
||||
export const fetchPostById = createAsyncThunk(
|
||||
'posts/fetchPostById',
|
||||
async (
|
||||
{ groupId, postId }: { groupId: number; postId: number },
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`/groups/${groupId}/feed/${postId}`,
|
||||
);
|
||||
return response.data as Post;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка загрузки поста',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Создать пост
|
||||
export const createPost = createAsyncThunk(
|
||||
'posts/createPost',
|
||||
async (
|
||||
{
|
||||
groupId,
|
||||
name,
|
||||
content,
|
||||
}: { groupId: number; name: string; content: string },
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.post(`/groups/${groupId}/feed`, {
|
||||
name,
|
||||
content,
|
||||
});
|
||||
return response.data as Post;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка создания поста',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Обновить пост
|
||||
export const updatePost = createAsyncThunk(
|
||||
'posts/updatePost',
|
||||
async (
|
||||
{
|
||||
groupId,
|
||||
postId,
|
||||
name,
|
||||
content,
|
||||
}: {
|
||||
groupId: number;
|
||||
postId: number;
|
||||
name: string;
|
||||
content: string;
|
||||
},
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.put(
|
||||
`/groups/${groupId}/feed/${postId}`,
|
||||
{
|
||||
name,
|
||||
content,
|
||||
},
|
||||
);
|
||||
return response.data as Post;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка обновления поста',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Удалить пост
|
||||
export const deletePost = createAsyncThunk(
|
||||
'posts/deletePost',
|
||||
async (
|
||||
{ groupId, postId }: { groupId: number; postId: number },
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
await axios.delete(`/groups/${groupId}/feed/${postId}`);
|
||||
return postId;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка удаления поста',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// =====================
|
||||
// Slice
|
||||
// =====================
|
||||
|
||||
const postsSlice = createSlice({
|
||||
name: 'posts',
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
// fetchGroupPosts
|
||||
builder.addCase(fetchGroupPosts.pending, (state) => {
|
||||
state.fetchPosts.status = 'loading';
|
||||
});
|
||||
builder.addCase(
|
||||
fetchGroupPosts.fulfilled,
|
||||
(
|
||||
state,
|
||||
action: PayloadAction<{ page: number; data: PostsPage }>,
|
||||
) => {
|
||||
const { page, data } = action.payload;
|
||||
state.fetchPosts.status = 'successful';
|
||||
state.fetchPosts.pages[page] = data;
|
||||
},
|
||||
);
|
||||
builder.addCase(fetchGroupPosts.rejected, (state, action: any) => {
|
||||
state.fetchPosts.status = 'failed';
|
||||
state.fetchPosts.error = action.payload;
|
||||
});
|
||||
|
||||
// fetchPostById
|
||||
builder.addCase(fetchPostById.pending, (state) => {
|
||||
state.fetchPostById.status = 'loading';
|
||||
});
|
||||
builder.addCase(
|
||||
fetchPostById.fulfilled,
|
||||
(state, action: PayloadAction<Post>) => {
|
||||
state.fetchPostById.status = 'successful';
|
||||
state.fetchPostById.post = action.payload;
|
||||
},
|
||||
);
|
||||
builder.addCase(fetchPostById.rejected, (state, action: any) => {
|
||||
state.fetchPostById.status = 'failed';
|
||||
state.fetchPostById.error = action.payload;
|
||||
});
|
||||
|
||||
// createPost
|
||||
builder.addCase(createPost.pending, (state) => {
|
||||
state.createPost.status = 'loading';
|
||||
});
|
||||
builder.addCase(
|
||||
createPost.fulfilled,
|
||||
(state, action: PayloadAction<Post>) => {
|
||||
state.createPost.status = 'successful';
|
||||
state.createPost.post = action.payload;
|
||||
|
||||
// добавляем сразу в первую страницу (page = 0)
|
||||
if (state.fetchPosts.pages[0]) {
|
||||
state.fetchPosts.pages[0].items.unshift(action.payload);
|
||||
}
|
||||
},
|
||||
);
|
||||
builder.addCase(createPost.rejected, (state, action: any) => {
|
||||
state.createPost.status = 'failed';
|
||||
state.createPost.error = action.payload;
|
||||
});
|
||||
|
||||
// updatePost
|
||||
builder.addCase(updatePost.pending, (state) => {
|
||||
state.updatePost.status = 'loading';
|
||||
});
|
||||
builder.addCase(
|
||||
updatePost.fulfilled,
|
||||
(state, action: PayloadAction<Post>) => {
|
||||
state.updatePost.status = 'successful';
|
||||
state.updatePost.post = action.payload;
|
||||
|
||||
// обновим в списках
|
||||
for (const page of Object.values(state.fetchPosts.pages)) {
|
||||
const index = page.items.findIndex(
|
||||
(p) => p.id === action.payload.id,
|
||||
);
|
||||
if (index !== -1) page.items[index] = action.payload;
|
||||
}
|
||||
|
||||
// обновим если открыт одиночный пост
|
||||
if (state.fetchPostById.post?.id === action.payload.id) {
|
||||
state.fetchPostById.post = action.payload;
|
||||
}
|
||||
},
|
||||
);
|
||||
builder.addCase(updatePost.rejected, (state, action: any) => {
|
||||
state.updatePost.status = 'failed';
|
||||
state.updatePost.error = action.payload;
|
||||
});
|
||||
|
||||
// deletePost
|
||||
builder.addCase(deletePost.pending, (state) => {
|
||||
state.deletePost.status = 'loading';
|
||||
});
|
||||
builder.addCase(
|
||||
deletePost.fulfilled,
|
||||
(state, action: PayloadAction<number>) => {
|
||||
state.deletePost.status = 'successful';
|
||||
state.deletePost.deletedId = action.payload;
|
||||
|
||||
// удалить из всех страниц
|
||||
for (const page of Object.values(state.fetchPosts.pages)) {
|
||||
page.items = page.items.filter(
|
||||
(p) => p.id !== action.payload,
|
||||
);
|
||||
}
|
||||
|
||||
// если открыт индивидуальный пост
|
||||
if (state.fetchPostById.post?.id === action.payload) {
|
||||
state.fetchPostById.post = undefined;
|
||||
}
|
||||
},
|
||||
);
|
||||
builder.addCase(deletePost.rejected, (state, action: any) => {
|
||||
state.deletePost.status = 'failed';
|
||||
state.deletePost.error = action.payload;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const groupFeedReducer = postsSlice.reducer;
|
||||
@@ -5,6 +5,7 @@ interface StorState {
|
||||
menu: {
|
||||
activePage: string;
|
||||
activeProfilePage: string;
|
||||
activeGroupPage: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,6 +14,7 @@ const initialState: StorState = {
|
||||
menu: {
|
||||
activePage: '',
|
||||
activeProfilePage: '',
|
||||
activeGroupPage: '',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -30,9 +32,19 @@ const storeSlice = createSlice({
|
||||
) => {
|
||||
state.menu.activeProfilePage = activeProfilePage.payload;
|
||||
},
|
||||
setMenuActiveGroupPage: (
|
||||
state,
|
||||
activeGroupPage: PayloadAction<string>,
|
||||
) => {
|
||||
state.menu.activeGroupPage = activeGroupPage.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setMenuActivePage, setMenuActiveProfilePage } =
|
||||
storeSlice.actions;
|
||||
export const {
|
||||
setMenuActivePage,
|
||||
setMenuActiveProfilePage,
|
||||
setMenuActiveGroupPage,
|
||||
} = storeSlice.actions;
|
||||
|
||||
export const storeReducer = storeSlice.reducer;
|
||||
|
||||
@@ -56,7 +56,6 @@ const initialState: SubmitState = {
|
||||
export const submitMission = createAsyncThunk(
|
||||
'submit/submitMission',
|
||||
async (submitData: Submit, { rejectWithValue }) => {
|
||||
console.log(submitData);
|
||||
try {
|
||||
const response = await axios.post('/submits', submitData);
|
||||
return response.data;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { submitReducer } from './slices/submit';
|
||||
import { contestsReducer } from './slices/contests';
|
||||
import { groupsReducer } from './slices/groups';
|
||||
import { articlesReducer } from './slices/articles';
|
||||
import { groupFeedReducer } from './slices/groupfeed';
|
||||
|
||||
// использование
|
||||
// import { useAppDispatch, useAppSelector } from '../redux/hooks';
|
||||
@@ -25,6 +26,7 @@ export const store = configureStore({
|
||||
contests: contestsReducer,
|
||||
groups: groupsReducer,
|
||||
articles: articlesReducer,
|
||||
groupfeed: groupFeedReducer,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user