feat: update ui

This commit is contained in:
2026-09-17 13:47:37 +08:00
parent e9c1d5f3c7
commit 65a2e534d7
18 changed files with 284 additions and 58 deletions
+75 -24
View File
@@ -5,6 +5,8 @@ import { useQuery } from "@tanstack/react-query";
import { cn } from "cn";
import {
ArrowLeftIcon,
CheckCheckIcon,
CheckIcon,
PaperclipIcon,
PhoneCallIcon,
SendIcon,
@@ -14,6 +16,8 @@ import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import MessageMedia from "@/components/message-media";
import Avatar from "@/components/ui/avatar";
import { useContacts } from "@/hooks/useContact";
import { useMessageEvent } from "@/hooks/useMessageEvent";
dayjs.extend(relativeTime);
@@ -32,11 +36,7 @@ export default function MessagesSection({
const [message, setMessage] = useState("");
const [isPending, setPending] = useState(false);
const { data: contacts } = useQuery({
queryKey: ["contacts"],
queryFn: () => client.listContacts(),
refetchOnWindowFocus: false,
});
const { data: contacts } = useContacts();
const chatId = getId(chat.id);
const { data: messages, refetch } = useQuery({
@@ -46,17 +46,35 @@ export default function MessagesSection({
refetchInterval: 10_1000,
});
useMessageEvent((data) => {
if (data.chatId === chatId) refetch();
});
const messageList = useMemo(() => {
return messages
?.filter((i) => !["revoked"].includes(i.type as never))
?.map((message) => {
?.map((i) => {
const from = contacts?.find(
(c) => getId(c.id) === getId(message.author || message.from),
(c) => getId(c.id) === getId(i.author || i.from),
);
const quotedMsg = i.quotedMsg
? {
...i.quotedMsg,
from: contacts?.find((c) => {
return (
getId(c.id) ===
getId(i.quotedMsg?.author || i.quotedMsg?.from)
);
}),
}
: null;
return {
...message,
id: getId(message.id),
...i,
id: getId(i.id),
from,
quotedMsg,
};
})
?.reverse();
@@ -135,6 +153,7 @@ export default function MessagesSection({
{messageList?.map((item, index) => (
<div
id={item.id}
key={`${item.timestamp}-${index}`}
className={`mb-3 flex ${item.fromMe ? "justify-end" : "justify-start"}`}
>
@@ -151,23 +170,48 @@ export default function MessagesSection({
</p>
) : null}
{item.quotedMsg && item.quotedMsg.type === "chat" ? (
<div
{/* <pre className="overflow-y-auto max-h-80">
{JSON.stringify(item, null, 2)}
</pre> */}
{item.quotedMsg &&
["chat", "image", "document"].includes(
item.quotedMsg.type as string,
) ? (
<a
href="#"
onClick={(e) => {
e.preventDefault();
const el = document.getElementById(
getId(item.quotedMsgId),
);
if (el) {
el.scrollIntoView({ behavior: "smooth" });
}
}}
className={cn(
"bg-card/30 px-2 py-1.5 rounded-lg mb-2 mt-1",
"bg-card/30 px-2 py-1.5 rounded-lg mb-2 mt-1 cursor-pointer block",
!item.fromMe && "bg-[#d8f5e8]/30 dark:bg-[#26855c]/30",
)}
>
<p className="whitespace-pre-wrap wrap-break-word">
{item.quotedMsg.body}
<p className="text-xs text-muted-foreground">
{getFormattedName(item.quotedMsg.from)}
</p>
</div>
<p className="whitespace-pre-wrap wrap-break-word">
{item.quotedMsg.filename ||
item.quotedMsg.caption ||
(item.quotedMsg.type === "chat"
? item.quotedMsg.body
: "Quoted")}
</p>
</a>
) : null}
{item.type === "image" ||
item.type === "sticker" ||
item.type === "document" ? (
<MessageMedia data={item} />
item.type === "document" ||
item.type === "ptt" ? (
<MessageMedia data={item as never} />
) : null}
{item.type === "chat" || item.caption ? (
@@ -187,12 +231,19 @@ export default function MessagesSection({
className={`mt-1 flex items-center justify-end gap-1 text-[10px] ${item.fromMe ? "text-primary" : "text-muted-foreground"}`}
>
<span>{dayjs((item.timestamp ?? 0) * 1000).fromNow()}</span>
{/* {item.fromMe &&
(item.read ? (
<CheckCheck size={14} />
) : (
<Check size={13} />
))} */}
{item.fromMe && (
<>
{item.ack && item.ack >= 2 ? (
<CheckCheckIcon
size={14}
className={item.ack === 3 ? "text-blue-300" : ""}
/>
) : (
<CheckIcon size={13} />
)}
</>
)}
</div>
</div>
</div>
@@ -232,7 +283,7 @@ export default function MessagesSection({
}}
placeholder="Write a message..."
rows={1}
className="max-h-24 min-h-10 flex-1 resize-none bg-transparent py-2.5 text-sm outline-none placeholder:text-muted-foreground"
className="max-h-24 min-h-10 flex-1 resize-none field-sizing-content bg-transparent py-2.5 text-sm outline-none placeholder:text-muted-foreground"
/>
</div>
<button