diff --git a/src/app/api/users/[id]/route.ts b/src/app/api/users/[id]/route.ts new file mode 100644 index 0000000..8b4e294 --- /dev/null +++ b/src/app/api/users/[id]/route.ts @@ -0,0 +1,24 @@ +import { NextRequest, NextResponse } from "next/server" +import { query } from "@/lib/db" +import { getSessionUser } from "@/lib/auth" + +export async function DELETE(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const sessionUser = await getSessionUser() + if (!sessionUser) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + } + + const { id } = await params + + await query( + `UPDATE users SET deleted_at = NOW() WHERE id = $1 AND deleted_at IS NULL`, + [id] + ) + + return NextResponse.json({ success: true }, { status: 200 }) + } catch (error) { + console.error("Error deleting user:", error) + return NextResponse.json({ error: "Failed to delete user" }, { status: 500 }) + } +} diff --git a/src/app/api/users/route.ts b/src/app/api/users/route.ts new file mode 100644 index 0000000..32bb235 --- /dev/null +++ b/src/app/api/users/route.ts @@ -0,0 +1,78 @@ +import { NextRequest, NextResponse } from "next/server" +import { query } from "@/lib/db" +import { hashPassword, getSessionUser } from "@/lib/auth" + +export async function GET() { + try { + const result = await query( + `SELECT u.id, u.username, u.email, u.first_name, u.last_name, + u.is_active AS active, u.created_at, + r.name AS role + FROM users u + JOIN user_roles ur ON ur.user_id = u.id + JOIN roles r ON r.id = ur.role_id + WHERE u.deleted_at IS NULL + ORDER BY u.created_at DESC` + ) + const users = result.rows.map((row: any) => ({ + id: row.id, + name: `${row.first_name} ${row.last_name}`, + email: row.email, + role: row.role.toLowerCase(), + active: row.active, + avatar: `https://ui-avatars.com/api/?name=${encodeURIComponent(`${row.first_name}+${row.last_name}`)}&background=1d4ed8&color=fff&size=128`, + createdAt: row.created_at, + })) + return NextResponse.json({ users }, { status: 200 }) + } catch (error) { + console.error("Error fetching users:", error) + return NextResponse.json({ error: "Failed to fetch users" }, { status: 500 }) + } +} + +export async function POST(request: NextRequest) { + try { + const sessionUser = await getSessionUser() + if (!sessionUser) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + } + + const { name, email, password, role, active } = await request.json() + if (!name || !email || !password || !role) { + return NextResponse.json({ error: "Name, email, password, and role are required" }, { status: 400 }) + } + + const nameParts = name.trim().split(/\s+/) + const firstName = nameParts[0] + const lastName = nameParts.slice(1).join(" ") || firstName + const username = email.split("@")[0] + const passwordHash = await hashPassword(password) + + const result = await query( + `INSERT INTO users (username, email, password_hash, first_name, last_name, is_active, created_by) + VALUES ($1, $2, $3, $4, $5, $6, $7) + RETURNING id`, + [username.toLowerCase(), email.toLowerCase(), passwordHash, firstName, lastName, active, sessionUser.id] + ) + + const roleId = ( + await query(`SELECT id FROM roles WHERE LOWER(name) = LOWER($1)`, [role]) + ).rows[0]?.id + + if (roleId) { + await query( + `INSERT INTO user_roles (user_id, role_id, assigned_by) + VALUES ($1, $2, $3)`, + [result.rows[0].id, roleId, sessionUser.id] + ) + } + + return NextResponse.json({ success: true, id: result.rows[0].id }, { status: 201 }) + } catch (error: any) { + console.error("Error creating user:", error) + if (error?.constraint === "uq_users_username" || error?.constraint === "uq_users_email") { + return NextResponse.json({ error: "A user with this email or username already exists" }, { status: 409 }) + } + return NextResponse.json({ error: error?.message || "Failed to create user" }, { status: 500 }) + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx deleted file mode 100644 index 64a14ed..0000000 --- a/src/app/layout.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { Metadata } from "next" -import { Inter } from "next/font/google" -import { ThemeProvider } from "@/providers/theme-provider" -import { Toaster } from "@/components/ui/sonner" -import "./globals.css" - -const inter = Inter({ - variable: "--font-inter", - subsets: ["latin"], -}) - -export const metadata: Metadata = { - title: "Coast IT - CRM", - description: "Customer Relationship Management System", - icons: { - icon: "/logo/CompanyMiniLogo.png", - }, -} - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode -}>) { - return ( - - - - {children} - - - - - ) -} diff --git a/src/app/login/layout.tsx b/src/app/login/layout.tsx new file mode 100644 index 0000000..a14e64f --- /dev/null +++ b/src/app/login/layout.tsx @@ -0,0 +1,16 @@ +export const metadata = { + title: 'Next.js', + description: 'Generated by Next.js', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +}