diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index f7f30bf..c836dff 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -353,11 +353,13 @@ export default function Home() { subject: string; body: string; draftId?: string; + fromEmail?: string; + identityId?: string; }) => { if (!client) return; try { - await sendEmail(client, data.to, data.subject, data.body, data.cc, data.bcc, data.draftId); + await sendEmail(client, data.to, data.subject, data.body, data.cc, data.bcc, data.draftId, data.fromEmail, data.identityId); setShowComposer(false); } catch (error) { console.error("Failed to send email:", error); diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index 5d9fad6..6da53ab 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -16,6 +16,8 @@ interface EmailComposerProps { subject: string; body: string; draftId?: string; + fromEmail?: string; + identityId?: string; }) => void; onClose?: () => void; onDiscardDraft?: (draftId: string) => void; @@ -97,8 +99,9 @@ export function EmailComposer({ const lastSavedDataRef = useRef(""); const [attachments, setAttachments] = useState>([]); const fileInputRef = useRef(null); + const [selectedIdentityId, setSelectedIdentityId] = useState(null); - const { client } = useAuthStore(); + const { client, identities, primaryIdentity } = useAuthStore(); // Handle file selection const handleFileSelect = async (event: React.ChangeEvent) => { @@ -255,6 +258,11 @@ export function EmailComposer({ } } + // Get the selected identity or primary identity + const currentIdentity = selectedIdentityId + ? identities.find(id => id.id === selectedIdentityId) + : primaryIdentity; + onSend?.({ to: toAddresses, cc: ccAddresses, @@ -262,6 +270,8 @@ export function EmailComposer({ subject, body, draftId: finalDraftId || undefined, + fromEmail: currentIdentity?.email, + identityId: currentIdentity?.id, }); // Reset form @@ -328,8 +338,32 @@ export function EmailComposer({
+ {/* From field - show dropdown if multiple identities, otherwise display email */}
- To: + {t('from')}: + {identities.length > 1 ? ( + + ) : ( + + {primaryIdentity?.name + ? `${primaryIdentity.name} <${primaryIdentity.email}>` + : primaryIdentity?.email || ''} + + )} +
+ +
+ {t('to')}: { + try { + const response = await this.request([ + ["Identity/get", { + accountId: this.accountId, + }, "0"] + ]); + + if (response.methodResponses?.[0]?.[0] === "Identity/get") { + const identities = (response.methodResponses[0][1].list || []) as Identity[]; + return identities; + } + + return []; + } catch (error) { + console.error('Failed to get identities:', error); + return []; + } + } + async createDraft( to: string[], subject: string, @@ -907,7 +927,8 @@ export class JMAPClient { cc?: string[], bcc?: string[], draftId?: string, - attachments?: Array<{ blobId: string; name: string; type: string; size: number }> + attachments?: Array<{ blobId: string; name: string; type: string; size: number }>, + fromEmail?: string ): Promise { // Find the drafts mailbox const mailboxes = await this.getMailboxes(); @@ -933,7 +954,7 @@ export class JMAPClient { attachments?: { blobId: string; type: string; name: string; disposition: string }[]; } const emailData: EmailDraft = { - from: [{ email: this.username }], + from: [{ email: fromEmail || this.username }], to: to.map(email => ({ email })), cc: cc?.map(email => ({ email })), bcc: bcc?.map(email => ({ email })), @@ -1025,7 +1046,9 @@ export class JMAPClient { body: string, cc?: string[], bcc?: string[], - draftId?: string + draftId?: string, + fromEmail?: string, + selectedIdentityId?: string ): Promise { const emailId = draftId || `draft-${Date.now()}`; @@ -1037,22 +1060,26 @@ export class JMAPClient { throw new Error('No sent mailbox found'); } - // Get the identity ID - fetch identities from server - const identityResponse = await this.request([ - ["Identity/get", { - accountId: this.accountId, - }, "0"] - ]); + // Use provided identity ID or fetch from server as fallback + let identityId = selectedIdentityId; - let identityId = this.accountId; // fallback + if (!identityId) { + const identityResponse = await this.request([ + ["Identity/get", { + accountId: this.accountId, + }, "0"] + ]); - if (identityResponse.methodResponses?.[0]?.[0] === "Identity/get") { - const identities = (identityResponse.methodResponses[0][1].list || []) as { id: string; email: string }[]; + identityId = this.accountId; // fallback - if (identities.length > 0) { - // Use the first identity (or find one matching the username) - const matchingIdentity = identities.find((id) => id.email === this.username); - identityId = matchingIdentity?.id || identities[0].id; + if (identityResponse.methodResponses?.[0]?.[0] === "Identity/get") { + const identities = (identityResponse.methodResponses[0][1].list || []) as { id: string; email: string }[]; + + if (identities.length > 0) { + // Use the first identity (or find one matching the fromEmail/username) + const matchingIdentity = identities.find((id) => id.email === (fromEmail || this.username)); + identityId = matchingIdentity?.id || identities[0].id; + } } } @@ -1085,7 +1112,7 @@ export class JMAPClient { accountId: this.accountId, create: { [emailId]: { - from: [{ email: this.username }], + from: [{ email: fromEmail || this.username }], to: to.map(email => ({ email })), cc: cc?.map(email => ({ email })), bcc: bcc?.map(email => ({ email })), diff --git a/locales/en/common.json b/locales/en/common.json index f66c5cf..d6be811 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -132,6 +132,7 @@ "reply_to": "Reply", "reply_all_to": "Reply All", "forward_message": "Forward", + "from": "From", "to": "To", "cc": "CC", "bcc": "BCC", diff --git a/locales/fr/common.json b/locales/fr/common.json index 142810f..c37a350 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -132,6 +132,7 @@ "reply_to": "Répondre", "reply_all_to": "Répondre à tous", "forward_message": "Transférer", + "from": "De", "to": "À", "cc": "CC", "bcc": "CCI", diff --git a/stores/auth-store.ts b/stores/auth-store.ts index 85c0b20..e9b65c7 100644 --- a/stores/auth-store.ts +++ b/stores/auth-store.ts @@ -2,6 +2,7 @@ import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import { JMAPClient } from '@/lib/jmap/client'; import { useEmailStore } from './email-store'; +import type { Identity } from '@/lib/jmap/types'; interface AuthState { isAuthenticated: boolean; @@ -10,6 +11,8 @@ interface AuthState { serverUrl: string | null; username: string | null; client: JMAPClient | null; + identities: Identity[]; + primaryIdentity: Identity | null; login: (serverUrl: string, username: string, password: string) => Promise; logout: () => void; @@ -26,6 +29,8 @@ export const useAuthStore = create()( serverUrl: null, username: null, client: null, + identities: [], + primaryIdentity: null, login: async (serverUrl, username, password) => { set({ isLoading: true, error: null }); @@ -37,6 +42,10 @@ export const useAuthStore = create()( // Try to connect await client.connect(); + // Fetch identities from the server + const identities = await client.getIdentities(); + const primaryIdentity = identities.length > 0 ? identities[0] : null; + // Success - save state (but NOT the password) set({ isAuthenticated: true, @@ -44,6 +53,8 @@ export const useAuthStore = create()( serverUrl, username, client, + identities, + primaryIdentity, error: null, }); @@ -87,6 +98,8 @@ export const useAuthStore = create()( serverUrl: null, username: null, client: null, + identities: [], + primaryIdentity: null, error: null, }); diff --git a/stores/email-store.ts b/stores/email-store.ts index 6a5ca4a..12f5a18 100644 --- a/stores/email-store.ts +++ b/stores/email-store.ts @@ -46,7 +46,7 @@ interface EmailStore { loadMoreEmails: (client: JMAPClient) => Promise; fetchEmailContent: (client: JMAPClient, emailId: string) => Promise; fetchQuota: (client: JMAPClient) => Promise; - sendEmail: (client: JMAPClient, to: string[], subject: string, body: string, cc?: string[], bcc?: string[], draftId?: string) => Promise; + sendEmail: (client: JMAPClient, to: string[], subject: string, body: string, cc?: string[], bcc?: string[], draftId?: string, fromEmail?: string, identityId?: string) => Promise; deleteEmail: (client: JMAPClient, emailId: string) => Promise; markAsRead: (client: JMAPClient, emailId: string, read: boolean) => Promise; moveToMailbox: (client: JMAPClient, emailId: string, mailboxId: string) => Promise; @@ -282,10 +282,10 @@ export const useEmailStore = create((set, get) => ({ } }, - sendEmail: async (client, to, subject, body, cc, bcc, draftId) => { + sendEmail: async (client, to, subject, body, cc, bcc, draftId, fromEmail, identityId) => { set({ isLoading: true, error: null }); try { - await client.sendEmail(to, subject, body, cc, bcc, draftId); + await client.sendEmail(to, subject, body, cc, bcc, draftId, fromEmail, identityId); // Refresh emails after sending await get().fetchEmails(client); set({ isLoading: false });