mirror of
https://github.com/Mail-0/Zero.git
synced 2026-06-29 07:16:19 +00:00
use aliases in email-composer (#1233)
# Add email alias selection to the email composer ## Description This PR adds the ability for users to select which email alias to send from in the email composer. It displays a dropdown of available email aliases when composing a new email, allowing users to choose their sending identity. The feature is only shown when creating new emails, not when replying to existing messages. ## Type of Change - [x] ✨ New feature (non-breaking change which adds functionality) - [x] 🎨 UI/UX improvement ## Areas Affected - [x] User Interface/Experience <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added the ability to select a "From" email address when composing a new email, allowing users to choose from multiple email aliases. - Introduced a dropdown menu to display and select available email aliases, with clear indication of the primary alias. - Enhanced reply email sender selection to automatically match the sender address with recipients from the original message for more accurate "From" email choice. - **Style** - Updated the subject input area with a new bottom border for improved visual separation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -57,4 +57,6 @@ worker-configuration.d.ts
|
||||
|
||||
# devcontainer
|
||||
.pnpm-store
|
||||
tsx-0/
|
||||
tsx-0/
|
||||
|
||||
tools.json
|
||||
@@ -101,9 +101,9 @@ export function CreateEmail({
|
||||
subject: string;
|
||||
message: string;
|
||||
attachments: File[];
|
||||
fromEmail?: string;
|
||||
}) => {
|
||||
// Use the selected from email or the first alias (or default user email)
|
||||
const fromEmail = aliases?.[0]?.email ?? userEmail;
|
||||
const fromEmail = data.fromEmail || aliases?.[0]?.email || userEmail;
|
||||
|
||||
const zeroSignature = settings?.settings.zeroSignature
|
||||
? '<p style="color: #666; font-size: 12px;">Sent via <a href="https://0.email/" style="color: #0066cc; text-decoration: none;">Zero</a></p>'
|
||||
@@ -185,6 +185,7 @@ export function CreateEmail({
|
||||
<EmailComposer
|
||||
key={typedDraft?.id || 'composer'}
|
||||
className="mb-12 rounded-2xl border"
|
||||
aliases={aliases}
|
||||
onSendEmail={handleSendEmail}
|
||||
initialMessage={typedDraft?.content || initialBody}
|
||||
initialTo={
|
||||
|
||||
@@ -7,6 +7,13 @@ import {
|
||||
X,
|
||||
Sparkles,
|
||||
} from '../icons/icons';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { TextEffect } from '@/components/motion-primitives/text-effect';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
||||
@@ -47,6 +54,12 @@ interface EmailComposerProps {
|
||||
initialSubject?: string;
|
||||
initialMessage?: string;
|
||||
initialAttachments?: File[];
|
||||
replyingTo?: string;
|
||||
aliases?: {
|
||||
email: string;
|
||||
name?: string;
|
||||
primary?: boolean;
|
||||
}[];
|
||||
onSendEmail: (data: {
|
||||
to: string[];
|
||||
cc?: string[];
|
||||
@@ -54,6 +67,7 @@ interface EmailComposerProps {
|
||||
subject: string;
|
||||
message: string;
|
||||
attachments: File[];
|
||||
fromEmail?: string;
|
||||
}) => Promise<void>;
|
||||
onClose?: () => void;
|
||||
className?: string;
|
||||
@@ -92,6 +106,8 @@ export function EmailComposer({
|
||||
className,
|
||||
autofocus = false,
|
||||
settingsLoading = false,
|
||||
replyingTo,
|
||||
aliases = [],
|
||||
editorClassName,
|
||||
}: EmailComposerProps) {
|
||||
const [showCc, setShowCc] = useState(initialCc.length > 0);
|
||||
@@ -164,6 +180,7 @@ export function EmailComposer({
|
||||
subject: initialSubject,
|
||||
message: initialMessage,
|
||||
attachments: initialAttachments,
|
||||
fromEmail: aliases?.find((alias) => alias.primary)?.email || aliases?.[0]?.email || '',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -247,6 +264,7 @@ export function EmailComposer({
|
||||
const bccEmails = watch('bcc');
|
||||
const subjectInput = watch('subject');
|
||||
const attachments = watch('attachments');
|
||||
const fromEmail = watch('fromEmail');
|
||||
|
||||
const handleAttachment = (files: File[]) => {
|
||||
if (files && files.length > 0) {
|
||||
@@ -304,6 +322,7 @@ export function EmailComposer({
|
||||
subject: values.subject,
|
||||
message: editor.getHTML(),
|
||||
attachments: values.attachments || [],
|
||||
fromEmail: values.fromEmail,
|
||||
});
|
||||
setHasUnsavedChanges(false);
|
||||
editor.commands.clearContent(true);
|
||||
@@ -908,7 +927,7 @@ export function EmailComposer({
|
||||
</div>
|
||||
|
||||
{/* Subject */}
|
||||
<div className="flex items-center gap-2 p-3">
|
||||
<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"
|
||||
@@ -932,6 +951,36 @@ export function EmailComposer({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* From */}
|
||||
{aliases.length > 0 && !replyingTo && (
|
||||
<div className="flex items-center gap-2 border-b p-3">
|
||||
<p className="text-sm font-medium text-[#8C8C8C]">From:</p>
|
||||
<Select
|
||||
value={fromEmail || ''}
|
||||
onValueChange={(value) => {
|
||||
setValue('fromEmail', value);
|
||||
setHasUnsavedChanges(true);
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
{aliases.map((alias) => (
|
||||
<SelectItem key={alias.email} value={alias.email}>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<span className="text-sm">
|
||||
{alias.name ? `${alias.name} <${alias.email}>` : alias.email}
|
||||
</span>
|
||||
{alias.primary && <span className="text-xs text-[#8C8C8C]">Primary</span>}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Message Content */}
|
||||
<div className="grow self-stretch overflow-y-auto border-t bg-[#FFFFFF] px-3 py-3 outline-white/5 dark:bg-[#202020]">
|
||||
<div
|
||||
@@ -939,7 +988,8 @@ export function EmailComposer({
|
||||
editor.commands.focus();
|
||||
}}
|
||||
className={cn(
|
||||
`max-h-[300px] min-h-[200px] w-full ${editorClassName}`,
|
||||
`max-h-[300px] min-h-[200px] w-full`,
|
||||
editorClassName,
|
||||
aiGeneratedMessage !== null ? 'blur-sm' : '',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -112,7 +112,29 @@ export default function ReplyCompose({ messageId }: ReplyComposeProps) {
|
||||
try {
|
||||
const userEmail = activeConnection.email.toLowerCase();
|
||||
|
||||
// Convert email strings to Sender objects
|
||||
let fromEmail = userEmail;
|
||||
|
||||
if (aliases && aliases.length > 0 && replyToMessage) {
|
||||
const allRecipients = [
|
||||
...(replyToMessage.to || []),
|
||||
...(replyToMessage.cc || []),
|
||||
...(replyToMessage.bcc || []),
|
||||
];
|
||||
|
||||
const matchingAlias = aliases.find((alias) =>
|
||||
allRecipients.some(
|
||||
(recipient) => recipient.email.toLowerCase() === alias.email.toLowerCase(),
|
||||
),
|
||||
);
|
||||
|
||||
if (matchingAlias) {
|
||||
fromEmail = matchingAlias.email;
|
||||
} else {
|
||||
fromEmail =
|
||||
aliases.find((alias) => alias.primary)?.email || aliases[0]?.email || userEmail;
|
||||
}
|
||||
}
|
||||
|
||||
const toRecipients: Sender[] = data.to.map((email) => ({
|
||||
email,
|
||||
name: email.split('@')[0] || 'User',
|
||||
@@ -159,7 +181,7 @@ export default function ReplyCompose({ messageId }: ReplyComposeProps) {
|
||||
subject: data.subject,
|
||||
message: emailBody,
|
||||
attachments: await serializeFiles(data.attachments),
|
||||
fromEmail: aliases?.[0]?.email || userEmail,
|
||||
fromEmail: fromEmail,
|
||||
headers: {
|
||||
'In-Reply-To': replyToMessage?.messageId ?? '',
|
||||
References: [
|
||||
@@ -246,6 +268,7 @@ export default function ReplyCompose({ messageId }: ReplyComposeProps) {
|
||||
})}
|
||||
autofocus={shouldFocus}
|
||||
settingsLoading={settingsLoading}
|
||||
replyingTo={replyToMessage?.sender.email}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user