feat: Add runtime config, trusted senders, JMAP identities, and UI improvements
- Runtime environment variables for Docker-friendly configuration - Trusted senders list for automatic image loading - JMAP identities for proper sender address - Improved email composer readability - Horizontal scroll for wide HTML emails
This commit is contained in:
@@ -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<boolean>;
|
||||
logout: () => void;
|
||||
@@ -26,6 +29,8 @@ export const useAuthStore = create<AuthState>()(
|
||||
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<AuthState>()(
|
||||
// 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<AuthState>()(
|
||||
serverUrl,
|
||||
username,
|
||||
client,
|
||||
identities,
|
||||
primaryIdentity,
|
||||
error: null,
|
||||
});
|
||||
|
||||
@@ -87,6 +98,8 @@ export const useAuthStore = create<AuthState>()(
|
||||
serverUrl: null,
|
||||
username: null,
|
||||
client: null,
|
||||
identities: [],
|
||||
primaryIdentity: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ interface EmailStore {
|
||||
loadMoreEmails: (client: JMAPClient) => Promise<void>;
|
||||
fetchEmailContent: (client: JMAPClient, emailId: string) => Promise<Email | null>;
|
||||
fetchQuota: (client: JMAPClient) => Promise<void>;
|
||||
sendEmail: (client: JMAPClient, to: string[], subject: string, body: string, cc?: string[], bcc?: string[], draftId?: string) => Promise<void>;
|
||||
sendEmail: (client: JMAPClient, to: string[], subject: string, body: string, cc?: string[], bcc?: string[], draftId?: string, fromEmail?: string, identityId?: string) => Promise<void>;
|
||||
deleteEmail: (client: JMAPClient, emailId: string) => Promise<void>;
|
||||
markAsRead: (client: JMAPClient, emailId: string, read: boolean) => Promise<void>;
|
||||
moveToMailbox: (client: JMAPClient, emailId: string, mailboxId: string) => Promise<void>;
|
||||
@@ -282,10 +282,10 @@ export const useEmailStore = create<EmailStore>((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 });
|
||||
|
||||
@@ -35,6 +35,7 @@ interface SettingsState {
|
||||
|
||||
// Privacy & Security
|
||||
sessionTimeout: number; // minutes (0 = never)
|
||||
trustedSenders: string[]; // Email addresses that can load external content
|
||||
|
||||
// Advanced
|
||||
debugMode: boolean;
|
||||
@@ -47,6 +48,11 @@ interface SettingsState {
|
||||
resetToDefaults: () => void;
|
||||
exportSettings: () => string;
|
||||
importSettings: (json: string) => boolean;
|
||||
|
||||
// Trusted senders
|
||||
addTrustedSender: (email: string) => void;
|
||||
removeTrustedSender: (email: string) => void;
|
||||
isSenderTrusted: (email: string) => boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
@@ -74,6 +80,7 @@ const DEFAULT_SETTINGS = {
|
||||
|
||||
// Privacy & Security
|
||||
sessionTimeout: 0, // Never
|
||||
trustedSenders: [] as string[],
|
||||
|
||||
// Advanced
|
||||
debugMode: false,
|
||||
@@ -124,6 +131,7 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
showPreview: state.showPreview,
|
||||
emailsPerPage: state.emailsPerPage,
|
||||
externalContentPolicy: state.externalContentPolicy,
|
||||
trustedSenders: state.trustedSenders,
|
||||
autoSaveDraftInterval: state.autoSaveDraftInterval,
|
||||
sendConfirmation: state.sendConfirmation,
|
||||
defaultReplyMode: state.defaultReplyMode,
|
||||
@@ -160,6 +168,27 @@ export const useSettingsStore = create<SettingsState>()(
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
// Trusted senders methods
|
||||
addTrustedSender: (email: string) => {
|
||||
const normalizedEmail = email.toLowerCase().trim();
|
||||
const current = get().trustedSenders;
|
||||
if (!current.includes(normalizedEmail)) {
|
||||
set({ trustedSenders: [...current, normalizedEmail] });
|
||||
}
|
||||
},
|
||||
|
||||
removeTrustedSender: (email: string) => {
|
||||
const normalizedEmail = email.toLowerCase().trim();
|
||||
set({
|
||||
trustedSenders: get().trustedSenders.filter(e => e !== normalizedEmail)
|
||||
});
|
||||
},
|
||||
|
||||
isSenderTrusted: (email: string) => {
|
||||
const normalizedEmail = email.toLowerCase().trim();
|
||||
return get().trustedSenders.includes(normalizedEmail);
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'settings-storage',
|
||||
|
||||
Reference in New Issue
Block a user