refactor(ui): migrate budgets, skills and ui-theme to shadcn

Replaces antd and Tremor with the installed shadcn primitives on the three
route-exclusive panels: Tremor tabs, buttons and text on budgets; the antd
delete Modal and Tremor button on skills; the Tremor card, inputs and buttons
on ui-theme.

Markup only, no behaviour change. The characterisation tests added in the
previous commit are untouched and stay green, and the ui-theme inputs now
carry real label associations.

Shared components stay on antd; they are reached by other routes and are
migrated separately. The form-bearing files on these routes are left alone.
This commit is contained in:
Yuneng Jiang 2026-07-23 22:40:25 -07:00
parent 212421207e
commit 39f0b56502
No known key found for this signature in database
4 changed files with 141 additions and 115 deletions

View file

@ -144,9 +144,6 @@
"src/app/(dashboard)/budgets/_components/budget_panel.tsx": {
"local/filename-pascal-case": {
"count": 1
},
"no-restricted-imports": {
"count": 1
}
},
"src/app/(dashboard)/budgets/_components/edit_budget_modal.tsx": {
@ -1840,9 +1837,6 @@
}
},
"src/app/(dashboard)/skills/_components/ClaudeCodePluginsPanel.tsx": {
"no-restricted-imports": {
"count": 2
},
"react-hooks/set-state-in-effect": {
"count": 1
}
@ -1888,9 +1882,6 @@
}
},
"src/app/(dashboard)/ui-theme/UIThemeSettings.tsx": {
"no-restricted-imports": {
"count": 1
},
"no-restricted-syntax": {
"count": 3
},

View file

@ -3,9 +3,10 @@
*
*/
import { Button, Tab, TabGroup, TabList, TabPanel, TabPanels, Text } from "@tremor/react";
import React, { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { useBudgets, useDeleteBudget, budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets";
@ -73,76 +74,82 @@ const BudgetPanel: React.FC<BudgetSettingsPageProps> = ({ accessToken }) => {
return (
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
{canModify && (
<Button size="sm" variant="primary" className="mb-2" onClick={() => setIsCreateModelVisible(true)}>
<Button size="sm" className="mb-2" onClick={() => setIsCreateModelVisible(true)}>
+ Create Budget
</Button>
)}
<TabGroup>
<TabList>
<Tab>Budgets</Tab>
<Tab>Examples</Tab>
</TabList>
<TabPanels>
<TabPanel>
<div className="mt-6">
<BudgetModal isModalVisible={isCreateModelVisible} setIsModalVisible={setIsCreateModelVisible} />
{selectedBudget && (
<EditBudgetModal
isModalVisible={isEditModalVisible}
setIsModalVisible={setIsEditModalVisible}
existingBudget={selectedBudget}
/>
)}
<Text className="mb-4">Create a budget to assign to customers.</Text>
<BudgetTable
budgets={budgetList}
isLoading={isLoading}
canModify={canModify}
onEditClick={handleEditCall}
onDeleteClick={handleDeleteClick}
<Tabs defaultValue="budgets">
<TabsList variant="line" className="h-auto w-full justify-start rounded-none border-b p-0">
<TabsTrigger value="budgets" className="flex-none rounded-none px-4 py-2">
Budgets
</TabsTrigger>
<TabsTrigger value="examples" className="flex-none rounded-none px-4 py-2">
Examples
</TabsTrigger>
</TabsList>
<TabsContent value="budgets">
<div className="mt-6">
<BudgetModal isModalVisible={isCreateModelVisible} setIsModalVisible={setIsCreateModelVisible} />
{selectedBudget && (
<EditBudgetModal
isModalVisible={isEditModalVisible}
setIsModalVisible={setIsEditModalVisible}
existingBudget={selectedBudget}
/>
<DeleteResourceModal
isOpen={isDeleteModalVisible}
title="Delete Budget?"
message="Are you sure you want to delete this budget? This action cannot be undone."
resourceInformationTitle="Budget Information"
resourceInformation={[
{ label: "Budget ID", value: selectedBudget?.budget_id, code: true },
{ label: "Max Budget", value: selectedBudget?.max_budget },
{ label: "TPM", value: selectedBudget?.tpm_limit },
{ label: "RPM", value: selectedBudget?.rpm_limit },
]}
onCancel={handleDeleteCancel}
onOk={handleDeleteConfirm}
confirmLoading={deleteBudget.isPending}
/>
</div>
</TabPanel>
<TabPanel>
<div className="mt-6">
<Text className="text-base">How to use budget id</Text>
<TabGroup>
<TabList>
<Tab>Assign Budget to Customer</Tab>
<Tab>Test it (Curl)</Tab>
<Tab>Test it (OpenAI SDK)</Tab>
</TabList>
<TabPanels>
<TabPanel>
<SyntaxHighlighter language="bash">{CREATE_END_USER_CURL_COMMAND}</SyntaxHighlighter>
</TabPanel>
<TabPanel>
<SyntaxHighlighter language="bash">{CHAT_COMPLETIONS_CURL_COMMAND}</SyntaxHighlighter>
</TabPanel>
<TabPanel>
<SyntaxHighlighter language="python">{OPENAI_SDK_PYTHON_CODE}</SyntaxHighlighter>
</TabPanel>
</TabPanels>
</TabGroup>
</div>
</TabPanel>
</TabPanels>
</TabGroup>
)}
<p className="mb-4 text-sm text-muted-foreground">Create a budget to assign to customers.</p>
<BudgetTable
budgets={budgetList}
isLoading={isLoading}
canModify={canModify}
onEditClick={handleEditCall}
onDeleteClick={handleDeleteClick}
/>
<DeleteResourceModal
isOpen={isDeleteModalVisible}
title="Delete Budget?"
message="Are you sure you want to delete this budget? This action cannot be undone."
resourceInformationTitle="Budget Information"
resourceInformation={[
{ label: "Budget ID", value: selectedBudget?.budget_id, code: true },
{ label: "Max Budget", value: selectedBudget?.max_budget },
{ label: "TPM", value: selectedBudget?.tpm_limit },
{ label: "RPM", value: selectedBudget?.rpm_limit },
]}
onCancel={handleDeleteCancel}
onOk={handleDeleteConfirm}
confirmLoading={deleteBudget.isPending}
/>
</div>
</TabsContent>
<TabsContent value="examples">
<div className="mt-6">
<p className="text-base text-muted-foreground">How to use budget id</p>
<Tabs defaultValue="assign-budget">
<TabsList variant="line" className="h-auto w-full justify-start rounded-none border-b p-0">
<TabsTrigger value="assign-budget" className="flex-none rounded-none px-4 py-2">
Assign Budget to Customer
</TabsTrigger>
<TabsTrigger value="curl" className="flex-none rounded-none px-4 py-2">
Test it (Curl)
</TabsTrigger>
<TabsTrigger value="openai-sdk" className="flex-none rounded-none px-4 py-2">
Test it (OpenAI SDK)
</TabsTrigger>
</TabsList>
<TabsContent value="assign-budget">
<SyntaxHighlighter language="bash">{CREATE_END_USER_CURL_COMMAND}</SyntaxHighlighter>
</TabsContent>
<TabsContent value="curl">
<SyntaxHighlighter language="bash">{CHAT_COMPLETIONS_CURL_COMMAND}</SyntaxHighlighter>
</TabsContent>
<TabsContent value="openai-sdk">
<SyntaxHighlighter language="python">{OPENAI_SDK_PYTHON_CODE}</SyntaxHighlighter>
</TabsContent>
</Tabs>
</div>
</TabsContent>
</Tabs>
</div>
);
};

View file

@ -1,6 +1,14 @@
import React, { useState, useEffect } from "react";
import { Button } from "@tremor/react";
import { Modal } from "antd";
import { Button } from "@/components/ui/button";
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { getClaudeCodePluginsList, deleteClaudeCodePlugin } from "@/components/networking";
import AddPluginForm from "./add_plugin_form";
import PluginTable from "./PluginTable";
@ -115,20 +123,28 @@ const ClaudeCodePluginsPanel: React.FC<ClaudeCodePluginsPanelProps> = ({ accessT
/>
{pluginToDelete && (
<Modal
title="Delete Skill"
open={pluginToDelete !== null}
onOk={handleDeleteConfirm}
onCancel={() => setPluginToDelete(null)}
confirmLoading={isDeleting}
okText="Delete"
okButtonProps={{ danger: true }}
<AlertDialog
open
onOpenChange={(open) => {
if (!open) setPluginToDelete(null);
}}
>
<p>
Are you sure you want to delete skill: <strong>{pluginToDelete.displayName}</strong>?
</p>
<p>This action cannot be undone.</p>
</Modal>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Skill</AlertDialogTitle>
<AlertDialogDescription>
Are you sure you want to delete skill: <strong>{pluginToDelete.displayName}</strong>?
</AlertDialogDescription>
<p className="text-sm text-muted-foreground">This action cannot be undone.</p>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button variant="destructive" onClick={handleDeleteConfirm} disabled={isDeleting}>
Delete
</Button>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
</div>
);

View file

@ -1,5 +1,9 @@
import React, { useState, useEffect } from "react";
import { Card, Title, Text, TextInput, Button } from "@tremor/react";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
import { useTheme } from "@/contexts/ThemeContext";
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
@ -113,50 +117,58 @@ const UIThemeSettings: React.FC<UIThemeSettingsProps> = ({ userID, userRole, acc
return (
<div className="w-full mx-auto max-w-4xl px-6 py-8">
<div className="mb-8">
<Title className="text-2xl font-bold mb-2">UI Theme Customization</Title>
<Text className="text-gray-600">Customize your LiteLLM admin dashboard with a custom logo and favicon.</Text>
<h1 className="mb-2 text-2xl font-bold">UI Theme Customization</h1>
<p className="text-sm text-muted-foreground">
Customize your LiteLLM admin dashboard with a custom logo and favicon.
</p>
</div>
<Card className="shadow-xs p-6">
<div className="space-y-6">
<Card>
<CardContent className="space-y-6">
<div>
<Text className="text-sm font-medium text-gray-700 mb-2 block">Custom Logo URL</Text>
<TextInput
<Label htmlFor="ui-theme-logo-url" className="mb-2">
Custom Logo URL
</Label>
<Input
id="ui-theme-logo-url"
placeholder="https://example.com/logo.png"
value={logoUrlInput}
onValueChange={(v) => {
setLogoUrlInput(v);
setLogoUrl(v || null);
onChange={(event) => {
setLogoUrlInput(event.target.value);
setLogoUrl(event.target.value || null);
}}
className="w-full"
/>
<Text className="text-xs text-gray-500 mt-1">
<p className="mt-1 text-xs text-muted-foreground">
Enter a URL for your custom logo or leave empty for default
</Text>
</p>
</div>
<div>
<Text className="text-sm font-medium text-gray-700 mb-2 block">Custom Favicon URL</Text>
<TextInput
<Label htmlFor="ui-theme-favicon-url" className="mb-2">
Custom Favicon URL
</Label>
<Input
id="ui-theme-favicon-url"
placeholder="https://example.com/favicon.ico"
value={faviconUrlInput}
onValueChange={(v) => {
setFaviconUrlInput(v);
setFaviconUrl(v || null);
onChange={(event) => {
setFaviconUrlInput(event.target.value);
setFaviconUrl(event.target.value || null);
}}
className="w-full"
/>
<Text className="text-xs text-gray-500 mt-1">
<p className="mt-1 text-xs text-muted-foreground">
Enter a URL for your custom favicon (.ico, .png, or .svg) or leave empty for default
</Text>
</p>
</div>
<div className="flex gap-3 pt-4">
<Button onClick={handleSave} loading={loading} disabled={loading} color="indigo">
<Button onClick={handleSave} disabled={loading}>
{loading && <UiLoadingSpinner className="size-4" />}
Save Changes
</Button>
<Button onClick={handleReset} loading={loading} disabled={loading} variant="secondary" color="gray">
<Button variant="outline" onClick={handleReset} disabled={loading}>
{loading && <UiLoadingSpinner className="size-4" />}
Reset to Default
</Button>
</div>
</div>
</CardContent>
</Card>
</div>
);