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
147 changes: 79 additions & 68 deletions apps/mail/components/create/create-email.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
'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';
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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -683,67 +687,74 @@ export function CreateEmail({
}}
/>
</div>
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="flex items-center gap-2">
<Paperclip className="h-4 w-4" />
<span>
{attachments.length || 'no'}{' '}
{t('common.replyCompose.attachmentCount', { count: attachments.length })}
</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 touch-auto" align="start">
<div className="space-y-2">
<div className="px-1">
<h4 className="font-medium leading-none">
{t('common.replyCompose.attachments')}
</h4>
<p className="text-muted-foreground text-sm">
{attachments.length > 0 && (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" className="flex items-center gap-2">
<Paperclip className="h-4 w-4" />
<span>
{attachments.length}{' '}
{t('common.replyCompose.fileCount', { count: attachments.length })}
</p>
</div>
<Separator />
<div className="h-[300px] touch-auto overflow-y-auto overscroll-contain px-1 py-1">
<div className="grid grid-cols-2 gap-2">
{attachments.map((file, index) => (
<div
key={index}
className="group relative overflow-hidden rounded-md border"
>
<UploadedFileIcon
removeAttachment={removeAttachment}
index={index}
file={file}
/>
<div className="bg-muted/10 p-2">
<p className="text-xs font-medium">
{truncateFileName(file.name, 20)}
</p>
<p className="text-muted-foreground text-xs">
{(file.size / (1024 * 1024)).toFixed(2)} MB
</p>
{t('common.replyCompose.attachmentCount', { count: attachments.length })}
</span>
</Button>
</PopoverTrigger>
<PopoverContent className="w-80 touch-auto" align="start">
<div className="space-y-2">
<div className="px-1">
<h4 className="font-medium leading-none">
{t('common.replyCompose.attachments')}
</h4>
<p className="text-muted-foreground text-sm">
{attachments.length}{' '}
{t('common.replyCompose.fileCount', { count: attachments.length })}
</p>
</div>
<Separator />
<div className="h-[300px] touch-auto overflow-y-auto overscroll-contain px-1 py-1">
<div className="grid grid-cols-2 gap-2">
{attachments.map((file, index) => (
<div
key={index}
className="group relative overflow-hidden rounded-md border"
>
<UploadedFileIcon
removeAttachment={removeAttachment}
index={index}
file={file}
/>
<div className="bg-muted/10 p-2">
<p className="text-xs font-medium">
{truncateFileName(file.name, 20)}
</p>
<p className="text-muted-foreground text-xs">
{(file.size / (1024 * 1024)).toFixed(2)} MB
</p>
</div>
</div>
</div>
))}
))}
</div>
</div>
</div>
</div>
</PopoverContent>
</Popover>
<div className='-left-5 relative group'>
<Input
type="file"
id="attachment-input"
className='w-10 opacity-0'
onChange={handleAttachment}
multiple
accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt"
/>
<Button variant={'outline'} size={'icon'} type='button' className='transition-transform group-hover:scale-90 scale-75 absolute top-0 left-0 rounded-full pointer-events-none'>
<Plus />
</Button>
</PopoverContent>
</Popover>
)}
<div className="-pb-1.5">

<Input
type="file"
id="attachment-input"
className="absolute h-full w-full cursor-pointer opacity-0"
onChange={handleAttachment}
multiple
accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt"
/>
<Button
variant="ghost"
className="rounded-full transition-transform cursor-pointer hover:bg-muted h-8 w-8 -ml-1"
tabIndex={-1}
>
<Plus className='h-4 w-4 cursor-pointer'/>
</Button>
</div>
</div>
<div className="flex justify-end gap-3">
Expand Down
23 changes: 21 additions & 2 deletions apps/mail/components/draft/drafts-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -379,10 +380,28 @@ export function DraftsList({ isCompact }: MailListProps) {
{items.map((item, index) => {
return rowRenderer({ index, data: item });
})}
{items.length >= 9 && nextPageToken && (
<Button
variant={'ghost'}
className="w-full rounded-none"
onMouseDown={handleScroll}
disabled={isLoading || isValidating}
>
{isLoading || isValidating ? (
<div className="flex items-center gap-2">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-neutral-900 border-t-transparent dark:border-white dark:border-t-transparent" />
{t('common.actions.loading')}
</div>
) : (
<>
{t('common.mail.loadMore')} <ChevronDown />
</>
)}
</Button>
)}
</ScrollArea>
</div>
<Button onClick={handleScroll}>{t('common.mail.loadMore')}</Button>
<div className="w-full pt-2 text-center">
<div className="w-full pt-4 text-center">
{isLoading || isValidating ? (
<div className="text-center">
<div className="mx-auto h-4 w-4 animate-spin rounded-full border-2 border-neutral-900 border-t-transparent dark:border-white dark:border-t-transparent" />
Expand Down
2 changes: 1 addition & 1 deletion apps/mail/components/mail/mail-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
25 changes: 13 additions & 12 deletions apps/mail/components/mail/mail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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<HTMLDivElement>(null);
const activeTabElementRef = useRef<HTMLButtonElement>(null);

Expand All @@ -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;
Expand Down Expand Up @@ -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',
Expand Down
Loading