From ef98f8e1ba2e64d804c3f87a5fde329149c861b4 Mon Sep 17 00:00:00 2001 From: Anushlinux Date: Thu, 3 Jul 2025 01:11:57 +0530 Subject: [PATCH] AI slop goes brr --- .gitignore | 2 +- apps/admin/app/(main)/jobs/[jobId]/page.tsx | 80 ++++++++++ apps/admin/app/(main)/jobs/page.tsx | 73 +++++++++ apps/admin/app/(main)/layout.tsx | 68 ++++++-- apps/admin/app/(main)/page.tsx | 163 ++++++++++++++++++-- apps/admin/app/(main)/students/page.tsx | 49 +++++- apps/admin/app/login/page.tsx | 29 +++- packages/ui/package.json | 6 +- packages/ui/src/components/accordion.tsx | 56 +++++++ packages/ui/src/components/badge.tsx | 36 +++++ packages/ui/src/components/dialog.tsx | 121 +++++++++++++++ packages/ui/src/styles/globals.css | 126 ++++++++------- pnpm-lock.yaml | 104 ++++++++++++- 13 files changed, 799 insertions(+), 114 deletions(-) create mode 100644 apps/admin/app/(main)/jobs/[jobId]/page.tsx create mode 100644 apps/admin/app/(main)/jobs/page.tsx create mode 100644 packages/ui/src/components/accordion.tsx create mode 100644 packages/ui/src/components/badge.tsx create mode 100644 packages/ui/src/components/dialog.tsx diff --git a/.gitignore b/.gitignore index 4133bab..34c176f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ node_modules .env.development.local .env.test.local .env.production.local - +.cursorignore # Testing coverage diff --git a/apps/admin/app/(main)/jobs/[jobId]/page.tsx b/apps/admin/app/(main)/jobs/[jobId]/page.tsx new file mode 100644 index 0000000..cb6e693 --- /dev/null +++ b/apps/admin/app/(main)/jobs/[jobId]/page.tsx @@ -0,0 +1,80 @@ +import { db, jobs, companies, applications, students } from '@workspace/db'; +import { eq } from '@workspace/db/drizzle'; +import { notFound } from 'next/navigation'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@workspace/ui/components/card'; +import { Table, TableBody, TableHead, TableHeader, TableRow, TableCell } from '@workspace/ui/components/table'; + +interface JobPageProps { + params: { jobId: string }; +} + +export const dynamic = 'force-dynamic'; + +export default async function JobDetailPage({ params }: JobPageProps) { + const jobId = Number(params.jobId); + if (isNaN(jobId)) notFound(); + + const jobRes = await db.select().from(jobs).where(eq(jobs.id, jobId)).limit(1); + if (jobRes.length === 0) notFound(); + const job = jobRes[0]; + + const companyRes = await db.select().from(companies).where(eq(companies.id, job.companyId)).limit(1); + const company = companyRes[0]; + + const applicants = await db + .select({ + applicationId: applications.id, + status: applications.status, + firstName: students.firstName, + lastName: students.lastName, + email: students.email, + }) + .from(applications) + .leftJoin(students, eq(applications.studentId, students.id)) + .where(eq(applications.jobId, jobId)); + + return ( +
+ + + {job.title} + Company: {company?.name ?? 'Unknown'} + + +

Location: {job.location}

+

Salary: {job.salary}

+

Deadline: {job.applicationDeadline.toLocaleDateString()}

+

{job.description}

+
+
+ +
+

Students Applied

+ {applicants.length === 0 ? ( +

No applications yet.

+ ) : ( +
+ + + + Name + Email + Status + + + + {applicants.map((a) => ( + + {`${a.firstName ?? ''} ${a.lastName ?? ''}`.trim()} + {a.email} + {a.status} + + ))} + +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/apps/admin/app/(main)/jobs/page.tsx b/apps/admin/app/(main)/jobs/page.tsx new file mode 100644 index 0000000..964c1ff --- /dev/null +++ b/apps/admin/app/(main)/jobs/page.tsx @@ -0,0 +1,73 @@ +import { db, jobs, companies } from '@workspace/db'; +import { eq } from '@workspace/db/drizzle'; +import Link from 'next/link'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@workspace/ui/components/card'; +import { Button } from '@workspace/ui/components/button'; + +export const dynamic = 'force-dynamic'; + +async function getAllJobsWithCompany() { + const allJobs = await db.select().from(jobs); + const allCompanies = await db.select().from(companies); + return allJobs.map(job => ({ + ...job, + company: allCompanies.find(c => c.id === job.companyId) || null, + })); +} + +export default async function JobsListPage() { + const jobsWithCompany = await getAllJobsWithCompany(); + + return ( +
+

All Jobs

+
+ {jobsWithCompany.length === 0 && ( +

No jobs found.

+ )} + {jobsWithCompany.map((job) => ( + + + + {job.title} + + {job.active ? 'Active' : 'Inactive'} + + + + Company: {job.company?.name ?? 'Unknown'} + + + +
+ {job.company?.name} +
+
{job.company?.name}
+
{job.company?.email}
+
+
+
+
Location: {job.location}
+
Salary: {job.salary}
+
Deadline: {job.applicationDeadline.toLocaleDateString()}
+
Min CGPA: {job.minCGPA}
+
Min SSC: {job.minSSC}
+
Min HSC: {job.minHSC}
+
Allow Dead KT: {job.allowDeadKT ? 'Yes' : 'No'}
+
Allow Live KT: {job.allowLiveKT ? 'Yes' : 'No'}
+
Job Link: {job.link}
+
Description: {job.description}
+
Created: {job.createdAt.toLocaleDateString()} | Updated: {job.updatedAt.toLocaleDateString()}
+
+
+
+ + + +
+
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/apps/admin/app/(main)/layout.tsx b/apps/admin/app/(main)/layout.tsx index 8a08165..acf3d42 100644 --- a/apps/admin/app/(main)/layout.tsx +++ b/apps/admin/app/(main)/layout.tsx @@ -9,28 +9,64 @@ import { } from '@workspace/ui/components/navigation-menu'; import Link from 'next/link'; +const navLinks = [ + { href: '/', label: 'Home', icon: '🏠' }, + { href: '/students', label: 'Students', icon: '🎓' }, + { href: '/jobs', label: 'Jobs', icon: '💼' }, +]; + export default function MainLayout({ children }: { children: React.ReactNode }) { + // Helper to check active link (client-side only) + const isActive = (href: string) => { + if (typeof window === 'undefined') return false; + if (href === '/') return window.location.pathname === '/'; + return window.location.pathname.startsWith(href); + }; + return ( -
-
- -
-
{children}
+ +
{children}
+
); } diff --git a/apps/admin/app/(main)/page.tsx b/apps/admin/app/(main)/page.tsx index 4103738..08b8992 100644 --- a/apps/admin/app/(main)/page.tsx +++ b/apps/admin/app/(main)/page.tsx @@ -1,26 +1,159 @@ -import Studs from '@/components/studs'; -import { db, students } from '@workspace/db'; -import { auth, signOut } from '@/auth'; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@workspace/ui/components/card'; +import { Input } from '@workspace/ui/components/input'; +import { Textarea } from '@workspace/ui/components/textarea'; +import { Button } from '@workspace/ui/components/button'; +import Link from 'next/link'; +import { revalidatePath } from 'next/cache'; +import { db, companies, jobs } from '@workspace/db'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogDescription } from '@workspace/ui/components/dialog'; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@workspace/ui/components/accordion'; +import { Badge } from '@workspace/ui/components/badge'; -async function getStudents() { +// ----------------------- +// Server Actions +// ----------------------- + +async function createCompany(formData: FormData) { 'use server'; - const s = await db.select().from(students); - console.log(s); + const name = String(formData.get('name') ?? '').trim(); + const email = String(formData.get('email') ?? '').trim(); + const link = String(formData.get('link') ?? '').trim(); + const description = String(formData.get('description') ?? '').trim() || 'N/A'; + const imageURL = String(formData.get('imageURL') ?? '').trim() || 'https://via.placeholder.com/200x200?text=Company'; + + if (!name) return; + + await db.insert(companies).values({ name, email, link, description, imageURL }); + revalidatePath('/'); } -async function logOut() { +async function createJob(formData: FormData) { 'use server'; - await signOut(); + const companyId = Number(formData.get('companyId')); + const title = String(formData.get('title') ?? '').trim(); + const link = String(formData.get('jobLink') ?? '').trim(); + const description = String(formData.get('jobDescription') ?? '').trim() || 'N/A'; + const location = String(formData.get('location') ?? '').trim() || 'N/A'; + const imageURL = String(formData.get('jobImageURL') ?? '').trim() || 'https://via.placeholder.com/100x100?text=Job'; + const salary = String(formData.get('salary') ?? '').trim() || 'N/A'; + const deadlineRaw = formData.get('deadline'); + const applicationDeadline = deadlineRaw ? new Date(String(deadlineRaw)) : new Date(); + + if (!companyId || !title) return; + + await db.insert(jobs).values({ + companyId, + title, + link, + description, + location, + imageURL, + salary, + applicationDeadline, + active: true, + }); + revalidatePath('/'); } -export default async function Page() { - const session = await auth(); +// ----------------------- +// Component Helpers +// ----------------------- + +async function getDashboardData() { + const comps = await db.select().from(companies); + const allJobs = await db.select().from(jobs); + + return comps.map((comp) => ({ + ...comp, + jobs: allJobs.filter((j) => j.companyId === comp.id), + })); +} + +export default async function DashboardPage() { + const data = await getDashboardData(); + return ( -
-
-

Hello admin {session?.user?.name}

- -
+
+
+
+

Companies Dashboard

+

Manage companies and their job openings.

+
+ + + + + + + Add a new company + + Fill in the details below to add a new company to the portal. + + +
+ + + + +