add night style

This commit is contained in:
songtianlun 2025-06-14 21:26:38 +08:00
parent 9a1523af96
commit f45fcc3321
6 changed files with 224 additions and 138 deletions

View File

@ -10,6 +10,7 @@ import { Label } from "@/components/ui/label"
import { Separator } from "@/components/ui/separator"
import { Mail, Chrome } from "lucide-react"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { ThemeToggle } from "@/components/theme-toggle"
function SignInContent() {
const [email, setEmail] = useState("")
@ -30,15 +31,20 @@ function SignInContent() {
// 如果正在加载会话状态显示loading
if (status === "loading") {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-full max-w-md">
<CardContent className="flex items-center justify-center p-6">
<div className="text-center">
<div className="w-8 h-8 bg-gray-200 rounded-full animate-pulse mx-auto mb-2"></div>
<p className="text-sm text-muted-foreground">...</p>
</div>
</CardContent>
</Card>
<div className="min-h-screen bg-background transition-colors">
<div className="absolute top-4 right-4 z-50">
<ThemeToggle />
</div>
<div className="flex items-center justify-center min-h-screen">
<Card className="w-full max-w-md shadow-lg">
<CardContent className="flex items-center justify-center p-6">
<div className="text-center">
<div className="w-8 h-8 bg-muted rounded-full animate-pulse mx-auto mb-2"></div>
<p className="text-sm text-muted-foreground">...</p>
</div>
</CardContent>
</Card>
</div>
</div>
)
}
@ -83,65 +89,71 @@ function SignInContent() {
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-full max-w-md">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl text-center"></CardTitle>
<CardDescription className="text-center">
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Button
onClick={handleGoogleSignIn}
variant="outline"
className="w-full"
disabled={isLoading}
>
<Chrome className="mr-2 h-4 w-4" />
使 Google
</Button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator className="w-full" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
</span>
</div>
</div>
<form onSubmit={handleEmailSignIn} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email"></Label>
<Input
id="email"
type="email"
placeholder="输入您的邮箱地址"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div className="min-h-screen bg-background transition-colors">
<div className="absolute top-4 right-4 z-50">
<ThemeToggle />
</div>
<div className="flex items-center justify-center min-h-screen">
<Card className="w-full max-w-md shadow-lg border">
<CardHeader className="space-y-1">
<CardTitle className="text-2xl text-center text-foreground"></CardTitle>
<CardDescription className="text-center text-muted-foreground">
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Button
type="submit"
onClick={handleGoogleSignIn}
variant="outline"
className="w-full"
disabled={isLoading || !email}
disabled={isLoading}
>
<Mail className="mr-2 h-4 w-4" />
{isLoading ? "发送中..." : "发送登录链接"}
<Chrome className="mr-2 h-4 w-4" />
使 Google
</Button>
</form>
{message && (
<Alert>
<AlertDescription>{message}</AlertDescription>
</Alert>
)}
</CardContent>
</Card>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator className="w-full" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
</span>
</div>
</div>
<form onSubmit={handleEmailSignIn} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email" className="text-foreground"></Label>
<Input
id="email"
type="email"
placeholder="输入您的邮箱地址"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="bg-background border-input"
/>
</div>
<Button
type="submit"
className="w-full"
disabled={isLoading || !email}
>
<Mail className="mr-2 h-4 w-4" />
{isLoading ? "发送中..." : "发送登录链接"}
</Button>
</form>
{message && (
<Alert className="bg-background border">
<AlertDescription className="text-foreground">{message}</AlertDescription>
</Alert>
)}
</CardContent>
</Card>
</div>
</div>
)
}
@ -149,15 +161,20 @@ function SignInContent() {
export default function SignInPage() {
return (
<Suspense fallback={
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<Card className="w-full max-w-md">
<CardContent className="flex items-center justify-center p-6">
<div className="text-center">
<div className="w-8 h-8 bg-gray-200 rounded-full animate-pulse mx-auto mb-2"></div>
<p className="text-sm text-muted-foreground">...</p>
</div>
</CardContent>
</Card>
<div className="min-h-screen bg-background transition-colors">
<div className="absolute top-4 right-4 z-50">
<ThemeToggle />
</div>
<div className="flex items-center justify-center min-h-screen">
<Card className="w-full max-w-md shadow-lg">
<CardContent className="flex items-center justify-center p-6">
<div className="text-center">
<div className="w-8 h-8 bg-muted rounded-full animate-pulse mx-auto mb-2"></div>
<p className="text-sm text-muted-foreground">...</p>
</div>
</CardContent>
</Card>
</div>
</div>
}>
<SignInContent />

View File

@ -11,42 +11,42 @@ export const metadata: Metadata = {
export default function Home() {
return (
<main className="container mx-auto py-8 px-4">
<main className="container mx-auto py-8 px-4 bg-background transition-colors">
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-3xl font-bold mb-2"></h1>
<h1 className="text-3xl font-bold mb-2 text-foreground"></h1>
<p className="text-lg text-muted-foreground"></p>
</div>
<UserNav />
</div>
<div className="mb-8">
<div className="bg-muted/50 rounded-lg p-6 mb-6">
<h2 className="text-xl font-semibold mb-3"></h2>
<div className="bg-muted/50 rounded-lg p-6 mb-6 border">
<h2 className="text-xl font-semibold mb-3 text-foreground"></h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 text-sm">
<div className="flex items-center space-x-2">
<span className="w-2 h-2 bg-primary rounded-full"></span>
<span></span>
<span className="text-foreground"></span>
</div>
<div className="flex items-center space-x-2">
<span className="w-2 h-2 bg-primary rounded-full"></span>
<span></span>
<span className="text-foreground"></span>
</div>
<div className="flex items-center space-x-2">
<span className="w-2 h-2 bg-primary rounded-full"></span>
<span></span>
<span className="text-foreground"></span>
</div>
<div className="flex items-center space-x-2">
<span className="w-2 h-2 bg-primary rounded-full"></span>
<span></span>
<span className="text-foreground"></span>
</div>
<div className="flex items-center space-x-2">
<span className="w-2 h-2 bg-primary rounded-full"></span>
<span></span>
<span className="text-foreground"></span>
</div>
<div className="flex items-center space-x-2">
<span className="w-2 h-2 bg-primary rounded-full"></span>
<span></span>
<span className="text-foreground"></span>
</div>
</div>
</div>
@ -57,7 +57,7 @@ export default function Home() {
<FinanceCalculator />
<footer className="mt-12 pt-8 border-t">
<footer className="mt-12 pt-8 border-t border-border">
<div className="text-center text-sm text-muted-foreground">
<p className="mb-2">
线

View File

@ -12,51 +12,63 @@ import {
} from "@/components/ui/dropdown-menu"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { User, LogOut } from "lucide-react"
import { ThemeToggle } from "@/components/theme-toggle"
export function UserNav() {
const { data: session, status } = useSession()
if (status === "loading") {
return <div className="w-8 h-8 bg-gray-200 rounded-full animate-pulse" />
return (
<div className="flex items-center gap-2">
<ThemeToggle />
<div className="w-8 h-8 bg-muted rounded-full animate-pulse" />
</div>
)
}
if (!session) {
return (
<Button onClick={() => signIn()} variant="outline">
</Button>
<div className="flex items-center gap-2">
<ThemeToggle />
<Button onClick={() => signIn()} variant="outline">
</Button>
</div>
)
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-8 w-8">
<AvatarImage src={session.user?.image || ""} alt={session.user?.name || ""} />
<AvatarFallback>
<User className="h-4 w-4" />
</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">
{session.user?.name}
</p>
<p className="text-xs leading-none text-muted-foreground">
{session.user?.email}
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => signOut()}>
<LogOut className="mr-2 h-4 w-4" />
<span>退</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<div className="flex items-center gap-2">
<ThemeToggle />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-8 w-8">
<AvatarImage src={session.user?.image || ""} alt={session.user?.name || ""} />
<AvatarFallback>
<User className="h-4 w-4" />
</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">
{session.user?.name}
</p>
<p className="text-xs leading-none text-muted-foreground">
{session.user?.email}
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => signOut()}>
<LogOut className="mr-2 h-4 w-4" />
<span>退</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
}

View File

@ -46,8 +46,8 @@ export function DataSyncDialog({
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<AlertCircle className="h-5 w-5 text-amber-500" />
<DialogTitle className="flex items-center gap-2 text-foreground">
<AlertCircle className="h-5 w-5 text-amber-500 dark:text-amber-400" />
</DialogTitle>
<DialogDescription>
@ -57,7 +57,7 @@ export function DataSyncDialog({
</DialogHeader>
<div className="space-y-4">
<Alert className="border-blue-200 bg-blue-50">
<Alert className="border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-950/50">
<Upload className="h-4 w-4" />
<AlertDescription>
<strong></strong>
@ -73,8 +73,8 @@ export function DataSyncDialog({
<div
className={`p-4 border rounded-lg cursor-pointer transition-all ${
selectedAction === 'sync'
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-blue-300'
? 'border-blue-500 bg-blue-50 dark:border-blue-600 dark:bg-blue-950/50'
: 'border-border hover:border-blue-300 dark:hover:border-blue-600 bg-background'
}`}
onClick={() => setSelectedAction('sync')}
>
@ -88,10 +88,10 @@ export function DataSyncDialog({
/>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<Upload className="h-4 w-4 text-blue-600" />
<h4 className="font-medium text-blue-800"></h4>
<Upload className="h-4 w-4 text-blue-600 dark:text-blue-400" />
<h4 className="font-medium text-blue-800 dark:text-blue-200"></h4>
</div>
<p className="text-sm text-gray-600">
<p className="text-sm text-muted-foreground">
</p>
</div>
@ -101,8 +101,8 @@ export function DataSyncDialog({
<div
className={`p-4 border rounded-lg cursor-pointer transition-all ${
selectedAction === 'skip'
? 'border-blue-500 bg-blue-50'
: 'border-gray-200 hover:border-blue-300'
? 'border-blue-500 bg-blue-50 dark:border-blue-600 dark:bg-blue-950/50'
: 'border-border hover:border-blue-300 dark:hover:border-blue-600 bg-background'
}`}
onClick={() => setSelectedAction('skip')}
>
@ -116,10 +116,10 @@ export function DataSyncDialog({
/>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<Download className="h-4 w-4 text-gray-600" />
<h4 className="font-medium text-gray-800">使</h4>
<Download className="h-4 w-4 text-muted-foreground" />
<h4 className="font-medium text-foreground">使</h4>
</div>
<p className="text-sm text-gray-600">
<p className="text-sm text-muted-foreground">
</p>
</div>

View File

@ -35,7 +35,7 @@ export function LoginSuggestion() {
return (
<div className="space-y-4">
{/* 警告提示 */}
<Alert className="border-orange-200 bg-orange-50 text-orange-800">
<Alert className="border-orange-200 bg-orange-50 text-orange-800 dark:border-orange-800 dark:bg-orange-950 dark:text-orange-200">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
<strong></strong>
@ -44,29 +44,29 @@ export function LoginSuggestion() {
</Alert>
{/* 登录建议卡片 */}
<Card className="border-blue-200 bg-blue-50/50">
<Card className="border-blue-200 bg-blue-50/50 dark:border-blue-800 dark:bg-blue-950/50">
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-lg text-blue-800">
<CardTitle className="flex items-center gap-2 text-lg text-blue-800 dark:text-blue-200">
<LogIn className="h-5 w-5" />
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-blue-700 mb-4">
<p className="text-sm text-blue-700 dark:text-blue-300 mb-4">
便
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
{loginFeatures.map((feature, index) => (
<div key={index} className="flex items-start space-x-2 p-3 bg-white rounded-lg border border-blue-100">
<div className="text-blue-600 mt-0.5">
<div key={index} className="flex items-start space-x-2 p-3 bg-white dark:bg-gray-800 rounded-lg border border-blue-100 dark:border-blue-800">
<div className="text-blue-600 dark:text-blue-400 mt-0.5">
{feature.icon}
</div>
<div className="flex-1 min-w-0">
<h4 className="text-sm font-medium text-blue-800 mb-1">
<h4 className="text-sm font-medium text-blue-800 dark:text-blue-200 mb-1">
{feature.title}
</h4>
<p className="text-xs text-blue-600">
<p className="text-xs text-blue-600 dark:text-blue-400">
{feature.description}
</p>
</div>
@ -77,7 +77,7 @@ export function LoginSuggestion() {
<div className="pt-2 flex justify-center">
<Button
onClick={() => signIn()}
className="bg-blue-600 hover:bg-blue-700 text-white px-6"
className="bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white px-6"
>
<LogIn className="h-4 w-4 mr-2" />

View File

@ -0,0 +1,57 @@
"use client"
import * as React from "react"
import { Moon, Sun, Monitor } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function ThemeToggle() {
const { setTheme, theme } = useTheme()
const [mounted, setMounted] = React.useState(false)
React.useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return (
<Button variant="outline" size="icon" disabled>
<Sun className="h-[1.2rem] w-[1.2rem]" />
<span className="sr-only"></span>
</Button>
)
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" className="bg-background hover:bg-accent hover:text-accent-foreground">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only"></span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="min-w-[160px]">
<DropdownMenuItem onClick={() => setTheme("light")} className="cursor-pointer">
<Sun className="mr-2 h-4 w-4" />
<span></span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")} className="cursor-pointer">
<Moon className="mr-2 h-4 w-4" />
<span></span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")} className="cursor-pointer">
<Monitor className="mr-2 h-4 w-4" />
<span></span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}