feat: fetch media draft

This commit is contained in:
2026-09-16 05:13:02 +08:00
parent 881ce919aa
commit 19b69c7a51
4 changed files with 41 additions and 111 deletions
+29 -3
View File
@@ -24,6 +24,7 @@ export default function MessagesSection({
}: Props) {
const { client, isReady } = useWAClient();
const [message, setMessage] = useState("");
const [isPending, setPending] = useState(false);
const { data: contacts } = useQuery({
queryKey: ["contacts"],
@@ -32,7 +33,7 @@ export default function MessagesSection({
});
const chatId = getId(chat.id);
const { data: messages } = useQuery({
const { data: messages, refetch } = useQuery({
queryKey: ["messages", chatId],
queryFn: () =>
client.getMessages({ chatId, count: 50 }).then((res) =>
@@ -60,10 +61,33 @@ export default function MessagesSection({
?.reverse();
}, [messages, contacts]);
function sendMessage() {
async function sendMessage() {
const text = message.trim();
if (!text) return;
try {
setPending(true);
await client.sendText({ chatId, text, options: { waitForAck: true } });
setTimeout(refetch, 100);
setMessage("");
} catch (err) {
alert((err as Error).message);
} finally {
setPending(false);
}
}
async function fetchMedia(msgId: string) {
const data = await client.getMedia(msgId);
// decode from base64
const blob = await fetch(data).then((res) => res.blob());
// download
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "media";
a.click();
URL.revokeObjectURL(url);
}
return (
@@ -156,11 +180,13 @@ export default function MessagesSection({
) : null}
{item.type === "image" && (
<a href="#" onClick={() => fetchMedia(item.id)}>
<img
src={"data:image/jpeg;base64," + item.body}
alt=""
className="w-full"
/>
</a>
)}
{item.type === "chat" || item.caption ? (
@@ -227,7 +253,7 @@ export default function MessagesSection({
aria-label="Send message"
onClick={sendMessage}
className="grid size-10 shrink-0 place-items-center rounded-full bg-primary text-primary-foreground shadow-sm transition hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-50"
disabled={!message.trim()}
disabled={!message.trim() || isPending}
>
<SendIcon size={17} />
</button>
+3 -91
View File
@@ -12,96 +12,6 @@ import { useQuery } from "@tanstack/react-query";
import { cn, getId, getInitial } from "@/lib/utils";
import MessagesSection from "./messages";
// type Chat = {
// id: number;
// name: string;
// initials: string;
// color: string;
// preview: string;
// time: string;
// unread?: number;
// online?: boolean;
// messages: { text: string; time: string; fromMe?: boolean; read?: boolean }[];
// };
// const chats: Chat[] = [
// {
// id: 1,
// name: "Maya Chen",
// initials: "MC",
// color: "bg-rose-200 text-rose-800",
// preview: "That sounds perfect. See you then!",
// time: "10:42 AM",
// online: true,
// messages: [
// { text: "Hey! Are we still on for coffee tomorrow?", time: "10:38 AM" },
// {
// text: "Absolutely. I found a new spot near the park.",
// time: "10:40 AM",
// fromMe: true,
// read: true,
// },
// { text: "That sounds perfect. See you then!", time: "10:42 AM" },
// ],
// },
// {
// id: 2,
// name: "Design team",
// initials: "DT",
// color: "bg-violet-200 text-violet-800",
// preview: "Jon: The new explorations are ready",
// time: "9:16 AM",
// unread: 3,
// messages: [
// { text: "Jon: The new explorations are ready", time: "9:16 AM" },
// ],
// },
// {
// id: 3,
// name: "Alex Morgan",
// initials: "AM",
// color: "bg-amber-200 text-amber-800",
// preview: "You: Thanks for sending that over",
// time: "Yesterday",
// messages: [
// {
// text: "Thanks for sending that over",
// time: "Yesterday",
// fromMe: true,
// read: true,
// },
// ],
// },
// {
// id: 4,
// name: "Weekend plans",
// initials: "WP",
// color: "bg-emerald-200 text-emerald-800",
// preview: "Priya: I can bring the snacks",
// time: "Yesterday",
// unread: 8,
// messages: [{ text: "Priya: I can bring the snacks", time: "Yesterday" }],
// },
// {
// id: 5,
// name: "Noah Williams",
// initials: "NW",
// color: "bg-sky-200 text-sky-800",
// preview: "Voice message",
// time: "Tuesday",
// messages: [{ text: "Voice message", time: "Tuesday" }],
// },
// {
// id: 6,
// name: "Sofia Patel",
// initials: "SP",
// color: "bg-orange-200 text-orange-800",
// preview: "See you at the studio!",
// time: "Monday",
// messages: [{ text: "See you at the studio!", time: "Monday" }],
// },
// ];
export default function Page() {
const [selectedId, setSelectedId] = useState<string>();
const [query, setQuery] = useState("");
@@ -234,7 +144,9 @@ export default function Page() {
</div>
<div className="mt-1 flex items-center justify-between gap-2">
<p className="truncate text-xs text-muted-foreground">
{chat.lastMessage?.body?.substring(0, 50)}
{chat.previewMessage?.type === "chat"
? chat.previewMessage?.body?.substring(0, 50)
: null}
</p>
{/* {chat.unread && (
<span className="grid size-5 shrink-0 place-items-center rounded-full bg-primary text-[10px] font-bold text-primary-foreground">
+2 -10
View File
@@ -15,13 +15,7 @@ export function useWAClient() {
return ctx;
}
export function WAProvider({
children,
url,
}: {
children: React.ReactNode;
url?: string;
}) {
export function WAProvider({ children }: { children: React.ReactNode }) {
const isInitialized = useRef(false);
const [isConnected, setConnected] = useState(false);
@@ -38,9 +32,7 @@ export function WAProvider({
isInitialized.current = true;
wa.on("status", setConnected);
}, [url]);
console.log("status", status);
}, []);
return (
<WAContext.Provider
+1 -1
View File
@@ -10,7 +10,7 @@ const queryCLient = new QueryClient();
createRoot(document.getElementById("root")!).render(
<StrictMode>
<QueryClientProvider client={queryCLient}>
<WAProvider url={import.meta.env.VITE_WHATSAPP_URL}>
<WAProvider>
<App />
</WAProvider>
</QueryClientProvider>