feat: add email verification and dark mode

This commit is contained in:
JetSprow
2026-04-29 10:55:20 +10:00
parent 2a50d789dd
commit 5215850bac
32 changed files with 1244 additions and 61 deletions

View File

@@ -2,9 +2,11 @@
import bcrypt from "bcryptjs";
import { revalidatePath } from "next/cache";
import { headers } from "next/headers";
import { randomBytes } from "crypto";
import { z } from "zod";
import { prisma } from "@/lib/prisma";
import { normalizeEmailAddress, sendEmailChangeConfirmation } from "@/services/email";
import { requireAuth } from "@/lib/require-auth";
const profileSchema = z.object({
@@ -17,6 +19,10 @@ const passwordSchema = z.object({
confirmPassword: z.string().min(6, "确认密码至少 6 位"),
});
const emailChangeSchema = z.object({
email: z.string().trim().email("请输入正确的新邮箱"),
});
async function generateUniqueInviteCode(): Promise<string> {
for (let i = 0; i < 10; i += 1) {
const code = randomBytes(4).toString("hex").toUpperCase();
@@ -44,6 +50,35 @@ export async function updateAccountProfile(formData: FormData) {
revalidatePath("/account");
}
export async function requestAccountEmailChange(formData: FormData) {
const session = await requireAuth();
const data = emailChangeSchema.parse(Object.fromEntries(formData));
const email = normalizeEmailAddress(data.email);
const current = await prisma.user.findUniqueOrThrow({
where: { id: session.user.id },
select: { email: true },
});
if (current.email === email) {
throw new Error("新邮箱不能与当前邮箱相同");
}
const existing = await prisma.user.findUnique({
where: { email },
select: { id: true },
});
if (existing) {
throw new Error("这个邮箱已经被其他账户使用");
}
const headerList = await headers();
await sendEmailChangeConfirmation({
userId: session.user.id,
email,
headers: headerList,
});
}
export async function changeAccountPassword(formData: FormData) {
const session = await requireAuth();
const data = passwordSchema.parse(Object.fromEntries(formData));