update articles slice
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||
import axios from '../../axios';
|
||||
|
||||
// ─── Типы ────────────────────────────────────────────
|
||||
// =====================
|
||||
// Типы
|
||||
// =====================
|
||||
|
||||
type Status = 'idle' | 'loading' | 'successful' | 'failed';
|
||||
|
||||
@@ -15,39 +17,145 @@ export interface Article {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface ArticlesState {
|
||||
articles: Article[];
|
||||
currentArticle?: Article;
|
||||
interface ArticlesResponse {
|
||||
hasNextPage: boolean;
|
||||
statuses: {
|
||||
create: Status;
|
||||
update: Status;
|
||||
delete: Status;
|
||||
fetchAll: Status;
|
||||
fetchById: Status;
|
||||
articles: Article[];
|
||||
}
|
||||
|
||||
// =====================
|
||||
// Состояние
|
||||
// =====================
|
||||
|
||||
interface ArticlesState {
|
||||
fetchArticles: {
|
||||
articles: Article[];
|
||||
hasNextPage: boolean;
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
fetchArticleById: {
|
||||
article?: Article;
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
createArticle: {
|
||||
article?: Article;
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
updateArticle: {
|
||||
article?: Article;
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
deleteArticle: {
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
fetchMyArticles: {
|
||||
articles: Article[];
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const initialState: ArticlesState = {
|
||||
articles: [],
|
||||
currentArticle: undefined,
|
||||
hasNextPage: false,
|
||||
statuses: {
|
||||
create: 'idle',
|
||||
update: 'idle',
|
||||
delete: 'idle',
|
||||
fetchAll: 'idle',
|
||||
fetchById: 'idle',
|
||||
fetchArticles: {
|
||||
articles: [],
|
||||
hasNextPage: false,
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
fetchArticleById: {
|
||||
article: undefined,
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
createArticle: {
|
||||
article: undefined,
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
updateArticle: {
|
||||
article: undefined,
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
deleteArticle: {
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
fetchMyArticles: {
|
||||
articles: [],
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
||||
// ─── Async Thunks ─────────────────────────────────────
|
||||
// =====================
|
||||
// Async Thunks
|
||||
// =====================
|
||||
|
||||
// POST /articles
|
||||
// Все статьи
|
||||
export const fetchArticles = createAsyncThunk(
|
||||
'articles/fetchArticles',
|
||||
async (
|
||||
{
|
||||
page = 0,
|
||||
pageSize = 10,
|
||||
tags,
|
||||
}: { page?: number; pageSize?: number; tags?: string[] } = {},
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const params: any = { page, pageSize };
|
||||
if (tags && tags.length > 0) params.tags = tags;
|
||||
const response = await axios.get<ArticlesResponse>('/articles', {
|
||||
params,
|
||||
});
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка при получении статей',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Мои статьи
|
||||
export const fetchMyArticles = createAsyncThunk(
|
||||
'articles/fetchMyArticles',
|
||||
async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await axios.get<Article[]>('/articles/my');
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message ||
|
||||
'Ошибка при получении моих статей',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Статья по ID
|
||||
export const fetchArticleById = createAsyncThunk(
|
||||
'articles/fetchById',
|
||||
async (articleId: number, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await axios.get<Article>(`/articles/${articleId}`);
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка при получении статьи',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Создание статьи
|
||||
export const createArticle = createAsyncThunk(
|
||||
'articles/createArticle',
|
||||
'articles/create',
|
||||
async (
|
||||
{
|
||||
name,
|
||||
@@ -57,12 +165,12 @@ export const createArticle = createAsyncThunk(
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.post('/articles', {
|
||||
const response = await axios.post<Article>('/articles', {
|
||||
name,
|
||||
content,
|
||||
tags,
|
||||
});
|
||||
return response.data as Article;
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка при создании статьи',
|
||||
@@ -71,9 +179,9 @@ export const createArticle = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// PUT /articles/{articleId}
|
||||
// Обновление статьи
|
||||
export const updateArticle = createAsyncThunk(
|
||||
'articles/updateArticle',
|
||||
'articles/update',
|
||||
async (
|
||||
{
|
||||
articleId,
|
||||
@@ -84,12 +192,15 @@ export const updateArticle = createAsyncThunk(
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const response = await axios.put(`/articles/${articleId}`, {
|
||||
name,
|
||||
content,
|
||||
tags,
|
||||
});
|
||||
return response.data as Article;
|
||||
const response = await axios.put<Article>(
|
||||
`/articles/${articleId}`,
|
||||
{
|
||||
name,
|
||||
content,
|
||||
tags,
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка при обновлении статьи',
|
||||
@@ -98,9 +209,9 @@ export const updateArticle = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// DELETE /articles/{articleId}
|
||||
// Удаление статьи
|
||||
export const deleteArticle = createAsyncThunk(
|
||||
'articles/deleteArticle',
|
||||
'articles/delete',
|
||||
async (articleId: number, { rejectWithValue }) => {
|
||||
try {
|
||||
await axios.delete(`/articles/${articleId}`);
|
||||
@@ -113,186 +224,136 @@ export const deleteArticle = createAsyncThunk(
|
||||
},
|
||||
);
|
||||
|
||||
// GET /articles
|
||||
export const fetchArticles = createAsyncThunk(
|
||||
'articles/fetchArticles',
|
||||
async (
|
||||
{
|
||||
page = 0,
|
||||
pageSize = 10,
|
||||
tags,
|
||||
}: { page?: number; pageSize?: number; tags?: string[] },
|
||||
{ rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const params: any = { page, pageSize };
|
||||
if (tags && tags.length > 0) params.tags = tags;
|
||||
const response = await axios.get('/articles', { params });
|
||||
return response.data as {
|
||||
hasNextPage: boolean;
|
||||
articles: Article[];
|
||||
};
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка при получении статей',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// GET /articles/{articleId}
|
||||
export const fetchArticleById = createAsyncThunk(
|
||||
'articles/fetchArticleById',
|
||||
async (articleId: number, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await axios.get(`/articles/${articleId}`);
|
||||
return response.data as Article;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка при получении статьи',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// ─── Slice ────────────────────────────────────────────
|
||||
// =====================
|
||||
// Slice
|
||||
// =====================
|
||||
|
||||
const articlesSlice = createSlice({
|
||||
name: 'articles',
|
||||
initialState,
|
||||
reducers: {
|
||||
clearCurrentArticle: (state) => {
|
||||
state.currentArticle = undefined;
|
||||
},
|
||||
setArticlesStatus: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
key: keyof ArticlesState['statuses'];
|
||||
status: Status;
|
||||
}>,
|
||||
action: PayloadAction<{ key: keyof ArticlesState; status: Status }>,
|
||||
) => {
|
||||
const { key, status } = action.payload;
|
||||
state.statuses[key] = status;
|
||||
if (state[key]) {
|
||||
(state[key] as any).status = status;
|
||||
}
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// ─── CREATE ARTICLE ───
|
||||
builder.addCase(createArticle.pending, (state) => {
|
||||
state.statuses.create = 'loading';
|
||||
state.error = null;
|
||||
});
|
||||
builder.addCase(
|
||||
createArticle.fulfilled,
|
||||
(state, action: PayloadAction<Article>) => {
|
||||
state.statuses.create = 'successful';
|
||||
state.articles.push(action.payload);
|
||||
},
|
||||
);
|
||||
builder.addCase(
|
||||
createArticle.rejected,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.statuses.create = 'failed';
|
||||
state.error = action.payload;
|
||||
},
|
||||
);
|
||||
|
||||
// ─── UPDATE ARTICLE ───
|
||||
builder.addCase(updateArticle.pending, (state) => {
|
||||
state.statuses.update = 'loading';
|
||||
state.error = null;
|
||||
});
|
||||
builder.addCase(
|
||||
updateArticle.fulfilled,
|
||||
(state, action: PayloadAction<Article>) => {
|
||||
state.statuses.update = 'successful';
|
||||
const index = state.articles.findIndex(
|
||||
(a) => a.id === action.payload.id,
|
||||
);
|
||||
if (index !== -1) state.articles[index] = action.payload;
|
||||
if (state.currentArticle?.id === action.payload.id)
|
||||
state.currentArticle = action.payload;
|
||||
},
|
||||
);
|
||||
builder.addCase(
|
||||
updateArticle.rejected,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.statuses.update = 'failed';
|
||||
state.error = action.payload;
|
||||
},
|
||||
);
|
||||
|
||||
// ─── DELETE ARTICLE ───
|
||||
builder.addCase(deleteArticle.pending, (state) => {
|
||||
state.statuses.delete = 'loading';
|
||||
state.error = null;
|
||||
});
|
||||
builder.addCase(
|
||||
deleteArticle.fulfilled,
|
||||
(state, action: PayloadAction<number>) => {
|
||||
state.statuses.delete = 'successful';
|
||||
state.articles = state.articles.filter(
|
||||
(a) => a.id !== action.payload,
|
||||
);
|
||||
if (state.currentArticle?.id === action.payload)
|
||||
state.currentArticle = undefined;
|
||||
},
|
||||
);
|
||||
builder.addCase(
|
||||
deleteArticle.rejected,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.statuses.delete = 'failed';
|
||||
state.error = action.payload;
|
||||
},
|
||||
);
|
||||
|
||||
// ─── FETCH ARTICLES ───
|
||||
// fetchArticles
|
||||
builder.addCase(fetchArticles.pending, (state) => {
|
||||
state.statuses.fetchAll = 'loading';
|
||||
state.error = null;
|
||||
state.fetchArticles.status = 'loading';
|
||||
state.fetchArticles.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchArticles.fulfilled,
|
||||
(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
hasNextPage: boolean;
|
||||
articles: Article[];
|
||||
}>,
|
||||
) => {
|
||||
state.statuses.fetchAll = 'successful';
|
||||
state.articles = action.payload.articles;
|
||||
state.hasNextPage = action.payload.hasNextPage;
|
||||
},
|
||||
);
|
||||
builder.addCase(
|
||||
fetchArticles.rejected,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.statuses.fetchAll = 'failed';
|
||||
state.error = action.payload;
|
||||
(state, action: PayloadAction<ArticlesResponse>) => {
|
||||
state.fetchArticles.status = 'successful';
|
||||
state.fetchArticles.articles = action.payload.articles;
|
||||
state.fetchArticles.hasNextPage = action.payload.hasNextPage;
|
||||
},
|
||||
);
|
||||
builder.addCase(fetchArticles.rejected, (state, action: any) => {
|
||||
state.fetchArticles.status = 'failed';
|
||||
state.fetchArticles.error = action.payload;
|
||||
});
|
||||
|
||||
// ─── FETCH ARTICLE BY ID ───
|
||||
// fetchMyArticles
|
||||
builder.addCase(fetchMyArticles.pending, (state) => {
|
||||
state.fetchMyArticles.status = 'loading';
|
||||
state.fetchMyArticles.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchMyArticles.fulfilled,
|
||||
(state, action: PayloadAction<Article[]>) => {
|
||||
state.fetchMyArticles.status = 'successful';
|
||||
state.fetchMyArticles.articles = action.payload;
|
||||
},
|
||||
);
|
||||
builder.addCase(fetchMyArticles.rejected, (state, action: any) => {
|
||||
state.fetchMyArticles.status = 'failed';
|
||||
state.fetchMyArticles.error = action.payload;
|
||||
});
|
||||
|
||||
// fetchArticleById
|
||||
builder.addCase(fetchArticleById.pending, (state) => {
|
||||
state.statuses.fetchById = 'loading';
|
||||
state.error = null;
|
||||
state.fetchArticleById.status = 'loading';
|
||||
state.fetchArticleById.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchArticleById.fulfilled,
|
||||
(state, action: PayloadAction<Article>) => {
|
||||
state.statuses.fetchById = 'successful';
|
||||
state.currentArticle = action.payload;
|
||||
state.fetchArticleById.status = 'successful';
|
||||
state.fetchArticleById.article = action.payload;
|
||||
},
|
||||
);
|
||||
builder.addCase(fetchArticleById.rejected, (state, action: any) => {
|
||||
state.fetchArticleById.status = 'failed';
|
||||
state.fetchArticleById.error = action.payload;
|
||||
});
|
||||
|
||||
// createArticle
|
||||
builder.addCase(createArticle.pending, (state) => {
|
||||
state.createArticle.status = 'loading';
|
||||
state.createArticle.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchArticleById.rejected,
|
||||
(state, action: PayloadAction<any>) => {
|
||||
state.statuses.fetchById = 'failed';
|
||||
state.error = action.payload;
|
||||
createArticle.fulfilled,
|
||||
(state, action: PayloadAction<Article>) => {
|
||||
state.createArticle.status = 'successful';
|
||||
state.createArticle.article = action.payload;
|
||||
},
|
||||
);
|
||||
builder.addCase(createArticle.rejected, (state, action: any) => {
|
||||
state.createArticle.status = 'failed';
|
||||
state.createArticle.error = action.payload;
|
||||
});
|
||||
|
||||
// updateArticle
|
||||
builder.addCase(updateArticle.pending, (state) => {
|
||||
state.updateArticle.status = 'loading';
|
||||
state.updateArticle.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
updateArticle.fulfilled,
|
||||
(state, action: PayloadAction<Article>) => {
|
||||
state.updateArticle.status = 'successful';
|
||||
state.updateArticle.article = action.payload;
|
||||
},
|
||||
);
|
||||
builder.addCase(updateArticle.rejected, (state, action: any) => {
|
||||
state.updateArticle.status = 'failed';
|
||||
state.updateArticle.error = action.payload;
|
||||
});
|
||||
|
||||
// deleteArticle
|
||||
builder.addCase(deleteArticle.pending, (state) => {
|
||||
state.deleteArticle.status = 'loading';
|
||||
state.deleteArticle.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
deleteArticle.fulfilled,
|
||||
(state, action: PayloadAction<number>) => {
|
||||
state.deleteArticle.status = 'successful';
|
||||
state.fetchArticles.articles =
|
||||
state.fetchArticles.articles.filter(
|
||||
(a) => a.id !== action.payload,
|
||||
);
|
||||
state.fetchMyArticles.articles =
|
||||
state.fetchMyArticles.articles.filter(
|
||||
(a) => a.id !== action.payload,
|
||||
);
|
||||
},
|
||||
);
|
||||
builder.addCase(deleteArticle.rejected, (state, action: any) => {
|
||||
state.deleteArticle.status = 'failed';
|
||||
state.deleteArticle.error = action.payload;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { clearCurrentArticle, setArticlesStatus } = articlesSlice.actions;
|
||||
export const { setArticlesStatus } = articlesSlice.actions;
|
||||
export const articlesReducer = articlesSlice.reducer;
|
||||
|
||||
Reference in New Issue
Block a user