diff --git a/apps/mail/components/draft/drafts-list.tsx b/apps/mail/components/draft/drafts-list.tsx index 4b3be850a..face800b6 100644 --- a/apps/mail/components/draft/drafts-list.tsx +++ b/apps/mail/components/draft/drafts-list.tsx @@ -1,9 +1,8 @@ "use client"; -import { ComponentProps, useCallback, useEffect, useRef, useState } from "react"; -import { AlertTriangle, Bell, Briefcase, Tag, User, Users } from "lucide-react"; +import { InitialThread, ThreadProps, MailListProps, MailSelectMode } from "@/types"; import { EmptyState, type FolderType } from "@/components/mail/empty-state"; -import { preloadThread, useThreads } from "@/hooks/use-threads"; +import { useCallback, useEffect, useRef, useState } from "react"; import { cn, defaultPageSize, formatDate } from "@/lib/utils"; import { useSearchValue } from "@/hooks/use-search-value"; import { markAsRead, markAsUnread } from "@/actions/mail"; @@ -13,25 +12,9 @@ import { useMail } from "@/components/mail/use-mail"; import { useHotKey } from "@/hooks/use-hot-key"; import { useDrafts } from "@/hooks/use-drafts"; import { useSession } from "@/lib/auth-client"; -import { Badge } from "@/components/ui/badge"; import { useRouter } from "next/navigation"; -import { InitialThread } from "@/types"; import { toast } from "sonner"; -interface DraftsListProps { - isCompact?: boolean; -} - -type MailSelectMode = "mass" | "range" | "single" | "selectAllBelow"; - -type ThreadProps = { - message: InitialThread; - selectMode: MailSelectMode; - onClick?: (message: InitialThread) => () => Promise | undefined; - isCompact?: boolean; - demo?: boolean; -}; - const highlightText = (text: string, highlight: string) => { if (!highlight?.trim()) return text; @@ -52,7 +35,7 @@ const highlightText = (text: string, highlight: string) => { }); }; -const Thread = ({ message, selectMode, demo, onClick }: ThreadProps) => { +const Draft = ({ message, onClick }: ThreadProps) => { const [mail] = useMail(); const [searchValue] = useSearchValue(); @@ -67,7 +50,6 @@ const Thread = ({ message, selectMode, demo, onClick }: ThreadProps) => { "hover:bg-offsetLight hover:bg-primary/5 group relative flex cursor-pointer flex-col items-start overflow-clip rounded-lg border border-transparent px-4 py-3 text-left text-sm transition-all hover:opacity-100", !message.unread && "opacity-50", (isMailSelected || isMailBulkSelected) && "border-border bg-primary/5 opacity-100", - // isCompact && "py-2", )} >
{ ); }; -export function DraftsList({ isCompact }: DraftsListProps) { +export function DraftsList({ isCompact }: MailListProps) { const [mail, setMail] = useMail(); const { data: session } = useSession(); const [searchValue] = useSearchValue(); @@ -418,10 +400,10 @@ export function DraftsList({ isCompact }: DraftsListProps) { }} className="absolute left-0 top-0 w-full p-[8px]" > - {virtualItems.map(({ index, key }) => { + {virtualItems.map(({ index }) => { const item = items[index]; return item ? ( - ); } - -function MailLabels({ labels }: { labels: string[] }) { - if (!labels.length) return null; - - const visibleLabels = labels.filter( - (label) => !["unread", "inbox"].includes(label.toLowerCase()), - ); - - if (!visibleLabels.length) return null; - - return ( -
- {visibleLabels.map((label) => { - const style = getDefaultBadgeStyle(label); - // Skip rendering if style is "secondary" (default case) - if (style === "secondary") return null; - - return ( - - {getLabelIcon(label)} - - ); - })} -
- ); -} - -function getLabelIcon(label: string) { - const normalizedLabel = label.toLowerCase().replace(/^category_/i, ""); - - switch (normalizedLabel) { - case "important": - return ; - case "promotions": - return ; - case "personal": - return ; - case "updates": - return ; - case "work": - return ; - case "forums": - return ; - default: - return null; - } -} - -function getDefaultBadgeStyle(label: string): ComponentProps["variant"] { - const normalizedLabel = label.toLowerCase().replace(/^category_/i, ""); - - switch (normalizedLabel) { - case "important": - return "important"; - case "promotions": - return "promotions"; - case "personal": - return "personal"; - case "updates": - return "updates"; - case "work": - return "default"; - case "forums": - return "forums"; - default: - return "secondary"; - } -} diff --git a/apps/mail/components/mail/mail-list.tsx b/apps/mail/components/mail/mail-list.tsx index 8e7847f69..6b6cb5307 100644 --- a/apps/mail/components/mail/mail-list.tsx +++ b/apps/mail/components/mail/mail-list.tsx @@ -1,43 +1,43 @@ "use client"; -import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { ComponentProps, memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { InitialThread, ThreadProps, MailSelectMode, MailListProps } from "@/types"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { AlertTriangle, Tag, User, Bell, Briefcase, Users } from "lucide-react"; import { EmptyState, type FolderType } from "@/components/mail/empty-state"; import { preloadThread, useThreads } from "@/hooks/use-threads"; +import { ThreadContextMenu } from "../context/thread-context"; +import { cn, defaultPageSize, formatDate } from "@/lib/utils"; import { useSearchValue } from "@/hooks/use-search-value"; import { markAsRead, markAsUnread } from "@/actions/mail"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useVirtualizer } from "@tanstack/react-virtual"; import { useMail } from "@/components/mail/use-mail"; +import { useSummary } from "@/hooks/use-summary"; import { useHotKey } from "@/hooks/use-hot-key"; import { useSession } from "@/lib/auth-client"; import { Badge } from "@/components/ui/badge"; -import { cn, defaultPageSize, formatDate } from "@/lib/utils"; -import { InitialThread } from "@/types"; +import { useParams } from "next/navigation"; import { useTheme } from "next-themes"; +import items from "./demo.json"; import Image from "next/image"; import { toast } from "sonner"; -import { ThreadContextMenu } from "../context/thread-context"; -import { useParams } from "next/navigation"; -import { useSummary } from "@/hooks/use-summary"; -import { AlertTriangle, Tag, User, Bell, Briefcase, Users } from "lucide-react"; -import items from './demo.json' -interface MailListProps { - isCompact?: boolean; -} +// interface MailListProps { +// isCompact?: boolean; +// } const HOVER_DELAY = 1000; // ms before prefetching -type MailSelectMode = "mass" | "range" | "single" | "selectAllBelow"; +// type MailSelectMode = "mass" | "range" | "single" | "selectAllBelow"; -type ThreadProps = { - message: InitialThread; - selectMode: MailSelectMode; - onClick?: (message: InitialThread) => () => Promise | undefined; - isCompact?: boolean; - demo?: boolean; -}; +// type ThreadProps = { +// message: InitialThread; +// selectMode: MailSelectMode; +// onClick?: (message: InitialThread) => () => Promise | undefined; +// isCompact?: boolean; +// demo?: boolean; +// }; const highlightText = (text: string, highlight: string) => { if (!highlight?.trim()) return text; @@ -145,15 +145,14 @@ const Thread = memo(({ message, selectMode, demo, onClick }: ThreadProps) => { {highlightText(message.sender.name, searchValue.highlight)} {" "} - {message.unread ? ( - - ) : null} - + {message.unread ? : null}

{message.totalReplies !== 1 ? ( - {message.totalReplies} - ) : null} + + {message.totalReplies} + + ) : null}
{message.receivedOn ? (

{

{ ); }); - export function MailListDemo({ items: filteredItems = items }) { - return -

-
- {filteredItems.map((item) => { - return item ? ( - - ) : null; - })} + return ( + +
+
+ {filteredItems.map((item) => { + return item ? : null; + })} +
-
- + + ); } export function MailList({ isCompact }: MailListProps) { - const { folder } = useParams<{ folder: string }>() + const { folder } = useParams<{ folder: string }>(); const [mail, setMail] = useMail(); const { data: session } = useSession(); const [searchValue] = useSearchValue(); - const { data: { threads: items, nextPageToken }, mutate, isValidating, isLoading, loadMore } = useThreads(folder, undefined, searchValue.value, defaultPageSize); + const { + data: { threads: items, nextPageToken }, + mutate, + isValidating, + isLoading, + loadMore, + } = useThreads(folder, undefined, searchValue.value, defaultPageSize); const parentRef = useRef(null); const scrollRef = useRef(null); @@ -238,7 +228,7 @@ export function MailList({ isCompact }: MailListProps) { if (scrolledToBottom) { console.log("Loading more items..."); - await loadMore() + await loadMore(); } }, [isLoading, isValidating, nextPageToken, itemHeight], @@ -425,63 +415,68 @@ export function MailList({ isCompact }: MailListProps) { ? "selectAllBelow" : "single"; - const handleMailClick = useCallback((message: InitialThread) => () => { - if (selectMode === "mass") { - const updatedBulkSelected = mail.bulkSelected.includes(message.id) - ? mail.bulkSelected.filter((id) => id !== message.id) - : [...mail.bulkSelected, message.id]; + const handleMailClick = useCallback( + (message: InitialThread) => () => { + if (selectMode === "mass") { + const updatedBulkSelected = mail.bulkSelected.includes(message.id) + ? mail.bulkSelected.filter((id) => id !== message.id) + : [...mail.bulkSelected, message.id]; - setMail({ ...mail, bulkSelected: updatedBulkSelected }); - return; - } - - if (selectMode === "range") { - const lastSelectedItem = - mail.bulkSelected[mail.bulkSelected.length - 1] ?? mail.selected ?? message.id; - - const mailsIndex = items.map((m) => m.id); - const startIdx = mailsIndex.indexOf(lastSelectedItem); - const endIdx = mailsIndex.indexOf(message.id); - - if (startIdx !== -1 && endIdx !== -1) { - const selectedRange = mailsIndex.slice( - Math.min(startIdx, endIdx), - Math.max(startIdx, endIdx) + 1, - ); - - setMail({ ...mail, bulkSelected: selectedRange }); + setMail({ ...mail, bulkSelected: updatedBulkSelected }); + return; } - return; - } - if (selectMode === "selectAllBelow") { - const mailsIndex = items.map((m) => m.id); - const startIdx = mailsIndex.indexOf(message.id); + if (selectMode === "range") { + const lastSelectedItem = + mail.bulkSelected[mail.bulkSelected.length - 1] ?? mail.selected ?? message.id; - if (startIdx !== -1) { - const selectedRange = mailsIndex.slice(startIdx); + const mailsIndex = items.map((m) => m.id); + const startIdx = mailsIndex.indexOf(lastSelectedItem); + const endIdx = mailsIndex.indexOf(message.id); - setMail({ ...mail, bulkSelected: selectedRange }); + if (startIdx !== -1 && endIdx !== -1) { + const selectedRange = mailsIndex.slice( + Math.min(startIdx, endIdx), + Math.max(startIdx, endIdx) + 1, + ); + + setMail({ ...mail, bulkSelected: selectedRange }); + } + return; } - return; - } - if (mail.selected === message.threadId || mail.selected === message.id) { - setMail({ - selected: null, - bulkSelected: [], - }); - } else { - setMail({ - ...mail, - selected: message.threadId ?? message.id, - bulkSelected: [], - }); - } - if (message.unread) { - return markAsRead({ ids: [message.id] }).then(() => mutate() as any).catch(console.error); - } - }, [mail, setMail, selectMode]); + if (selectMode === "selectAllBelow") { + const mailsIndex = items.map((m) => m.id); + const startIdx = mailsIndex.indexOf(message.id); + + if (startIdx !== -1) { + const selectedRange = mailsIndex.slice(startIdx); + + setMail({ ...mail, bulkSelected: selectedRange }); + } + return; + } + + if (mail.selected === message.threadId || mail.selected === message.id) { + setMail({ + selected: null, + bulkSelected: [], + }); + } else { + setMail({ + ...mail, + selected: message.threadId ?? message.id, + bulkSelected: [], + }); + } + if (message.unread) { + return markAsRead({ ids: [message.id] }) + .then(() => mutate() as any) + .catch(console.error); + } + }, + [mail, setMail, selectMode], + ); const isEmpty = items.length === 0; const isFiltering = searchValue.value.trim().length > 0; @@ -508,22 +503,27 @@ export function MailList({ isCompact }: MailListProps) { )} style={{ height: `${virtualizer.getTotalSize()}px`, - willChange: "transform", contain: 'paint' + willChange: "transform", + contain: "paint", }} >
{virtualItems.map(({ index, key }) => { const item = items[index]; return item ? ( - ) : null; })} @@ -542,33 +542,36 @@ export function MailList({ isCompact }: MailListProps) { ); } -const MailLabels = memo(({ labels }: { labels: string[] }) => { - if (!labels.length) return null; +const MailLabels = memo( + ({ labels }: { labels: string[] }) => { + if (!labels.length) return null; - const visibleLabels = labels.filter( - (label) => !["unread", "inbox"].includes(label.toLowerCase()), - ); + const visibleLabels = labels.filter( + (label) => !["unread", "inbox"].includes(label.toLowerCase()), + ); - if (!visibleLabels.length) return null; + if (!visibleLabels.length) return null; - return ( -
- {visibleLabels.map((label) => { - const style = getDefaultBadgeStyle(label); - // Skip rendering if style is "secondary" (default case) - if (style === "secondary") return null; - - return ( - - {getLabelIcon(label)} - - ); - })} -
- ); -}, (prev, next) => { - return prev.labels === next.labels; -}); + return ( +
+ {visibleLabels.map((label) => { + const style = getDefaultBadgeStyle(label); + // Skip rendering if style is "secondary" (default case) + if (style === "secondary") return null; + + return ( + + {getLabelIcon(label)} + + ); + })} +
+ ); + }, + (prev, next) => { + return prev.labels === next.labels; + }, +); function getLabelIcon(label: string) { const normalizedLabel = label.toLowerCase().replace(/^category_/i, ""); diff --git a/apps/mail/types/index.ts b/apps/mail/types/index.ts index 13dd05be5..f37725007 100644 --- a/apps/mail/types/index.ts +++ b/apps/mail/types/index.ts @@ -74,3 +74,17 @@ export interface InitialThread { references?: string; inReplyTo?: string; } + +export interface MailListProps { + isCompact?: boolean; +} + +export type MailSelectMode = "mass" | "range" | "single" | "selectAllBelow"; + +export interface ThreadProps { + message: InitialThread; + selectMode: MailSelectMode; + onClick?: (message: InitialThread) => () => Promise | undefined; + isCompact?: boolean; + demo?: boolean; +}