111 lines
3.2 KiB
TypeScript
Executable file
111 lines
3.2 KiB
TypeScript
Executable file
/**
|
|
* useSendMessage Hook
|
|
*
|
|
* Sends messages with optimistic updates
|
|
*/
|
|
|
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
|
|
|
import type { SendMessagePayload, Message } from '../types'
|
|
|
|
interface SendMessageResponse {
|
|
success: boolean
|
|
message?: Message
|
|
error?: string
|
|
}
|
|
|
|
/**
|
|
* Send message to API endpoint
|
|
*/
|
|
async function sendMessageAPI(payload: SendMessagePayload): Promise<SendMessageResponse> {
|
|
const response = await fetch(`/api/messaging/threads/${payload.roomId}/messages`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
content: payload.content,
|
|
messageType: payload.messageType || 'TEXT',
|
|
metadata: payload.metadata,
|
|
}),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to send message: ${response.statusText}`)
|
|
}
|
|
|
|
return response.json()
|
|
}
|
|
|
|
/**
|
|
* Hook to send messages with optimistic updates
|
|
*/
|
|
export function useSendMessage(threadId: string) {
|
|
const queryClient = useQueryClient()
|
|
|
|
const mutation = useMutation({
|
|
mutationFn: (payload: SendMessagePayload) => sendMessageAPI(payload),
|
|
|
|
// Optimistic update
|
|
onMutate: async (payload: SendMessagePayload) => {
|
|
// Cancel outgoing refetches
|
|
await queryClient.cancelQueries({ queryKey: ['messages', threadId] })
|
|
|
|
// Snapshot previous value
|
|
const previousMessages = queryClient.getQueryData<Message[]>(['messages', threadId])
|
|
|
|
// Optimistically update to the new value
|
|
const optimisticMessage: Message = {
|
|
id: `temp-${Date.now()}`,
|
|
roomId: payload.roomId,
|
|
senderId: 'current-user', // TODO: Get from auth context
|
|
content: payload.content,
|
|
messageType: payload.messageType || 'TEXT',
|
|
metadata: payload.metadata,
|
|
deliveredTo: [],
|
|
readBy: [],
|
|
createdAt: new Date().toISOString(),
|
|
}
|
|
|
|
queryClient.setQueryData<Message[]>(['messages', threadId], (old: Message[] | undefined) => [
|
|
...(old || []),
|
|
optimisticMessage,
|
|
])
|
|
|
|
// Return context with previous value
|
|
return { previousMessages }
|
|
},
|
|
|
|
// On error, roll back to previous value
|
|
onError: (_err: Error, _payload: SendMessagePayload, context: { previousMessages?: Message[] } | undefined) => {
|
|
if (context?.previousMessages) {
|
|
queryClient.setQueryData(['messages', threadId], context.previousMessages)
|
|
}
|
|
},
|
|
|
|
// Always refetch after error or success
|
|
onSettled: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['messages', threadId] })
|
|
},
|
|
|
|
// Update with server response
|
|
onSuccess: (data: SendMessageResponse) => {
|
|
if (data.message) {
|
|
queryClient.setQueryData<Message[]>(['messages', threadId], (old: Message[] | undefined) => {
|
|
// Remove optimistic message and add real one
|
|
const withoutOptimistic = (old || []).filter((msg: Message) => !msg.id.startsWith('temp-'))
|
|
return [...withoutOptimistic, data.message!]
|
|
})
|
|
}
|
|
},
|
|
})
|
|
|
|
return {
|
|
sendMessage: mutation.mutate,
|
|
sendMessageAsync: mutation.mutateAsync,
|
|
isPending: mutation.isPending,
|
|
isError: mutation.isError,
|
|
error: mutation.error,
|
|
reset: mutation.reset,
|
|
}
|
|
}
|