Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 33 additions & 14 deletions apps/mail/components/mail/mail-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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<ReturnType<typeof setTimeout> | undefined>(undefined);
const isHovering = useRef<boolean>(false);
const hasPrefetched = useRef<boolean>(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);

Expand Down Expand Up @@ -136,7 +139,7 @@ const Thread = memo(
'text-md flex items-baseline gap-1 group-hover:opacity-100',
)}
>
<span className={cn(mail.selected && 'max-w-[120px] truncate')}>
<span className={cn(threadIdParam && 'max-w-[120px] truncate')}>
{highlightText(message.sender.name, searchValue.highlight)}
</span>{' '}
{message.unread ? <span className="size-2 rounded bg-[#006FFE]" /> : null}
Expand Down Expand Up @@ -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(
() => ({
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
38 changes: 25 additions & 13 deletions apps/mail/components/mail/mail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -148,7 +157,7 @@ export function DemoMailLayout() {
</DrawerHeader>
<div className="flex h-full flex-col overflow-hidden">
<div className="flex-1 overflow-hidden">
<ThreadDisplay mail={mail.selected} onClose={handleClose} isMobile={true} />
<ThreadDisplay onClose={handleClose} isMobile={true} />
</div>
</div>
</DrawerContent>
Expand Down Expand Up @@ -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);
Expand All @@ -232,10 +247,7 @@ export function MailLayout() {
className="rounded-inherit gap-1.5 overflow-hidden"
>
<ResizablePanel
className={cn(
'border-none !bg-transparent',
mail?.selected ? 'md:hidden lg:block' : '',
)}
className={cn('border-none !bg-transparent', threadIdParam ? 'md:hidden lg:block' : '')}
defaultSize={isMobile ? 100 : 25}
minSize={isMobile ? 100 : 25}
>
Expand Down Expand Up @@ -292,7 +304,7 @@ export function MailLayout() {
) : (
<>
<div className="flex-1 text-center text-sm font-medium capitalize">
<MailCategoryTabs iconsOnly={!!mail.selected} />
<MailCategoryTabs iconsOnly={!!threadIdParam} />
</div>
<div className="flex items-center gap-1.5">
<Button
Expand Down Expand Up @@ -337,15 +349,15 @@ export function MailLayout() {
</div>
</ResizablePanel>

{isDesktop && mail.selected && (
{isDesktop && threadIdParam && (
<>
<ResizablePanel
className="bg-offsetLight dark:bg-offsetDark shadow-sm md:flex md:rounded-2xl md:border md:shadow-sm"
defaultSize={75}
minSize={25}
>
<div className="relative hidden h-[calc(100vh-(12px+14px))] flex-1 md:block">
<ThreadDisplay mail={mail.selected} onClose={handleClose} />
<ThreadDisplay onClose={handleClose} />
</div>
</ResizablePanel>
</>
Expand All @@ -361,7 +373,7 @@ export function MailLayout() {
</DrawerHeader>
<div className="flex h-full flex-col overflow-hidden">
<div className="flex-1 overflow-hidden">
<ThreadDisplay mail={mail.selected} onClose={handleClose} isMobile={true} />
<ThreadDisplay onClose={handleClose} isMobile={true} />
</div>
</div>
</DrawerContent>
Expand Down
10 changes: 7 additions & 3 deletions apps/mail/components/mail/thread-display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DropdownMenu, DropdownMenuTrigger } from '@/components/ui/dropdown-menu
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { ArchiveX, Forward, ReplyAll } from 'lucide-react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { useSearchParams } from 'next/navigation';

import { MoreVerticalIcon } from '../icons/animated/more-vertical';
import { useState, useEffect, useCallback, useRef } from 'react';
Expand Down Expand Up @@ -185,7 +186,11 @@ function ThreadActionButton({

export function ThreadDisplay({ mail, onClose, isMobile }: ThreadDisplayProps) {
const [, setMail] = useMail();
const { data: emailData, isLoading } = useThread(mail ?? '');
const searchParams = useSearchParams();
const threadIdParam = searchParams.get('threadId');
const threadId = mail ?? threadIdParam ?? '';
// Only fetch thread data if we have a valid threadId
const { data: emailData, isLoading } = useThread(threadId || null);
const [isMuted, setIsMuted] = useState(false);
const [isReplyOpen, setIsReplyOpen] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
Expand All @@ -201,8 +206,7 @@ export function ThreadDisplay({ mail, onClose, isMobile }: ThreadDisplayProps) {

const handleClose = useCallback(() => {
onClose?.();
setMail((m) => ({ ...m, selected: null }));
}, [onClose, setMail]);
}, [onClose]);

useEffect(() => {
const handleEsc = (event: KeyboardEvent) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/mail/components/mail/thread-subject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function ThreadSubject({ subject, isMobile }: ThreadSubjectProps)
<span
ref={textRef}
className={cn(
'line-clamp-1 block cursor-pointer font-semibold max-w-[30ch] truncate',
'line-clamp-1 block max-w-[30ch] cursor-pointer truncate font-semibold',
!subject && 'opacity-50',
)}
>
Expand Down
4 changes: 2 additions & 2 deletions apps/mail/hooks/use-threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,11 @@ export const useThreads = (folder: string, labelIds?: string[], query?: string,
};
};

export const useThread = (id: string) => {
export const useThread = (id: string | null) => {
const { data: session } = useSession();

const { data, isLoading, error, mutate } = useSWR<ParsedMessage[]>(
session?.user.id ? [session.user.id, id, session.connectionId] : null,
session?.user.id && id ? [session.user.id, id, session.connectionId] : null,
fetchThread as any,
);

Expand Down