Port meetups, discussions, gigs, dating/chat, VIP pay (ZPay/XorPay), MiroTalk live, digital academy, and ebook reader. Add persistent community_store, git-first deploy docs, and env template for production secrets. Co-authored-by: Cursor <cursoragent@cursor.com>
68 lines
1.9 KiB
TypeScript
68 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
import Link from "next/link";
|
|
import { api } from "@/lib/api";
|
|
import { useAuth } from "@/lib/auth";
|
|
import { useI18n } from "@/lib/i18n";
|
|
import type { ChatMessage } from "@/lib/types";
|
|
|
|
export default function ChatThreadClient({ convId }: { convId: string }) {
|
|
const { token } = useAuth();
|
|
const { t } = useI18n();
|
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
|
const [text, setText] = useState("");
|
|
const bottomRef = useRef<HTMLDivElement>(null);
|
|
|
|
const load = () => {
|
|
if (!token) return;
|
|
api.getMessages(token, convId).then(setMessages).catch(() => setMessages([]));
|
|
};
|
|
|
|
useEffect(() => {
|
|
load();
|
|
const id = setInterval(load, 4000);
|
|
return () => clearInterval(id);
|
|
}, [token, convId]);
|
|
|
|
useEffect(() => {
|
|
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
}, [messages]);
|
|
|
|
const send = async () => {
|
|
if (!token || !text.trim()) return;
|
|
try {
|
|
await api.sendMessage(token, convId, text.trim());
|
|
setText("");
|
|
load();
|
|
} catch { /* ignore */ }
|
|
};
|
|
|
|
return (
|
|
<div className="chat-thread-page">
|
|
<div className="container chat-thread-wrap">
|
|
<nav className="detail-nav">
|
|
<Link href="/chat">← {t.chat.back}</Link>
|
|
</nav>
|
|
<div className="chat-thread-messages">
|
|
{messages.map((m) => (
|
|
<div key={m.id} className={`chat-bubble${m.mine ? " mine" : ""}`}>
|
|
{m.body}
|
|
</div>
|
|
))}
|
|
<div ref={bottomRef} />
|
|
</div>
|
|
<div className="chat-compose">
|
|
<input
|
|
value={text}
|
|
onChange={(e) => setText(e.target.value)}
|
|
placeholder={t.chat.placeholder}
|
|
onKeyDown={(e) => e.key === "Enter" && send()}
|
|
/>
|
|
<button type="button" className="btn btn-primary" onClick={send}>{t.chat.send}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|