feat: update ui

This commit is contained in:
2026-09-18 07:30:23 +08:00
parent be41a5b188
commit 13989c0702
2 changed files with 67 additions and 39 deletions
+17 -12
View File
@@ -41,6 +41,7 @@ export default function ChatsSection({ selected, onSelect }: Props) {
return items.slice(0, 100).map((i) => ({ return items.slice(0, 100).map((i) => ({
...i, ...i,
contact: i.accountLid ? contacts.findById(i.accountLid) : null,
from: i.isGroup from: i.isGroup
? contacts.findById(i.previewMessage?.author || null) ? contacts.findById(i.previewMessage?.author || null)
: null, : null,
@@ -81,34 +82,37 @@ export default function ChatsSection({ selected, onSelect }: Props) {
{chats == null || isLoading {chats == null || isLoading
? [...Array(20)].map((_, i) => ( ? [...Array(20)].map((_, i) => (
<div key={i} className="w-full flex items-center gap-3 px-3 mb-4"> <div key={i} className="w-full flex items-center gap-3 px-3 mb-4">
<Skeleton key={i} className="size-12 rounded-full bg-accent" /> <Skeleton className="size-12 rounded-full bg-accent" />
<div className="flex-1"> <div className="flex-1">
<Skeleton <Skeleton className="flex-1 h-4 bg-accent mb-2 w-1/2" />
key={i} <Skeleton className="flex-1 h-11 bg-accent" />
className="flex-1 h-4 bg-accent mb-2 w-1/2"
/>
<Skeleton key={i} className="flex-1 h-11 bg-accent" />
</div> </div>
</div> </div>
)) ))
: null} : null}
{filteredChats?.map((chat) => ( {filteredChats?.map((chat) => {
const title =
chat.contact?.formattedName ||
chat.contact?.name ||
chat.formattedTitle ||
chat.name ||
"No title";
return (
<button <button
key={chat.id} key={chat.id}
onClick={() => onSelect(chat.id)} onClick={() => onSelect(chat.id)}
className={`flex w-full items-center gap-3 rounded-xl px-3 py-3 text-left transition cursor-pointer ${selected === chat.id ? "bg-accent" : "hover:bg-muted/70"}`} className={`flex w-full items-center gap-3 rounded-xl px-3 py-3 text-left transition cursor-pointer ${selected === chat.id ? "bg-accent" : "hover:bg-muted/70"}`}
> >
<Avatar <Avatar
name={chat.formattedTitle || chat.name} name={title}
icon={chat.isGroup ? <UsersIcon size={16} /> : null} icon={chat.isGroup ? <UsersIcon size={16} /> : null}
/> />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<p className="truncate text-sm font-semibold"> <p className="truncate text-sm font-semibold">{title}</p>
{chat.formattedTitle || chat.name}
</p>
<span className="shrink-0 text-[11px] text-muted-foreground"> <span className="shrink-0 text-[11px] text-muted-foreground">
{dayjs((chat.timestamp || 0) * 1000).fromNow()} {dayjs((chat.timestamp || 0) * 1000).fromNow()}
</span> </span>
@@ -128,7 +132,8 @@ export default function ChatsSection({ selected, onSelect }: Props) {
</div> </div>
</div> </div>
</button> </button>
))} );
})}
</div> </div>
</> </>
); );
+28 -5
View File
@@ -12,7 +12,7 @@ import {
SendIcon, SendIcon,
XIcon, XIcon,
} from "lucide-react"; } from "lucide-react";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import dayjs from "dayjs"; import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime"; import relativeTime from "dayjs/plugin/relativeTime";
import MessageMedia from "@/components/message-media"; import MessageMedia from "@/components/message-media";
@@ -187,9 +187,18 @@ export default function MessagesSection({ chatId, maxCount = 50 }: Props) {
className={`mb-3 flex ${item.fromMe ? "justify-end" : "justify-start"}`} className={`mb-3 flex ${item.fromMe ? "justify-end" : "justify-start"}`}
> >
<div className="max-w-[82%] sm:max-w-[65%]"> <div className="max-w-[82%] sm:max-w-[65%]">
<p className="mb-1 text-xs text-muted-foreground"> <a
href="#"
onClick={(e) => {
e.preventDefault();
router.push(
`/?chatId=${getId(item.author || item.from?.id)}`,
);
}}
className="mb-1 text-xs text-muted-foreground"
>
{getFormattedName(item.from) || item.notifyName} {getFormattedName(item.from) || item.notifyName}
</p> </a>
<div <div
className={`rounded-2xl px-4 py-2.5 text-sm shadow-sm ${item.fromMe ? "rounded-br-md bg-[#d8f5e8] dark:bg-[#26855c] text-foreground" : "rounded-bl-md bg-card text-card-foreground"}`} className={`rounded-2xl px-4 py-2.5 text-sm shadow-sm ${item.fromMe ? "rounded-br-md bg-[#d8f5e8] dark:bg-[#26855c] text-foreground" : "rounded-bl-md bg-card text-card-foreground"}`}
> >
@@ -290,9 +299,8 @@ export default function MessagesSection({ chatId, maxCount = 50 }: Props) {
i % 2 === 0 ? "self-start" : "self-end", i % 2 === 0 ? "self-start" : "self-end",
)} )}
> >
<Skeleton key={i} className="w-20 mb-3 h-6 bg-card" /> <Skeleton className="w-20 mb-3 h-6 bg-card" />
<Skeleton <Skeleton
key={i}
className={cn( className={cn(
"w-full h-20 bg-card rounded-2xl", "w-full h-20 bg-card rounded-2xl",
["h-20", "h-32", "h-24", "h-40"][i % 4], ["h-20", "h-32", "h-24", "h-40"][i % 4],
@@ -332,6 +340,7 @@ function MessageInputSection({
chatId?: string | null; chatId?: string | null;
refetch: () => void; refetch: () => void;
}) { }) {
const fileRef = useRef<HTMLInputElement>(null);
const { client, isReady } = useWAClient(); const { client, isReady } = useWAClient();
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [files, setFiles] = useState<{ id: string; file: File }[]>([]); const [files, setFiles] = useState<{ id: string; file: File }[]>([]);
@@ -414,6 +423,19 @@ function MessageInputSection({
return ( return (
<div className="border-t border-border bg-card px-3 py-3 sm:py-4"> <div className="border-t border-border bg-card px-3 py-3 sm:py-4">
<input
type="file"
ref={fileRef}
multiple
className="hidden"
onChange={(e) => {
if (e.target.files?.length) {
onFileAttached([...e.target.files]);
}
e.target.value = "";
}}
/>
{files.length > 0 && ( {files.length > 0 && (
<div className="flex items-center gap-2 mb-3 overflow-x-auto"> <div className="flex items-center gap-2 mb-3 overflow-x-auto">
{files.map((i) => ( {files.map((i) => (
@@ -429,6 +451,7 @@ function MessageInputSection({
<button <button
aria-label="Add attachment" aria-label="Add attachment"
className="grid size-10 shrink-0 place-items-center rounded-full text-muted-foreground hover:bg-muted hover:text-muted-foreground" className="grid size-10 shrink-0 place-items-center rounded-full text-muted-foreground hover:bg-muted hover:text-muted-foreground"
onClick={() => fileRef.current?.click()}
> >
<PaperclipIcon size={19} /> <PaperclipIcon size={19} />
</button> </button>