From d47e9755747cd6ef897ae9bb33e8b1727caa216f Mon Sep 17 00:00:00 2001 From: Leon Date: Wed, 16 Jul 2025 18:20:45 +0200 Subject: [PATCH] feat: toast for settings save --- .../components/letterfeed/SettingsDialog.tsx | 105 ++++++++++++++---- .../__tests__/SettingsDialog.test.tsx | 59 +++++++++- 2 files changed, 137 insertions(+), 27 deletions(-) diff --git a/frontend/components/letterfeed/SettingsDialog.tsx b/frontend/components/letterfeed/SettingsDialog.tsx index 324343c..0fc3438 100644 --- a/frontend/components/letterfeed/SettingsDialog.tsx +++ b/frontend/components/letterfeed/SettingsDialog.tsx @@ -11,9 +11,21 @@ import { import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Checkbox } from "@/components/ui/checkbox" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { Loader2, CheckCircle, XCircle } from "lucide-react" -import { Settings as AppSettings, SettingsCreate, updateSettings, testImapConnection } from "@/lib/api" +import { + Settings as AppSettings, + SettingsCreate, + updateSettings, + testImapConnection, +} from "@/lib/api" +import { toast } from "sonner" interface SettingsDialogProps { settings: AppSettings @@ -23,9 +35,19 @@ interface SettingsDialogProps { onSuccess: () => void } -export function SettingsDialog({ settings, folderOptions, isOpen, onOpenChange, onSuccess }: SettingsDialogProps) { - const [currentSettings, setCurrentSettings] = useState(null) - const [testConnectionStatus, setTestConnectionStatus] = useState<"idle" | "loading" | "success" | "error">("idle") +export function SettingsDialog({ + settings, + folderOptions, + isOpen, + onOpenChange, + onSuccess, +}: SettingsDialogProps) { + const [currentSettings, setCurrentSettings] = useState( + null + ) + const [testConnectionStatus, setTestConnectionStatus] = useState< + "idle" | "loading" | "success" | "error" + >("idle") const [testConnectionMessage, setTestConnectionMessage] = useState("") useEffect(() => { @@ -36,7 +58,10 @@ export function SettingsDialog({ settings, folderOptions, isOpen, onOpenChange, if (!currentSettings) return null - const handleSettingsChange = (key: K, value: SettingsCreate[K]) => { + const handleSettingsChange = ( + key: K, + value: SettingsCreate[K] + ) => { setCurrentSettings((prev) => (prev ? { ...prev, [key]: value } : null)) } @@ -44,10 +69,12 @@ export function SettingsDialog({ settings, folderOptions, isOpen, onOpenChange, if (!currentSettings) return try { await updateSettings(currentSettings) + toast.success("Settings saved successfully!") onOpenChange(false) onSuccess() } catch (error) { console.error("Failed to save settings:", error) + toast.error("Failed to save settings.") } } @@ -74,18 +101,24 @@ export function SettingsDialog({ settings, folderOptions, isOpen, onOpenChange, Settings - Fields are locked if they are set by environment variables. + + Fields are locked if they are set by environment variables. +
{/* IMAP Configuration */}
-

IMAP Configuration

+

+ IMAP Configuration +

handleSettingsChange("imap_server", e.target.value)} + onChange={(e) => + handleSettingsChange("imap_server", e.target.value) + } placeholder="imap.gmail.com" disabled={settings.locked_fields.includes("imap_server")} /> @@ -95,7 +128,9 @@ export function SettingsDialog({ settings, folderOptions, isOpen, onOpenChange, handleSettingsChange("imap_username", e.target.value)} + onChange={(e) => + handleSettingsChange("imap_username", e.target.value) + } placeholder="your-email@gmail.com" disabled={settings.locked_fields.includes("imap_username")} /> @@ -106,7 +141,9 @@ export function SettingsDialog({ settings, folderOptions, isOpen, onOpenChange, id="imap-password" type="password" value={currentSettings.imap_password} - onChange={(e) => handleSettingsChange("imap_password", e.target.value)} + onChange={(e) => + handleSettingsChange("imap_password", e.target.value) + } placeholder="Your password or app password" disabled={settings.locked_fields.includes("imap_password")} /> @@ -118,18 +155,26 @@ export function SettingsDialog({ settings, folderOptions, isOpen, onOpenChange, variant="outline" size="sm" > - {testConnectionStatus === "loading" && } + {testConnectionStatus === "loading" && ( + + )} Test Connection {testConnectionStatus !== "idle" && (
- {testConnectionStatus === "success" && } - {testConnectionStatus === "error" && } + {testConnectionStatus === "success" && ( + + )} + {testConnectionStatus === "error" && ( + + )} {testConnectionMessage}
)} @@ -138,12 +183,16 @@ export function SettingsDialog({ settings, folderOptions, isOpen, onOpenChange, {/* Email Processing */}
-

Email Processing

+

+ Email Processing +

handleSettingsChange("move_to_folder", value === "None" ? null : value)} + onValueChange={(value) => + handleSettingsChange( + "move_to_folder", + value === "None" ? null : value + ) + } disabled={settings.locked_fields.includes("move_to_folder")} > @@ -186,7 +240,12 @@ export function SettingsDialog({ settings, folderOptions, isOpen, onOpenChange, min="1" max="1440" value={currentSettings.email_check_interval} - onChange={(e) => handleSettingsChange("email_check_interval", Number.parseInt(e.target.value) || 15)} + onChange={(e) => + handleSettingsChange( + "email_check_interval", + Number.parseInt(e.target.value) || 15 + ) + } placeholder="15" disabled={settings.locked_fields.includes("email_check_interval")} /> @@ -195,7 +254,9 @@ export function SettingsDialog({ settings, folderOptions, isOpen, onOpenChange, handleSettingsChange("mark_as_read", !!checked)} + onCheckedChange={(checked) => + handleSettingsChange("mark_as_read", !!checked) + } disabled={settings.locked_fields.includes("mark_as_read")} />