fixes from review

This commit is contained in:
Lucas Gaitzsch
2026-05-22 14:46:57 +02:00
parent 5c1d59f38a
commit e6d939ba43
4 changed files with 68 additions and 23 deletions
+30
View File
@@ -117,6 +117,30 @@ export default function Home() {
const sendDelaySeconds = useSettingsStore((state) => state.sendDelaySeconds); const sendDelaySeconds = useSettingsStore((state) => state.sendDelaySeconds);
const { loadTrustedSendersBook, trustedSendersLoaded } = useContactStore(); const { loadTrustedSendersBook, trustedSendersLoaded } = useContactStore();
const promptForRescheduleDelayedUntil = useCallback((): string | null => {
const value = window.prompt(t('email_viewer.reschedule_prompt'));
if (!value) return null;
const time = new Date(value).getTime();
if (!Number.isFinite(time)) {
toast.error(t('email_composer.schedule_send_invalid'));
return null;
}
if (time <= Date.now()) {
toast.error(t('email_composer.schedule_send_future'));
return null;
}
if (!client?.hasDelayedSend()) {
toast.error(t('email_composer.schedule_send_unsupported'));
return null;
}
const maxDelayedSend = client.getMaxDelayedSend();
if (maxDelayedSend > 0 && time > Date.now() + maxDelayedSend * 1000) {
toast.error(t('email_composer.schedule_send_too_late'));
return null;
}
return new Date(time).toISOString();
}, [client, t]);
// Load trusted senders address book when feature is enabled // Load trusted senders address book when feature is enabled
useEffect(() => { useEffect(() => {
if (trustedSendersAddressBook && client && !trustedSendersLoaded) { if (trustedSendersAddressBook && client && !trustedSendersLoaded) {
@@ -2721,6 +2745,12 @@ export default function Home() {
setShowComposer(true); setShowComposer(true);
if (isMobile) setActiveView('viewer'); if (isMobile) setActiveView('viewer');
}} }}
onRescheduleScheduled={async (email) => {
const delayedUntil = promptForRescheduleDelayedUntil();
if (delayedUntil && client && email.emailSubmissionId && email.scheduledIdentityId) {
await rescheduleScheduledEmail(client, email.emailSubmissionId, email.id, email.scheduledIdentityId, delayedUntil);
}
}}
onEmailSelect={handleEmailSelect} onEmailSelect={handleEmailSelect}
onEmailDoubleClick={isEmbedded ? ((email) => { onEmailDoubleClick={isEmbedded ? ((email) => {
useProTabStore.getState().openEmailTab({ useProTabStore.getState().openEmailTab({
+5 -3
View File
@@ -45,6 +45,7 @@ interface EmailListProps {
onLoadMoreScheduled?: () => void; onLoadMoreScheduled?: () => void;
onCancelScheduled?: (email: Email) => void | Promise<void>; onCancelScheduled?: (email: Email) => void | Promise<void>;
onCancelScheduledForEdit?: (email: Email) => void | Promise<void>; onCancelScheduledForEdit?: (email: Email) => void | Promise<void>;
onRescheduleScheduled?: (email: Email) => void | Promise<void>;
} }
export function EmailList({ export function EmailList({
@@ -73,6 +74,7 @@ export function EmailList({
onLoadMoreScheduled, onLoadMoreScheduled,
onCancelScheduled, onCancelScheduled,
onCancelScheduledForEdit, onCancelScheduledForEdit,
onRescheduleScheduled,
}: EmailListProps) { }: EmailListProps) {
const t = useTranslations('email_list'); const t = useTranslations('email_list');
const { client } = useAuthStore(); const { client } = useAuthStore();
@@ -535,9 +537,9 @@ export function EmailList({
onMarkAsSpam={() => onMarkAsSpam?.(contextMenu.data!)} onMarkAsSpam={() => onMarkAsSpam?.(contextMenu.data!)}
onUndoSpam={() => onUndoSpam?.(contextMenu.data!)} onUndoSpam={() => onUndoSpam?.(contextMenu.data!)}
onEditDraft={() => onEditDraft?.(contextMenu.data!)} onEditDraft={() => onEditDraft?.(contextMenu.data!)}
onCancelScheduled={() => onCancelScheduled?.(contextMenu.data!)} onCancelScheduled={onCancelScheduled ? () => onCancelScheduled(contextMenu.data!) : undefined}
onCancelScheduledForEdit={() => onCancelScheduledForEdit?.(contextMenu.data!)} onCancelScheduledForEdit={onCancelScheduledForEdit ? () => onCancelScheduledForEdit(contextMenu.data!) : undefined}
onRescheduleScheduled={undefined} onRescheduleScheduled={onRescheduleScheduled ? () => onRescheduleScheduled(contextMenu.data!) : undefined}
onBatchMarkAsRead={(read) => client && batchMarkAsRead(client, read)} onBatchMarkAsRead={(read) => client && batchMarkAsRead(client, read)}
onBatchDelete={() => client && batchDelete(client)} onBatchDelete={() => client && batchDelete(client)}
onBatchArchive={async () => { onBatchArchive={async () => {
+24 -20
View File
@@ -335,16 +335,18 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
</div> </div>
{/* Hover Quick Actions */} {/* Hover Quick Actions */}
<EmailHoverActions {!email.isScheduled && (
email={email} <EmailHoverActions
backgroundClassName={resolvedColorTag ? resolvedColorTag : ((selected || isChecked) ? "bg-accent" : "bg-muted")} email={email}
onToggleStar={onToggleStar} backgroundClassName={resolvedColorTag ? resolvedColorTag : ((selected || isChecked) ? "bg-accent" : "bg-muted")}
onMarkAsRead={onMarkAsRead} onToggleStar={onToggleStar}
onDelete={onDelete} onMarkAsRead={onMarkAsRead}
onArchive={onArchive} onDelete={onDelete}
onSetColorTag={onSetColorTag} onArchive={onArchive}
onMarkAsSpam={onMarkAsSpam} onSetColorTag={onSetColorTag}
/> onMarkAsSpam={onMarkAsSpam}
/>
)}
</div> </div>
); );
} }
@@ -751,16 +753,18 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
</div> </div>
{/* Hover Quick Actions for thread header */} {/* Hover Quick Actions for thread header */}
<EmailHoverActions {!latestEmail.isScheduled && (
email={latestEmail} <EmailHoverActions
backgroundClassName={colorTag ? colorTag : ((isSelected || isChecked) ? "bg-accent" : "bg-muted")} email={latestEmail}
onToggleStar={onToggleStar ? () => onToggleStar(latestEmail) : undefined} backgroundClassName={colorTag ? colorTag : ((isSelected || isChecked) ? "bg-accent" : "bg-muted")}
onMarkAsRead={onMarkAsRead ? (read) => onMarkAsRead(latestEmail, read) : undefined} onToggleStar={onToggleStar ? () => onToggleStar(latestEmail) : undefined}
onDelete={onDelete ? () => onDelete(latestEmail) : undefined} onMarkAsRead={onMarkAsRead ? (read) => onMarkAsRead(latestEmail, read) : undefined}
onArchive={onArchive ? () => onArchive(latestEmail) : undefined} onDelete={onDelete ? () => onDelete(latestEmail) : undefined}
onSetColorTag={onSetColorTag ? (color) => onSetColorTag(latestEmail.id, color) : undefined} onArchive={onArchive ? () => onArchive(latestEmail) : undefined}
onMarkAsSpam={onMarkAsSpam ? () => onMarkAsSpam(latestEmail) : undefined} onSetColorTag={onSetColorTag ? (color) => onSetColorTag(latestEmail.id, color) : undefined}
/> onMarkAsSpam={onMarkAsSpam ? () => onMarkAsSpam(latestEmail) : undefined}
/>
)}
</div> </div>
{isExpanded && !isMobile && !isFocusedMailLayout && ( {isExpanded && !isMobile && !isFocusedMailLayout && (
+9
View File
@@ -2566,6 +2566,15 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
const submissionId = email.emailSubmissionId; const submissionId = email.emailSubmissionId;
if (!submissionId) return null; if (!submissionId) return null;
await client.cancelEmailSubmission(submissionId); await client.cancelEmailSubmission(submissionId);
if (email.isSmimeScheduled) {
await client.deleteEmail(email.id);
set(state => ({
selectedEmail: state.selectedEmail?.id === email.id ? null : state.selectedEmail,
selectedEmailIds: new Set(Array.from(state.selectedEmailIds).filter(id => id !== email.id)),
}));
await get().fetchScheduledEmails(client);
return null;
}
const mailboxes = get().mailboxes.length > 0 ? get().mailboxes : await client.getMailboxes(); const mailboxes = get().mailboxes.length > 0 ? get().mailboxes : await client.getMailboxes();
const draftsMailbox = mailboxes.find(mb => mb.role === 'drafts'); const draftsMailbox = mailboxes.find(mb => mb.role === 'drafts');
const sentMailbox = mailboxes.find(mb => mb.role === 'sent'); const sentMailbox = mailboxes.find(mb => mb.role === 'sent');