import { create } from "@bufbuild/protobuf"; import { isEqual } from "lodash-es"; import type { ChangeEvent } from "react"; import { useEffect, useMemo, useState } from "react"; import { toast } from "react-hot-toast"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { instanceServiceClient } from "@/connect"; import { useInstance } from "@/contexts/InstanceContext"; import useCurrentUser from "@/hooks/useCurrentUser"; import { handleError } from "@/lib/error"; import { InstanceSetting_Key, InstanceSetting_NotificationSetting, InstanceSetting_NotificationSetting_EmailSetting, InstanceSetting_NotificationSetting_EmailSettingSchema, InstanceSetting_NotificationSettingSchema, InstanceSettingSchema, TestInstanceEmailSettingRequestSchema, } from "@/types/proto/api/v1/instance_service_pb"; import { useTranslate } from "@/utils/i18n"; import SettingGroup from "./SettingGroup"; import SettingRow from "./SettingRow"; import SettingSection from "./SettingSection"; import useInstanceSettingUpdater, { buildInstanceSettingName } from "./useInstanceSettingUpdater"; const defaultEmailSetting = () => create(InstanceSetting_NotificationSetting_EmailSettingSchema, { smtpPort: 587, useTls: true, }); const isEmailSettingConfigured = (email?: InstanceSetting_NotificationSetting_EmailSetting) => { return Boolean( email && (email.enabled || email.smtpHost.trim() || email.smtpPort > 0 || email.smtpUsername.trim() || email.fromEmail.trim() || email.fromName.trim() || email.replyTo.trim() || email.useTls || email.useSsl), ); }; const normalizeNotificationSetting = (setting: InstanceSetting_NotificationSetting) => create(InstanceSetting_NotificationSettingSchema, { ...setting, email: isEmailSettingConfigured(setting.email) ? setting.email : defaultEmailSetting(), }); type Requirement = "required" | "optional" | "gmail" | "recommended"; const FieldLabel = ({ label, requirementLabel, requirement }: { label: string; requirementLabel: string; requirement: Requirement }) => ( {label} {requirementLabel} ); const NotificationSection = () => { const t = useTranslate(); const saveInstanceSetting = useInstanceSettingUpdater(); const currentUser = useCurrentUser(); const { notificationSetting: originalSetting } = useInstance(); const normalizedOriginalSetting = useMemo(() => normalizeNotificationSetting(originalSetting), [originalSetting]); const [notificationSetting, setNotificationSetting] = useState(normalizedOriginalSetting); const [isTestingEmail, setIsTestingEmail] = useState(false); useEffect(() => { setNotificationSetting(normalizedOriginalSetting); }, [normalizedOriginalSetting]); const emailSetting = notificationSetting.email ?? defaultEmailSetting(); const hasExistingEmailSetting = isEmailSettingConfigured(originalSetting.email); const requirementLabel = (requirement: Requirement) => t(`setting.notification.requirement-${requirement}`); const fieldLabel = (label: string, requirement: Requirement) => ( ); const updateEmailSetting = (partial: Partial) => { const nextEmail = create(InstanceSetting_NotificationSetting_EmailSettingSchema, { ...emailSetting, ...partial, }); setNotificationSetting( create(InstanceSetting_NotificationSettingSchema, { ...notificationSetting, email: nextEmail, }), ); }; const handlePortChanged = (event: ChangeEvent) => { const port = parseInt(event.target.value); updateEmailSetting({ smtpPort: Number.isNaN(port) ? 0 : port }); }; const handleUseTLSChanged = (checked: boolean) => { updateEmailSetting({ useTls: checked, useSsl: checked ? false : emailSetting.useSsl }); }; const handleUseSSLChanged = (checked: boolean) => { updateEmailSetting({ useSsl: checked, useTls: checked ? false : emailSetting.useTls }); }; const allowSave = useMemo(() => { if (isEqual(normalizedOriginalSetting, notificationSetting)) { return false; } const email = notificationSetting.email; if (!email?.enabled) { return true; } return Boolean(email.smtpHost.trim() && email.smtpPort > 0 && email.smtpPort <= 65535 && email.fromEmail.trim()); }, [notificationSetting, normalizedOriginalSetting]); const canTestEmail = useMemo(() => { return Boolean( currentUser?.email && emailSetting.smtpHost.trim() && emailSetting.smtpPort > 0 && emailSetting.smtpPort <= 65535 && emailSetting.fromEmail.trim(), ); }, [currentUser?.email, emailSetting.fromEmail, emailSetting.smtpHost, emailSetting.smtpPort]); const saveNotificationSetting = async () => { await saveInstanceSetting({ key: InstanceSetting_Key.NOTIFICATION, setting: create(InstanceSettingSchema, { name: buildInstanceSettingName(InstanceSetting_Key.NOTIFICATION), value: { case: "notificationSetting", value: notificationSetting, }, }), errorContext: "Update notification settings", }); }; const testEmailSetting = async () => { if (!currentUser?.email) { toast.error(t("setting.notification.test-email-missing-recipient")); return; } setIsTestingEmail(true); try { await instanceServiceClient.testInstanceEmailSetting( create(TestInstanceEmailSettingRequestSchema, { email: emailSetting, recipientEmail: currentUser.email, }), ); toast.success(t("setting.notification.test-email-success", { email: currentUser.email })); } catch (error: unknown) { await handleError(error, toast.error, { context: "Send test email" }); } finally { setIsTestingEmail(false); } }; return ( updateEmailSetting({ enabled })} /> updateEmailSetting({ smtpHost: e.target.value })} /> updateEmailSetting({ smtpUsername: e.target.value })} /> updateEmailSetting({ smtpPassword: e.target.value })} /> updateEmailSetting({ fromEmail: e.target.value })} /> updateEmailSetting({ fromName: e.target.value })} /> updateEmailSetting({ replyTo: e.target.value })} />
); }; export default NotificationSection;