- 6-step onboarding wizard (welcome, language, connect providers, view style, work hours, styling) for new signups - Added hasCompletedOnboarding field to User schema - Fixed subtask progress bar in GridTaskBlock (simple/calendar views) - Fixed kanban drag-and-drop to someday area - Fixed weather/provider icon overlap in time grid tasks - Provider icon positioning: inline when weather on, top-right when off v1.56.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
113 lines
3.2 KiB
TypeScript
113 lines
3.2 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
export const dynamic = 'force-dynamic';
|
|
import { PrismaClient } from '@prisma/client';
|
|
import { hash } from 'bcryptjs';
|
|
import { sendVerificationEmail, generateVerificationCode, generateVerificationToken } from '@/lib/email';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const { name, email, password } = await request.json();
|
|
|
|
if (!email || !password) {
|
|
return NextResponse.json(
|
|
{ error: 'Email and password are required' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
if (password.length < 8) {
|
|
return NextResponse.json(
|
|
{ error: 'Password must be at least 8 characters' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Check if user already exists
|
|
const existingUser = await prisma.user.findUnique({
|
|
where: { email }
|
|
});
|
|
|
|
if (existingUser) {
|
|
// If user exists but isn't verified, resend verification
|
|
if (!existingUser.emailVerified) {
|
|
const code = generateVerificationCode();
|
|
const token = generateVerificationToken();
|
|
const expires = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes
|
|
|
|
await prisma.user.update({
|
|
where: { id: existingUser.id },
|
|
data: {
|
|
emailVerificationCode: code,
|
|
emailVerificationToken: token,
|
|
emailVerificationExpires: expires,
|
|
},
|
|
});
|
|
|
|
try {
|
|
await sendVerificationEmail(email, code, token);
|
|
} catch (emailError) {
|
|
console.error('Failed to send verification email:', emailError);
|
|
}
|
|
|
|
return NextResponse.json({
|
|
message: 'Verification email resent',
|
|
requiresVerification: true,
|
|
});
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{ error: 'User already exists' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Hash password
|
|
const passwordHash = await hash(password, 12);
|
|
|
|
// Generate verification code and token
|
|
const code = generateVerificationCode();
|
|
const token = generateVerificationToken();
|
|
const expires = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes
|
|
|
|
// Create user (NOT verified)
|
|
const user = await prisma.user.create({
|
|
data: {
|
|
name: name || undefined,
|
|
email,
|
|
passwordHash,
|
|
emailVerificationCode: code,
|
|
emailVerificationToken: token,
|
|
emailVerificationExpires: expires,
|
|
hasCompletedOnboarding: false,
|
|
// emailVerified is NOT set — user must verify
|
|
},
|
|
select: {
|
|
id: true,
|
|
email: true,
|
|
name: true,
|
|
}
|
|
});
|
|
|
|
// Send verification email
|
|
try {
|
|
await sendVerificationEmail(email, code, token);
|
|
} catch (emailError) {
|
|
console.error('Failed to send verification email:', emailError);
|
|
// User is created but email failed — they can use "resend" later
|
|
}
|
|
|
|
return NextResponse.json({
|
|
message: 'Account created. Please check your email for a verification code.',
|
|
requiresVerification: true,
|
|
user,
|
|
});
|
|
} catch (error) {
|
|
console.error('Signup error:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Failed to create user' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
} |