group chat init
This commit is contained in:
190
src/redux/slices/groupChat.ts
Normal file
190
src/redux/slices/groupChat.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
|
||||
import axios from '../../axios';
|
||||
|
||||
// =========================================
|
||||
// Типы
|
||||
// =========================================
|
||||
|
||||
export type Status = 'idle' | 'loading' | 'successful' | 'failed';
|
||||
|
||||
export interface ChatMessage {
|
||||
id: number;
|
||||
groupId: number;
|
||||
authorId: number;
|
||||
authorUsername: string;
|
||||
content: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface FetchMessagesParams {
|
||||
groupId: number;
|
||||
limit?: number;
|
||||
afterMessageId?: number | null;
|
||||
afterCreatedAt?: string | null;
|
||||
timeoutSeconds?: number;
|
||||
}
|
||||
|
||||
interface SendMessageParams {
|
||||
groupId: number;
|
||||
content: string;
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// State
|
||||
// =========================================
|
||||
|
||||
interface GroupChatState {
|
||||
messages: Record<number, ChatMessage[]>; // messages[groupId][]
|
||||
fetchMessages: {
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
sendMessage: {
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
}
|
||||
|
||||
const initialState: GroupChatState = {
|
||||
messages: {},
|
||||
fetchMessages: {
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
sendMessage: {
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// =========================================
|
||||
// Thunks
|
||||
// =========================================
|
||||
|
||||
// Получить сообщения группы (обычный запрос или long polling)
|
||||
export const fetchGroupMessages = createAsyncThunk(
|
||||
'groupChat/fetchGroupMessages',
|
||||
async (params: FetchMessagesParams, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await axios.get(`/groups/${params.groupId}/chat`, {
|
||||
params: {
|
||||
limit: params.limit,
|
||||
afterMessageId: params.afterMessageId,
|
||||
afterCreatedAt: params.afterCreatedAt,
|
||||
timeoutSeconds: params.timeoutSeconds,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
groupId: params.groupId,
|
||||
messages: response.data as ChatMessage[],
|
||||
};
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message ||
|
||||
'Ошибка при получении сообщений группы',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Отправить новое сообщение
|
||||
export const sendGroupMessage = createAsyncThunk(
|
||||
'groupChat/sendGroupMessage',
|
||||
async ({ groupId, content }: SendMessageParams, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await axios.post(`/groups/${groupId}/chat`, {
|
||||
content,
|
||||
});
|
||||
|
||||
return response.data as ChatMessage;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Ошибка при отправке сообщения',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// =========================================
|
||||
// Slice
|
||||
// =========================================
|
||||
|
||||
const groupChatSlice = createSlice({
|
||||
name: 'groupChat',
|
||||
initialState,
|
||||
reducers: {
|
||||
clearChat(state, action: PayloadAction<number>) {
|
||||
delete state.messages[action.payload];
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// fetchGroupMessages
|
||||
builder.addCase(fetchGroupMessages.pending, (state) => {
|
||||
state.fetchMessages.status = 'loading';
|
||||
});
|
||||
|
||||
builder.addCase(
|
||||
fetchGroupMessages.fulfilled,
|
||||
(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
groupId: number;
|
||||
messages: ChatMessage[];
|
||||
}>,
|
||||
) => {
|
||||
state.fetchMessages.status = 'successful';
|
||||
|
||||
const { groupId, messages } = action.payload;
|
||||
|
||||
if (!state.messages[groupId]) state.messages[groupId] = [];
|
||||
|
||||
// Если пришли последние сообщения — заменяем
|
||||
// Если long polling — дополняем
|
||||
if (messages.length > 0) {
|
||||
const existing = state.messages[groupId];
|
||||
|
||||
// Фильтруем дубликаты
|
||||
const ids = new Set(existing.map((m) => m.id));
|
||||
|
||||
const newMessages = messages.filter((m) => !ids.has(m.id));
|
||||
|
||||
state.messages[groupId] = [...existing, ...newMessages];
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
builder.addCase(fetchGroupMessages.rejected, (state, action: any) => {
|
||||
state.fetchMessages.status = 'failed';
|
||||
state.fetchMessages.error = action.payload;
|
||||
});
|
||||
|
||||
// sendMessage
|
||||
builder.addCase(sendGroupMessage.pending, (state) => {
|
||||
state.sendMessage.status = 'loading';
|
||||
});
|
||||
|
||||
builder.addCase(
|
||||
sendGroupMessage.fulfilled,
|
||||
(state, action: PayloadAction<ChatMessage>) => {
|
||||
state.sendMessage.status = 'successful';
|
||||
|
||||
const msg = action.payload;
|
||||
|
||||
if (!state.messages[msg.groupId]) {
|
||||
state.messages[msg.groupId] = [];
|
||||
}
|
||||
|
||||
state.messages[msg.groupId].push(msg);
|
||||
},
|
||||
);
|
||||
|
||||
builder.addCase(sendGroupMessage.rejected, (state, action: any) => {
|
||||
state.sendMessage.status = 'failed';
|
||||
state.sendMessage.error = action.payload;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { clearChat } = groupChatSlice.actions;
|
||||
export const groupChatReducer = groupChatSlice.reducer;
|
||||
@@ -7,6 +7,7 @@ import { contestsReducer } from './slices/contests';
|
||||
import { groupsReducer } from './slices/groups';
|
||||
import { articlesReducer } from './slices/articles';
|
||||
import { groupFeedReducer } from './slices/groupfeed';
|
||||
import { groupChatReducer } from './slices/groupChat';
|
||||
|
||||
// использование
|
||||
// import { useAppDispatch, useAppSelector } from '../redux/hooks';
|
||||
@@ -27,6 +28,7 @@ export const store = configureStore({
|
||||
groups: groupsReducer,
|
||||
articles: articlesReducer,
|
||||
groupfeed: groupFeedReducer,
|
||||
groupchat: groupChatReducer,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user