CoompanionApp/front/play/chat/ChatThread.tsx

117 lines
2.9 KiB
TypeScript
Raw Normal View History

2026-02-03 13:48:56 +01:00
import React, { useEffect, useRef } from "react";
import type { ChatMessage } from "../../../shared/types";
import type { ChatThread } from "./types";
import { useI18n } from "../i18n";
function formatTime(value: number) {
return new Date(value).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
}
export default function ChatThread({
thread,
messages,
meId,
onSend,
readOnly,
nameById,
}: {
thread: ChatThread;
messages: ChatMessage[];
meId: string | null;
onSend?: (body: string) => void;
readOnly?: boolean;
nameById: Record<string, string>;
}) {
const { t } = useI18n();
const listRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (listRef.current) {
listRef.current.scrollTop = listRef.current.scrollHeight;
}
}, [messages]);
const showSender = thread.kind === "group" || thread.kind === "global";
return (
<div className="chat-thread">
<div className="chat-thread-body" ref={listRef}>
{messages.length === 0 && (
<div className="chat-empty">
<p>{t("chat.noMessages")}</p>
<span>{t("chat.startConversation")}</span>
</div>
)}
{messages.map((message) => {
const isMe = message.fromId === meId;
return (
<div key={message.id} className={`chat-message-row ${isMe ? "me" : ""}`}>
<div className={`chat-bubble ${isMe ? "me" : ""}`}>
{showSender && !isMe && (
<span className="chat-sender">
{nameById[message.fromId] ?? t("common.player")}
</span>
)}
<p>{message.body}</p>
<span className="chat-time">{formatTime(message.createdAt)}</span>
</div>
</div>
);
})}
</div>
{!readOnly && (
<ChatComposer
placeholder={t("chat.messagePlaceholder")}
sendLabel={t("common.send")}
onSend={(body) => {
if (!body.trim()) return;
onSend?.(body);
}}
/>
)}
</div>
);
}
function ChatComposer({
onSend,
placeholder,
sendLabel,
}: {
onSend: (body: string) => void;
placeholder: string;
sendLabel: string;
}) {
const [value, setValue] = React.useState("");
return (
<div className="chat-composer">
<input
value={value}
onChange={(event) => setValue(event.target.value)}
placeholder={placeholder}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
if (!value.trim()) return;
onSend(value);
setValue("");
}
}}
/>
<button
className="chat-send"
type="button"
onClick={() => {
if (!value.trim()) return;
onSend(value);
setValue("");
}}
>
{sendLabel}
</button>
</div>
);
}