diff --git a/apps/mail/app/(routes)/settings/general/page.tsx b/apps/mail/app/(routes)/settings/general/page.tsx index 0bf2e5692d..25cbda55a4 100644 --- a/apps/mail/app/(routes)/settings/general/page.tsx +++ b/apps/mail/app/(routes)/settings/general/page.tsx @@ -27,13 +27,14 @@ import { saveUserSettings } from '@/actions/settings'; import { getBrowserTimezone } from '@/lib/timezones'; import { Textarea } from '@/components/ui/textarea'; import { useSettings } from '@/hooks/use-settings'; +import { Globe, Clock, XIcon } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Switch } from '@/components/ui/switch'; -import { Globe, Clock } from 'lucide-react'; import { changeLocale } from '@/i18n/utils'; import { cn } from '@/lib/utils'; import { toast } from 'sonner'; import * as z from 'zod'; +import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; const formSchema = z.object({ language: z.enum(locales as [string, ...string[]]), @@ -41,6 +42,7 @@ const formSchema = z.object({ dynamicContent: z.boolean(), externalImages: z.boolean(), customPrompt: z.string(), + trustedSenders: z.string().array(), signature: z.object({ enabled: z.boolean(), content: z.string(), @@ -140,6 +142,7 @@ export default function GeneralPage() { dynamicContent: false, externalImages: true, customPrompt: '', + trustedSenders: [], signature: { enabled: false, content: '', @@ -148,6 +151,8 @@ export default function GeneralPage() { }, }); + const externalImages = form.watch("externalImages") + useEffect(() => { if (settings) { form.reset(settings); @@ -229,7 +234,7 @@ export default function GeneralPage() { )} /> -
+
{/* )} /> + field.value.length > 0 && !externalImages ? ( + +
+ + {t('pages.settings.general.trustedSenders')} + + + {t('pages.settings.general.trustedSendersDescription')} + +
+ + {field.value.map((senderEmail) => ( +
+ {senderEmail} + + + + + + {t('common.actions.remove')} + + +
+ ))} +
+
+ ) : <>} + />
{emailData?.decodedBody ? ( - + ) : (
(null); const [height, setHeight] = useState(300); const { resolvedTheme } = useTheme(); + const onTrustSender = useCallback(async (senderEmail: string) => { + setImagesEnabled(true); + + const existingSettings = settings ?? { + ...defaultUserSettings, + timezone: getBrowserTimezone(), + }; + + const { success } = await saveUserSettings({ + ...existingSettings, + trustedSenders: settings?.trustedSenders + ? settings.trustedSenders.concat(senderEmail) + : [senderEmail], + }); + + if (!success) { + toast.error('Failed to trust sender'); + } else { + mutate(); + } + }, [settings, mutate]); + const iframeDoc = useMemo(() => template(html, imagesEnabled), [html, imagesEnabled]); const t = useTranslations(); @@ -64,12 +93,18 @@ export function MailIframe({ html }: { html: string }) { {!imagesEnabled && !settings?.externalImages && (

{t('common.actions.hiddenImagesWarning')}

-

setImagesEnabled(!imagesEnabled)} className="ml-2 cursor-pointer underline" > {imagesEnabled ? t('common.actions.disableImages') : t('common.actions.showImages')} -

+ +
)} {/* {!loaded && ( diff --git a/apps/mail/locales/en.json b/apps/mail/locales/en.json index ced40f6d8a..a40ca4066a 100644 --- a/apps/mail/locales/en.json +++ b/apps/mail/locales/en.json @@ -34,8 +34,10 @@ "hiddenImagesWarning": "Images are hidden by default for security reasons.", "showImages": "Show Images", "disableImages": "Hide Images", + "trustSender": "Trust Sender", "cancel": "Cancel", - "save": "Save" + "save": "Save", + "remove": "Remove" }, "themes": { "dark": "Dark", @@ -304,6 +306,8 @@ "dynamicContentDescription": "Allow emails to display dynamic content.", "externalImages": "Display External Images", "externalImagesDescription": "Allow emails to display images from external sources.", + "trustedSenders": "Trusted Senders", + "trustedSendersDescription": "Always display images for these senders.", "languageChangedTo": "Language changed to {locale}", "customPrompt": "Custom AI Prompt", "customPromptPlaceholder": "Enter your custom prompt for the AI...", diff --git a/packages/db/src/user_settings_default.ts b/packages/db/src/user_settings_default.ts index d9ed86921a..b43b56ed1c 100644 --- a/packages/db/src/user_settings_default.ts +++ b/packages/db/src/user_settings_default.ts @@ -6,12 +6,13 @@ export const defaultUserSettings = { dynamicContent: false, externalImages: true, customPrompt: "", + trustedSenders: [], signature: { enabled: false, content: "", includeByDefault: true, }, -}; +} satisfies UserSettings; export const userSettingsSchema = z.object({ language: z.string(), @@ -19,6 +20,7 @@ export const userSettingsSchema = z.object({ dynamicContent: z.boolean(), externalImages: z.boolean(), customPrompt: z.string(), + trustedSenders: z.string().array(), signature: z.object({ enabled: z.boolean(), content: z.string(), @@ -26,4 +28,4 @@ export const userSettingsSchema = z.object({ }), }); -export type UserSettings = typeof defaultUserSettings; \ No newline at end of file +export type UserSettings = z.infer \ No newline at end of file