- Add transactions_dirty table and zero-tolerance cleansing action - Implement bookkeeping workbench (/bookkeeping) with searchable Combobox channel selector and card suffix / account identifier - Implement transaction ledger (/transactions) with date groupings, multi-currency metrics, and filters - Update AGENTS.md guidelines for Base UI Select and Combobox bindings
90 lines
2.1 KiB
TypeScript
90 lines
2.1 KiB
TypeScript
"use server"
|
|
|
|
import { z } from "zod"
|
|
import { eq } from "drizzle-orm"
|
|
import { db } from "@/lib/db"
|
|
import { users } from "@/lib/db/schema"
|
|
import { hashPassword } from "@/lib/auth/password"
|
|
|
|
const registerSchema = z
|
|
.object({
|
|
name: z.string().min(1, "请输入姓名/昵称").max(100),
|
|
email: z.string().email("请输入有效的邮箱地址").max(255),
|
|
password: z.string().min(8, "密码长度至少需要 8 个字符"),
|
|
confirmPassword: z.string().min(1, "请确认密码"),
|
|
})
|
|
.refine(
|
|
(data: { password: string; confirmPassword: string }) =>
|
|
data.password === data.confirmPassword,
|
|
{
|
|
message: "两次输入的密码不一致",
|
|
path: ["confirmPassword"],
|
|
}
|
|
)
|
|
|
|
export type RegisterState = {
|
|
success?: boolean
|
|
error?: string
|
|
fieldErrors?: Record<string, string[]>
|
|
}
|
|
|
|
export async function registerAction(
|
|
prevState: RegisterState | null,
|
|
formData: FormData
|
|
): Promise<RegisterState> {
|
|
const rawData = {
|
|
name: formData.get("name"),
|
|
email: formData.get("email"),
|
|
password: formData.get("password"),
|
|
confirmPassword: formData.get("confirmPassword"),
|
|
}
|
|
|
|
const parsed = registerSchema.safeParse(rawData)
|
|
if (!parsed.success) {
|
|
return {
|
|
success: false,
|
|
fieldErrors: parsed.error.flatten().fieldErrors,
|
|
}
|
|
}
|
|
|
|
const { name, email, password } = parsed.data
|
|
const normalizedEmail = email.toLowerCase().trim()
|
|
|
|
try {
|
|
// 检查邮箱是否已被注册
|
|
const [existing] = await db
|
|
.select()
|
|
.from(users)
|
|
.where(eq(users.email, normalizedEmail))
|
|
.limit(1)
|
|
|
|
if (existing) {
|
|
return {
|
|
success: false,
|
|
error: "该邮箱已被注册,请直接登录",
|
|
}
|
|
}
|
|
|
|
// 使用 Argon2 哈希密码
|
|
const passwordHash = await hashPassword(password)
|
|
|
|
// 插入新用户
|
|
await db.insert(users).values({
|
|
name: name.trim(),
|
|
email: normalizedEmail,
|
|
passwordHash,
|
|
isActive: true,
|
|
})
|
|
|
|
return {
|
|
success: true,
|
|
}
|
|
} catch (err) {
|
|
console.error("Registration error:", err)
|
|
return {
|
|
success: false,
|
|
error: "注册失败,请稍后重试",
|
|
}
|
|
}
|
|
}
|