diff --git a/next.config.ts b/next.config.ts index 6184807..3404332 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,11 +4,13 @@ import remarkGfm from 'remark-gfm' const nextConfig: NextConfig = { output: "standalone", - pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'], + pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'], + experimental: { + mdxRs: true, + } } const withMdx = createMDX({ - extension: /\.(md|mdx)$/, options: { remarkPlugins: [remarkGfm], rehypePlugins: [], diff --git a/package.json b/package.json index b8fb5bd..27ed784 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev -H 0.0.0.0", + "dev": "next dev -H 0.0.0.0 --turbo", "build": "next build", "start": "next start", "lint": "next lint" diff --git a/src/actions/base.ts b/src/actions/base.ts index 1f0ea6d..bef4cf3 100644 --- a/src/actions/base.ts +++ b/src/actions/base.ts @@ -44,7 +44,6 @@ const _callByDevice = cache(async ( endpoint: string, data?: string, ): Promise> => { - // 获取设备令牌 if (!CLIENT_ID || !CLIENT_SECRET) { return { @@ -74,7 +73,7 @@ async function callByUser( endpoint: string, data?: unknown, ): Promise> { - return postCall(_callByUser(endpoint, data ? JSON.stringify(data) : undefined)) + return _callByUser(endpoint, data ? JSON.stringify(data) : undefined) } const _callByUser = cache(async ( @@ -107,7 +106,6 @@ const _callByUser = cache(async ( }) }) - // ====================== // call // ====================== @@ -150,7 +148,7 @@ async function call(url: string, request: RequestInit): Promise(url: string, request: RequestInit): Promise(rawResp: Promise>) { const header = await headers() const pathname = header.get('x-pathname') || '/' diff --git a/src/app/(home)/@header/_client/provider.tsx b/src/app/(home)/@header/_client/provider.tsx index 25c440e..0710d0d 100644 --- a/src/app/(home)/@header/_client/provider.tsx +++ b/src/app/(home)/@header/_client/provider.tsx @@ -54,7 +54,7 @@ export default function Provider(props: ProviderProps) { // 用户信息 // ====================== - const profile = useProfileStore(store=>store.profile) + const profile = useProfileStore(store => store.profile) // ====================== // render @@ -144,7 +144,7 @@ export default function Provider(props: ProviderProps) { : ( - diff --git a/src/app/admin/_client/profile.tsx b/src/app/admin/_client/profile.tsx index bbda2d7..8dad115 100644 --- a/src/app/admin/_client/profile.tsx +++ b/src/app/admin/_client/profile.tsx @@ -3,22 +3,23 @@ import {Button} from '@/components/ui/button' import {logout} from '@/actions/auth' import {useProfileStore} from '@/components/providers/StoreProvider' import {useRouter} from 'next/navigation' -import {toast} from 'sonner' export type ProfileProps = {} export default function Profile(props: ProfileProps) { + const router = useRouter() const refreshProfile = useProfileStore(store => store.refreshProfile) const doLogout = async () => { const resp = await logout() if (resp.success) { - await refreshProfile() + refreshProfile().then() + router.replace('/') } } return (
-
diff --git a/src/app/admin/whitelist/page.tsx b/src/app/admin/whitelist/page.tsx index ea14589..abe2b0a 100644 --- a/src/app/admin/whitelist/page.tsx +++ b/src/app/admin/whitelist/page.tsx @@ -231,7 +231,7 @@ export default function WhitelistPage(props: WhitelistPageProps) { 添加白名单 - + diff --git a/src/components/date-range-picker.tsx b/src/components/date-range-picker.tsx new file mode 100644 index 0000000..5a114b2 --- /dev/null +++ b/src/components/date-range-picker.tsx @@ -0,0 +1,129 @@ +'use client' + +import * as React from "react" +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { Button } from './ui/button' +import { merge } from '@/lib/utils' +import { CalendarIcon } from 'lucide-react' +import { format, isValid } from 'date-fns' +import { Calendar } from './ui/calendar' +import { DateRange } from 'react-day-picker' + +export type DateRangePickerProps = { + className?: string + onChange?: (dateRange: DateRange | undefined) => void + value?: DateRange + disabled?: boolean + required?: boolean + placeholder?: string + error?: boolean + errorMessage?: string + description?: string + helperText?: string + format?: string + name?: string + fromDate?: Date + toDate?: Date + numberOfMonths?: number + showOutsideDays?: boolean + fixedWeeks?: boolean + weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 + onBlur?: () => void + calendarClassName?: string +} + +export default function DateRangePicker({ + className, + onChange, + value, + disabled, + required, + placeholder = "选择日期范围", + format: dateFormat = "yyyy-MM-dd", + name, + fromDate, + toDate, + numberOfMonths = 2, + showOutsideDays = true, + fixedWeeks = false, + weekStartsOn = 1, + onBlur, + calendarClassName, +}: DateRangePickerProps) { + const [open, setOpen] = React.useState(false) + + const handleSelectRange = (range: DateRange | undefined) => { + onChange?.(range) + if (range?.from && range?.to) { + setOpen(false) + } + } + + const formatDate = (date: Date | undefined) => { + return date && isValid(date) ? format(date, dateFormat) : '' + } + + // 格式化显示的日期范围 + const displayValue = React.useMemo(() => { + if (!value?.from) return placeholder + + if (!value.to) { + return `${formatDate(value.from)}` + } + + return `${formatDate(value.from)} ~ ${formatDate(value.to)}` + }, [value, placeholder, dateFormat]) + + const handleOpenChange = (isOpen: boolean) => { + setOpen(isOpen) + if (!isOpen) { + onBlur?.() + } + } + + return ( + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 0246d86..153e6b3 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,67 +1,44 @@ import * as React from 'react' import {merge} from '@/lib/utils' -import {cva} from 'class-variance-authority' +import {cva, VariantProps} from 'class-variance-authority' -const buttonVariants = cva( - 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', +export const buttonVariants = cva( + [ + `transition-all duration-200 ease-in-out`, + `h-10 px-4 rounded-md cursor-pointer whitespace-nowrap`, + 'inline-flex items-center justify-center gap-2', + '[&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4 shrink-0 [&_svg]:shrink-0 ', + 'outline-none focus-visible:ring-4 ring-blue-200', + 'disabled:pointer-events-none disabled:opacity-50 ', + 'aria-invalid:ring-fail/20 dark:aria-invalid:ring-fail/40 aria-invalid:border-fail', + ], { variants: { - variant: { - default: - 'bg-primary text-primary-foreground shadow hover:bg-primary/90', - destructive: - 'bg-fail text-fail-foreground shadow-sm hover:bg-destructive/90', - outline: - 'border border-input shadow-sm hover:bg-secondary hover:text-secondary-foreground bg-card', - secondary: - 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', - ghost: 'hover:bg-secondary hover:text-secondary-foreground', - link: 'text-primary underline-offset-4 hover:underline', - }, - size: { - default: 'h-9 px-4 py-2', - sm: 'h-8 rounded-md px-3 text-xs', - lg: 'h-10 rounded-md px-8', - icon: 'h-9 w-9', + theme: { + gradient: 'bg-gradient-to-r from-blue-400 to-cyan-300 text-white ring-offset-2', + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + outline: 'border bg-background hover:bg-secondary hover:text-secondary-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50', + ghost: 'text-foreground hover:bg-muted', + accent: 'bg-accent text-accent-foreground hover:bg-accent/90', + fail: 'bg-fail text-white hover:bg-fail/90', + warn: '', + done: '', }, }, defaultVariants: { - variant: 'default', - size: 'default', + theme: 'default', }, }, ) -type ButtonProps = React.ComponentProps<'button'> & { - theme?: 'default' | 'outline' | 'gradient' | 'error' | 'accent' | 'ghost' -} +type ButtonProps = React.ComponentProps<'button'> & VariantProps -function Button(rawProps: ButtonProps) { +export function Button(rawProps: ButtonProps) { const {className, theme, ...props} = rawProps return (