feat:v1.0.0

This commit is contained in:
liqupan
2026-02-09 21:54:32 +08:00
parent 8f19377517
commit 68d25581e8
49 changed files with 1522 additions and 528 deletions

View File

@@ -0,0 +1,4 @@
{
"orgId": "team_k7XASAgXeoo4C8RJJzrkEMK3",
"projectId": "prj_E7eJmO5ydZwNTgA5OH1ECjr4JnO9"
}

View File

@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link
@@ -29,37 +29,37 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Primary Meta Tags -->
<title>Shadcn Admin</title>
<meta name="title" content="Shadcn Admin" />
<title>WEIAI 角色卡</title>
<meta name="title" content="WEIAI 角色卡" />
<meta
name="description"
content="Admin Dashboard UI built with Shadcn and Vite."
content="WEIAI 角色卡管理与配置控制台。"
/>
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://shadcn-admin.netlify.app" />
<meta property="og:title" content="Shadcn Admin" />
<meta property="og:url" content="/" />
<meta property="og:title" content="WEIAI 角色卡" />
<meta
property="og:description"
content="Admin Dashboard UI built with Shadcn and Vite."
content="WEIAI 角色卡管理与配置控制台。"
/>
<meta
property="og:image"
content="https://shadcn-admin.netlify.app/images/shadcn-admin.png"
content="/images/favicon.png"
/>
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://shadcn-admin.netlify.app" />
<meta property="twitter:title" content="Shadcn Admin" />
<meta property="twitter:url" content="/" />
<meta property="twitter:title" content="WEIAI 角色卡" />
<meta
property="twitter:description"
content="Admin Dashboard UI built with Shadcn and Vite."
content="WEIAI 角色卡管理与配置控制台。"
/>
<meta
property="twitter:image"
content="https://shadcn-admin.netlify.app/images/shadcn-admin.png"
content="/images/favicon.png"
/>
<!-- font family -->

View File

@@ -4,7 +4,7 @@ import { cn } from '@/lib/utils'
export function Logo({ className, ...props }: SVGProps<SVGSVGElement>) {
return (
<svg
id='shadcn-admin-logo'
id='weiai-logo'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
height='24'
@@ -17,7 +17,7 @@ export function Logo({ className, ...props }: SVGProps<SVGSVGElement>) {
className={cn('size-6', className)}
{...props}
>
<title>Shadcn-Admin</title>
<title>WEIAI</title>
<path d='M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3' />
</svg>
)

View File

@@ -30,10 +30,10 @@ export function CommandMenu() {
return (
<CommandDialog modal open={open} onOpenChange={setOpen}>
<CommandInput placeholder='Type a command or search...' />
<CommandInput placeholder='输入命令或搜索...' />
<CommandList>
<ScrollArea type='hover' className='h-72 pe-1'>
<CommandEmpty>No results found.</CommandEmpty>
<CommandEmpty></CommandEmpty>
{sidebarData.navGroups.map((group) => (
<CommandGroup key={group.title} heading={group.title}>
{group.items.map((navItem, i) => {
@@ -71,17 +71,17 @@ export function CommandMenu() {
</CommandGroup>
))}
<CommandSeparator />
<CommandGroup heading='Theme'>
<CommandGroup heading='主题'>
<CommandItem onSelect={() => runCommand(() => setTheme('light'))}>
<Sun /> <span>Light</span>
<Sun /> <span></span>
</CommandItem>
<CommandItem onSelect={() => runCommand(() => setTheme('dark'))}>
<Moon className='scale-90' />
<span>Dark</span>
<span></span>
</CommandItem>
<CommandItem onSelect={() => runCommand(() => setTheme('system'))}>
<Laptop />
<span>System</span>
<span></span>
</CommandItem>
</CommandGroup>
</ScrollArea>

View File

@@ -51,14 +51,14 @@ export function ConfirmDialog(props: ConfirmDialogProps) {
{children}
<AlertDialogFooter>
<AlertDialogCancel disabled={isLoading}>
{cancelBtnText ?? 'Cancel'}
{cancelBtnText ?? '取消'}
</AlertDialogCancel>
<Button
variant={destructive ? 'destructive' : 'default'}
onClick={handleConfirm}
disabled={disabled || isLoading}
>
{confirmText ?? 'Continue'}
{confirmText ?? '继续'}
</Button>
</AlertDialogFooter>
</AlertDialogContent>

View File

@@ -52,18 +52,18 @@ export function DataTableColumnHeader<TData, TValue>({
<DropdownMenuContent align='start'>
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
<ArrowUpIcon className='size-3.5 text-muted-foreground/70' />
Asc
</DropdownMenuItem>
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
<ArrowDownIcon className='size-3.5 text-muted-foreground/70' />
Desc
</DropdownMenuItem>
{column.getCanHide() && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
<EyeNoneIcon className='size-3.5 text-muted-foreground/70' />
Hide
</DropdownMenuItem>
</>
)}

View File

@@ -59,7 +59,7 @@ export function DataTableFacetedFilter<TData, TValue>({
variant='secondary'
className='rounded-sm px-1 font-normal'
>
{selectedValues.size} selected
{selectedValues.size}
</Badge>
) : (
options
@@ -83,7 +83,7 @@ export function DataTableFacetedFilter<TData, TValue>({
<Command>
<CommandInput placeholder={title} />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandEmpty></CommandEmpty>
<CommandGroup>
{options.map((option) => {
const isSelected = selectedValues.has(option.value)
@@ -133,7 +133,7 @@ export function DataTableFacetedFilter<TData, TValue>({
onSelect={() => column?.setFilterValue(undefined)}
className='justify-center text-center'
>
Clear filters
</CommandItem>
</CommandGroup>
</>

View File

@@ -39,7 +39,7 @@ export function DataTablePagination<TData>({
>
<div className='flex w-full items-center justify-between'>
<div className='flex w-[100px] items-center justify-center text-sm font-medium @2xl/content:hidden'>
Page {currentPage} of {totalPages}
{currentPage} / {totalPages}
</div>
<div className='flex items-center gap-2 @max-2xl/content:flex-row-reverse'>
<Select
@@ -59,13 +59,13 @@ export function DataTablePagination<TData>({
))}
</SelectContent>
</Select>
<p className='hidden text-sm font-medium sm:block'>Rows per page</p>
<p className='hidden text-sm font-medium sm:block'></p>
</div>
</div>
<div className='flex items-center sm:space-x-6 lg:space-x-8'>
<div className='flex w-[100px] items-center justify-center text-sm font-medium @max-3xl/content:hidden'>
Page {currentPage} of {totalPages}
{currentPage} / {totalPages}
</div>
<div className='flex items-center space-x-2'>
<Button
@@ -74,7 +74,7 @@ export function DataTablePagination<TData>({
onClick={() => table.setPageIndex(0)}
disabled={!table.getCanPreviousPage()}
>
<span className='sr-only'>Go to first page</span>
<span className='sr-only'></span>
<DoubleArrowLeftIcon className='h-4 w-4' />
</Button>
<Button
@@ -83,7 +83,7 @@ export function DataTablePagination<TData>({
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<span className='sr-only'>Go to previous page</span>
<span className='sr-only'></span>
<ChevronLeftIcon className='h-4 w-4' />
</Button>
@@ -98,7 +98,7 @@ export function DataTablePagination<TData>({
className='h-8 min-w-8 px-2'
onClick={() => table.setPageIndex((pageNumber as number) - 1)}
>
<span className='sr-only'>Go to page {pageNumber}</span>
<span className='sr-only'> {pageNumber} </span>
{pageNumber}
</Button>
)}
@@ -111,7 +111,7 @@ export function DataTablePagination<TData>({
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<span className='sr-only'>Go to next page</span>
<span className='sr-only'></span>
<ChevronRightIcon className='h-4 w-4' />
</Button>
<Button
@@ -120,7 +120,7 @@ export function DataTablePagination<TData>({
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
disabled={!table.getCanNextPage()}
>
<span className='sr-only'>Go to last page</span>
<span className='sr-only'></span>
<DoubleArrowRightIcon className='h-4 w-4' />
</Button>
</div>

View File

@@ -22,7 +22,7 @@ type DataTableToolbarProps<TData> = {
export function DataTableToolbar<TData>({
table,
searchPlaceholder = 'Filter...',
searchPlaceholder = '筛选...',
searchKey,
filters = [],
}: DataTableToolbarProps<TData>) {
@@ -74,7 +74,7 @@ export function DataTableToolbar<TData>({
}}
className='h-8 px-2 lg:px-3'
>
Reset
<Cross2Icon className='ms-2 h-4 w-4' />
</Button>
)}

View File

@@ -26,11 +26,11 @@ export function DataTableViewOptions<TData>({
className='ms-auto hidden h-8 lg:flex'
>
<MixerHorizontalIcon className='size-4' />
View
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end' className='w-[150px]'>
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
<DropdownMenuLabel></DropdownMenuLabel>
<DropdownMenuSeparator />
{table
.getAllColumns()

View File

@@ -6,22 +6,17 @@ import {
SidebarHeader,
SidebarRail,
} from '@/components/ui/sidebar'
// import { AppTitle } from './app-title'
import { AppTitle } from './app-title'
import { sidebarData } from './data/sidebar-data'
import { NavGroup } from './nav-group'
import { NavUser } from './nav-user'
import { TeamSwitcher } from './team-switcher'
export function AppSidebar() {
const { collapsible, variant } = useLayout()
return (
<Sidebar collapsible={collapsible} variant={variant}>
<SidebarHeader>
<TeamSwitcher teams={sidebarData.teams} />
{/* Replace <TeamSwitch /> with the following <AppTitle />
/* if you want to use the normal app title instead of TeamSwitch dropdown */}
{/* <AppTitle /> */}
<AppTitle />
</SidebarHeader>
<SidebarContent>
{sidebarData.navGroups.map((props) => (

View File

@@ -25,8 +25,8 @@ export function AppTitle() {
onClick={() => setOpenMobile(false)}
className='grid flex-1 text-start text-sm leading-tight'
>
<span className='truncate font-bold'>Shadcn-Admin</span>
<span className='truncate text-xs'>Vite + ShadcnUI</span>
<span className='truncate font-bold'>WEIAI</span>
<span className='truncate text-xs'></span>
</Link>
<ToggleSidebar />
</div>
@@ -58,7 +58,7 @@ function ToggleSidebar({
>
<X className='md:hidden' />
<Menu className='max-md:hidden' />
<span className='sr-only'>Toggle Sidebar</span>
<span className='sr-only'></span>
</Button>
)
}

View File

@@ -1,209 +1,28 @@
import {
Construction,
LayoutDashboard,
Monitor,
Bug,
ListTodo,
FileX,
HelpCircle,
Lock,
Bell,
Package,
Palette,
ServerOff,
Settings,
Wrench,
UserCog,
UserX,
Users,
MessagesSquare,
ShieldCheck,
AudioWaveform,
Command,
GalleryVerticalEnd,
} from 'lucide-react'
import { ClerkLogo } from '@/assets/clerk-logo'
import { Command, Users } from 'lucide-react'
import { type SidebarData } from '../types'
export const sidebarData: SidebarData = {
user: {
name: 'satnaing',
email: 'satnaingdev@gmail.com',
avatar: '/avatars/shadcn.jpg',
name: 'WEIAI',
email: 'weiai@example.com',
avatar: '/avatars/01.png',
},
teams: [
{
name: 'Shadcn Admin',
name: 'WEIAI',
logo: Command,
plan: 'Vite + ShadcnUI',
},
{
name: 'Acme Inc',
logo: GalleryVerticalEnd,
plan: 'Enterprise',
},
{
name: 'Acme Corp.',
logo: AudioWaveform,
plan: 'Startup',
plan: '角色卡',
},
],
navGroups: [
{
title: 'General',
title: '功能',
items: [
{
title: 'Dashboard',
url: '/',
icon: LayoutDashboard,
},
{
title: 'Tasks',
url: '/tasks',
icon: ListTodo,
},
{
title: 'Characters',
title: '角色卡',
url: '/characters',
icon: Users,
},
{
title: 'Apps',
url: '/apps',
icon: Package,
},
{
title: 'Chats',
url: '/chats',
badge: '3',
icon: MessagesSquare,
},
{
title: 'Users',
url: '/users',
icon: Users,
},
{
title: 'Secured by Clerk',
icon: ClerkLogo,
items: [
{
title: 'Sign In',
url: '/clerk/sign-in',
},
{
title: 'Sign Up',
url: '/clerk/sign-up',
},
{
title: 'User Management',
url: '/clerk/user-management',
},
],
},
],
},
{
title: 'Pages',
items: [
{
title: 'Auth',
icon: ShieldCheck,
items: [
{
title: 'Sign In',
url: '/sign-in',
},
{
title: 'Sign In (2 Col)',
url: '/sign-in-2',
},
{
title: 'Sign Up',
url: '/sign-up',
},
{
title: 'Forgot Password',
url: '/forgot-password',
},
{
title: 'OTP',
url: '/otp',
},
],
},
{
title: 'Errors',
icon: Bug,
items: [
{
title: 'Unauthorized',
url: '/errors/unauthorized',
icon: Lock,
},
{
title: 'Forbidden',
url: '/errors/forbidden',
icon: UserX,
},
{
title: 'Not Found',
url: '/errors/not-found',
icon: FileX,
},
{
title: 'Internal Server Error',
url: '/errors/internal-server-error',
icon: ServerOff,
},
{
title: 'Maintenance Error',
url: '/errors/maintenance-error',
icon: Construction,
},
],
},
],
},
{
title: 'Other',
items: [
{
title: 'Settings',
icon: Settings,
items: [
{
title: 'Profile',
url: '/settings',
icon: UserCog,
},
{
title: 'Account',
url: '/settings/account',
icon: Wrench,
},
{
title: 'Appearance',
url: '/settings/appearance',
icon: Palette,
},
{
title: 'Notifications',
url: '/settings/notifications',
icon: Bell,
},
{
title: 'Display',
url: '/settings/display',
icon: Monitor,
},
],
},
{
title: 'Help Center',
url: '/help-center',
icon: HelpCircle,
},
],
},
],

View File

@@ -1,18 +1,9 @@
import { Link } from '@tanstack/react-router'
import {
BadgeCheck,
Bell,
ChevronsUpDown,
CreditCard,
LogOut,
Sparkles,
} from 'lucide-react'
import { ChevronsUpDown, LogOut } from 'lucide-react'
import useDialogState from '@/hooks/use-dialog-state'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
@@ -50,7 +41,7 @@ export function NavUser({ user }: NavUserProps) {
>
<Avatar className='h-8 w-8 rounded-lg'>
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className='rounded-lg'>SN</AvatarFallback>
<AvatarFallback className='rounded-lg'>WA</AvatarFallback>
</Avatar>
<div className='grid flex-1 text-start text-sm leading-tight'>
<span className='truncate font-semibold'>{user.name}</span>
@@ -69,7 +60,7 @@ export function NavUser({ user }: NavUserProps) {
<div className='flex items-center gap-2 px-1 py-1.5 text-start text-sm'>
<Avatar className='h-8 w-8 rounded-lg'>
<AvatarImage src={user.avatar} alt={user.name} />
<AvatarFallback className='rounded-lg'>SN</AvatarFallback>
<AvatarFallback className='rounded-lg'>WA</AvatarFallback>
</Avatar>
<div className='grid flex-1 text-start text-sm leading-tight'>
<span className='truncate font-semibold'>{user.name}</span>
@@ -78,40 +69,12 @@ export function NavUser({ user }: NavUserProps) {
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Sparkles />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<Link to='/settings/account'>
<BadgeCheck />
Account
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to='/settings'>
<CreditCard />
Billing
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to='/settings/notifications'>
<Bell />
Notifications
</Link>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem
variant='destructive'
onClick={() => setOpen(true)}
>
<LogOut />
Sign out
退
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@@ -1,11 +1,9 @@
import { Link } from '@tanstack/react-router'
import useDialogState from '@/hooks/use-dialog-state'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
@@ -23,45 +21,23 @@ export function ProfileDropdown() {
<DropdownMenuTrigger asChild>
<Button variant='ghost' className='relative h-8 w-8 rounded-full'>
<Avatar className='h-8 w-8'>
<AvatarImage src='/avatars/01.png' alt='@shadcn' />
<AvatarFallback>SN</AvatarFallback>
<AvatarImage src='/avatars/01.png' alt='@weiai' />
<AvatarFallback>WA</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className='w-56' align='end' forceMount>
<DropdownMenuLabel className='font-normal'>
<div className='flex flex-col gap-1.5'>
<p className='text-sm leading-none font-medium'>satnaing</p>
<p className='text-sm leading-none font-medium'>WEIAI</p>
<p className='text-xs leading-none text-muted-foreground'>
satnaingdev@gmail.com
weiai@example.com
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<Link to='/settings'>
Profile
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to='/settings'>
Billing
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to='/settings'>
Settings
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</Link>
</DropdownMenuItem>
<DropdownMenuItem>New Team</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem variant='destructive' onClick={() => setOpen(true)}>
Sign out
退
<DropdownMenuShortcut className='text-current'>
Q
</DropdownMenuShortcut>

View File

@@ -11,7 +11,7 @@ type SearchProps = {
export function Search({
className = '',
placeholder = 'Search',
placeholder = '搜索',
}: SearchProps) {
const { setOpen } = useSearch()
return (

View File

@@ -27,9 +27,9 @@ export function SignOutDialog({ open, onOpenChange }: SignOutDialogProps) {
<ConfirmDialog
open={open}
onOpenChange={onOpenChange}
title='Sign out'
desc='Are you sure you want to sign out? You will need to sign in again to access your account.'
confirmText='Sign out'
title='退出登录'
desc='确定要退出登录吗?退出后需要重新登录才能访问账号。'
confirmText='退出登录'
destructive
handleConfirm={handleSignOut}
className='sm:max-w-sm'

View File

@@ -27,26 +27,26 @@ export function ThemeSwitch() {
<Button variant='ghost' size='icon' className='scale-95 rounded-full'>
<Sun className='size-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90' />
<Moon className='absolute size-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0' />
<span className='sr-only'>Toggle theme</span>
<span className='sr-only'></span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align='end'>
<DropdownMenuItem onClick={() => setTheme('light')}>
Light{' '}
{' '}
<Check
size={14}
className={cn('ms-auto', theme !== 'light' && 'hidden')}
/>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
Dark
<Check
size={14}
className={cn('ms-auto', theme !== 'dark' && 'hidden')}
/>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
System
<Check
size={14}
className={cn('ms-auto', theme !== 'system' && 'hidden')}

View File

@@ -10,7 +10,7 @@ export function AuthLayout({ children }: AuthLayoutProps) {
<div className='mx-auto flex w-full flex-col justify-center space-y-2 py-8 sm:w-[480px] sm:p-8'>
<div className='mb-4 flex items-center justify-center'>
<Logo className='me-2' />
<h1 className='text-xl font-medium'>Shadcn Admin</h1>
<h1 className='text-xl font-medium'>WEIAI</h1>
</div>
{children}
</div>

View File

@@ -5,9 +5,8 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { Link, useNavigate } from '@tanstack/react-router'
import { Loader2, LogIn } from 'lucide-react'
import { toast } from 'sonner'
import { IconFacebook, IconGithub, IconGoogle } from '@/assets/brand-icons'
import { useAuthStore } from '@/stores/auth-store'
import { sleep, cn } from '@/lib/utils'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
import { supabase } from '@/lib/supabase'
import {
@@ -85,22 +84,6 @@ export function UserAuthForm({
}
}
const handleGoogleLogin = async () => {
setIsLoading(true)
try {
const { error } = await supabase.auth.signInWithOAuth({
provider: 'google',
options: {
redirectTo: `${window.location.origin}/`,
},
})
if (error) throw error
} catch (error: any) {
toast.error(error.message || 'Google login failed')
setIsLoading(false)
}
}
return (
<Form {...form}>
<form
@@ -113,7 +96,7 @@ export function UserAuthForm({
name='email'
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder='name@example.com' {...field} />
</FormControl>
@@ -126,7 +109,7 @@ export function UserAuthForm({
name='password'
render={({ field }) => (
<FormItem className='relative'>
<FormLabel>Password</FormLabel>
<FormLabel></FormLabel>
<FormControl>
<PasswordInput placeholder='********' {...field} />
</FormControl>
@@ -135,14 +118,14 @@ export function UserAuthForm({
to='/forgot-password'
className='absolute end-0 -top-0.5 text-sm font-medium text-muted-foreground hover:opacity-75'
>
Forgot password?
</Link>
</FormItem>
)}
/>
<Button className='mt-2' disabled={isLoading}>
{isLoading ? <Loader2 className='animate-spin' /> : <LogIn />}
Sign in
</Button>
<div className='relative my-2'>
@@ -151,7 +134,7 @@ export function UserAuthForm({
</div>
<div className='relative flex justify-center text-xs uppercase'>
<span className='bg-background px-2 text-muted-foreground'>
Or continue with
使
</span>
</div>
</div>

View File

@@ -17,10 +17,9 @@ export function SignIn() {
<AuthLayout>
<Card className='gap-4'>
<CardHeader>
<CardTitle className='text-lg tracking-tight'>Sign in</CardTitle>
<CardTitle className='text-lg tracking-tight'></CardTitle>
<CardDescription>
Enter your email and password below to <br />
log into your account
</CardDescription>
</CardHeader>
<CardContent>
@@ -28,19 +27,19 @@ export function SignIn() {
</CardContent>
<CardFooter>
<p className='px-8 text-center text-sm text-muted-foreground'>
By clicking sign in, you agree to our{' '}
{' '}
<a
href='/terms'
className='underline underline-offset-4 hover:text-primary'
>
Terms of Service
</a>{' '}
and{' '}
{' '}
<a
href='/privacy'
className='underline underline-offset-4 hover:text-primary'
>
Privacy Policy
</a>
.
</p>

View File

@@ -11,32 +11,31 @@ export function SignIn2() {
<div className='mx-auto flex w-full flex-col justify-center space-y-2 py-8 sm:w-[480px] sm:p-8'>
<div className='mb-4 flex items-center justify-center'>
<Logo className='me-2' />
<h1 className='text-xl font-medium'>Shadcn Admin</h1>
<h1 className='text-xl font-medium'>WEIAI</h1>
</div>
</div>
<div className='mx-auto flex w-full max-w-sm flex-col justify-center space-y-2'>
<div className='flex flex-col space-y-2 text-start'>
<h2 className='text-lg font-semibold tracking-tight'>Sign in</h2>
<h2 className='text-lg font-semibold tracking-tight'></h2>
<p className='text-sm text-muted-foreground'>
Enter your email and password below <br />
to log into your account
</p>
</div>
<UserAuthForm />
<p className='px-8 text-center text-sm text-muted-foreground'>
By clicking sign in, you agree to our{' '}
{' '}
<a
href='/terms'
className='underline underline-offset-4 hover:text-primary'
>
Terms of Service
</a>{' '}
and{' '}
{' '}
<a
href='/privacy'
className='underline underline-offset-4 hover:text-primary'
>
Privacy Policy
</a>
.
</p>
@@ -54,14 +53,14 @@ export function SignIn2() {
className='dark:hidden'
width={1024}
height={1151}
alt='Shadcn-Admin'
alt='WEIAI'
/>
<img
src={dashboardDark}
className='hidden dark:block'
width={1024}
height={1138}
alt='Shadcn-Admin'
alt='WEIAI'
/>
</div>
</div>

View File

@@ -82,13 +82,13 @@ export function CharacterDialog() {
console.log('🔧 处理后的数据:', sanitizedValues);
if (isEdit) {
console.log('🔄 更新角色ID:', character.id);
console.log('🔄 更新角色ID:', character.id);
await updateCharacter({ ...sanitizedValues, id: character.id } as any);
toast.success('角色更新成功');
toast.success('角色更新成功');
} else {
console.log(' 创建新角色');
console.log(' 创建新角色');
await createCharacter(sanitizedValues as any);
toast.success('角色创建成功');
toast.success('角色创建成功');
}
setOpen(false);
queryClient.invalidateQueries({ queryKey: ['characters'] });
@@ -100,7 +100,7 @@ export function CharacterDialog() {
? error.message
: JSON.stringify(error);
toast.error(`保存角色失败: ${errorMessage}`);
toast.error(`保存角色失败: ${errorMessage}`);
}
};
@@ -108,11 +108,11 @@ export function CharacterDialog() {
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className='max-h-[85vh] overflow-y-auto sm:max-w-2xl'>
<DialogHeader>
<DialogTitle>{isEdit ? '编辑角色' : '创建角色'}</DialogTitle>
<DialogTitle>{isEdit ? '编辑角色' : '创建角色'}</DialogTitle>
<DialogDescription>
{isEdit
? '在下方修改角色详情。'
: '填写详情以创建新角色。'}
? '在下方修改角色详情。'
: '填写详情以创建新角色。'}
</DialogDescription>
</DialogHeader>
<Form {...form}>
@@ -140,7 +140,7 @@ export function CharacterDialog() {
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Input placeholder='角色名称' {...field} />
<Input placeholder='角色名称' {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -150,13 +150,13 @@ export function CharacterDialog() {
<FormField
control={form.control}
name='avatar_path'
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<ImageUpload
value={field.value}
name='avatar_path'
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<ImageUpload
value={field.value}
onChange={field.onChange}
/>
</FormControl>
@@ -172,7 +172,7 @@ export function CharacterDialog() {
<FormItem>
<FormLabel></FormLabel>
<FormControl>
<Textarea placeholder='完整角色描述' {...field} value={field.value || ''} />
<Textarea placeholder='完整角色描述' {...field} value={field.value || ''} />
</FormControl>
<FormMessage />
</FormItem>
@@ -241,7 +241,7 @@ export function CharacterDialog() {
name='ai_system_prompt'
render={({ field }) => (
<FormItem>
<FormLabel> (System Prompt)</FormLabel>
<FormLabel></FormLabel>
<FormControl>
<Textarea
placeholder='你是一个乐于助人的助手...'

View File

@@ -11,7 +11,7 @@ export function CharactersPrimaryButtons() {
return (
<div className='flex gap-2'>
<Button onClick={handleCreate}></Button>
<Button onClick={handleCreate}></Button>
</div>
);
}

View File

@@ -96,11 +96,11 @@ export function CharactersTable({ data }: DataTableProps) {
<div className='flex flex-1 flex-col gap-4'>
<DataTableToolbar
table={table}
searchPlaceholder='Filter by name...'
searchPlaceholder='按名称搜索...'
filters={[
{
columnId: 'status',
title: 'Status',
title: '状态',
options: statuses,
},
]}
@@ -159,7 +159,7 @@ export function CharactersTable({ data }: DataTableProps) {
colSpan={columns.length}
className='h-24 text-center'
>
No results.
</TableCell>
</TableRow>
)}

View File

@@ -31,7 +31,7 @@ export function DataTableRowActions<TData>({
};
const handleDelete = async () => {
if (confirm('确认删除该角色吗?')) {
if (confirm('确认删除该角色吗?')) {
try {
if (character.id) {
await deleteCharacter(character.id);

View File

@@ -3,17 +3,17 @@ import { Signal, User, UserX } from 'lucide-react';
export const statuses = [
{
value: 'online',
label: 'Online',
label: '在线',
icon: Signal,
},
{
value: 'busy',
label: 'Busy',
label: '忙碌',
icon: User,
},
{
value: 'offline',
label: 'Offline',
label: '离线',
icon: UserX,
},
];

View File

@@ -17,7 +17,7 @@ export default function Characters() {
});
if (error) {
return <div>Error loading characters: {(error as Error).message}</div>;
return <div>{(error as Error).message}</div>;
}
return (
@@ -33,16 +33,16 @@ export default function Characters() {
<Main className='flex flex-1 flex-col gap-4 sm:gap-6'>
<div className='flex flex-wrap items-end justify-between gap-2'>
<div>
<h2 className='text-2xl font-bold tracking-tight'>Characters</h2>
<h2 className='text-2xl font-bold tracking-tight'></h2>
<p className='text-muted-foreground'>
Manage your AI characters and their configurations.
AI
</p>
</div>
<CharactersPrimaryButtons />
</div>
{isLoading ? (
<div>Loading...</div>
<div>...</div>
) : (
<CharactersTable data={characters || []} />
)}

View File

@@ -1,6 +1,7 @@
import { createFileRoute } from '@tanstack/react-router'
import { Dashboard } from '@/features/dashboard'
import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated/')({
component: Dashboard,
beforeLoad: () => {
throw redirect({ to: '/characters' })
},
})

View File

@@ -18,7 +18,7 @@ function ClerkAuthLayout() {
className='relative z-20 flex items-center text-lg font-medium'
>
<Logo className='me-2' />
Shadcn Admin
WEIAI
</Link>
<ClerkFullLogo className='relative m-auto size-96' />
@@ -42,13 +42,13 @@ function ClerkAuthLayout() {
}}
contentProps={{ side: 'top', align: 'end', className: 'w-auto' }}
>
Welcome to the example Clerk auth page. <br />
Back to{' '}
使 Clerk <br />
{' '}
<Link
to='/'
className='underline decoration-dashed underline-offset-2'
>
Dashboard
</Link>{' '}
?
</LearnMore>

8
shadcn-admin/vercel.json Normal file
View File

@@ -0,0 +1,8 @@
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}