group posts
This commit is contained in:
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;
|
||||
Reference in New Issue
Block a user