From 7cd5643db1a7955d4a3ef991e11f94a70477a18e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 07:19:05 +0000 Subject: [PATCH] feat(ui): migrate alerting DynamicForm + email_event_settings to shadcn - alerting/dynamic_form.tsx: antd Form/Input/InputNumber/Button + @tremor/react Badge/Icon/Text/TableRow/TableCell/Switch/Button + @heroicons/react TrashIcon/CheckCircleIcon \u2192 native
with local state snapshot, shadcn Badge/Button/Input/Switch/TableCell/ TableRow, lucide Trash2/CheckCircle. Emits children expected to be rendered inside an ambient / parent. - email_events/email_event_settings.tsx: antd Typography/Divider/Spin/ Checkbox + @tremor/react Card/Text/Button \u2192 shadcn Card/Button/ Checkbox/Separator/Skeleton, plain

/

/

`/`` parent). + * + * Post phase-1 migration: antd Form replaced with a native form element + * driving a local state bag; no rhf/zod here because the field set is + * dynamic (driven by `alertingSettings`). + */ const DynamicForm: React.FC = ({ alertingSettings, handleInputChange, @@ -26,121 +42,121 @@ const DynamicForm: React.FC = ({ handleSubmit, premiumUser, }) => { - const [form] = Form.useForm(); + // Mirror the form state locally so we can submit a consolidated snapshot + // without depending on antd's Form.getFieldsValue(). + const [formValues, setFormValues] = useState>({}); - const onFinish = () => { - console.log(`INSIDE ONFINISH`); - const formData = form.getFieldsValue(); - const isEmpty = Object.entries(formData).every(([key, value]) => { - if (typeof value === "boolean") { - return false; // Boolean values are never considered empty - } + const updateLocal = (name: string, value: unknown) => { + setFormValues((prev) => ({ ...prev, [name]: value })); + }; + + const onSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const isEmpty = Object.entries(formValues).every(([, value]) => { + if (typeof value === "boolean") return false; return value === "" || value === null || value === undefined; }); - console.log(`formData: ${JSON.stringify(formData)}, isEmpty: ${isEmpty}`); if (!isEmpty) { - handleSubmit(formData); + handleSubmit(formValues); } else { console.log("Some form fields are empty."); } }; return ( - - {alertingSettings.map((value, index) => ( - - - {value.field_name} -

+ {alertingSettings.map((value, index) => { + const renderInput = () => { + if (value.field_type === "Integer") { + return ( + { + const v = e.target.value === "" ? null : Number(e.target.value); + handleInputChange(value.field_name, v); + updateLocal(value.field_name, v); + }} + /> + ); + } + if (value.field_type === "Boolean") { + return ( + { + handleInputChange(value.field_name, checked); + updateLocal(value.field_name, checked); + }} + /> + ); + } + return ( + { + handleInputChange(value.field_name, e.target.value); + updateLocal(value.field_name, e.target.value); }} - className="mt-1" - > - {value.field_description} -

-
- {value.premium_field ? ( - premiumUser ? ( - - - {value.field_type === "Integer" ? ( - handleInputChange(value.field_name, e)} - /> - ) : value.field_type === "Boolean" ? ( - handleInputChange(value.field_name, checked)} - /> - ) : ( - handleInputChange(value.field_name, e)} /> - )} - - - ) : ( - - - - ) - ) : ( - - - {value.field_type === "Integer" ? ( - handleInputChange(value.field_name, e)} - className="p-0" - /> - ) : value.field_type === "Boolean" ? ( - { - handleInputChange(value.field_name, checked); - form.setFieldsValue({ [value.field_name]: checked }); - }} - /> - ) : ( - handleInputChange(value.field_name, e)} /> - )} - - - )} - - {value.stored_in_db == true ? ( - - In DB - - ) : value.stored_in_db == false ? ( - In Config - ) : ( - Not Set - )} - - - handleResetField(value.field_name, index)}> - Reset - - -
- ))} + ) : ( + renderInput() + )} + + + {value.stored_in_db === true ? ( + + + In DB + + ) : value.stored_in_db === false ? ( + In Config + ) : ( + Not Set + )} + + + + + + ); + })}
- Update Settings +
- + ); }; diff --git a/ui/litellm-dashboard/src/components/email_events/email_event_settings.tsx b/ui/litellm-dashboard/src/components/email_events/email_event_settings.tsx index 8d0555f17bb..4b28209a765 100644 --- a/ui/litellm-dashboard/src/components/email_events/email_event_settings.tsx +++ b/ui/litellm-dashboard/src/components/email_events/email_event_settings.tsx @@ -1,41 +1,45 @@ import React, { useState, useEffect } from "react"; -import { Card, Text, Button } from "@tremor/react"; -import { Typography, Divider, Spin, Checkbox } from "antd"; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Separator } from "@/components/ui/separator"; +import { Skeleton } from "@/components/ui/skeleton"; import NotificationsManager from "../molecules/notifications_manager"; -import { getEmailEventSettings, updateEmailEventSettings, resetEmailEventSettings } from "../networking"; +import { + getEmailEventSettings, + updateEmailEventSettings, + resetEmailEventSettings, +} from "../networking"; import { EmailEvent } from "../../types"; import { EmailEventSetting } from "./types"; -const { Title } = Typography; - interface EmailEventSettingsProps { accessToken: string | null; } -const EmailEventSettings: React.FC = ({ accessToken }) => { +const EmailEventSettings: React.FC = ({ + accessToken, +}) => { const [loading, setLoading] = useState(true); const [eventSettings, setEventSettings] = useState([]); - // Fetch email event settings on component mount useEffect(() => { + const fetchEventSettings = async () => { + if (!accessToken) return; + setLoading(true); + try { + const response = await getEmailEventSettings(accessToken); + setEventSettings(response.settings); + } catch (error) { + console.error("Failed to fetch email event settings:", error); + NotificationsManager.fromBackend(error); + } finally { + setLoading(false); + } + }; fetchEventSettings(); }, [accessToken]); - const fetchEventSettings = async () => { - if (!accessToken) return; - - setLoading(true); - try { - const response = await getEmailEventSettings(accessToken); - setEventSettings(response.settings); - } catch (error) { - console.error("Failed to fetch email event settings:", error); - NotificationsManager.fromBackend(error); - } finally { - setLoading(false); - } - }; - const handleCheckboxChange = (event: EmailEvent, checked: boolean) => { const updatedSettings = eventSettings.map((setting) => setting.event === event ? { ...setting, enabled: checked } : setting, @@ -45,7 +49,6 @@ const EmailEventSettings: React.FC = ({ accessToken }) const handleSaveSettings = async () => { if (!accessToken) return; - try { await updateEmailEventSettings(accessToken, { settings: eventSettings }); NotificationsManager.success("Email event settings updated successfully"); @@ -57,27 +60,26 @@ const EmailEventSettings: React.FC = ({ accessToken }) const handleResetSettings = async () => { if (!accessToken) return; - try { await resetEmailEventSettings(accessToken); NotificationsManager.success("Email event settings reset to defaults"); - // Refresh settings after reset - fetchEventSettings(); + // Refresh + setLoading(true); + const response = await getEmailEventSettings(accessToken); + setEventSettings(response.settings); + setLoading(false); } catch (error) { console.error("Failed to reset email event settings:", error); NotificationsManager.fromBackend(error); } }; - // Helper function to get a description for each event type const getEventDescription = (event: EmailEvent): string => { - // Convert event name to a sentence with more context if (event.includes("Virtual Key Created")) { return "An email will be sent to the user when a new virtual key is created with their user ID"; } else if (event.includes("New User Invitation")) { return "An email will be sent to the email address of the user when a new user is created"; } else { - // Handle any other event type from the API const words = event .split(/(?=[A-Z])/) .join(" ") @@ -87,37 +89,54 @@ const EmailEventSettings: React.FC = ({ accessToken }) }; return ( - - Email Notifications - Select which events should trigger email notifications. - + +

Email Notifications

+

+ Select which events should trigger email notifications. +

+ {loading ? ( -
- +
+
) : (
{eventSettings.map((setting) => ( -
+
handleCheckboxChange(setting.event, e.target.checked)} + onCheckedChange={(checked) => + handleCheckboxChange(setting.event, !!checked) + } + className="mt-1" /> -
- {setting.event} -
{getEventDescription(setting.event)}
+
+ +
+ {getEventDescription(setting.event)} +
))}
)} -
+
-