diff --git a/apps/mail/actions/settings.ts b/apps/mail/actions/settings.ts index 7222256c00..2d3aaa2ac6 100644 --- a/apps/mail/actions/settings.ts +++ b/apps/mail/actions/settings.ts @@ -22,7 +22,12 @@ function validateSettings(settings: unknown): UserSettings { export async function saveUserSettings(settings: UserSettings) { try { const userId = await getAuthenticatedUserId(); + console.log(settings, 'before'); + settings = validateSettings(settings); + + console.log(settings, 'after'); + const timestamp = new Date(); const [existingSettings] = await db diff --git a/apps/mail/app/api/driver/google.ts b/apps/mail/app/api/driver/google.ts index 63b4050250..73d39d00cb 100644 --- a/apps/mail/app/api/driver/google.ts +++ b/apps/mail/app/api/driver/google.ts @@ -268,10 +268,20 @@ export const driver = async (config: IConfig): Promise => { } return false; }) - .map((recipient) => ({ - name: recipient.name || '', - addr: recipient.email, - })); + .map((recipient) => { + // Parse the email address from the recipient string + const emailMatch = recipient.email.match(/<([^>]+)>/); + const email = emailMatch ? emailMatch[1] : recipient.email; + // Ensure we have a valid email address + if (!email) { + console.error('Debug - Invalid email address:', recipient.email); + throw new Error('Invalid email address'); + } + return { + name: recipient.name || '', + addr: email, + }; + }); console.log('Debug - Filtered to recipients:', JSON.stringify(toRecipients, null, 2)); diff --git a/apps/mail/components/mail/mail-iframe.tsx b/apps/mail/components/mail/mail-iframe.tsx index 2f4261660b..7875a57883 100644 --- a/apps/mail/components/mail/mail-iframe.tsx +++ b/apps/mail/components/mail/mail-iframe.tsx @@ -6,14 +6,14 @@ import { getBrowserTimezone } from '@/lib/timezones'; import { useSettings } from '@/hooks/use-settings'; import { useTranslations } from 'next-intl'; import { useTheme } from 'next-themes'; +import DOMPurify from 'dompurify'; import { cn } from '@/lib/utils'; import { toast } from 'sonner'; -import DOMPurify from 'dompurify'; export function MailIframe({ html, senderEmail }: { html: string; senderEmail: string }) { const { settings, mutate } = useSettings(); const isTrustedSender = settings?.trustedSenders?.includes(senderEmail); - const [cspViolation, setCspViolation] = useState(false) + const [cspViolation, setCspViolation] = useState(false); const [imagesEnabled, setImagesEnabled] = useState( isTrustedSender || settings?.externalImages || false, ); @@ -21,27 +21,32 @@ export function MailIframe({ html, senderEmail }: { html: string; senderEmail: s const [height, setHeight] = useState(300); const { resolvedTheme } = useTheme(); - const onTrustSender = useCallback(async (senderEmail: string) => { - setImagesEnabled(true); + const onTrustSender = useCallback( + async (senderEmail: string) => { + setImagesEnabled(true); - const existingSettings = settings ?? { - ...defaultUserSettings, - timezone: getBrowserTimezone(), - }; + console.log(settings); - const { success } = await saveUserSettings({ - ...existingSettings, - trustedSenders: settings?.trustedSenders - ? settings.trustedSenders.concat(senderEmail) - : [senderEmail], - }); + const existingSettings = settings ?? { + ...defaultUserSettings, + timezone: getBrowserTimezone(), + }; - if (!success) { - toast.error('Failed to trust sender'); - } else { - mutate(); - } - }, [settings, mutate]); + const { success } = await saveUserSettings({ + ...existingSettings, + trustedSenders: settings?.trustedSenders + ? settings.trustedSenders.concat(senderEmail) + : [senderEmail], + }); + + if (!success) { + toast.error('Failed to trust sender'); + } else { + mutate(); + } + }, + [settings, mutate], + ); const iframeDoc = useMemo(() => template(html, imagesEnabled), [html, imagesEnabled]); @@ -95,7 +100,7 @@ export function MailIframe({ html, senderEmail }: { html: string; senderEmail: s 'message', (event) => { if (event.data.type === 'csp-violation') { - setCspViolation(true) + setCspViolation(true); } }, { signal: ctrl.signal }, @@ -254,7 +259,7 @@ export function DynamicIframe({ const parentStyle = window.getComputedStyle(iframe.parentElement || document.body); const parentColor = parentStyle.color; const parentBg = parentStyle.backgroundColor; - + const styleElement = iframeDoc.createElement('style'); styleElement.textContent = ` body { @@ -265,21 +270,21 @@ export function DynamicIframe({ iframeDoc.head.appendChild(styleElement); } }; - + updateStyles(); // Size adjustment const resizeIframe = () => { if (!iframe.contentWindow || !iframeDoc.body) return; - + const newHeight = iframeDoc.body.scrollHeight; const newWidth = iframeDoc.body.scrollWidth; - + if (newHeight !== height) { setHeight(newHeight); iframe.style.height = `${newHeight}px`; } - + if (newWidth !== width && newWidth > iframe.clientWidth) { setWidth(newWidth); } @@ -289,12 +294,12 @@ export function DynamicIframe({ const resizeObserver = new ResizeObserver(() => { resizeIframe(); }); - + resizeObserver.observe(iframeDoc.body); - + // Additional event listeners for images loading const images = iframeDoc.querySelectorAll('img'); - images.forEach(img => { + images.forEach((img) => { img.addEventListener('load', resizeIframe); img.addEventListener('error', (e) => { console.error('Image failed to load:', e); @@ -304,7 +309,7 @@ export function DynamicIframe({ // Handle link clicks to open in new tab const links = iframeDoc.querySelectorAll('a'); - links.forEach(link => { + links.forEach((link) => { link.setAttribute('target', '_blank'); link.setAttribute('rel', 'noopener noreferrer'); }); @@ -316,7 +321,7 @@ export function DynamicIframe({ // Cleanup return () => { resizeObserver.disconnect(); - images.forEach(img => { + images.forEach((img) => { img.removeEventListener('load', resizeIframe); img.removeEventListener('error', resizeIframe); }); @@ -333,4 +338,4 @@ export function DynamicIframe({ {...props} /> ); -} \ No newline at end of file +} diff --git a/apps/mail/components/mail/mail-list.tsx b/apps/mail/components/mail/mail-list.tsx index fb909968f3..6dbadcaaa1 100644 --- a/apps/mail/components/mail/mail-list.tsx +++ b/apps/mail/components/mail/mail-list.tsx @@ -220,18 +220,22 @@ const Thread = memo( ) : null}

- {Math.random() > 0.5 ? ( - - - - {Math.random() * 10} - - - - {t('common.mail.replies', { count: Math.random() * 10 })} - - - ) : null} + {Math.random() > 0.5 && + (() => { + const count = Math.floor(Math.random() * 10) + 1; + return ( + + + + {count} + + + + {t('common.mail.replies', { count })} + + + ); + })()} {latestMessage.receivedOn ? (

{ if (!emailData || !id) return; const unreadEmails = emailData.messages.filter((e) => e.unread); - if (unreadEmails.length === 0) { - console.log('Marking email as read:', id); - markAsRead({ ids: [id] }) - .catch((error) => { - console.error('Failed to mark email as read:', error); - toast.error(t('common.mail.failedToMarkAsRead')); - }) - .then(() => Promise.all([mutateThread(), mutateStats()])); - } else { - console.log('Marking email as read:', id, ...unreadEmails.map((e) => e.id)); + console.log({ + totalReplies: emailData.totalReplies, + unreadEmails: unreadEmails.length, + }); + if (unreadEmails.length > 0) { const ids = [id, ...unreadEmails.map((e) => e.id)]; markAsRead({ ids }) .catch((error) => { console.error('Failed to mark email as read:', error); toast.error(t('common.mail.failedToMarkAsRead')); }) - .then(() => Promise.all([mutateThread(), mutateStats()])); + .then(() => Promise.allSettled([mutateThread(), mutateStats()])); } }, [emailData, id]); @@ -232,7 +227,7 @@ export function ThreadDisplay({ isMobile, id }: ThreadDisplayProps) { if (!result.success) throw new Error('Failed to mark as unread'); setMail((prev) => ({ ...prev, bulkSelected: [] })); - await Promise.all([mutateStats(), mutateThread()]); + await Promise.allSettled([mutateStats(), mutateThread()]); handleClose(); }; diff --git a/apps/mail/components/ui/nav-main.tsx b/apps/mail/components/ui/nav-main.tsx index ed3fb7a347..5d1937bacf 100644 --- a/apps/mail/components/ui/nav-main.tsx +++ b/apps/mail/components/ui/nav-main.tsx @@ -11,7 +11,10 @@ import { Collapsible, CollapsibleTrigger } from '@/components/ui/collapsible'; import { usePathname, useSearchParams } from 'next/navigation'; import { clearBulkSelectionAtom } from '../mail/use-mail'; import { type MessageKey } from '@/config/navigation'; +import { type NavItem } from '@/config/navigation'; +import { useSession } from '@/lib/auth-client'; import { Badge } from '@/components/ui/badge'; +import { GoldenTicketModal } from '../golden'; import { useStats } from '@/hooks/use-stats'; import { useTranslations } from 'next-intl'; import { useRef, useCallback } from 'react'; @@ -20,9 +23,6 @@ import { cn } from '@/lib/utils'; import { useAtom } from 'jotai'; import * as React from 'react'; import Link from 'next/link'; -import {type NavItem} from '@/config/navigation' -import { GoldenTicketModal } from '../golden'; -import { useSession } from '@/lib/auth-client'; interface IconProps extends React.SVGProps { ref?: React.Ref; @@ -86,7 +86,7 @@ export function NavMain({ items }: NavMainProps) { // Handle settings navigation // if (item.isSettingsButton) { - // Include current path with category query parameter if present + // Include current path with category query parameter if present // const currentPath = category // ? `${pathname}?category=${encodeURIComponent(category)}` // : pathname; @@ -179,9 +179,9 @@ export function NavMain({ items }: NavMainProps) { } function NavItem(item: NavItemProps & { href: string }) { - const iconRef = useRef(null); - const { data: stats } = useStats(); - const t = useTranslations(); + const iconRef = useRef(null); + const { data: stats } = useStats(); + const t = useTranslations(); if (item.disabled) { return ( @@ -190,7 +190,7 @@ function NavItem(item: NavItemProps & { href: string }) { className="flex cursor-not-allowed items-center opacity-50" > {item.icon && } -

{t(item.title as MessageKey)}

+

{t(item.title as MessageKey)}

); } @@ -214,14 +214,16 @@ function NavItem(item: NavItemProps & { href: string }) { onClick={() => setOpenMobile(false)} > {item.icon && } -

{t(item.title as MessageKey)}

- {stats && stats.find((stat) => stat.label?.toLowerCase() === item.id?.toLowerCase()) && ( - - {stats - .find((stat) => stat.label?.toLowerCase() === item.id?.toLowerCase()) - ?.count?.toLocaleString() || '0'} - - )} +

{t(item.title as MessageKey)}

+ {stats + ? stats.find((stat) => stat.label?.toLowerCase() === item.id?.toLowerCase()) && ( + + {stats + .find((stat) => stat.label?.toLowerCase() === item.id?.toLowerCase()) + ?.count?.toLocaleString() || '0'} + + ) + : null} ); @@ -232,7 +234,9 @@ function NavItem(item: NavItemProps & { href: string }) { return ( - {buttonContent} + + {buttonContent} + );