From 59f170c19189325efc962c296df36ab5e5b6caa8 Mon Sep 17 00:00:00 2001 From: Rogee Date: Sun, 20 Sep 2026 17:14:13 +0800 Subject: [PATCH] refactor: use original dashboard UI primitives --- web/src/app/Layout.jsx | 4 +- web/src/app/LoginPage.jsx | 17 +- web/src/components/ui/NOTICE.md | 25 + web/src/components/ui/alert-dialog.tsx | 179 +++++ web/src/components/ui/alert.tsx | 60 ++ web/src/components/ui/badge.tsx | 46 ++ web/src/components/ui/card.tsx | 75 +++ web/src/components/ui/checkbox.tsx | 29 + web/src/components/ui/dialog.tsx | 142 ++++ web/src/components/ui/label.tsx | 19 + web/src/components/ui/select.tsx | 175 +++++ web/src/components/ui/switch.tsx | 33 + web/src/components/ui/table.tsx | 92 +++ web/src/components/ui/textarea.tsx | 18 + web/src/features/accounts/AccountEditPage.jsx | 266 ++++---- web/src/features/accounts/AccountsPage.jsx | 411 +++++------- .../competitors/CreatorCompetitorsPage.jsx | 313 ++++----- .../creator/settings/CreatorSettingsPage.jsx | 41 +- .../workbench/CreatorWorkbenchPage.jsx | 315 ++++----- .../environments/BrowserVersionsPage.jsx | 220 +++---- .../features/environments/BrowsersPage.jsx | 356 +++------- .../features/environments/GatewaysPage.jsx | 185 ++---- .../environments/NetworkExitsPage.jsx | 263 +++----- web/src/features/operations/AuditPage.jsx | 92 +-- web/src/features/operations/DraftPage.jsx | 61 +- web/src/features/operations/TasksPage.jsx | 82 ++- web/src/features/operations/TracePages.jsx | 13 +- web/src/router.tsx | 4 +- web/src/routes/_app.tsx | 2 +- web/src/shared/ui/ui.jsx | 611 ++---------------- web/src/styles/globals.css | 5 - web/src/test/setup.js | 6 + 32 files changed, 1981 insertions(+), 2179 deletions(-) create mode 100644 web/src/components/ui/NOTICE.md create mode 100644 web/src/components/ui/alert-dialog.tsx create mode 100644 web/src/components/ui/alert.tsx create mode 100644 web/src/components/ui/badge.tsx create mode 100644 web/src/components/ui/card.tsx create mode 100644 web/src/components/ui/checkbox.tsx create mode 100644 web/src/components/ui/dialog.tsx create mode 100644 web/src/components/ui/label.tsx create mode 100644 web/src/components/ui/select.tsx create mode 100644 web/src/components/ui/switch.tsx create mode 100644 web/src/components/ui/table.tsx create mode 100644 web/src/components/ui/textarea.tsx diff --git a/web/src/app/Layout.jsx b/web/src/app/Layout.jsx index 14a73ce..f6e9145 100644 --- a/web/src/app/Layout.jsx +++ b/web/src/app/Layout.jsx @@ -1,5 +1,5 @@ import { Link, Outlet, useLocation, useNavigate } from '../shared/router.jsx' -import { cn } from '../shared/ui/ui.jsx' +import { cn } from '../lib/utils' import { Sidebar, SidebarContent, @@ -128,7 +128,7 @@ export function CreatorHubLayout({ children }) { - + 跳到主要内容
diff --git a/web/src/app/LoginPage.jsx b/web/src/app/LoginPage.jsx index 5ed03d5..89f2b74 100644 --- a/web/src/app/LoginPage.jsx +++ b/web/src/app/LoginPage.jsx @@ -1,6 +1,9 @@ import { useState } from "react"; import { useNavigate } from "../shared/router.jsx"; -import { Alert, Button, Field, Input } from "../shared/ui/ui.jsx"; +import { Alert } from "../components/ui/alert"; +import { Button } from "../components/ui/button"; +import { Input } from "../components/ui/input"; +import { Field } from "../shared/ui/ui.jsx"; // 开发阶段:本地保存 Basic 凭证,仅在请求层自动附带。开发代理(vite)不注入 Authorization。 export function LoginPage() { @@ -39,8 +42,8 @@ export function LoginPage() { } return ( -
-
+
+
diff --git a/web/src/components/ui/NOTICE.md b/web/src/components/ui/NOTICE.md new file mode 100644 index 0000000..ce31cff --- /dev/null +++ b/web/src/components/ui/NOTICE.md @@ -0,0 +1,25 @@ +The UI primitives in this directory are adapted from +https://github.com/kiranism/tanstack-start-dashboard +under the MIT License. + +MIT License + +Copyright (c) 2025 Kiranism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/web/src/components/ui/alert-dialog.tsx b/web/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..c6d2b09 --- /dev/null +++ b/web/src/components/ui/alert-dialog.tsx @@ -0,0 +1,179 @@ +'use client'; + +import * as React from 'react'; +import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'; + +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; + +function AlertDialog({ ...props }: React.ComponentProps) { + return ; +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ; +} + +function AlertDialogPortal({ ...props }: React.ComponentProps) { + return ; +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogContent({ + className, + size = 'default', + ...props +}: React.ComponentProps & { + size?: 'default' | 'sm'; +}) { + return ( + + + + + ); +} + +function AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogMedia({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function AlertDialogAction({ + className, + variant = 'default', + size = 'default', + ...props +}: React.ComponentProps & + Pick, 'variant' | 'size'>) { + return ( + + ); +} + +function AlertDialogCancel({ + className, + variant = 'outline', + size = 'default', + ...props +}: React.ComponentProps & + Pick, 'variant' | 'size'>) { + return ( + + ); +} + +export { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogMedia, + AlertDialogOverlay, + AlertDialogPortal, + AlertDialogTitle, + AlertDialogTrigger +}; diff --git a/web/src/components/ui/alert.tsx b/web/src/components/ui/alert.tsx new file mode 100644 index 0000000..5506082 --- /dev/null +++ b/web/src/components/ui/alert.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; + +import { cn } from '@/lib/utils'; + +const alertVariants = cva( + 'relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', + { + variants: { + variant: { + default: 'bg-card text-card-foreground', + destructive: + 'bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current' + } + }, + defaultVariants: { + variant: 'default' + } + } +); + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<'div'> & VariantProps) { + return ( +
+ ); +} + +function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function AlertDescription({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +export { Alert, AlertTitle, AlertDescription }; diff --git a/web/src/components/ui/badge.tsx b/web/src/components/ui/badge.tsx new file mode 100644 index 0000000..f0a176d --- /dev/null +++ b/web/src/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { cva, type VariantProps } from 'class-variance-authority'; +import { Slot } from 'radix-ui'; + +import { cn } from '@/lib/utils'; + +const badgeVariants = cva( + 'inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90', + secondary: 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', + destructive: + 'bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90', + outline: + 'border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + ghost: '[a&]:hover:bg-accent [a&]:hover:text-accent-foreground', + link: 'text-primary underline-offset-4 [a&]:hover:underline' + } + }, + defaultVariants: { + variant: 'default' + } + } +); + +function Badge({ + className, + variant = 'default', + asChild = false, + ...props +}: React.ComponentProps<'span'> & VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : 'span'; + + return ( + + ); +} + +export { Badge, badgeVariants }; diff --git a/web/src/components/ui/card.tsx b/web/src/components/ui/card.tsx new file mode 100644 index 0000000..43d43dc --- /dev/null +++ b/web/src/components/ui/card.tsx @@ -0,0 +1,75 @@ +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +function Card({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function CardHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function CardTitle({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function CardDescription({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function CardAction({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function CardContent({ className, ...props }: React.ComponentProps<'div'>) { + return
; +} + +function CardFooter({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent }; diff --git a/web/src/components/ui/checkbox.tsx b/web/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..aec2332 --- /dev/null +++ b/web/src/components/ui/checkbox.tsx @@ -0,0 +1,29 @@ +'use client'; + +import * as React from 'react'; +import { CheckIcon } from 'lucide-react'; +import { Checkbox as CheckboxPrimitive } from 'radix-ui'; + +import { cn } from '@/lib/utils'; + +function Checkbox({ className, ...props }: React.ComponentProps) { + return ( + + + + + + ); +} + +export { Checkbox }; diff --git a/web/src/components/ui/dialog.tsx b/web/src/components/ui/dialog.tsx new file mode 100644 index 0000000..734b867 --- /dev/null +++ b/web/src/components/ui/dialog.tsx @@ -0,0 +1,142 @@ +import * as React from 'react'; +import { XIcon } from 'lucide-react'; +import { Dialog as DialogPrimitive } from 'radix-ui'; + +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; + +function Dialog({ ...props }: React.ComponentProps) { + return ; +} + +function DialogTrigger({ ...props }: React.ComponentProps) { + return ; +} + +function DialogPortal({ ...props }: React.ComponentProps) { + return ; +} + +function DialogClose({ ...props }: React.ComponentProps) { + return ; +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean; +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ); +} + +function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) { + return ( +
+ ); +} + +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<'div'> & { + showCloseButton?: boolean; +}) { + return ( +
+ {children} + {showCloseButton && ( + + + + )} +
+ ); +} + +function DialogTitle({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger +}; diff --git a/web/src/components/ui/label.tsx b/web/src/components/ui/label.tsx new file mode 100644 index 0000000..9a846a6 --- /dev/null +++ b/web/src/components/ui/label.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { Label as LabelPrimitive } from 'radix-ui'; + +import { cn } from '@/lib/utils'; + +function Label({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +export { Label }; diff --git a/web/src/components/ui/select.tsx b/web/src/components/ui/select.tsx new file mode 100644 index 0000000..88ea3a2 --- /dev/null +++ b/web/src/components/ui/select.tsx @@ -0,0 +1,175 @@ +'use client'; + +import * as React from 'react'; +import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react'; +import { Select as SelectPrimitive } from 'radix-ui'; + +import { cn } from '@/lib/utils'; + +function Select({ ...props }: React.ComponentProps) { + return ; +} + +function SelectGroup({ ...props }: React.ComponentProps) { + return ; +} + +function SelectValue({ ...props }: React.ComponentProps) { + return ; +} + +function SelectTrigger({ + className, + size = 'default', + children, + ...props +}: React.ComponentProps & { + size?: 'sm' | 'default'; +}) { + return ( + + {children} + + + + + ); +} + +function SelectContent({ + className, + children, + position = 'item-aligned', + align = 'center', + ...props +}: React.ComponentProps) { + return ( + + + + + {children} + + + + + ); +} + +function SelectLabel({ className, ...props }: React.ComponentProps) { + return ( + + ); +} + +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ); +} + +function SelectSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ); +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ); +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue +}; diff --git a/web/src/components/ui/switch.tsx b/web/src/components/ui/switch.tsx new file mode 100644 index 0000000..38d2b97 --- /dev/null +++ b/web/src/components/ui/switch.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import { Switch as SwitchPrimitive } from 'radix-ui'; + +import { cn } from '@/lib/utils'; + +function Switch({ + className, + size = 'default', + ...props +}: React.ComponentProps & { + size?: 'sm' | 'default'; +}) { + return ( + + + + ); +} + +export { Switch }; diff --git a/web/src/components/ui/table.tsx b/web/src/components/ui/table.tsx new file mode 100644 index 0000000..78ec59b --- /dev/null +++ b/web/src/components/ui/table.tsx @@ -0,0 +1,92 @@ +'use client'; + +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +function Table({ className, ...props }: React.ComponentProps<'table'>) { + return ( +
+ + + ); +} + +function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) { + return ; +} + +function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) { + return ( + + ); +} + +function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) { + return ( + tr]:last:border-b-0', className)} + {...props} + /> + ); +} + +function TableRow({ className, ...props }: React.ComponentProps<'tr'>) { + return ( + + ); +} + +function TableHead({ className, ...props }: React.ComponentProps<'th'>) { + return ( +
[role=checkbox]]:translate-y-[2px]', + className + )} + {...props} + /> + ); +} + +function TableCell({ className, ...props }: React.ComponentProps<'td'>) { + return ( + [role=checkbox]]:translate-y-[2px]', + className + )} + {...props} + /> + ); +} + +function TableCaption({ className, ...props }: React.ComponentProps<'caption'>) { + return ( +
+ ); +} + +export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }; diff --git a/web/src/components/ui/textarea.tsx b/web/src/components/ui/textarea.tsx new file mode 100644 index 0000000..712cc3f --- /dev/null +++ b/web/src/components/ui/textarea.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) { + return ( +