Merge pull request #35 from ArnasDon/fix/dashboard-deep-link-to-conversation

fix(dashboard): open the clicked conversation when deep-linking from dashboard
This commit is contained in:
Arnas Donauskas 2026-04-17 21:20:57 +03:00 committed by GitHub
commit 5943c59193
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 4 deletions

View File

@ -261,7 +261,7 @@ export default function DashboardPage() {
recentConversations.map((conv) => ( recentConversations.map((conv) => (
<button <button
key={conv.id} key={conv.id}
onClick={() => router.push("/inbox")} onClick={() => router.push(`/inbox?c=${conv.id}`)}
className="flex w-full items-center gap-3 px-5 py-3 text-left transition-colors hover:bg-slate-800/50" className="flex w-full items-center gap-3 px-5 py-3 text-left transition-colors hover:bg-slate-800/50"
> >
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-emerald-500/10 text-sm font-medium text-emerald-500"> <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-emerald-500/10 text-sm font-medium text-emerald-500">

View File

@ -1,6 +1,7 @@
"use client"; "use client";
import { useState, useCallback, useEffect } from "react"; import { useState, useCallback, useEffect, useRef } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { createClient } from "@/lib/supabase/client"; import { createClient } from "@/lib/supabase/client";
import type { Conversation, Message, Contact, ConversationStatus } from "@/types"; import type { Conversation, Message, Contact, ConversationStatus } from "@/types";
import { useRealtime } from "@/hooks/use-realtime"; import { useRealtime } from "@/hooks/use-realtime";
@ -11,6 +12,15 @@ import { toast } from "sonner";
import { WifiOff } from "lucide-react"; import { WifiOff } from "lucide-react";
export default function InboxPage() { export default function InboxPage() {
const router = useRouter();
const searchParams = useSearchParams();
/**
* `?c=<id>` deep-link support. Used when landing here from the
* dashboard's recent-conversations list so the right thread opens
* automatically instead of showing the empty center panel.
*/
const deepLinkConvId = searchParams.get("c");
const [conversations, setConversations] = useState<Conversation[]>([]); const [conversations, setConversations] = useState<Conversation[]>([]);
const [activeConversation, setActiveConversation] = const [activeConversation, setActiveConversation] =
useState<Conversation | null>(null); useState<Conversation | null>(null);
@ -20,6 +30,12 @@ export default function InboxPage() {
null null
); );
// Fire the deep-link auto-select exactly once per URL — subsequent
// list refreshes (realtime, manual refetch) must not snap the user
// back to the deep-linked conversation if they've already clicked
// elsewhere.
const autoSelectedForDeepLinkRef = useRef<string | null>(null);
// Check WhatsApp connection status on mount // Check WhatsApp connection status on mount
useEffect(() => { useEffect(() => {
const checkConnection = async () => { const checkConnection = async () => {
@ -135,8 +151,26 @@ export default function InboxPage() {
const handleConversationsLoaded = useCallback( const handleConversationsLoaded = useCallback(
(loaded: Conversation[]) => { (loaded: Conversation[]) => {
setConversations(loaded); setConversations(loaded);
// Resolve a pending deep-link here rather than in an effect — this
// is an event handler, so the setState calls below are allowed by
// react-hooks/set-state-in-effect. Runs once per ?c=<id> URL value
// via the ref, so realtime refreshes of the list can't snap the
// user back to the deep-linked thread after they've navigated.
if (
deepLinkConvId &&
autoSelectedForDeepLinkRef.current !== deepLinkConvId &&
loaded.length > 0
) {
autoSelectedForDeepLinkRef.current = deepLinkConvId;
const match = loaded.find((c) => c.id === deepLinkConvId);
if (match) {
setActiveConversation(match);
setActiveContact(match.contact ?? null);
setMessages([]);
}
}
}, },
[] [deepLinkConvId]
); );
const handleSelectConversation = useCallback( const handleSelectConversation = useCallback(
@ -149,10 +183,15 @@ export default function InboxPage() {
setActiveConversation(conv); setActiveConversation(conv);
setActiveContact(conv.contact ?? null); setActiveContact(conv.contact ?? null);
setMessages([]); setMessages([]);
// Reflect the selection in the URL so a refresh lands the user
// back in the same thread, and so copy-paste links work. Use
// replace() to avoid polluting browser history with every click.
router.replace(`/inbox?c=${conv.id}`, { scroll: false });
}, },
[activeConversation?.id] [activeConversation?.id, router]
); );
const handleMessagesLoaded = useCallback((loaded: Message[]) => { const handleMessagesLoaded = useCallback((loaded: Message[]) => {
setMessages(loaded); setMessages(loaded);
}, []); }, []);