Refactor invoice data table and templates page for improved readability and functionality

- Cleaned up imports and formatted code for better readability in invoices-data-table.tsx.
- Enhanced invoice interface definitions for clarity.
- Improved toast messages for bulk delete and update actions.
- Refactored date formatting and status type retrieval for better readability.
- Simplified template management in templates page, extracting TemplateList component.
- Added registration toggle based on environment variable DISABLE_SIGNUPS.
- Updated navbar to conditionally render registration link based on allowRegistration prop.
- Enhanced error handling and validation in expenses and settings routers.
- Improved PDF export footer handling.
- Updated TRPC react integration for cleaner type imports.
This commit is contained in:
2026-04-29 22:49:07 -04:00
parent dbb739b060
commit ddc2b42672
20 changed files with 916 additions and 483 deletions
+55 -17
View File
@@ -2,59 +2,97 @@ import bcrypt from "bcryptjs";
import { eq } from "drizzle-orm";
import { type NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { env } from "~/env";
import { db } from "~/server/db";
import { users } from "~/server/db/schema";
import { accounts, users } from "~/server/db/schema";
const registerSchema = z.object({
firstName: z.string().min(1, "First name is required"),
lastName: z.string().min(1, "Last name is required"),
firstName: z.string().trim().min(1, "First name is required"),
lastName: z.string().trim().min(1, "Last name is required"),
email: z.string().email("Invalid email address"),
password: z.string().min(6, "Password must be at least 6 characters"),
password: z.string().min(8, "Password must be at least 8 characters"),
});
const fieldLabels: Record<string, string> = {
firstName: "First name",
lastName: "Last name",
email: "Email address",
password: "Password",
};
export async function POST(request: NextRequest) {
try {
const body = await request.json() as z.infer<typeof registerSchema>;
if (env.DISABLE_SIGNUPS === true) {
return NextResponse.json(
{ error: "New account registration is currently disabled" },
{ status: 403 },
);
}
const body = (await request.json()) as unknown;
const { firstName, lastName, email, password } = registerSchema.parse(body);
const normalizedEmail = email.toLowerCase();
// Check if user already exists
const existingUser = await db.query.users.findFirst({
where: eq(users.email, email),
where: eq(users.email, normalizedEmail),
});
if (existingUser) {
return NextResponse.json(
{ error: "User with this email already exists" },
{ status: 400 }
{ status: 400 },
);
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 12);
// Create user
await db.insert(users).values({
name: `${firstName} ${lastName}`,
email,
password: hashedPassword,
await db.transaction(async (tx) => {
const [user] = await tx
.insert(users)
.values({
name: `${firstName} ${lastName}`,
email: normalizedEmail,
password: hashedPassword,
})
.returning({ id: users.id });
if (!user) {
throw new Error("Failed to create user");
}
await tx.insert(accounts).values({
userId: user.id,
accountId: user.id,
providerId: "credential",
password: hashedPassword,
});
});
return NextResponse.json(
{ message: "User created successfully" },
{ status: 201 }
{ status: 201 },
);
} catch (error) {
if (error instanceof z.ZodError) {
const issue = error.errors[0];
const field = issue?.path[0];
const fallback =
typeof field === "string"
? `${fieldLabels[field] ?? field} is required`
: "Please check the registration form";
return NextResponse.json(
{ error: error.errors[0]?.message ?? "Validation error" },
{ status: 400 }
{ error: issue?.message === "Required" ? fallback : issue?.message },
{ status: 400 },
);
}
console.error("Registration error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
{ status: 500 },
);
}
}
}
+35 -10
View File
@@ -2,7 +2,7 @@ import { type NextRequest, NextResponse } from "next/server";
import { eq, and, gt } from "drizzle-orm";
import bcrypt from "bcryptjs";
import { db } from "~/server/db";
import { users } from "~/server/db/schema";
import { accounts, users } from "~/server/db/schema";
export async function POST(request: NextRequest) {
try {
@@ -47,15 +47,40 @@ export async function POST(request: NextRequest) {
// Hash the new password
const hashedPassword = await bcrypt.hash(password, 12);
// Update user with new password and clear reset token
await db
.update(users)
.set({
password: hashedPassword,
resetToken: null,
resetTokenExpiry: null,
})
.where(eq(users.id, user.id));
await db.transaction(async (tx) => {
await tx
.update(users)
.set({
password: hashedPassword,
resetToken: null,
resetTokenExpiry: null,
})
.where(eq(users.id, user.id));
const credentialAccount = await tx.query.accounts.findFirst({
where: and(
eq(accounts.userId, user.id),
eq(accounts.providerId, "credential"),
),
});
if (credentialAccount) {
await tx
.update(accounts)
.set({
password: hashedPassword,
updatedAt: new Date(),
})
.where(eq(accounts.id, credentialAccount.id));
} else {
await tx.insert(accounts).values({
userId: user.id,
accountId: user.id,
providerId: "credential",
password: hashedPassword,
});
}
});
return NextResponse.json(
{