feat: update ui
This commit is contained in:
+17
-12
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user