From 220336f3d7c25e232b0613a77dd8e9ac3c8fcf24 Mon Sep 17 00:00:00 2001 From: Om Lanke Date: Tue, 8 Jul 2025 15:21:04 +0530 Subject: [PATCH] fix job application --- apps/student/app/(main)/actions.ts | 220 ++++----- apps/student/app/(main)/jobs/JobClient.tsx | 353 +++++++++++++++ apps/student/app/(main)/jobs/page.tsx | 490 +-------------------- apps/student/app/signup/action.ts | 132 +++--- 4 files changed, 546 insertions(+), 649 deletions(-) create mode 100644 apps/student/app/(main)/jobs/JobClient.tsx diff --git a/apps/student/app/(main)/actions.ts b/apps/student/app/(main)/actions.ts index 9cd2194..e6be2f0 100644 --- a/apps/student/app/(main)/actions.ts +++ b/apps/student/app/(main)/actions.ts @@ -1,134 +1,142 @@ -'use server' -import { signOut } from "@/auth"; -import { db, applications, jobs, students, resumes } from "@workspace/db"; -import { eq, and } from "@workspace/db/drizzle"; -import { revalidatePath } from "next/cache"; +'use server'; +import { signOut } from '@/auth'; +import { db, applications, jobs, students, resumes } from '@workspace/db'; +import { eq, and } from '@workspace/db/drizzle'; +import { revalidatePath } from 'next/cache'; export async function signOutAction() { - await signOut(); + await signOut(); } export async function applyForJob(jobId: number, studentId: number, resumeId: number) { - try { - // Check if student has already applied for this job - const existingApplication = await db.query.applications.findFirst({ - where: and( - eq(applications.jobId, jobId), - eq(applications.studentId, studentId) - ) - }); + try { + // Check if student has already applied for this job + const existingApplication = await db.query.applications.findFirst({ + where: and(eq(applications.jobId, jobId), eq(applications.studentId, studentId)), + }); - if (existingApplication) { - return { success: false, error: "You have already applied for this job" }; - } - - // Create new application - await db.insert(applications).values({ - jobId, - studentId, - resumeId, - status: 'pending' - }); - - revalidatePath('/applications'); - return { success: true }; - } catch (error) { - console.error("Error applying for job:", error); - return { success: false, error: "Failed to apply for job" }; + if (existingApplication) { + return { success: false, error: 'You have already applied for this job' }; } + + // Create new application + await db.insert(applications).values({ + jobId, + studentId, + resumeId, + status: 'pending', + }); + + revalidatePath('/applications'); + return { success: true }; + } catch (error) { + console.error('Error applying for job:', error); + return { success: false, error: 'Failed to apply for job' }; + } } export async function getStudentApplications(studentId: number) { - try { - const studentApplications = await db.query.applications.findMany({ - where: eq(applications.studentId, studentId), - with: { - job: { - with: { - company: true - } - }, - resume: true - } - }); + try { + const studentApplications = await db.query.applications.findMany({ + where: eq(applications.studentId, studentId), + with: { + job: { + with: { + company: true, + }, + }, + resume: true, + }, + }); - return { success: true, applications: studentApplications }; - } catch (error) { - console.error("Error fetching student applications:", error); - return { success: false, error: "Failed to fetch applications" }; - } + return { success: true, applications: studentApplications }; + } catch (error) { + console.error('Error fetching student applications:', error); + return { success: false, error: 'Failed to fetch applications' }; + } } export async function getStudentProfile(studentId: number) { - try { - const student = await db.query.students.findFirst({ - where: eq(students.id, studentId), - with: { - grades: true, - resumes: true, - internships: true - } - }); + try { + const student = await db.query.students.findFirst({ + where: eq(students.id, studentId), + with: { + grades: true, + resumes: true, + internships: true, + }, + }); - return { success: true, student }; - } catch (error) { - console.error("Error fetching student profile:", error); - return { success: false, error: "Failed to fetch student profile" }; - } + return { success: true, student }; + } catch (error) { + console.error('Error fetching student profile:', error); + return { success: false, error: 'Failed to fetch student profile' }; + } } export async function updateStudentProfile(studentId: number, data: any) { - try { - await db.update(students) - .set({ - ...data, - updatedAt: new Date() - }) - .where(eq(students.id, studentId)); + try { + await db + .update(students) + .set({ + ...data, + updatedAt: new Date(), + }) + .where(eq(students.id, studentId)); - revalidatePath('/profile'); - return { success: true }; - } catch (error) { - console.error("Error updating student profile:", error); - return { success: false, error: "Failed to update profile" }; - } + revalidatePath('/profile'); + return { success: true }; + } catch (error) { + console.error('Error updating student profile:', error); + return { success: false, error: 'Failed to update profile' }; + } } export async function getAvailableJobs() { - try { - const availableJobs = await db.query.jobs.findMany({ - where: eq(jobs.active, true), - with: { - company: true - } - }); + try { + const availableJobs = await db.query.jobs.findMany({ + where: eq(jobs.active, true), + with: { + company: true, + }, + }); - return { success: true, jobs: availableJobs }; - } catch (error) { - console.error("Error fetching available jobs:", error); - return { success: false, error: "Failed to fetch jobs" }; - } + return { success: true, jobs: availableJobs }; + } catch (error) { + console.error('Error fetching available jobs:', error); + return { success: false, error: 'Failed to fetch jobs' }; + } } export async function getFeaturedCompanies() { - try { - const companies = await db.query.companies.findMany({ - with: { - jobs: { - where: eq(jobs.active, true) - } - } - }); + try { + const companies = await db.query.companies.findMany({ + with: { + jobs: { + where: eq(jobs.active, true), + }, + }, + }); - // Filter companies with active jobs and sort by number of jobs - const companiesWithJobs = companies - .filter(company => company.jobs.length > 0) - .sort((a, b) => b.jobs.length - a.jobs.length) - .slice(0, 6); // Top 6 companies + // Filter companies with active jobs and sort by number of jobs + const companiesWithJobs = companies + .filter((company) => company.jobs.length > 0) + .sort((a, b) => b.jobs.length - a.jobs.length) + .slice(0, 6); // Top 6 companies - return { success: true, companies: companiesWithJobs }; - } catch (error) { - console.error("Error fetching featured companies:", error); - return { success: false, error: "Failed to fetch companies" }; - } -} \ No newline at end of file + return { success: true, companies: companiesWithJobs }; + } catch (error) { + console.error('Error fetching featured companies:', error); + return { success: false, error: 'Failed to fetch companies' }; + } +} + +export async function getResumes(studentId: number) { + try { + const r = await db.select().from(resumes).where(eq(resumes.studentId, studentId)); + return { success: true, resumes: r }; + } catch (error) { + console.error('Error fetching resumes for studentId:', studentId, '\n', error); + return { success: false, error: 'Failed to fetch resumes' }; + } +} diff --git a/apps/student/app/(main)/jobs/JobClient.tsx b/apps/student/app/(main)/jobs/JobClient.tsx new file mode 100644 index 0000000..b368e4e --- /dev/null +++ b/apps/student/app/(main)/jobs/JobClient.tsx @@ -0,0 +1,353 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card'; +import { Button } from '@workspace/ui/components/button'; +import { Badge } from '@workspace/ui/components/badge'; +import { Input } from '@workspace/ui/components/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@workspace/ui/components/select'; +import { + Building2, + MapPin, + DollarSign, + Calendar, + Clock, + Search, + Filter, + Bookmark, + Share2, + Eye, + ArrowRight, + Briefcase, + Users, + Star, + CheckCircle, + ExternalLink, + Loader2, + AlertCircle, +} from 'lucide-react'; +import JobApplicationModal from '@/components/job-application-modal'; +import { type InferSelectModel } from '@workspace/db/drizzle'; +import { jobs, companies, resumes } from '@workspace/db/schema'; + +export type Job = InferSelectModel & { + company: typeof companies.$inferSelect; +}; + +export type Resume = typeof resumes.$inferSelect; + +export default function JobsPage({ + jobs, + resumes, + studentId, +}: { + jobs: Job[]; + resumes: Resume[]; + studentId: number; +}) { + const [filteredJobs, setFilteredJobs] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); + const [locationFilter, setLocationFilter] = useState('all'); + const [jobTypeFilter, setJobTypeFilter] = useState('all'); + const [showLoadMore, setShowLoadMore] = useState(false); + + useEffect(() => { + filterJobs(); + }, [jobs, searchTerm, locationFilter, jobTypeFilter]); + + const filterJobs = () => { + let filtered = [...jobs]; + + // Search filter + if (searchTerm) { + filtered = filtered.filter( + (job) => + job.title.toLowerCase().includes(searchTerm.toLowerCase()) || + job.company.name.toLowerCase().includes(searchTerm.toLowerCase()) || + job.description.toLowerCase().includes(searchTerm.toLowerCase()), + ); + } + + // Location filter + if (locationFilter && locationFilter !== 'all') { + filtered = filtered.filter((job) => + job.location.toLowerCase().includes(locationFilter.toLowerCase()), + ); + } + + // Job type filter (simplified - could be enhanced with job type field) + if (jobTypeFilter && jobTypeFilter !== 'all') { + filtered = filtered.filter((job) => + job.title.toLowerCase().includes(jobTypeFilter.toLowerCase()), + ); + } + + setFilteredJobs(filtered); + setShowLoadMore(filtered.length > 6); + }; + + const handleSearch = (value: string) => { + setSearchTerm(value); + }; + + const handleLocationFilter = (value: string) => { + setLocationFilter(value); + }; + + const handleJobTypeFilter = (value: string) => { + setJobTypeFilter(value); + }; + + const clearFilters = () => { + setSearchTerm(''); + setLocationFilter('all'); + setJobTypeFilter('all'); + }; + + const displayedJobs = filteredJobs.slice(0, showLoadMore ? 6 : filteredJobs.length); + + return ( +
+
+ {/* Header */} +
+

Browse Jobs

+

+ Find the perfect opportunity that matches your skills and aspirations +

+
+ + {/* Search and Filter Section */} + + +
+
+
+ + handleSearch(e.target.value)} + /> +
+
+
+ +
+
+ +
+
+ {(searchTerm || + (locationFilter && locationFilter !== 'all') || + (jobTypeFilter && jobTypeFilter !== 'all')) && ( +
+ + + {filteredJobs.length} of {jobs.length} jobs + +
+ )} +
+
+ + {/* Stats */} +
+ + +
+
+

Total Jobs

+

{jobs.length}

+
+ +
+
+
+ + + +
+
+

Active Companies

+

+ {new Set(jobs.map((job) => job.company.name)).size} +

+
+ +
+
+
+ + + +
+
+

Remote Jobs

+

+ {jobs.filter((job) => job.location.toLowerCase().includes('remote')).length} +

+
+ +
+
+
+ + + +
+
+

Avg Salary

+

$28/hr

+
+ +
+
+
+
+ + {/* Jobs Grid */} +
+ {displayedJobs.map((job) => ( + + +
+
+
+ +
+
+

+ {job.title} +

+

{job.company.name}

+
+ + + {job.location} + + + + {job.salary} + +
+
+
+
+ + + Active + + +
+
+ +

{job.description}

+ +
+
+ + Deadline: {job.applicationDeadline.toLocaleDateString()} +
+
+ + Min CGPA: {job.minCGPA} +
+
+ +
+
+ + {job.link && ( + + )} +
+
+ + +
+
+
+
+ ))} +
+ + {/* Load More */} + {showLoadMore && ( +
+ +
+ )} + + {/* Empty State */} + {filteredJobs.length === 0 && ( + + + +

No jobs found

+

+ Try adjusting your search criteria or check back later for new opportunities +

+ +
+
+ )} +
+
+ ); +} diff --git a/apps/student/app/(main)/jobs/page.tsx b/apps/student/app/(main)/jobs/page.tsx index 31436db..add5f78 100644 --- a/apps/student/app/(main)/jobs/page.tsx +++ b/apps/student/app/(main)/jobs/page.tsx @@ -1,479 +1,17 @@ -'use client'; +import JobsClient from './JobClient'; +import { auth } from '@/auth'; +import { db, resumes } from '@workspace/db'; +import { eq } from '@workspace/db/drizzle'; -import { useState, useEffect } from 'react'; -import { Card, CardContent, CardHeader, CardTitle } from "@workspace/ui/components/card" -import { Button } from "@workspace/ui/components/button" -import { Badge } from "@workspace/ui/components/badge" -import { Input } from "@workspace/ui/components/input" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@workspace/ui/components/select" -import { - Building2, - MapPin, - DollarSign, - Calendar, - Clock, - Search, - Filter, - Bookmark, - Share2, - Eye, - ArrowRight, - Briefcase, - Users, - Star, - CheckCircle, - ExternalLink, - Loader2, - AlertCircle -} from "lucide-react" -import { getAvailableJobs } from "../actions" -import JobApplicationModal from "../../../components/job-application-modal" +export default async function JobsPage() { + const session = await auth(); + const studentId = session?.user?.studentId!; + const jobs = await db.query.jobs.findMany({ + with: { + company: true, + }, + }); + let reusmes = await db.select().from(resumes).where(eq(resumes.studentId, studentId)); -interface Job { - id: number; - title: string; - company: { - name: string; - email: string; - }; - location: string; - salary: string; - description: string; - applicationDeadline: Date; - minCGPA: number; - active: boolean; - link?: string; + return ; } - -export default function JobsPage() { - const [jobs, setJobs] = useState([]); - const [filteredJobs, setFilteredJobs] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [searchTerm, setSearchTerm] = useState(''); - const [locationFilter, setLocationFilter] = useState('all'); - const [jobTypeFilter, setJobTypeFilter] = useState('all'); - const [showLoadMore, setShowLoadMore] = useState(false); - - // Mock data for demonstration - const mockJobs: Job[] = [ - { - id: 1, - title: "Software Engineer Intern", - company: { - name: "TechCorp Solutions", - email: "careers@techcorp.com" - }, - location: "San Francisco, CA", - salary: "$25/hour", - description: "Join our dynamic team and work on cutting-edge projects. We're looking for passionate developers who love to learn and grow.", - applicationDeadline: new Date("2024-02-15"), - minCGPA: 7.5, - active: true, - link: "https://techcorp.com/careers" - }, - { - id: 2, - title: "Data Analyst", - company: { - name: "DataFlow Inc", - email: "hr@dataflow.com" - }, - location: "New York, NY", - salary: "$30/hour", - description: "Analyze large datasets and provide insights to drive business decisions. Experience with Python and SQL required.", - applicationDeadline: new Date("2024-02-10"), - minCGPA: 7.0, - active: true, - link: "https://dataflow.com/jobs" - }, - { - id: 3, - title: "Frontend Developer", - company: { - name: "WebSolutions", - email: "jobs@websolutions.com" - }, - location: "Remote", - salary: "$28/hour", - description: "Build beautiful and responsive user interfaces. Strong knowledge of React, TypeScript, and modern CSS required.", - applicationDeadline: new Date("2024-02-05"), - minCGPA: 7.2, - active: true, - link: "https://websolutions.com/careers" - }, - { - id: 4, - title: "Product Manager Intern", - company: { - name: "InnovateTech", - email: "careers@innovatetech.com" - }, - location: "Seattle, WA", - salary: "$32/hour", - description: "Work closely with cross-functional teams to define product requirements and drive product development.", - applicationDeadline: new Date("2024-02-01"), - minCGPA: 7.8, - active: true, - link: "https://innovatetech.com/jobs" - }, - { - id: 5, - title: "DevOps Engineer", - company: { - name: "CloudTech Systems", - email: "hr@cloudtech.com" - }, - location: "Austin, TX", - salary: "$35/hour", - description: "Manage cloud infrastructure and deployment pipelines. Experience with AWS, Docker, and Kubernetes preferred.", - applicationDeadline: new Date("2024-02-20"), - minCGPA: 7.0, - active: true, - link: "https://cloudtech.com/careers" - }, - { - id: 6, - title: "UX/UI Designer", - company: { - name: "DesignStudio", - email: "jobs@designstudio.com" - }, - location: "Los Angeles, CA", - salary: "$26/hour", - description: "Create intuitive and beautiful user experiences. Proficiency in Figma, Adobe Creative Suite, and user research methods.", - applicationDeadline: new Date("2024-02-12"), - minCGPA: 7.5, - active: true, - link: "https://designstudio.com/jobs" - } - ]; - - useEffect(() => { - loadJobs(); - }, []); - - const loadJobs = async () => { - try { - const result = await getAvailableJobs(); - if (result.success && result.jobs) { - setJobs(result.jobs as any); - setFilteredJobs(result.jobs as any); - } else { - setJobs(mockJobs); - setFilteredJobs(mockJobs); - setError(result.error || 'Using demo data'); - } - } catch (err) { - setJobs(mockJobs); - setFilteredJobs(mockJobs); - setError('Failed to load jobs, using demo data'); - } finally { - setIsLoading(false); - } - }; - - useEffect(() => { - filterJobs(); - }, [jobs, searchTerm, locationFilter, jobTypeFilter]); - - const filterJobs = () => { - let filtered = [...jobs]; - - // Search filter - if (searchTerm) { - filtered = filtered.filter(job => - job.title.toLowerCase().includes(searchTerm.toLowerCase()) || - job.company.name.toLowerCase().includes(searchTerm.toLowerCase()) || - job.description.toLowerCase().includes(searchTerm.toLowerCase()) - ); - } - - // Location filter - if (locationFilter && locationFilter !== 'all') { - filtered = filtered.filter(job => - job.location.toLowerCase().includes(locationFilter.toLowerCase()) - ); - } - - // Job type filter (simplified - could be enhanced with job type field) - if (jobTypeFilter && jobTypeFilter !== 'all') { - filtered = filtered.filter(job => - job.title.toLowerCase().includes(jobTypeFilter.toLowerCase()) - ); - } - - setFilteredJobs(filtered); - setShowLoadMore(filtered.length > 6); - }; - - const handleSearch = (value: string) => { - setSearchTerm(value); - }; - - const handleLocationFilter = (value: string) => { - setLocationFilter(value); - }; - - const handleJobTypeFilter = (value: string) => { - setJobTypeFilter(value); - }; - - const clearFilters = () => { - setSearchTerm(''); - setLocationFilter('all'); - setJobTypeFilter('all'); - }; - - const displayedJobs = filteredJobs.slice(0, showLoadMore ? 6 : filteredJobs.length); - - if (isLoading) { - return ( -
-
- -

Loading jobs...

-
-
- ); - } - - return ( -
-
- {/* Header */} -
-

Browse Jobs

-

Find the perfect opportunity that matches your skills and aspirations

- {error && ( -
- - {error} -
- )} -
- - {/* Search and Filter Section */} - - -
-
-
- - handleSearch(e.target.value)} - /> -
-
-
- -
-
- -
-
- {(searchTerm || (locationFilter && locationFilter !== 'all') || (jobTypeFilter && jobTypeFilter !== 'all')) && ( -
- - - {filteredJobs.length} of {jobs.length} jobs - -
- )} -
-
- - {/* Stats */} -
- - -
-
-

Total Jobs

-

{jobs.length}

-
- -
-
-
- - - -
-
-

Active Companies

-

- {new Set(jobs.map(job => job.company.name)).size} -

-
- -
-
-
- - - -
-
-

Remote Jobs

-

- {jobs.filter(job => job.location.toLowerCase().includes('remote')).length} -

-
- -
-
-
- - - -
-
-

Avg Salary

-

$28/hr

-
- -
-
-
-
- - {/* Jobs Grid */} -
- {displayedJobs.map((job) => ( - - -
-
-
- -
-
-

- {job.title} -

-

{job.company.name}

-
- - - {job.location} - - - - {job.salary} - -
-
-
-
- - - Active - - -
-
- -

{job.description}

- -
-
- - Deadline: {job.applicationDeadline.toLocaleDateString()} -
-
- - Min CGPA: {job.minCGPA} -
-
- -
-
- - {job.link && ( - - )} -
-
- - -
-
-
-
- ))} -
- - {/* Load More */} - {showLoadMore && ( -
- -
- )} - - {/* Empty State */} - {filteredJobs.length === 0 && ( - - - -

No jobs found

-

Try adjusting your search criteria or check back later for new opportunities

- -
-
- )} -
-
- ) -} \ No newline at end of file diff --git a/apps/student/app/signup/action.ts b/apps/student/app/signup/action.ts index 9a05f55..fa53835 100644 --- a/apps/student/app/signup/action.ts +++ b/apps/student/app/signup/action.ts @@ -27,82 +27,80 @@ export async function signupAction(data: StudentSignup) { const student = parsedData.data; - // Use a transaction to ensure all operations succeed or fail together - await db.transaction(async (tx) => { - // Update student table - await tx - .update(students) - .set({ - rollNumber: student.rollNumber, - firstName: student.firstName, - middleName: student.middleName, - lastName: student.lastName, - mothersName: student.mothersName, - gender: student.gender, - dob: student.dob, - personalGmail: student.personalGmail, - phoneNumber: student.phoneNumber, - address: student.address, - degree: student.degree, - branch: student.branch, - year: student.year, - skills: student.skills, // store as array - linkedin: student.linkedin, - github: student.github, - ssc: String(student.ssc), - hsc: String(student.hsc), - isDiploma: student.isDiploma, - }) - .where(eq(students.id, studentId)); + // Sequential DB operations (no transaction) + // Update student table + await db + .update(students) + .set({ + rollNumber: student.rollNumber, + firstName: student.firstName, + middleName: student.middleName, + lastName: student.lastName, + mothersName: student.mothersName, + gender: student.gender, + dob: student.dob, + personalGmail: student.personalGmail, + phoneNumber: student.phoneNumber, + address: student.address, + degree: student.degree, + branch: student.branch, + year: student.year, + skills: student.skills, // store as array + linkedin: student.linkedin, + github: student.github, + ssc: String(student.ssc), + hsc: String(student.hsc), + isDiploma: student.isDiploma, + }) + .where(eq(students.id, studentId)); - // Clear existing grades for this student - await tx.delete(grades).where(eq(grades.studentId, studentId)); + // Clear existing grades for this student + await db.delete(grades).where(eq(grades.studentId, studentId)); - // Insert grades (sgpi) - if (Array.isArray(student.sgpi)) { - for (const grade of student.sgpi) { - await tx.insert(grades).values({ - studentId: studentId, - sem: grade.sem, - sgpi: String(grade.sgpi), - isKT: grade.kt, - deadKT: grade.ktDead, - }); - } + // Insert grades (sgpi) + if (Array.isArray(student.sgpi)) { + for (const grade of student.sgpi) { + await db.insert(grades).values({ + studentId: studentId, + sem: grade.sem, + sgpi: String(grade.sgpi), + isKT: grade.kt, + deadKT: grade.ktDead, + }); } + } - // Clear existing internships for this student - await tx.delete(internshipsTable).where(eq(internshipsTable.studentId, studentId)); + // Clear existing internships for this student + await db.delete(internshipsTable).where(eq(internshipsTable.studentId, studentId)); - // Insert internships - if (Array.isArray(student.internships)) { - for (const internship of student.internships) { - await tx.insert(internshipsTable).values({ - studentId, - title: internship.title, - company: internship.company, - description: internship.description, - location: internship.location, - startDate: internship.startDate, - endDate: internship.endDate, - }); - } + // Insert internships + if (Array.isArray(student.internships)) { + for (const internship of student.internships) { + await db.insert(internshipsTable).values({ + studentId, + title: internship.title, + company: internship.company, + description: internship.description, + location: internship.location, + startDate: internship.startDate, + endDate: internship.endDate, + }); } + } - // Clear existing resumes for this student - await tx.delete(resumesTable).where(eq(resumesTable.studentId, studentId)); + // Clear existing resumes for this student + await db.delete(resumesTable).where(eq(resumesTable.studentId, studentId)); - // Insert resumes - if (Array.isArray(student.resume)) { - for (const resume of student.resume) { - await tx.insert(resumesTable).values({ - studentId, - title: resume.title, - link: resume.link, - }); - } + // Insert resumes + if (Array.isArray(student.resume)) { + for (const resume of student.resume) { + await db.insert(resumesTable).values({ + studentId, + title: resume.title, + link: resume.link, + }); } - }); + } return { success: true }; } catch (error) {