From 3186198fad190011f7147762e544d0729bd56767 Mon Sep 17 00:00:00 2001
From: Linus Rath <139418639+rathlinus@users.noreply.github.com>
Date: Tue, 17 Mar 2026 21:50:35 +0100
Subject: [PATCH] feat: enhance identity management with identity refresh
functionality and improved modal behavior
---
components/email/email-composer.tsx | 23 ++++++++--
.../identity/identity-manager-modal.tsx | 43 +++++++++++++------
2 files changed, 49 insertions(+), 17 deletions(-)
diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx
index cd7e090d..01e1bbbd 100644
--- a/components/email/email-composer.tsx
+++ b/components/email/email-composer.tsx
@@ -664,13 +664,24 @@ export function EmailComposer({
finalBody = body + '\n\n-- \n' + currentIdentity.textSignature;
}
- // Build HTML body when replying/forwarding with original HTML content
+ // Build HTML signature block (prefer htmlSignature, fall back to escaped textSignature)
+ const buildSignatureHtml = (): string => {
+ if (currentIdentity?.htmlSignature) {
+ return `
--
${sanitizeEmailHtml(currentIdentity.htmlSignature)}`;
+ }
+ if (currentIdentity?.textSignature) {
+ return `
--
${currentIdentity.textSignature.replace(/&/g, '&').replace(//g, '>').replace(/\n/g, '
')}`;
+ }
+ return '';
+ };
+
+ // Build HTML body
let finalHtmlBody: string | undefined;
+ const signatureHtml = buildSignatureHtml();
+
if (replyTo?.htmlBody && (mode === 'reply' || mode === 'replyAll' || mode === 'forward')) {
+ // Reply/forward with original HTML content
const escapedBody = body.replace(/&/g, '&').replace(//g, '>').replace(/\n/g, '
');
- const signatureHtml = currentIdentity?.textSignature
- ? `
--
${currentIdentity.textSignature.replace(/&/g, '&').replace(//g, '>').replace(/\n/g, '
')}`
- : '';
const date = replyTo.receivedAt ? formatDateTime(replyTo.receivedAt, timeFormat, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' }) : '';
const fromAddr = replyTo.from?.[0];
const fromStr = fromAddr ? `${fromAddr.name || fromAddr.email}` : tCommon('unknown');
@@ -679,6 +690,10 @@ export function EmailComposer({
: `On ${date}, ${fromStr} wrote:
`;
finalHtmlBody = `
${escapedBody}
${signatureHtml}
${quoteHeader}
${replyTo.htmlBody}
`;
+ } else if (signatureHtml) {
+ // New compose or plain-text reply — include HTML body with signature
+ const escapedBody = body.replace(/&/g, '&').replace(//g, '>').replace(/\n/g, '
');
+ finalHtmlBody = `${escapedBody}
${signatureHtml}`;
}
try {
diff --git a/components/identity/identity-manager-modal.tsx b/components/identity/identity-manager-modal.tsx
index 7125f10c..e5479e39 100644
--- a/components/identity/identity-manager-modal.tsx
+++ b/components/identity/identity-manager-modal.tsx
@@ -38,7 +38,7 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
const tNotif = useTranslations('notifications');
const client = useAuthStore((state) => state.client);
- const { identities, addIdentity, updateIdentityLocal, removeIdentity } = useIdentityStore();
+ const identities = useIdentityStore((state) => state.identities);
const syncIdentities = useSyncIdentities();
const [editingId, setEditingId] = useState(null);
@@ -46,6 +46,25 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
const [deletingId, setDeletingId] = useState(null);
const { dialogProps: confirmDialogProps, confirm: confirmDialog } = useConfirmDialog();
+ // Re-fetch all identities from server and update stores
+ const refreshIdentities = useCallback(async () => {
+ if (!client) return;
+ try {
+ const serverIdentities = await client.getIdentities();
+ const username = useAuthStore.getState().username;
+ const sorted = [...serverIdentities].sort((a, b) => {
+ const aMatch = a.email === username ? -1 : 0;
+ const bMatch = b.email === username ? -1 : 0;
+ return aMatch - bMatch;
+ });
+ useIdentityStore.getState().setIdentities(sorted);
+ syncIdentities();
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'Failed to refresh identities';
+ toast.error(message);
+ }
+ }, [client, syncIdentities]);
+
// Focus trap with Escape handling
const modalRef = useFocusTrap({
isActive: isOpen,
@@ -60,9 +79,10 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
restoreFocus: true,
});
- // Close on click outside
+ // Close on click outside (but not when ConfirmDialog is open)
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
+ if (confirmDialogProps.isOpen) return;
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
onClose();
}
@@ -72,13 +92,13 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}
- }, [isOpen, onClose, modalRef]);
+ }, [isOpen, onClose, modalRef, confirmDialogProps.isOpen]);
const handleCreate = useCallback(async (data: IdentityFormData) => {
if (!client) return;
try {
- const newIdentity = await client.createIdentity(
+ await client.createIdentity(
data.name,
data.email,
data.replyTo,
@@ -87,8 +107,7 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
data.htmlSignature
);
- addIdentity(newIdentity);
- syncIdentities();
+ await refreshIdentities();
setIsCreating(false);
toast.success(tNotif('identity_created'));
} catch (error) {
@@ -96,7 +115,7 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
toast.error(tNotif('identity_create_failed', { error: message }));
throw error;
}
- }, [client, addIdentity, t, tNotif]);
+ }, [client, refreshIdentities, t, tNotif]);
const handleUpdate = useCallback(async (identity: Identity, data: IdentityFormData) => {
if (!client) return;
@@ -110,8 +129,7 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
htmlSignature: data.htmlSignature,
});
- updateIdentityLocal(identity.id, data);
- syncIdentities();
+ await refreshIdentities();
setEditingId(null);
toast.success(tNotif('identity_updated'));
} catch (error) {
@@ -119,7 +137,7 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
toast.error(tNotif('identity_update_failed', { error: message }));
throw error;
}
- }, [client, updateIdentityLocal, t, tNotif]);
+ }, [client, refreshIdentities, t, tNotif]);
const handleDelete = useCallback(async (identity: Identity) => {
if (!client) return;
@@ -140,8 +158,7 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
try {
await client.deleteIdentity(identity.id);
- removeIdentity(identity.id);
- syncIdentities();
+ await refreshIdentities();
toast.success(tNotif('identity_deleted'));
} catch (error) {
const message = error instanceof Error ? error.message : t('validation_errors.unknown_error');
@@ -149,7 +166,7 @@ export function IdentityManagerModal({ isOpen, onClose }: IdentityManagerModalPr
} finally {
setDeletingId(null);
}
- }, [client, removeIdentity, t, tNotif, confirmDialog]);
+ }, [client, refreshIdentities, t, tNotif, confirmDialog]);
if (!isOpen) return null;