From 449629ece2bcb78a1a9e99c91dfcb7e737967dfa Mon Sep 17 00:00:00 2001 From: Om Lanke Date: Wed, 2 Jul 2025 12:05:38 +0530 Subject: [PATCH] finally building --- apps/admin/app/(main)/layout.tsx | 36 + apps/admin/app/{ => (main)}/page.tsx | 2 +- apps/admin/app/(main)/students/columns.tsx | 25 + apps/admin/app/(main)/students/data-table.tsx | 66 + apps/admin/app/(main)/students/page.tsx | 18 + .../admin/app/api/auth/[...nextauth]/route.ts | 2 +- apps/admin/app/login/page.tsx | 29 + packages/auth/index.ts => apps/admin/auth.ts | 39 +- apps/admin/components/login.tsx | 2 - apps/admin/components/studs.tsx | 2 +- apps/admin/middleware.ts | 17 +- apps/admin/next-auth.d.ts | 19 - apps/admin/next.config.mjs | 2 +- apps/admin/package.json | 25 +- apps/admin/tsconfig.json | 27 +- apps/student/app/(main)/layout.tsx | 36 + apps/student/app/{ => (main)}/page.tsx | 4 +- apps/student/app/{ => (main)}/signup/page.tsx | 0 .../app/api/auth/[...nextauth]/route.ts | 2 +- apps/student/app/login/page.tsx | 29 + apps/student/auth.ts | 87 + apps/student/components/login.tsx | 6 +- apps/student/components/studs.tsx | 2 +- apps/student/middleware.ts | 22 +- apps/student/next-auth.d.ts | 20 - apps/student/next.config.mjs | 2 +- apps/student/package.json | 29 +- apps/student/tsconfig.json | 27 +- package.json | 2 +- packages/auth/package.json | 22 - packages/auth/types.d.ts | 20 - packages/db/drizzle.ts | 1 + packages/db/index.ts | 2 +- packages/db/package.json | 16 +- packages/db/schema.ts | 4 +- packages/db/tsconfig.json | 12 + packages/ui/package.json | 1 + .../ui/src/components/navigation-menu.tsx | 168 + packages/ui/src/components/table.tsx | 116 + pnpm-lock.yaml | 5023 +++++------------ 40 files changed, 2253 insertions(+), 3711 deletions(-) create mode 100644 apps/admin/app/(main)/layout.tsx rename apps/admin/app/{ => (main)}/page.tsx (93%) create mode 100644 apps/admin/app/(main)/students/columns.tsx create mode 100644 apps/admin/app/(main)/students/data-table.tsx create mode 100644 apps/admin/app/(main)/students/page.tsx create mode 100644 apps/admin/app/login/page.tsx rename packages/auth/index.ts => apps/admin/auth.ts (68%) delete mode 100644 apps/admin/next-auth.d.ts create mode 100644 apps/student/app/(main)/layout.tsx rename apps/student/app/{ => (main)}/page.tsx (87%) rename apps/student/app/{ => (main)}/signup/page.tsx (100%) create mode 100644 apps/student/app/login/page.tsx create mode 100644 apps/student/auth.ts delete mode 100644 apps/student/next-auth.d.ts delete mode 100644 packages/auth/package.json delete mode 100644 packages/auth/types.d.ts create mode 100644 packages/db/drizzle.ts create mode 100644 packages/ui/src/components/navigation-menu.tsx create mode 100644 packages/ui/src/components/table.tsx diff --git a/apps/admin/app/(main)/layout.tsx b/apps/admin/app/(main)/layout.tsx new file mode 100644 index 0000000..8a08165 --- /dev/null +++ b/apps/admin/app/(main)/layout.tsx @@ -0,0 +1,36 @@ +import { + NavigationMenu, + NavigationMenuContent, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + NavigationMenuTrigger, + navigationMenuTriggerStyle, +} from '@workspace/ui/components/navigation-menu'; +import Link from 'next/link'; + +export default function MainLayout({ children }: { children: React.ReactNode }) { + return ( +
+
+ +
+
{children}
+
+ ); +} diff --git a/apps/admin/app/page.tsx b/apps/admin/app/(main)/page.tsx similarity index 93% rename from apps/admin/app/page.tsx rename to apps/admin/app/(main)/page.tsx index 17ce246..adda14f 100644 --- a/apps/admin/app/page.tsx +++ b/apps/admin/app/(main)/page.tsx @@ -1,7 +1,7 @@ import Login from '@/components/login'; import Studs from '@/components/studs'; import { db, admins } from '@workspace/db'; -import { auth, signIn, signOut } from '@workspace/auth'; +import { auth, signIn, signOut } from '@/auth'; async function getStudents() { 'use server'; diff --git a/apps/admin/app/(main)/students/columns.tsx b/apps/admin/app/(main)/students/columns.tsx new file mode 100644 index 0000000..239ac0e --- /dev/null +++ b/apps/admin/app/(main)/students/columns.tsx @@ -0,0 +1,25 @@ +import { ColumnDef } from '@tanstack/react-table'; +import { createSelectSchema, students } from '@workspace/db'; +import * as z from 'zod/v4'; + +const studentSelectSchema = createSelectSchema(students); +export type Student = z.infer; + +export const columns: ColumnDef[] = [ + { + accessorKey: 'id', + header: 'ID', + }, + { + accessorKey: 'firstName', + header: 'First Name', + }, + { + accessorKey: 'lastName', + header: 'Last Name', + }, + { + accessorKey: 'email', + header: 'Email', + }, +]; diff --git a/apps/admin/app/(main)/students/data-table.tsx b/apps/admin/app/(main)/students/data-table.tsx new file mode 100644 index 0000000..3a8dd74 --- /dev/null +++ b/apps/admin/app/(main)/students/data-table.tsx @@ -0,0 +1,66 @@ +'use client'; + +import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@workspace/ui/components/table'; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ columns, data }: DataTableProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+ ); +} diff --git a/apps/admin/app/(main)/students/page.tsx b/apps/admin/app/(main)/students/page.tsx new file mode 100644 index 0000000..ec18ffb --- /dev/null +++ b/apps/admin/app/(main)/students/page.tsx @@ -0,0 +1,18 @@ +import { columns, Student } from './columns'; +import { DataTable } from './data-table'; +import { db, students } from '@workspace/db'; + +async function getData(): Promise { + const data = db.select().from(students); + return data; +} + +export default async function DemoPage() { + const data = await getData(); + + return ( +
+ +
+ ); +} diff --git a/apps/admin/app/api/auth/[...nextauth]/route.ts b/apps/admin/app/api/auth/[...nextauth]/route.ts index 7a2b1b9..0aa1cd7 100644 --- a/apps/admin/app/api/auth/[...nextauth]/route.ts +++ b/apps/admin/app/api/auth/[...nextauth]/route.ts @@ -1,2 +1,2 @@ -import { handlers } from '@workspace/auth'; +import { handlers } from '@/auth'; export const { GET, POST } = handlers; diff --git a/apps/admin/app/login/page.tsx b/apps/admin/app/login/page.tsx new file mode 100644 index 0000000..88cea65 --- /dev/null +++ b/apps/admin/app/login/page.tsx @@ -0,0 +1,29 @@ +import { Button } from '@workspace/ui/components/button'; +import { signIn } from '@/auth'; + +async function logIn() { + 'use server'; + await signIn('google', { redirectTo: '/' }); +} + +export default async function Page() { + return ( +
+
+
+ +
+
+
+ ); +} diff --git a/packages/auth/index.ts b/apps/admin/auth.ts similarity index 68% rename from packages/auth/index.ts rename to apps/admin/auth.ts index 288b007..83e44c2 100644 --- a/packages/auth/index.ts +++ b/apps/admin/auth.ts @@ -1,7 +1,31 @@ -import NextAuth, { type NextAuthConfig } from 'next-auth'; -import Google from 'next-auth/providers/google'; +import NextAuth, { type DefaultSession } from 'next-auth'; +import type { NextAuthConfig } from 'next-auth'; +import Google from "next-auth/providers/google"; import { db, admins, students } from '@workspace/db'; -import { eq } from 'drizzle-orm'; +import { eq } from '@workspace/db/drizzle'; + +declare module 'next-auth' { + interface Session { + user: { + role?: 'ADMIN' | 'USER'; + adminId?: number; + studentId?: number; + [key: string]: any; + } & DefaultSession["user"]; + } + + interface JWT { + role?: 'ADMIN' | 'USER'; + adminId?: number; + studentId?: number; + } +} + +declare module 'next/server' { + interface NextRequest { + auth: import('next-auth').Session | null; + } +} const authConfig: NextAuthConfig = { providers: [Google], @@ -58,9 +82,6 @@ const authConfig: NextAuthConfig = { }, }; -const nextAuth = NextAuth(authConfig); - -export const handlers: typeof nextAuth.handlers = nextAuth.handlers; -export const signIn: typeof nextAuth.signIn = nextAuth.signIn; -export const signOut: typeof nextAuth.signOut = nextAuth.signOut; -export const auth: typeof nextAuth.auth = nextAuth.auth; +// Note: TypeScript warnings about inferred types are expected with NextAuth v5 beta +// These warnings don't affect functionality +export const { handlers, signIn, signOut, auth } = NextAuth(authConfig); diff --git a/apps/admin/components/login.tsx b/apps/admin/components/login.tsx index d442f81..01fe923 100644 --- a/apps/admin/components/login.tsx +++ b/apps/admin/components/login.tsx @@ -1,5 +1,3 @@ -'use client'; -import { signIn } from '@workspace/auth'; import { Button } from '@workspace/ui/components/button'; export default function Login({ action }: { action: () => Promise }) { diff --git a/apps/admin/components/studs.tsx b/apps/admin/components/studs.tsx index e771e7c..721f6aa 100644 --- a/apps/admin/components/studs.tsx +++ b/apps/admin/components/studs.tsx @@ -1,5 +1,5 @@ import { Button } from '@workspace/ui/components/button'; -import { auth } from '@workspace/auth'; +import { auth } from '@/auth'; export default async function Studs({ action, diff --git a/apps/admin/middleware.ts b/apps/admin/middleware.ts index 0b87fc6..2c6edd9 100644 --- a/apps/admin/middleware.ts +++ b/apps/admin/middleware.ts @@ -1,9 +1,12 @@ -import { auth } from '@workspace/auth'; -import { NextResponse } from 'next/server'; +import { auth } from '@/auth'; +import { NextResponse, NextRequest } from 'next/server'; -export default auth((req: any) => { - // If the user is unauthenticated or an admin, allow the request. - if (!req.auth || req.auth.user?.role === 'ADMIN') { +export default auth((req: NextRequest) => { + if (!req.auth) { + return NextResponse.redirect(new URL('/login', req.url)); + } + + if (req.auth.user?.role === 'ADMIN') { return NextResponse.next(); } @@ -11,8 +14,8 @@ export default auth((req: any) => { const studentUrl = process.env.STUDENT_URL ?? 'http://localhost:3000'; return NextResponse.redirect(new URL(studentUrl, req.url)); -}) as any; +}); export const config = { - matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], + matcher: ['/((?!api|_next/static|_next/image|favicon.ico|login).*)'], }; diff --git a/apps/admin/next-auth.d.ts b/apps/admin/next-auth.d.ts deleted file mode 100644 index f201cb6..0000000 --- a/apps/admin/next-auth.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import 'next-auth'; - -declare module 'next-auth' { - interface Session { - user: { - role?: 'ADMIN' | 'USER'; - adminId?: number; - studentId?: number; - [key: string]: any; - }; - } -} -declare module 'next-auth/jwt' { - interface JWT { - role?: 'ADMIN' | 'USER'; - adminId?: number; - studentId?: number; - } -} diff --git a/apps/admin/next.config.mjs b/apps/admin/next.config.mjs index 1e6e901..3538f42 100644 --- a/apps/admin/next.config.mjs +++ b/apps/admin/next.config.mjs @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - transpilePackages: ['@workspace/ui', '@workspace/db', '@workspace/auth'], + transpilePackages: ['@workspace/ui', '@workspace/db'], }; export default nextConfig; diff --git a/apps/admin/package.json b/apps/admin/package.json index 564d5e8..039df49 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -12,24 +12,25 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@workspace/auth": "workspace:*", + "@tanstack/react-table": "^8.21.3", "@workspace/db": "workspace:*", "@workspace/ui": "workspace:*", - "framer-motion": "^12.19.1", + "framer-motion": "^12.22.0", "lucide-react": "^0.475.0", - "next": "^15.2.3", - "next-auth": "^4.24.11", - "next-themes": "^0.4.4", - "react": "^19.0.0", - "react-dom": "^19.0.0" + "next": "^15.3.4", + "next-auth": "5.0.0-beta.29", + "next-themes": "^0.4.6", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "zod": "^3.25.67" }, "devDependencies": { - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/node": "^20.19.4", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", "@workspace/eslint-config": "workspace:^", "@workspace/typescript-config": "workspace:*", "dotenv-cli": "^8.0.0", - "typescript": "^5.7.3" + "typescript": "^5.8.3" } -} +} \ No newline at end of file diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json index 21c6218..b00e4ef 100644 --- a/apps/admin/tsconfig.json +++ b/apps/admin/tsconfig.json @@ -2,11 +2,18 @@ "extends": "@workspace/typescript-config/nextjs.json", "compilerOptions": { "baseUrl": ".", + "declaration": false, + "declarationMap": false, "paths": { - "@/*": ["./*"], - "@workspace/ui/*": ["../../packages/ui/src/*"], - "@workspace/db/*": ["../../packages/db/src/*"], - "@workspace/auth/*": ["../../packages/auth/src/*"] + "@/*": [ + "./*" + ], + "@workspace/ui/*": [ + "../../packages/ui/src/*" + ], + "@workspace/db": [ + "../../packages/db/index.ts" + ] }, "plugins": [ { @@ -14,6 +21,14 @@ } ] }, - "include": ["next-env.d.ts", "next.config.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "**/*.ts", + "**/*.tsx", + "next-env.d.ts", + "next.config.ts", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } diff --git a/apps/student/app/(main)/layout.tsx b/apps/student/app/(main)/layout.tsx new file mode 100644 index 0000000..da26ba0 --- /dev/null +++ b/apps/student/app/(main)/layout.tsx @@ -0,0 +1,36 @@ +import { + NavigationMenu, + NavigationMenuContent, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + NavigationMenuTrigger, + navigationMenuTriggerStyle, +} from '@workspace/ui/components/navigation-menu'; +import Link from 'next/link'; + +export default function MainLayout({ children }: { children: React.ReactNode }) { + return ( +
+
+ +
+
{children}
+
+ ); +} diff --git a/apps/student/app/page.tsx b/apps/student/app/(main)/page.tsx similarity index 87% rename from apps/student/app/page.tsx rename to apps/student/app/(main)/page.tsx index b265cf9..cebd26c 100644 --- a/apps/student/app/page.tsx +++ b/apps/student/app/(main)/page.tsx @@ -1,7 +1,7 @@ import Login from '@/components/login'; import Studs from '@/components/studs'; import { db, admins } from '@workspace/db'; -import { auth, signIn, signOut } from '@workspace/auth'; +import { auth, signIn, signOut } from '@/auth'; async function getStudents() { 'use server'; @@ -25,7 +25,7 @@ export default async function Page() {

Hello student {session?.user?.name}

- {!session?.user && } + {!session?.user && }
diff --git a/apps/student/app/signup/page.tsx b/apps/student/app/(main)/signup/page.tsx similarity index 100% rename from apps/student/app/signup/page.tsx rename to apps/student/app/(main)/signup/page.tsx diff --git a/apps/student/app/api/auth/[...nextauth]/route.ts b/apps/student/app/api/auth/[...nextauth]/route.ts index 7a2b1b9..0aa1cd7 100644 --- a/apps/student/app/api/auth/[...nextauth]/route.ts +++ b/apps/student/app/api/auth/[...nextauth]/route.ts @@ -1,2 +1,2 @@ -import { handlers } from '@workspace/auth'; +import { handlers } from '@/auth'; export const { GET, POST } = handlers; diff --git a/apps/student/app/login/page.tsx b/apps/student/app/login/page.tsx new file mode 100644 index 0000000..88cea65 --- /dev/null +++ b/apps/student/app/login/page.tsx @@ -0,0 +1,29 @@ +import { Button } from '@workspace/ui/components/button'; +import { signIn } from '@/auth'; + +async function logIn() { + 'use server'; + await signIn('google', { redirectTo: '/' }); +} + +export default async function Page() { + return ( +
+
+
+ +
+
+
+ ); +} diff --git a/apps/student/auth.ts b/apps/student/auth.ts new file mode 100644 index 0000000..83e44c2 --- /dev/null +++ b/apps/student/auth.ts @@ -0,0 +1,87 @@ +import NextAuth, { type DefaultSession } from 'next-auth'; +import type { NextAuthConfig } from 'next-auth'; +import Google from "next-auth/providers/google"; +import { db, admins, students } from '@workspace/db'; +import { eq } from '@workspace/db/drizzle'; + +declare module 'next-auth' { + interface Session { + user: { + role?: 'ADMIN' | 'USER'; + adminId?: number; + studentId?: number; + [key: string]: any; + } & DefaultSession["user"]; + } + + interface JWT { + role?: 'ADMIN' | 'USER'; + adminId?: number; + studentId?: number; + } +} + +declare module 'next/server' { + interface NextRequest { + auth: import('next-auth').Session | null; + } +} + +const authConfig: NextAuthConfig = { + providers: [Google], + callbacks: { + async jwt({ token, account, user, profile }) { + // Only check DB on first sign in + if (account && user && user.email) { + const admin = await db.select().from(admins).where(eq(admins.email, user.email)).limit(1); + if (admin.length > 0 && admin[0]) { + token.role = 'ADMIN'; + token.adminId = admin[0].id; + } else { + token.role = 'USER'; + const student = await db + .select() + .from(students) + .where(eq(students.email, user.email)) + .limit(1); + if (student.length > 0 && student[0]) { + token.studentId = student[0].id; + } else { + const nameParts = user.name?.split(' ') ?? []; + const firstName = nameParts[0] || ''; + const lastName = nameParts.slice(1).join(' ') || ''; + const newStudent = await db + .insert(students) + .values({ + email: user.email, + firstName: firstName, + lastName: lastName, + profilePicture: user.image, + }) + .returning({ id: students.id }); + if (newStudent[0]) { + token.studentId = newStudent[0].id; + } + } + } + } + return token; + }, + async session({ session, token }) { + if (token?.role) { + session.user.role = token.role as 'ADMIN' | 'USER'; + } + if (token?.adminId) { + session.user.adminId = token.adminId as number; + } + if (token?.studentId) { + session.user.studentId = token.studentId as number; + } + return session; + }, + }, +}; + +// Note: TypeScript warnings about inferred types are expected with NextAuth v5 beta +// These warnings don't affect functionality +export const { handlers, signIn, signOut, auth } = NextAuth(authConfig); diff --git a/apps/student/components/login.tsx b/apps/student/components/login.tsx index 2644e08..01fe923 100644 --- a/apps/student/components/login.tsx +++ b/apps/student/components/login.tsx @@ -1,10 +1,8 @@ -'use client'; -import { signIn } from '@workspace/auth'; import { Button } from '@workspace/ui/components/button'; -export default function Login({ logIn }: { logIn: () => Promise }) { +export default function Login({ action }: { action: () => Promise }) { return ( -
+