diff --git a/apps/mail/components/mail/mail-list.tsx b/apps/mail/components/mail/mail-list.tsx index 675120095c..a04f6eee2e 100644 --- a/apps/mail/components/mail/mail-list.tsx +++ b/apps/mail/components/mail/mail-list.tsx @@ -5,6 +5,7 @@ import { AlertTriangle, Bell, Briefcase, StickyNote, Tag, User, Users } from 'lu import { type ComponentProps, memo, useCallback, useEffect, useMemo, useRef } from 'react'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { EmptyState, type FolderType } from '@/components/mail/empty-state'; +import { useParams, useRouter, useSearchParams } from 'next/navigation'; import { preloadThread, useThreads } from '@/hooks/use-threads'; import { cn, defaultPageSize, formatDate } from '@/lib/utils'; import { useHotKey, useKeyState } from '@/hooks/use-hot-key'; @@ -15,7 +16,6 @@ import { useMail } from '@/components/mail/use-mail'; import type { VirtuosoHandle } from 'react-virtuoso'; import { useSession } from '@/lib/auth-client'; import { Badge } from '@/components/ui/badge'; -import { useParams } from 'next/navigation'; import { useTranslations } from 'next-intl'; import { Virtuoso } from 'react-virtuoso'; import items from './demo.json'; @@ -48,13 +48,16 @@ const Thread = memo( const [mail] = useMail(); const [searchValue] = useSearchValue(); const t = useTranslations(); + const searchParams = useSearchParams(); + const threadIdParam = searchParams.get('threadId'); const hoverTimeoutRef = useRef | undefined>(undefined); const isHovering = useRef(false); const hasPrefetched = useRef(false); const isMailSelected = useMemo(() => { - return message.id === mail.selected; - }, [message.id, mail.selected]); + const threadId = message.threadId ?? message.id; + return threadId === threadIdParam; + }, [message.id, message.threadId, threadIdParam]); const isMailBulkSelected = mail.bulkSelected.includes(message.id); @@ -136,7 +139,7 @@ const Thread = memo( 'text-md flex items-baseline gap-1 group-hover:opacity-100', )} > - + {highlightText(message.sender.name, searchValue.highlight)} {' '} {message.unread ? : null} @@ -204,6 +207,9 @@ export const MailList = memo(({ isCompact }: MailListProps) => { const [mail, setMail] = useMail(); const { data: session } = useSession(); const t = useTranslations(); + const router = useRouter(); + const searchParams = useSearchParams(); + const threadIdParam = searchParams.get('threadId'); const sessionData = useMemo( () => ({ @@ -351,7 +357,7 @@ export const MailList = memo(({ isCompact }: MailListProps) => { if (selectMode === 'range') { const lastSelectedItem = - mail.bulkSelected[mail.bulkSelected.length - 1] ?? mail.selected ?? message.id; + mail.bulkSelected[mail.bulkSelected.length - 1] ?? threadIdParam ?? message.id; const mailsIndex = items.map((m) => m.id); const startIdx = mailsIndex.indexOf(lastSelectedItem); @@ -380,25 +386,38 @@ export const MailList = memo(({ isCompact }: MailListProps) => { return; } - if (mail.selected === message.threadId || mail.selected === message.id) { - setMail({ - selected: null, + const threadId = message.threadId ?? message.id; + const currentParams = new URLSearchParams(searchParams.toString()); + + if (threadIdParam === threadId) { + // Deselect the thread and update URL to remove threadId + currentParams.delete('threadId'); + setMail((prev) => ({ + ...prev, bulkSelected: [], - }); + })); + + // Update URL to remove threadId + router.push(`/mail/${folder}?${currentParams.toString()}`); } else { - setMail({ - ...mail, - selected: message.threadId ?? message.id, + // Select the thread and update URL with threadId + currentParams.set('threadId', threadId); + setMail((prev) => ({ + ...prev, bulkSelected: [], - }); + })); + + // Update URL with threadId + router.push(`/mail/${folder}?${currentParams.toString()}`); } + if (message.unread) { return markAsRead({ ids: [message.id] }) .then(() => mutate()) .catch(console.error); } }, - [mail, setMail, items, getSelectMode], + [mail, setMail, items, getSelectMode, router, searchParams, folder], ); const isEmpty = items.length === 0; diff --git a/apps/mail/components/mail/mail.tsx b/apps/mail/components/mail/mail.tsx index aaab5780ed..f56da923a6 100644 --- a/apps/mail/components/mail/mail.tsx +++ b/apps/mail/components/mail/mail.tsx @@ -35,12 +35,21 @@ export function DemoMailLayout() { const isValidating = false; const isLoading = false; const isDesktop = true; + const searchParams = useSearchParams(); + const threadIdParam = searchParams?.get('threadId'); const [open, setOpen] = useState(false); const handleClose = () => setOpen(false); const [activeCategory, setActiveCategory] = useState('Primary'); const [filteredItems, setFilteredItems] = useState(items); + // Ensure thread display is open when threadId is in URL parameters + useEffect(() => { + if (threadIdParam) { + setOpen(true); + } + }, [threadIdParam]); + useEffect(() => { if (activeCategory === 'Primary') { setFilteredItems(items); @@ -148,7 +157,7 @@ export function DemoMailLayout() {
- +
@@ -197,18 +206,24 @@ export function MailLayout() { return () => window.removeEventListener('resize', checkIsMobile); }, []); + const searchParams = useSearchParams(); + const threadIdParam = searchParams.get('threadId'); + useEffect(() => { - if (mail.selected) { + if (threadIdParam) { setOpen(true); } else { setOpen(false); } - }, [mail.selected]); + }, [threadIdParam]); const handleClose = useCallback(() => { setOpen(false); - setMail((mail) => ({ ...mail, selected: null })); - }, [setMail]); + // Update URL to remove threadId parameter + const currentParams = new URLSearchParams(searchParams.toString()); + currentParams.delete('threadId'); + router.push(`/mail/${folder}?${currentParams.toString()}`); + }, [router, folder, searchParams]); useHotKey('/', () => { setSearchMode(true); @@ -232,10 +247,7 @@ export function MailLayout() { className="rounded-inherit gap-1.5 overflow-hidden" > @@ -292,7 +304,7 @@ export function MailLayout() { ) : ( <>
- +