feat(web): add telegram-like message status indicators
All checks were successful
CI / test (push) Successful in 21s
All checks were successful
CI / test (push) Successful in 21s
- optimistic sending state with pending clock icon - transition statuses sent -> delivered -> read via realtime events - render checkmarks next to outgoing message timestamps
This commit is contained in:
@@ -6,7 +6,10 @@ import { buildWsUrl } from "../utils/ws";
|
||||
|
||||
export function MessageComposer() {
|
||||
const activeChatId = useChatStore((s) => s.activeChatId);
|
||||
const prependMessage = useChatStore((s) => s.prependMessage);
|
||||
const me = useAuthStore((s) => s.me);
|
||||
const addOptimisticMessage = useChatStore((s) => s.addOptimisticMessage);
|
||||
const confirmMessageByClientId = useChatStore((s) => s.confirmMessageByClientId);
|
||||
const removeOptimisticMessage = useChatStore((s) => s.removeOptimisticMessage);
|
||||
const accessToken = useAuthStore((s) => s.accessToken);
|
||||
const [text, setText] = useState("");
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
@@ -48,30 +51,47 @@ export function MessageComposer() {
|
||||
}
|
||||
|
||||
async function handleSend() {
|
||||
if (!activeChatId || !text.trim()) {
|
||||
if (!activeChatId || !text.trim() || !me) {
|
||||
return;
|
||||
}
|
||||
const message = await sendMessageWithClientId(activeChatId, text.trim(), "text", makeClientMessageId());
|
||||
prependMessage(activeChatId, message);
|
||||
setText("");
|
||||
const ws = getWs();
|
||||
ws?.send(JSON.stringify({ event: "typing_stop", payload: { chat_id: activeChatId } }));
|
||||
const clientMessageId = makeClientMessageId();
|
||||
const textValue = text.trim();
|
||||
addOptimisticMessage({ chatId: activeChatId, senderId: me.id, type: "text", text: textValue, clientMessageId });
|
||||
try {
|
||||
const message = await sendMessageWithClientId(activeChatId, textValue, "text", clientMessageId);
|
||||
confirmMessageByClientId(activeChatId, clientMessageId, message);
|
||||
setText("");
|
||||
const ws = getWs();
|
||||
ws?.send(JSON.stringify({ event: "typing_stop", payload: { chat_id: activeChatId } }));
|
||||
} catch {
|
||||
removeOptimisticMessage(activeChatId, clientMessageId);
|
||||
setUploadError("Message send failed. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUpload(file: File, messageType: "file" | "image" | "video" | "audio" | "voice" = "file") {
|
||||
if (!activeChatId) {
|
||||
if (!activeChatId || !me) {
|
||||
return;
|
||||
}
|
||||
setIsUploading(true);
|
||||
setUploadProgress(0);
|
||||
setUploadError(null);
|
||||
const clientMessageId = makeClientMessageId();
|
||||
try {
|
||||
const upload = await requestUploadUrl(file);
|
||||
await uploadToPresignedUrl(upload.upload_url, upload.required_headers, file, setUploadProgress);
|
||||
const message = await sendMessageWithClientId(activeChatId, upload.file_url, messageType, makeClientMessageId());
|
||||
addOptimisticMessage({
|
||||
chatId: activeChatId,
|
||||
senderId: me.id,
|
||||
type: messageType,
|
||||
text: upload.file_url,
|
||||
clientMessageId
|
||||
});
|
||||
const message = await sendMessageWithClientId(activeChatId, upload.file_url, messageType, clientMessageId);
|
||||
await attachFile(message.id, upload.file_url, file.type || "application/octet-stream", file.size);
|
||||
prependMessage(activeChatId, message);
|
||||
confirmMessageByClientId(activeChatId, clientMessageId, message);
|
||||
} catch {
|
||||
removeOptimisticMessage(activeChatId, clientMessageId);
|
||||
setUploadError("Upload failed. Please try again.");
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
|
||||
Reference in New Issue
Block a user