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/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..497ba5e
--- /dev/null
+++ b/src/components/ui/tabs.tsx
@@ -0,0 +1,66 @@
+"use client"
+
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "@/lib/utils"
+
+function Tabs({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsList({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
new file mode 100644
index 0000000..5467339
--- /dev/null
+++ b/src/lib/auth.ts
@@ -0,0 +1,8 @@
+import NextAuth from "next-auth"
+import { PrismaAdapter } from "@next-auth/prisma-adapter"
+import { prisma } from "./prisma"
+
+export const { handlers, auth, signIn, signOut } = NextAuth({
+ adapter: PrismaAdapter(prisma),
+ providers: [],
+})
\ No newline at end of file
diff --git a/src/lib/db.ts b/src/lib/db.ts
new file mode 100644
index 0000000..6220b63
--- /dev/null
+++ b/src/lib/db.ts
@@ -0,0 +1,8 @@
+// import { PrismaClient } from '@prisma/client'
+
+// const globalForPrisma = global as { prisma?: PrismaClient }
+
+// export const db = globalForPrisma.prisma || new PrismaClient()
+
+// if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db
+export { prisma as db } from './prisma'
diff --git a/src/lib/definitions.ts b/src/lib/definitions.ts
new file mode 100644
index 0000000..f7e0725
--- /dev/null
+++ b/src/lib/definitions.ts
@@ -0,0 +1,29 @@
+import { z } from 'zod'
+
+export const SignupFormSchema = z.object({
+ name: z
+ .string()
+ .min(2, { message: 'Name must be at least 2 characters long.' })
+ .trim(),
+ email: z.string().email({ message: 'Please enter a valid email.' }).trim(),
+ password: z
+ .string()
+ .min(8, { message: 'Be at least 8 characters long' })
+ .regex(/[a-zA-Z]/, { message: 'Contain at least one letter.' })
+ .regex(/[0-9]/, { message: 'Contain at least one number.' })
+ .regex(/[^a-zA-Z0-9]/, {
+ message: 'Contain at least one special character.',
+ })
+ .trim(),
+})
+
+export type FormState =
+ | {
+ errors?: {
+ name?: string[]
+ email?: string[]
+ password?: string[]
+ }
+ message?: string
+ }
+ | undefined
\ No newline at end of file
diff --git a/src/lib/formatters.ts b/src/lib/formatters.ts
new file mode 100644
index 0000000..9e5bc2c
--- /dev/null
+++ b/src/lib/formatters.ts
@@ -0,0 +1,26 @@
+// 数字格式化工具函数
+export const formatNumber = (num: number | string): string => {
+ const numberValue = typeof num === 'string' ? parseInt(num) || 0 : num
+ return numberValue.toLocaleString('zh-CN')
+}
+
+export const formatLargeNumber = (num: number | string): string => {
+ const numberValue = typeof num === 'string' ? parseInt(num) || 0 : num
+
+ if (numberValue > 1e9) return `${(numberValue / 1e9).toFixed(1)}亿`
+ if (numberValue > 1e6) return `${(numberValue / 1e6).toFixed(1)}百万`
+ if (numberValue > 1e4) return `${(numberValue / 1e4).toFixed(1)}万`
+ if (numberValue > 1e3) return `${(numberValue / 1e3).toFixed(1)}千`
+
+ return numberValue.toLocaleString('zh-CN')
+}
+
+// 数据验证函数
+export const validateNumber = (value: unknown): number => {
+ if (typeof value === 'number') return value
+ if (typeof value === 'string') {
+ const num = parseInt(value)
+ return isNaN(num) ? 0 : num
+ }
+ return 0
+}
\ No newline at end of file
diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts
new file mode 100644
index 0000000..5a5e41f
--- /dev/null
+++ b/src/lib/prisma.ts
@@ -0,0 +1,13 @@
+import { PrismaClient } from '@prisma/client'
+
+const globalForPrisma = global as unknown as {
+ prisma: PrismaClient | undefined
+}
+
+export const prisma = globalForPrisma.prisma ?? new PrismaClient()
+
+if (process.env.NODE_ENV !== 'production') {
+ globalForPrisma.prisma = prisma
+}
+
+export default prisma
\ No newline at end of file
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..bd0c391
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..3ac3e08
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,29 @@
+import { NextResponse } from 'next/server'
+import type { NextRequest } from 'next/server'
+
+export async function middleware(request: NextRequest) {
+ const session = request.cookies.get('session')
+ console.log(session, 'sessionsessionsession');
+
+ // 保护的路由需要验证会话
+ if (request.nextUrl.pathname.startsWith('/dashboard')) {
+ if (!session?.value) {
+ return NextResponse.redirect(new URL('/login', request.url))
+ }
+ }
+
+ // 已登录用户重定向
+ if (session?.value && (
+ request.nextUrl.pathname === '/login' ||
+ request.nextUrl.pathname === '/register'
+ )) {
+ console.log(session, 'sessionsessionsession');
+ return NextResponse.redirect(new URL('/dashboard', request.url))
+ }
+
+ return NextResponse.next()
+}
+
+export const config = {
+ matcher: ['/dashboard/:path*', '/login', '/register']
+}
\ No newline at end of file
diff --git a/src/store/auth.ts b/src/store/auth.ts
new file mode 100644
index 0000000..c322e1b
--- /dev/null
+++ b/src/store/auth.ts
@@ -0,0 +1,19 @@
+import { create } from 'zustand'
+import { persist } from 'zustand/middleware'
+
+interface AuthState {
+ isAuthenticated: boolean
+ setAuth: (state: boolean) => void
+}
+
+export const useAuthStore = create()(
+ persist(
+ (set) => ({
+ isAuthenticated: false,
+ setAuth: (state) => set({ isAuthenticated: state }),
+ }),
+ {
+ name: 'auth-storage',
+ }
+ )
+)
diff --git a/src/types/auth.d.ts b/src/types/auth.d.ts
new file mode 100644
index 0000000..7f5d47e
--- /dev/null
+++ b/src/types/auth.d.ts
@@ -0,0 +1,27 @@
+export interface User {
+ id: number
+ phone: string
+ name?: string | null
+ verifiedPhone: boolean
+}
+
+export interface Session {
+ id: string
+ userId: number
+ expires: Date
+}
+
+export interface LoginResponse {
+ success: boolean
+ error?: string
+ user?: {
+ id: number
+ phone: string
+ name?: string | null
+ }
+}
+
+export interface RegisterResponse {
+ success: boolean
+ error?: string
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..53d5beb
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,43 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": [
+ "./src/*"
+ ],
+ "@/lib/*": [
+ "./src/lib/*"
+ ]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
\ No newline at end of file
|