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
5 changes: 2 additions & 3 deletions apps/mail/components/create/create-email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ export function CreateEmail({

const handleDialogClose = (open: boolean) => {
setIsComposeOpen(open ? 'true' : null);
if(!open){
setDraftId(null)
if (!open) {
setDraftId(null);
}
};

Expand Down Expand Up @@ -185,7 +185,6 @@ export function CreateEmail({
<EmailComposer
key={typedDraft?.id || 'composer'}
className="mb-12 rounded-2xl border"
aliases={aliases}
onSendEmail={handleSendEmail}
initialMessage={typedDraft?.content || initialBody}
initialTo={
Expand Down
20 changes: 8 additions & 12 deletions apps/mail/components/create/email-composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import { TextEffect } from '@/components/motion-primitives/text-effect';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
import { useActiveConnection } from '@/hooks/use-connections';
import { useEmailAliases } from '@/hooks/use-email-aliases';
import useComposeEditor from '@/hooks/use-compose-editor';
import { Loader, Check, X as XIcon } from 'lucide-react';
import { Command, Paperclip, Plus } from 'lucide-react';
Expand Down Expand Up @@ -55,11 +56,6 @@ interface EmailComposerProps {
initialMessage?: string;
initialAttachments?: File[];
replyingTo?: string;
aliases?: {
email: string;
name?: string;
primary?: boolean;
}[];
onSendEmail: (data: {
to: string[];
cc?: string[];
Expand Down Expand Up @@ -107,9 +103,9 @@ export function EmailComposer({
autofocus = false,
settingsLoading = false,
replyingTo,
aliases = [],
editorClassName,
}: EmailComposerProps) {
const { data: aliases } = useEmailAliases();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent undefined.length runtime crash by normalizing aliases early

useEmailAliases() may return undefined while the query is still loading.
Any code that blindly dereferences aliases (see 179 and 998) will throw.

-const { data: aliases } = useEmailAliases();
+// Normalise to an empty array so the rest of the component can safely use `.length`
+const { data: aliases = [] } = useEmailAliases();

With this guard in place, aliases.length and aliases.map() are always safe.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data: aliases } = useEmailAliases();
// Normalise to an empty array so the rest of the component can safely use `.length`
const { data: aliases = [] } = useEmailAliases();
🤖 Prompt for AI Agents
In apps/mail/components/create/email-composer.tsx at line 108, the variable
aliases from useEmailAliases() can be undefined during loading, causing runtime
errors when accessing aliases.length or aliases.map(). To fix this, initialize
aliases to an empty array if it is undefined immediately after the hook call,
ensuring aliases is always an array and safe to use throughout the component.

const [showCc, setShowCc] = useState(initialCc.length > 0);
const [showBcc, setShowBcc] = useState(initialBcc.length > 0);
const [isLoading, setIsLoading] = useState(false);
Expand Down Expand Up @@ -312,18 +308,18 @@ export function EmailComposer({
const handleSend = async () => {
try {
if (isLoading || isSavingDraft) return;

const values = getValues();

// Validate recipient field
if (!values.to || values.to.length === 0) {
toast.error('Recipient is required');
return;
}

setIsLoading(true);
setAiGeneratedMessage(null);

await onSendEmail({
to: values.to,
cc: showCc ? values.cc : undefined,
Expand Down Expand Up @@ -999,7 +995,7 @@ export function EmailComposer({
</div>

{/* From */}
{aliases.length > 0 && !replyingTo && (
{aliases.length > 0 && (
<div className="flex items-center gap-2 border-b p-3">
<p className="text-sm font-medium text-[#8C8C8C]">From:</p>
<Select
Expand All @@ -1012,7 +1008,7 @@ export function EmailComposer({
<SelectTrigger className="h-6 flex-1 border-0 bg-transparent p-0 text-sm font-normal text-black placeholder:text-[#797979] focus:outline-none focus:ring-0 dark:text-white/90">
<SelectValue placeholder="Select an email address" />
</SelectTrigger>
<SelectContent>
<SelectContent className="z-[99999]">
{aliases.map((alias) => (
<SelectItem key={alias.email} value={alias.email}>
<div className="flex flex-row items-center gap-1">
Expand Down
19 changes: 9 additions & 10 deletions apps/mail/components/mail/mail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -958,18 +958,18 @@ function CategorySelect({ isMultiSelectMode }: { isMultiSelectMode: boolean }) {
if (!container) return;

const containerWidth = container.offsetWidth;
const selectedCategory = categories.find(cat => cat.id === category);
const selectedCategory = categories.find((cat) => cat.id === category);

// Calculate approximate widths needed for different text sizes
const baseIconWidth = (categories.length - 1) * 40; // unselected icons + gaps
const selectedTextLength = selectedCategory ? selectedCategory.name.length : 10;

// Estimate width needed for different text sizes
const normalTextWidth = selectedTextLength * 8 + 60; // normal text
const smallTextWidth = selectedTextLength * 7 + 50; // smaller text
const xsTextWidth = selectedTextLength * 6 + 40; // extra small text
const smallTextWidth = selectedTextLength * 7 + 50; // smaller text
const xsTextWidth = selectedTextLength * 6 + 40; // extra small text
const minIconWidth = 40; // minimum width for icon-only selected button

const totalNormal = baseIconWidth + normalTextWidth;
const totalSmall = baseIconWidth + smallTextWidth;
const totalXs = baseIconWidth + xsTextWidth;
Expand Down Expand Up @@ -1066,10 +1066,9 @@ function CategorySelect({ isMultiSelectMode }: { isMultiSelectMode: boolean }) {
<div className="relative overflow-visible">{cat.icon}</div>
{isSelected && showText && (
<div className="flex items-center justify-center gap-2.5 px-0.5">
<div className={cn(
" justify-start leading-none text-white truncate",
getTextClasses()
)}>
<div
className={cn('justify-start truncate leading-none text-white', getTextClasses())}
>
{cat.name}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/mail/components/ui/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ function ComposeButton() {
if (!open) {
await setDialogOpen(null);
} else {
setDialogOpen('true');
await setDialogOpen('true');
}
await Promise.all([setDraftId(null), setTo(null), setActiveReplyId(null), setMode(null)]);
};
Expand Down
1 change: 0 additions & 1 deletion apps/mail/hooks/use-email-aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export function useEmailAliases() {
const emailAliasesQuery = useQuery(
trpc.mail.getEmailAliases.queryOptions(void 0, {
initialData: [] as { email: string; name: string; primary?: boolean }[],
staleTime: 1000 * 60 * 60, // 1 hour
}),
);
return emailAliasesQuery;
Expand Down
25 changes: 20 additions & 5 deletions apps/server/src/lib/driver/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,30 @@ export class GoogleMailManager implements MailManager {
}
public getEmailAliases() {
return this.withErrorHandler('getEmailAliases', async () => {
console.log('Fetching email aliases...');

const profile = await this.gmail.users.getProfile({
userId: 'me',
});
console.log('Retrieved user profile:', { email: profile.data.emailAddress });

const primaryEmail = profile.data.emailAddress || '';
const aliases: { email: string; name?: string; primary?: boolean }[] = [
{ email: primaryEmail, primary: true },
];
console.log('Added primary email to aliases:', { primaryEmail });

const settings = await this.gmail.users.settings.sendAs.list({
userId: 'me',
});
console.log('Retrieved sendAs settings:', {
sendAsCount: settings.data.sendAs?.length || 0,
});

if (settings.data.sendAs) {
settings.data.sendAs.forEach((alias) => {
if (alias.isPrimary && alias.sendAsEmail === primaryEmail) {
console.log('Skipping duplicate primary email:', { email: alias.sendAsEmail });
return;
}

Expand All @@ -89,9 +97,15 @@ export class GoogleMailManager implements MailManager {
name: alias.displayName || undefined,
primary: alias.isPrimary || false,
});
console.log('Added alias:', {
email: alias.sendAsEmail,
name: alias.displayName,
primary: alias.isPrimary,
});
});
}

console.log('Returning aliases:', { aliasCount: aliases.length });
return aliases;
});
}
Expand Down Expand Up @@ -732,13 +746,14 @@ export class GoogleMailManager implements MailManager {
});
// Process res.data.messages to extract id and labelIds
return {
messages: res.data.messages?.map(msg => ({
id: msg.id,
labelIds: msg.labelIds
})) || []
messages:
res.data.messages?.map((msg) => ({
id: msg.id,
labelIds: msg.labelIds,
})) || [],
};
},
{ threadId, email: this.config.auth?.email }
{ threadId, email: this.config.auth?.email },
);
}

Expand Down