Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e572dbb
improved error handling during attachment downloads. (#1751)
MrgSub Jul 18, 2025
cb0822f
hotfixes (#1753)
MrgSub Jul 18, 2025
3136e86
fix deployment (#1754)
MrgSub Jul 18, 2025
ecb2126
Add auto draft generation (#1645)
ahmetskilinc Jul 18, 2025
6637cb9
fix: prevent browser search from triggering on Ctrl+K (#1757)
ayushsharma74 Jul 18, 2025
e4148d3
refactor durable objects (#1764)
MrgSub Jul 20, 2025
929649d
remove unused ZeroDriver code (#1765)
MrgSub Jul 20, 2025
7d9a6e4
Make agent property nullable in ZeroDriver (#1766)
MrgSub Jul 20, 2025
bf2c8f3
fix: eval lint err for extra arguments and autofix failing test (#1768)
retrogtx Jul 20, 2025
364e90b
Remove console logs and simplify AI chat prompt (#1769)
MrgSub Jul 20, 2025
d040186
Refactor Google mail count implementation and add staleTime to stats …
MrgSub Jul 20, 2025
2e44025
Uncomment and enable automatic draft generation in thread workflow (#…
MrgSub Jul 20, 2025
b0177bc
hotfix stuf (#1775)
MrgSub Jul 21, 2025
4c3753e
feat: ability to snooze emails (#1477)
retrogtx Jul 21, 2025
718b303
fix alignment
nizzyabi Jul 21, 2025
6cc6730
fix deployment (#1779)
MrgSub Jul 21, 2025
2ace344
Remove unused code and optimize database connections (#1778)
MrgSub Jul 21, 2025
b70b880
Add agent documentation and sync additional folders in production (#1…
MrgSub Jul 21, 2025
4caab6b
Implement Sentry error tracking and onboarding email sequence (#1781)
MrgSub Jul 21, 2025
0602b87
feat: update translations via @LingoDotDev (#1776)
github-actions[bot] Jul 21, 2025
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
4 changes: 2 additions & 2 deletions apps/mail/components/context/command-palette-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ export function CommandPalette({ children }: { children: React.ReactNode }) {
}
};

document.addEventListener('keydown', down);
return () => document.removeEventListener('keydown', down);
document.addEventListener('keydown', down, { capture: true });
return () => document.removeEventListener('keydown', down, { capture: true });
}, [open, currentView]);

const runCommand = useCallback((command: () => unknown) => {
Expand Down
55 changes: 29 additions & 26 deletions apps/mail/components/create/email-composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export function EmailComposer({
const [imageQuality, setImageQuality] = useState<ImageQuality>(
settings?.settings?.imageCompression || 'medium',
);
const [activeReplyId] = useQueryState('activeReplyId');
const [toggleToolbar, setToggleToolbar] = useState(false);
const processAndSetAttachments = async (
filesToProcess: File[],
Expand Down Expand Up @@ -1224,33 +1225,35 @@ export function EmailComposer({
</div>

{/* Subject */}
<div className="flex items-center gap-2 border-b p-3">
<p className="text-sm font-medium text-[#8C8C8C]">Subject:</p>
<input
className="h-4 w-full bg-transparent text-sm font-normal leading-normal text-black placeholder:text-[#797979] focus:outline-none dark:text-white/90"
placeholder="Re: Design review feedback"
value={subjectInput}
onChange={(e) => {
const value = replaceEmojiShortcodes(e.target.value);
setValue('subject', value);
setHasUnsavedChanges(true);
}}
/>
<button
onClick={handleGenerateSubject}
disabled={isLoading || isGeneratingSubject || messageLength < 1}
>
<div className="flex items-center justify-center gap-2.5 pl-0.5">
<div className="flex h-5 items-center justify-center gap-1 rounded-sm">
{isGeneratingSubject ? (
<Loader className="h-3.5 w-3.5 animate-spin fill-black dark:fill-white" />
) : (
<Sparkles className="h-3.5 w-3.5 fill-black dark:fill-white" />
)}
{!activeReplyId ? (
<div className="flex items-center gap-2 border-b p-3">
<p className="text-sm font-medium text-[#8C8C8C]">Subject:</p>
<input
className="h-4 w-full bg-transparent text-sm font-normal leading-normal text-black placeholder:text-[#797979] focus:outline-none dark:text-white/90"
placeholder="Re: Design review feedback"
value={subjectInput}
onChange={(e) => {
const value = replaceEmojiShortcodes(e.target.value);
setValue('subject', value);
setHasUnsavedChanges(true);
}}
/>
<button
onClick={handleGenerateSubject}
disabled={isLoading || isGeneratingSubject || messageLength < 1}
>
<div className="flex items-center justify-center gap-2.5 pl-0.5">
<div className="flex h-5 items-center justify-center gap-1 rounded-sm">
{isGeneratingSubject ? (
<Loader className="h-3.5 w-3.5 animate-spin fill-black dark:fill-white" />
) : (
<Sparkles className="h-3.5 w-3.5 fill-black dark:fill-white" />
)}
</div>
</div>
</div>
</button>
</div>
</button>
</div>
) : null}

{/* From */}
{aliases && aliases.length > 1 ? (
Expand Down
55 changes: 41 additions & 14 deletions apps/mail/components/mail/mail-display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
import type { Sender, ParsedMessage, Attachment } from '@/types';
import { useActiveConnection } from '@/hooks/use-connections';
import { useAttachments } from '@/hooks/use-attachments';
import { useBrainState } from '../../hooks/use-summary';
import { useTRPC } from '@/providers/query-provider';
import { useThreadLabels } from '@/hooks/use-labels';
Expand Down Expand Up @@ -378,9 +379,20 @@ const ActionButton = ({ onClick, icon, text, shortcut }: ActionButtonProps) => {
);
};

const downloadAttachment = (attachment: { body: string; mimeType: string; filename: string }) => {
const downloadAttachment = async (attachment: {
body: string;
mimeType: string;
filename: string;
attachmentId: string;
}) => {
try {
const byteCharacters = atob(attachment.body);
const attachmentData = attachment.body;

if (!attachmentData) {
throw new Error('Attachment data not found');
}

const byteCharacters = atob(attachmentData);
const byteNumbers: number[] = Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
Expand All @@ -398,6 +410,7 @@ const downloadAttachment = (attachment: { body: string; mimeType: string; filena
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Error downloading attachment:', error);
toast.error('Failed to download attachment');
}
};

Expand Down Expand Up @@ -455,9 +468,20 @@ const handleDownloadAllAttachments =
console.log('downloaded', subject, attachments);
};

const openAttachment = (attachment: { body: string; mimeType: string; filename: string }) => {
const openAttachment = async (attachment: {
body: string;
mimeType: string;
filename: string;
attachmentId: string;
}) => {
try {
const byteCharacters = atob(attachment.body);
const attachmentData = attachment.body;

if (!attachmentData) {
throw new Error('Attachment data not found');
}

const byteCharacters = atob(attachmentData);
const byteNumbers: number[] = Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
Expand All @@ -484,6 +508,7 @@ const openAttachment = (attachment: { body: string; mimeType: string; filename:
}
} catch (error) {
console.error('Error opening attachment:', error);
toast.error('Failed to open attachment');
}
};

Expand Down Expand Up @@ -638,6 +663,7 @@ const MoreAboutQuery = ({
const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }: Props) => {
const [isCollapsed, setIsCollapsed] = useState<boolean>(false);
const { data: threadData } = useThread(emailData.threadId ?? null);
const { data: messageAttachments } = useAttachments(emailData.id);
// const [unsubscribed, setUnsubscribed] = useState(false);
// const [isUnsubscribing, setIsUnsubscribing] = useState(false);
const [preventCollapse, setPreventCollapse] = useState(false);
Expand All @@ -660,6 +686,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
const { data: activeConnection } = useActiveConnection();
const [researchSender, setResearchSender] = useState<Sender | null>(null);
const [searchQuery, setSearchQuery] = useState<string | null>(null);
// const trpc = useTRPC();

const isLastEmail = useMemo(
() => emailData.id === threadData?.latest?.id,
Expand Down Expand Up @@ -1077,11 +1104,11 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:

<!-- Attachments -->
${
emailData.attachments && emailData.attachments.length > 0
messageAttachments && messageAttachments.length > 0
? `
<div class="attachments-section">
<h2 class="attachments-title">Attachments (${emailData.attachments.length})</h2>
${emailData.attachments
<h2 class="attachments-title">Attachments (${messageAttachments.length})</h2>
${messageAttachments
.map(
(attachment) => `
<div class="attachment-item">
Expand Down Expand Up @@ -1491,11 +1518,11 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
<Printer className="fill-iconLight dark:fill-iconDark mr-2 h-4 w-4" />
{m['common.mailDisplay.print']()}
</DropdownMenuItem>
{(emailData.attachments?.length ?? 0) > 0 && (
{(messageAttachments?.length ?? 0) > 0 && (
<DropdownMenuItem
disabled={!emailData.attachments?.length}
disabled={!messageAttachments?.length}
className={
!emailData.attachments?.length
!messageAttachments?.length
? 'data-[disabled]:pointer-events-auto'
: ''
}
Expand All @@ -1504,7 +1531,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
e.preventDefault();
handleDownloadAllAttachments(
emailData.subject || 'email',
emailData.attachments || [],
messageAttachments || [],
)();
}}
>
Expand Down Expand Up @@ -1642,9 +1669,9 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
/>
) : null}
{/* mail attachments */}
{emailData?.attachments && emailData?.attachments.length > 0 ? (
{messageAttachments && messageAttachments.length > 0 ? (
<div className="mb-4 flex flex-wrap items-center gap-2 px-4 pt-4">
{emailData?.attachments.map((attachment) => (
{messageAttachments.map((attachment) => (
<div
key={`${attachment.filename}-${attachment.attachmentId}`}
className="flex"
Expand All @@ -1667,7 +1694,7 @@ const MailDisplay = ({ emailData, index, totalEmails, demo, threadAttachments }:
>
<HardDriveDownload className="text-muted-foreground dark:text-muted-foreground h-4 w-4 fill-[#FAFAFA] dark:fill-[#262626]" />
</button>
{index < (emailData?.attachments?.length || 0) - 1 && (
{index < (messageAttachments?.length || 0) - 1 && (
<div className="m-auto h-2 w-[1px] bg-[#E0E0E0] dark:bg-[#424242]" />
)}
</div>
Expand Down
9 changes: 3 additions & 6 deletions apps/mail/components/mail/mail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ export function MailLayout() {
<Button
variant="outline"
className={cn(
'text-muted-foreground relative flex h-[1.875rem] w-full select-none items-center justify-start overflow-hidden rounded-lg border bg-white pl-2 text-left text-sm font-normal shadow-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 dark:border-none dark:bg-[#141414]',
'text-muted-foreground relative flex h-7 w-full select-none items-center justify-start overflow-hidden rounded-lg border bg-white pl-2 text-left text-sm font-normal shadow-none ring-0 focus-visible:ring-0 focus-visible:ring-offset-0 dark:border-none dark:bg-[#141414]',
)}
onClick={() => setIsCommandPaletteOpen('true')}
>
Expand Down Expand Up @@ -768,18 +768,15 @@ function CategoryDropdown({ isMultiSelectMode }: CategoryDropdownProps) {
<Button
variant="outline"
className={cn(
'flex h-7 min-w-fit items-center gap-1 rounded-md border-none bg-[#006FFE] px-2 text-white',
'black:text-white text-muted-foreground flex h-7 min-w-fit items-center gap-1 rounded-md border-none px-2',
)}
aria-label="Filter by labels"
aria-expanded={isOpen}
aria-haspopup="menu"
>
<div className="relative overflow-visible">
<Mail className="h-4 w-4 fill-white dark:fill-white" />
</div>
<span className="text-xs font-medium">Categories</span>
<ChevronDown
className={`h-2 w-2 text-white transition-transform duration-200 ${isOpen ? 'rotate-180' : 'rotate-0'}`}
className={`black:text-white text-muted-foreground h-2 w-2 transition-transform duration-200 ${isOpen ? 'rotate-180' : 'rotate-0'}`}
/>
</Button>
</DropdownMenuTrigger>
Expand Down
8 changes: 4 additions & 4 deletions apps/mail/components/ui/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,13 @@ function ComposeButton() {
<DialogDescription></DialogDescription>

<DialogTrigger asChild>
<button className="relative mb-1.5 inline-flex h-8 w-full items-center justify-center gap-1 self-stretch overflow-hidden rounded-lg border border-gray-200 bg-white text-black dark:border-none dark:bg-[#2C2C2C] dark:text-white">
<button className="relative mb-1.5 inline-flex h-8 w-full items-center justify-center gap-1 self-stretch overflow-hidden rounded-lg border border-gray-200 bg-[#006FFE] text-black dark:border-none dark:text-white">
{state === 'collapsed' && !isMobile ? (
<PencilCompose className="fill-iconLight dark:fill-iconDark mt-0.5 text-black" />
<PencilCompose className="mt-0.5 fill-white text-black" />
) : (
<div className="flex items-center justify-center gap-2.5 pl-0.5 pr-1">
<PencilCompose className="fill-iconLight dark:fill-iconDark" />
<div className="justify-start text-sm leading-none">
<PencilCompose className="fill-white" />
<div className="justify-start text-sm leading-none text-white">
{m['common.commandPalette.commands.newEmail']()}
</div>
</div>
Expand Down
11 changes: 6 additions & 5 deletions apps/mail/components/ui/nav-main.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { SidebarGroup, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from './sidebar';
import { Collapsible, CollapsibleTrigger } from '@/components/ui/collapsible';
import { useActiveConnection, } from '@/hooks/use-connections';
import { useCommandPalette } from '../context/command-palette-context.jsx';
import { LabelDialog } from '@/components/labels/label-dialog';
import { useActiveConnection } from '@/hooks/use-connections';
import { useMutation, useQuery } from '@tanstack/react-query';
import { Link, useLocation, } from 'react-router';
import Intercom, { show } from '@intercom/messenger-js-sdk';
import { MessageSquare, OldPhone } from '../icons/icons';
import { useSidebar } from '../context/sidebar-context';
import { useTRPC } from '@/providers/query-provider';
import { type NavItem } from '@/config/navigation';
import type { Label as LabelType } from '@/types';
import { Link, useLocation } from 'react-router';
import { m } from '../../paraglide/messages.js';
import { Button } from '@/components/ui/button';
import { useLabels } from '@/hooks/use-labels';
Expand Down Expand Up @@ -55,9 +56,7 @@ export function NavMain({ items }: NavMainProps) {
const pathname = location.pathname;
const searchParams = new URLSearchParams();
const [category] = useQueryState('category');




const trpc = useTRPC();
const { data: intercomToken } = useQuery(trpc.user.getIntercomToken.queryOptions());

Expand Down Expand Up @@ -271,6 +270,7 @@ export function NavMain({ items }: NavMainProps) {
function NavItem(item: NavItemProps & { href: string }) {
const iconRef = useRef<IconRefType>(null);
const { data: stats } = useStats();
const { clearAllFilters } = useCommandPalette();

const { state, setOpenMobile } = useSidebar();

Expand All @@ -290,6 +290,7 @@ function NavItem(item: NavItemProps & { href: string }) {
if (item.onClick) {
item.onClick(e as React.MouseEvent<HTMLAnchorElement>);
}
clearAllFilters();
setOpenMobile(false);
};

Expand Down
16 changes: 16 additions & 0 deletions apps/mail/hooks/use-attachments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useTRPC } from '@/providers/query-provider';
import { useQuery } from '@tanstack/react-query';
import { useSession } from '@/lib/auth-client';

export const useAttachments = (messageId: string) => {
const { data: session } = useSession();
const trpc = useTRPC();
const AttachmentsQuery = useQuery(
trpc.mail.getMessageAttachments.queryOptions(
{ messageId },
{ enabled: !!session?.user.id && !!messageId, staleTime: 1000 * 60 * 60 },
),
);

return AttachmentsQuery;
};
4 changes: 2 additions & 2 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@
"@hono/trpc-server": "^0.3.4",
"@microsoft/microsoft-graph-client": "^3.0.7",
"@microsoft/microsoft-graph-types": "^2.40.0",
"@modelcontextprotocol/sdk": "1.12.0",
"@modelcontextprotocol/sdk": "1.15.1",
"@react-email/components": "^0.0.41",
"@react-email/render": "1.1.0",
"@trpc/client": "catalog:",
"@trpc/server": "catalog:",
"@tsndr/cloudflare-worker-jwt": "3.2.0",
"@upstash/ratelimit": "^2.0.5",
"@upstash/redis": "^1.34.9",
"agents": "0.0.93",
"agents": "0.0.106",
"ai": "^4.3.13",
"autumn-js": "catalog:",
"base64-js": "1.5.1",
Expand Down
Loading
Loading