feat: implement complete invoicing application with CSV import and PDF export

- Add comprehensive CSV import system with drag-and-drop upload and validation
- Create UniversalTable component with advanced filtering, searching, and batch actions
- Implement invoice management (view, edit, delete) with professional PDF export
- Add client management with full CRUD operations
- Set up authentication with NextAuth.js and email/password login
- Configure database schema with users, clients, invoices, and invoice_items tables
- Build responsive UI with shadcn/ui components and emerald branding
- Add type-safe API layer with tRPC and Zod validation
- Include proper error handling and user feedback with toast notifications
- Set up development environment with Bun, TypeScript, and Tailwind CSS
This commit is contained in:
2025-07-10 04:07:19 -04:00
commit 2d217fab47
85 changed files with 17074 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
import { handlers } from "~/server/auth";
export const { GET, POST } = handlers;
+60
View File
@@ -0,0 +1,60 @@
import bcrypt from "bcryptjs";
import { eq } from "drizzle-orm";
import { type NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { db } from "~/server/db";
import { 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"),
email: z.string().email("Invalid email address"),
password: z.string().min(6, "Password must be at least 6 characters"),
});
export async function POST(request: NextRequest) {
try {
const body = await request.json() as z.infer<typeof registerSchema>;
const { firstName, lastName, email, password } = registerSchema.parse(body);
// Check if user already exists
const existingUser = await db.query.users.findFirst({
where: eq(users.email, email),
});
if (existingUser) {
return NextResponse.json(
{ error: "User with this email already exists" },
{ status: 400 }
);
}
// Hash password
const hashedPassword = await bcrypt.hash(password, 12);
// Create user
await db.insert(users).values({
name: `${firstName} ${lastName}`,
email,
password: hashedPassword,
});
return NextResponse.json(
{ message: "User created successfully" },
{ status: 201 }
);
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? "Validation error" },
{ status: 400 }
);
}
console.error("Registration error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 }
);
}
}
+34
View File
@@ -0,0 +1,34 @@
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { type NextRequest } from "next/server";
import { env } from "~/env";
import { appRouter } from "~/server/api/root";
import { createTRPCContext } from "~/server/api/trpc";
/**
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
* handling a HTTP request (e.g. when you make requests from Client Components).
*/
const createContext = async (req: NextRequest) => {
return createTRPCContext({
headers: req.headers,
});
};
const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: "/api/trpc",
req,
router: appRouter,
createContext: () => createContext(req),
onError:
env.NODE_ENV === "development"
? ({ path, error }) => {
console.error(
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`,
);
}
: undefined,
});
export { handler as GET, handler as POST };