diff --git a/ui/src/app/devices.tsx b/ui/src/app/devices.tsx
new file mode 100644
index 0000000..568ba04
--- /dev/null
+++ b/ui/src/app/devices.tsx
@@ -0,0 +1,91 @@
+import { useEffect, useMemo } from "react";
+import { useWAClient } from "./context";
+import { useQuery } from "@tanstack/react-query";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+
+type Props = {
+ curDeviceId: string;
+ setDeviceId: (id: string) => void;
+};
+
+export default function DevicesSection({ curDeviceId, setDeviceId }: Props) {
+ const { client, isReady } = useWAClient();
+ const { data: devices } = useQuery({
+ queryKey: ["devices"],
+ queryFn: () => client.listDevices(),
+ enabled: isReady,
+ refetchInterval: 10_000,
+ });
+
+ useEffect(() => {
+ if (!curDeviceId && devices?.length && devices[0].id) {
+ setDeviceId(devices[0].id);
+ }
+ }, [curDeviceId, devices]);
+
+ const deviceList = useMemo(() => {
+ return devices?.map((i, idx) => {
+ const lastAlive = i.lastAlive || 0;
+ const isOnline = Date.now() - lastAlive <= 60_000;
+
+ return {
+ label: i.name || "Unknown",
+ value: i.id || String(idx),
+ lastAlive,
+ isOnline,
+ };
+ });
+ }, [devices]);
+
+ const curDevice = useMemo(() => {
+ return deviceList?.find((i) => i.value === curDeviceId);
+ }, [deviceList, curDeviceId]);
+
+ return (
+
+
+
+ );
+}
diff --git a/ui/src/app/messages.tsx b/ui/src/app/messages.tsx
index 7575ed5..000ff73 100644
--- a/ui/src/app/messages.tsx
+++ b/ui/src/app/messages.tsx
@@ -1,13 +1,19 @@
import { useWAClient } from "./context";
-import { getFormattedName, getId, getInitial } from "@/lib/utils";
+import { getFormattedName, getId } from "@/lib/utils";
import type { ChatDTO } from "@shared/dto";
import { useQuery } from "@tanstack/react-query";
import { cn } from "cn";
-import { ArrowLeftIcon, PaperclipIcon, SendIcon } from "lucide-react";
+import {
+ ArrowLeftIcon,
+ PaperclipIcon,
+ PhoneCallIcon,
+ SendIcon,
+} from "lucide-react";
import React, { useMemo, useState } from "react";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import MessageMedia from "@/components/message-media";
+import Avatar from "@/components/ui/avatar";
dayjs.extend(relativeTime);
@@ -42,8 +48,8 @@ export default function MessagesSection({
const messageList = useMemo(() => {
return messages
- ?.filter((i) => !["call_log", "revoked"].includes(i.type as never))
- .map((message) => {
+ ?.filter((i) => !["revoked"].includes(i.type as never))
+ ?.map((message) => {
const from = contacts?.find(
(c) => getId(c.id) === getId(message.author || message.from),
);
@@ -81,18 +87,13 @@ export default function MessagesSection({
-
- {getInitial(chat.formattedTitle)}
-
+
+
{chat.formattedTitle || chat.name}
@@ -175,6 +176,13 @@ export default function MessagesSection({
) : null}
+ {item.type === "call_log" && (
+
+ )}
+
diff --git a/ui/src/app/page.tsx b/ui/src/app/page.tsx
index 2ebf68d..6df46da 100644
--- a/ui/src/app/page.tsx
+++ b/ui/src/app/page.tsx
@@ -1,19 +1,27 @@
-import { useState } from "react";
import ChatsSection from "./chats";
import type { ChatDTO } from "@shared/dto";
import MessagesSection from "./messages";
+import DevicesSection from "./devices";
+import { useLocalStorage } from "@/hooks/useLocalStorage";
export default function Page() {
- const [chat, setChat] = useState
(null);
- const [mobileChat, setMobileChat] = useState(false);
+ const [chat, setChat] = useLocalStorage(
+ "app/current-chat",
+ null,
+ );
+ const [mobileChat, setMobileChat] = useLocalStorage(
+ "app/mobile-chat",
+ false,
+ );
+ const [curDeviceId, setDeviceId] = useLocalStorage("app/device", "");
return (
-
-