diff --git a/apps/mail/components/create/create-email.tsx b/apps/mail/components/create/create-email.tsx index b2ce0aeb5f..f70e06cfc1 100644 --- a/apps/mail/components/create/create-email.tsx +++ b/apps/mail/components/create/create-email.tsx @@ -1,14 +1,19 @@ 'use client'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { UploadedFileIcon } from '@/components/create/uploaded-file-icon'; import { generateHTML, generateJSON } from '@tiptap/core'; import { useConnections } from '@/hooks/use-connections'; import { createDraft, getDraft } from '@/actions/drafts'; import { ArrowUpIcon, Paperclip, X } from 'lucide-react'; +import { Separator } from '@/components/ui/separator'; import { SidebarToggle } from '../ui/sidebar-toggle'; import Paragraph from '@tiptap/extension-paragraph'; import { useSettings } from '@/hooks/use-settings'; import Document from '@tiptap/extension-document'; import { Button } from '@/components/ui/button'; import { useSession } from '@/lib/auth-client'; +import { truncateFileName } from '@/lib/utils'; +import { Input } from '@/components/ui/input'; import { AIAssistant } from './ai-assistant'; import { useTranslations } from 'next-intl'; import { sendEmail } from '@/actions/send'; @@ -16,16 +21,11 @@ import Text from '@tiptap/extension-text'; import Bold from '@tiptap/extension-bold'; import { type JSONContent } from 'novel'; import { useQueryState } from 'nuqs'; +import { Plus } from 'lucide-react'; import { toast } from 'sonner'; import * as React from 'react'; import Editor from './editor'; import './prosemirror.css'; -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; -import { UploadedFileIcon } from '@/components/create/uploaded-file-icon'; -import { Separator } from '@/components/ui/separator'; -import { Input } from '@/components/ui/input'; -import { Plus } from 'lucide-react'; -import { truncateFileName } from '@/lib/utils'; const MAX_VISIBLE_ATTACHMENTS = 12; @@ -272,8 +272,12 @@ export function CreateEmail({ setIsLoading(true); await sendEmail({ to: toEmails.map((email) => ({ email, name: email.split('@')[0] || email })), - cc: showCc ? ccEmails.map((email) => ({ email, name: email.split('@')[0] || email })) : undefined, - bcc: showBcc ? bccEmails.map((email) => ({ email, name: email.split('@')[0] || email })) : undefined, + cc: showCc + ? ccEmails.map((email) => ({ email, name: email.split('@')[0] || email })) + : undefined, + bcc: showBcc + ? bccEmails.map((email) => ({ email, name: email.split('@')[0] || email })) + : undefined, subject: subjectInput, message: messageContent, attachments: attachments, @@ -442,7 +446,7 @@ export function CreateEmail({ value={toInput} onChange={(e) => handleEmailInputChange('to', e.target.value)} onKeyDown={(e) => { - if (e.key === 'Enter') { + if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleAddEmail('to', toInput); } @@ -519,7 +523,7 @@ export function CreateEmail({ value={ccInput} onChange={(e) => handleEmailInputChange('cc', e.target.value)} onKeyDown={(e) => { - if (e.key === 'Enter') { + if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleAddEmail('cc', ccInput); } @@ -565,7 +569,7 @@ export function CreateEmail({ value={bccInput} onChange={(e) => handleEmailInputChange('bcc', e.target.value)} onKeyDown={(e) => { - if (e.key === 'Enter') { + if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleAddEmail('bcc', bccInput); } @@ -683,67 +687,74 @@ export function CreateEmail({ }} /> - - - - - -
-
-

- {t('common.replyCompose.attachments')} -

-

+ {attachments.length > 0 && ( + + +

- -
-
- {attachments.map((file, index) => ( -
- -
-

- {truncateFileName(file.name, 20)} -

-

- {(file.size / (1024 * 1024)).toFixed(2)} MB -

+ {t('common.replyCompose.attachmentCount', { count: attachments.length })} + + + + +
+
+

+ {t('common.replyCompose.attachments')} +

+

+ {attachments.length}{' '} + {t('common.replyCompose.fileCount', { count: attachments.length })} +

+
+ +
+
+ {attachments.map((file, index) => ( +
+ +
+

+ {truncateFileName(file.name, 20)} +

+

+ {(file.size / (1024 * 1024)).toFixed(2)} MB +

+
-
- ))} + ))} +
-
- - -
- - + + + )} +
+ + +
diff --git a/apps/mail/components/draft/drafts-list.tsx b/apps/mail/components/draft/drafts-list.tsx index 1301b513c6..7e69571006 100644 --- a/apps/mail/components/draft/drafts-list.tsx +++ b/apps/mail/components/draft/drafts-list.tsx @@ -17,6 +17,7 @@ import { useRouter } from 'next/navigation'; import { useTranslations } from 'use-intl'; import { Button } from '../ui/button'; import { toast } from 'sonner'; +import { ChevronDown } from 'lucide-react'; const Draft = ({ message, onClick }: ThreadProps) => { const [mail] = useMail(); @@ -379,10 +380,28 @@ export function DraftsList({ isCompact }: MailListProps) { {items.map((item, index) => { return rowRenderer({ index, data: item }); })} + {items.length >= 9 && nextPageToken && ( + + )}
- -
+
{isLoading || isValidating ? (
diff --git a/apps/mail/components/mail/mail-list.tsx b/apps/mail/components/mail/mail-list.tsx index 869de003e0..4587815065 100644 --- a/apps/mail/components/mail/mail-list.tsx +++ b/apps/mail/components/mail/mail-list.tsx @@ -423,7 +423,7 @@ export const MailList = memo(({ isCompact }: MailListProps) => { folder: '', }); } - }, [allCategories]); // Run only once on mount + }, [allCategories, category, shouldFilter, searchValue.value, setSearchValue]); // Add event listener for refresh useEffect(() => { diff --git a/apps/mail/components/mail/mail.tsx b/apps/mail/components/mail/mail.tsx index eaa8eaeed0..58202d5361 100644 --- a/apps/mail/components/mail/mail.tsx +++ b/apps/mail/components/mail/mail.tsx @@ -84,11 +84,11 @@ export function DemoMailLayout() { }, []); useEffect(() => { - if (activeCategory === 'Primary' || activeCategory === 'All Mail') { + if (activeCategory === 'All Mail') { setFilteredItems(items); } else { const categoryMap = { - Important: 'important', + Primary: 'important', Personal: 'personal', Updates: 'updates', Promotions: 'promotions', @@ -723,15 +723,6 @@ function MailCategoryTabs({ // Initialize with just the initialCategory or "Primary" const [activeCategory, setActiveCategory] = useState(initialCategory || 'Primary'); - // Move localStorage logic to a useEffect - useEffect(() => { - // Check localStorage only after initial render - const savedCategory = localStorage.getItem('mailActiveCategory'); - if (savedCategory) { - setActiveCategory(savedCategory); - } - }, [initialCategory]); - const containerRef = useRef(null); const activeTabElementRef = useRef(null); @@ -757,6 +748,17 @@ function MailCategoryTabs({ } }, [activeCategory, setSearchValue, isLoading]); + // Cleanup on unmount + useEffect(() => { + return () => { + setSearchValue({ + value: '', + highlight: '', + folder: '', + }); + }; + }, [setSearchValue]); + // Function to update clip path const updateClipPath = useCallback(() => { const container = containerRef.current; @@ -811,7 +813,6 @@ function MailCategoryTabs({ data-tab={category.id} onClick={() => { setActiveCategory(category.id); - localStorage.setItem('mailActiveCategory', category.id); }} className={cn( 'flex h-7 items-center gap-1.5 rounded-full px-2 text-xs font-medium transition-all duration-200', diff --git a/apps/mail/components/mail/reply-composer.tsx b/apps/mail/components/mail/reply-composer.tsx index 995b67ba7e..92f23a95cb 100644 --- a/apps/mail/components/mail/reply-composer.tsx +++ b/apps/mail/components/mail/reply-composer.tsx @@ -1150,67 +1150,79 @@ export default function ReplyCompose({ mode = 'reply' }: ReplyComposeProps) {
)} - - - - - -
-
-

- {t('common.replyCompose.attachments')} -

-

+ {/* Conditionally render the Popover only if attachments exist */} + {attachments.length > 0 && ( + + +

- -
-
- {attachments.map((file, index) => ( -
- -
-

- {truncateFileName(file.name, 20)} -

-

- {(file.size / (1024 * 1024)).toFixed(2)} MB -

+ {t('common.replyCompose.attachmentCount', { count: attachments.length })} + + + + +
+
+

+ {t('common.replyCompose.attachments')} +

+

+ {attachments.length}{' '} + {t('common.replyCompose.fileCount', { count: attachments.length })} +

+
+ +
+
+ {attachments.map((file, index) => ( +
+ +
+

+ {truncateFileName(file.name, 20)} +

+

+ {(file.size / (1024 * 1024)).toFixed(2)} MB +

+
-
- ))} + ))} +
-
- - -
- - + + + )} + {/* The Plus button is always visible, wrapped in a label for better click handling */} +
+
diff --git a/apps/mail/components/mail/thread-display.tsx b/apps/mail/components/mail/thread-display.tsx index d6d1ce61de..d476b10028 100644 --- a/apps/mail/components/mail/thread-display.tsx +++ b/apps/mail/components/mail/thread-display.tsx @@ -30,6 +30,7 @@ import MailDisplay from './mail-display'; import { ParsedMessage } from '@/types'; import { Inbox } from 'lucide-react'; import { toast } from 'sonner'; +import { NotesPanel } from './note-panel'; interface ThreadDisplayProps { @@ -327,8 +328,7 @@ export function ThreadDisplay({ threadParam, onClose, isMobile, id }: ThreadDisp
- {/* disable notes for now, it's still a bit buggy and not ready for prod. */} - {/* */} + { posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY as string, { api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST, @@ -13,5 +16,14 @@ export function PostHogProvider({ children }: { children: React.ReactNode }) { }); }, []); + useEffect(() => { + if (session?.user) { + posthog.identify(session.user.id, { + email: session.user.email, + name: session.user.name, + }); + } + }, [session]); + return {children}; } diff --git a/apps/mail/lib/providers.tsx b/apps/mail/lib/providers.tsx index db91dcee69..f34bb31724 100644 --- a/apps/mail/lib/providers.tsx +++ b/apps/mail/lib/providers.tsx @@ -5,6 +5,8 @@ import { AISidebarProvider } from '@/components/ui/ai-sidebar'; import { SidebarProvider } from '@/components/ui/sidebar'; import { NuqsAdapter } from 'nuqs/adapters/next/app'; import { Provider as JotaiProvider } from 'jotai'; +import { PostHogProvider } from './posthog-provider'; + export function Providers({ children, ...props }: React.ComponentProps) { return ( @@ -12,7 +14,9 @@ export function Providers({ children, ...props }: React.ComponentProps - {children} + + {children} +