diff --git a/app/(app)/chats/[id]/page.tsx b/app/(app)/chats/[id]/page.tsx index 9ba2d268..72a13118 100644 --- a/app/(app)/chats/[id]/page.tsx +++ b/app/(app)/chats/[id]/page.tsx @@ -35,6 +35,7 @@ import { toast } from "sonner"; import { channelAuthorizer } from "@/lib/realtime"; import { cn } from "@/lib/utils"; import { useVisualViewport } from "@/lib/use-visual-viewport"; +import { ConfirmModal } from "@/components/ui/confirm-modal"; import { useNotifications } from "@/components/notifications/notifications-provider"; import { chat, @@ -71,6 +72,9 @@ export default function ChatThreadPage() { const [replyTo, setReplyTo] = useState(null); const [menu, setMenu] = useState<{ message: ChatMessage; point: MenuPoint } | null>(null); const [forwarding, setForwarding] = useState(null); + // Non-null asks before deleting for everyone. Deleting only for yourself is + // reversible by the others still having it, so it goes straight through. + const [confirmDelete, setConfirmDelete] = useState(null); const [starredIds, setStarredIds] = useState>(new Set()); // user id → when they last typed. Kept as timestamps rather than a boolean // so the indicator expires on its own: there is no "stopped typing" event, @@ -295,12 +299,6 @@ export default function ChatThreadPage() { const deleteMessage = useCallback( async (m: ChatMessage, scope: "me" | "everyone") => { - if ( - scope === "everyone" && - !window.confirm("Delete this message for everyone?") - ) { - return; - } try { await chat.deleteMessage(id, m.id, scope); if (scope === "me") { @@ -695,7 +693,8 @@ export default function ChatThreadPage() { // Only the sender may erase a message for everyone. onDeleteForEveryone: menu.message.sender_id === user.id - ? () => void deleteMessage(menu.message, "everyone") + ? // Ask first — the modal calls deleteMessage on confirm. + () => setConfirmDelete(menu.message) : undefined, // Reporting your own message is meaningless. onReport: @@ -714,6 +713,17 @@ export default function ChatThreadPage() { /> )} + setConfirmDelete(null)} + title="Delete for everyone?" + message="This removes the message from the conversation for all participants. It can't be undone." + confirmLabel="Delete" + onConfirm={async () => { + if (confirmDelete) await deleteMessage(confirmDelete, "everyone"); + }} + /> + {forwarding && ( ([]); const ref = useRef(null); const fileRef = useRef(null); - const [pickingSticker, setPickingSticker] = useState(false); + // Non-null opens the sticker picker AND positions it: the popover hangs + // off the button's viewport rect, which only exists at click time. + const [stickerAnchor, setStickerAnchor] = useState(null); const lookupSeq = useRef(0); // Grow with the content, up to a cap. @@ -248,61 +253,84 @@ export function ChatComposer({ e.target.value = ""; }} /> - - - -