feat(07-02): implement sendMessage async thunk with optimistic updates
Add sendMessage async thunk with complete optimistic UI flow: - pending: Adds optimistic message immediately with temp ID - fulfilled: Replaces optimistic message with real server response - rejected: Removes optimistic message and sets error - Optimistic messages marked with isOptimistic flag - Channel initialization if not exists - Preserves existing messages during replace/remove operations All 53 unit tests passing. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5d3b6d42e4
commit
7729f2767c
|
|
@ -1,5 +1,5 @@
|
|||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { getChatMessages } from '../../helpers/rest';
|
||||
import { getChatMessages, sendChatMessage } from '../../helpers/rest';
|
||||
|
||||
/**
|
||||
* Async thunk to fetch chat history for a channel
|
||||
|
|
@ -20,6 +20,28 @@ export const fetchChatHistory = createAsyncThunk(
|
|||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Async thunk to send a chat message with optimistic UI updates
|
||||
* @param {Object} params - Request parameters
|
||||
* @param {string} params.channel - Channel ID (session ID, lesson ID, or 'global')
|
||||
* @param {string} params.sessionId - Session ID for session channels
|
||||
* @param {string} params.message - Message content
|
||||
* @param {string} params.optimisticId - Temporary ID for optimistic update
|
||||
* @param {string} params.userId - Current user ID
|
||||
* @param {string} params.userName - Current user name
|
||||
*/
|
||||
export const sendMessage = createAsyncThunk(
|
||||
'sessionChat/sendMessage',
|
||||
async ({ channel, sessionId, message }) => {
|
||||
const response = await sendChatMessage({
|
||||
channel: sessionId ? 'session' : 'global',
|
||||
sessionId,
|
||||
message
|
||||
});
|
||||
return response;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Initial state for session chat
|
||||
* @type {Object}
|
||||
|
|
@ -198,6 +220,66 @@ const sessionChatSlice = createSlice({
|
|||
const channel = action.meta.arg.channel;
|
||||
state.fetchStatus[channel] = 'failed';
|
||||
state.fetchError[channel] = action.error.message;
|
||||
})
|
||||
// sendMessage pending - optimistic update
|
||||
.addCase(sendMessage.pending, (state, action) => {
|
||||
state.sendStatus = 'loading';
|
||||
state.sendError = null;
|
||||
|
||||
// Optimistic update: add message immediately
|
||||
const { channel, message, optimisticId, userId, userName } = action.meta.arg;
|
||||
if (!state.messagesByChannel[channel]) {
|
||||
state.messagesByChannel[channel] = [];
|
||||
}
|
||||
|
||||
state.messagesByChannel[channel].push({
|
||||
id: optimisticId,
|
||||
senderId: userId,
|
||||
senderName: userName,
|
||||
message,
|
||||
createdAt: new Date().toISOString(),
|
||||
channel,
|
||||
isOptimistic: true
|
||||
});
|
||||
})
|
||||
// sendMessage fulfilled - replace optimistic message
|
||||
.addCase(sendMessage.fulfilled, (state, action) => {
|
||||
state.sendStatus = 'succeeded';
|
||||
state.sendError = null;
|
||||
|
||||
// Replace optimistic message with real one
|
||||
const { channel, optimisticId } = action.meta.arg;
|
||||
const realMessage = action.payload.message;
|
||||
|
||||
const messages = state.messagesByChannel[channel];
|
||||
if (messages) {
|
||||
const index = messages.findIndex(m => m.id === optimisticId);
|
||||
if (index !== -1) {
|
||||
messages[index] = {
|
||||
id: realMessage.id,
|
||||
senderId: realMessage.sender_id,
|
||||
senderName: realMessage.sender_name,
|
||||
message: realMessage.message,
|
||||
createdAt: realMessage.created_at,
|
||||
channel: realMessage.channel
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
// sendMessage rejected - remove optimistic message
|
||||
.addCase(sendMessage.rejected, (state, action) => {
|
||||
state.sendStatus = 'failed';
|
||||
state.sendError = action.error.message;
|
||||
|
||||
// Remove optimistic message on failure
|
||||
const { channel, optimisticId } = action.meta.arg;
|
||||
const messages = state.messagesByChannel[channel];
|
||||
if (messages) {
|
||||
const index = messages.findIndex(m => m.id === optimisticId);
|
||||
if (index !== -1) {
|
||||
messages.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue