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) { }: Props) {
const { client, isReady } = useWAClient(); const { client, isReady } = useWAClient();
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [isPending, setPending] = useState(false);
const { data: contacts } = useQuery({ const { data: contacts } = useQuery({
queryKey: ["contacts"], queryKey: ["contacts"],
@@ -32,7 +33,7 @@ export default function MessagesSection({
}); });
const chatId = getId(chat.id); const chatId = getId(chat.id);
const { data: messages } = useQuery({ const { data: messages, refetch } = useQuery({
queryKey: ["messages", chatId], queryKey: ["messages", chatId],
queryFn: () => queryFn: () =>
client.getMessages({ chatId, count: 50 }).then((res) => client.getMessages({ chatId, count: 50 }).then((res) =>
@@ -60,10 +61,33 @@ export default function MessagesSection({
?.reverse(); ?.reverse();
}, [messages, contacts]); }, [messages, contacts]);
function sendMessage() { async function sendMessage() {
const text = message.trim(); const text = message.trim();
if (!text) return; if (!text) return;
try {
setPending(true);
await client.sendText({ chatId, text, options: { waitForAck: true } });
setTimeout(refetch, 100);
setMessage(""); 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 ( return (
@@ -156,11 +180,13 @@ export default function MessagesSection({
) : null} ) : null}
{item.type === "image" && ( {item.type === "image" && (
<a href="#" onClick={() => fetchMedia(item.id)}>
<img <img
src={"data:image/jpeg;base64," + item.body} src={"data:image/jpeg;base64," + item.body}
alt="" alt=""
className="w-full" className="w-full"
/> />
</a>
)} )}
{item.type === "chat" || item.caption ? ( {item.type === "chat" || item.caption ? (
@@ -227,7 +253,7 @@ export default function MessagesSection({
aria-label="Send message" aria-label="Send message"
onClick={sendMessage} 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" 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} /> <SendIcon size={17} />
</button> </button>
+3 -91
View File
@@ -12,96 +12,6 @@ import { useQuery } from "@tanstack/react-query";
import { cn, getId, getInitial } from "@/lib/utils"; import { cn, getId, getInitial } from "@/lib/utils";
import MessagesSection from "./messages"; 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() { export default function Page() {
const [selectedId, setSelectedId] = useState<string>(); const [selectedId, setSelectedId] = useState<string>();
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
@@ -234,7 +144,9 @@ export default function Page() {
</div> </div>
<div className="mt-1 flex items-center justify-between gap-2"> <div className="mt-1 flex items-center justify-between gap-2">
<p className="truncate text-xs text-muted-foreground"> <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> </p>
{/* {chat.unread && ( {/* {chat.unread && (
<span className="grid size-5 shrink-0 place-items-center rounded-full bg-primary text-[10px] font-bold text-primary-foreground"> <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; return ctx;
} }
export function WAProvider({ export function WAProvider({ children }: { children: React.ReactNode }) {
children,
url,
}: {
children: React.ReactNode;
url?: string;
}) {
const isInitialized = useRef(false); const isInitialized = useRef(false);
const [isConnected, setConnected] = useState(false); const [isConnected, setConnected] = useState(false);
@@ -38,9 +32,7 @@ export function WAProvider({
isInitialized.current = true; isInitialized.current = true;
wa.on("status", setConnected); wa.on("status", setConnected);
}, [url]); }, []);
console.log("status", status);
return ( return (
<WAContext.Provider <WAContext.Provider
+1 -1
View File
@@ -10,7 +10,7 @@ const queryCLient = new QueryClient();
createRoot(document.getElementById("root")!).render( createRoot(document.getElementById("root")!).render(
<StrictMode> <StrictMode>
<QueryClientProvider client={queryCLient}> <QueryClientProvider client={queryCLient}>
<WAProvider url={import.meta.env.VITE_WHATSAPP_URL}> <WAProvider>
<App /> <App />
</WAProvider> </WAProvider>
</QueryClientProvider> </QueryClientProvider>