diff --git a/apps/student/app/(main)/dashboard-client.tsx b/apps/student/app/(main)/dashboard-client.tsx new file mode 100644 index 0000000..d9486d6 --- /dev/null +++ b/apps/student/app/(main)/dashboard-client.tsx @@ -0,0 +1,411 @@ +'use client'; + +import React from 'react'; +import Link from 'next/link'; +import { motion } from 'framer-motion'; +import { + Building2, + Briefcase, + MapPin, + DollarSign, + ExternalLink, + TrendingUp, + Users, + Star, + Clock, + CheckCircle, + ArrowRight, + Search, + Filter, + Bookmark, + Share2, + Eye, + AlertCircle, +} from 'lucide-react'; +import { Card, CardContent } from '@workspace/ui/components/card'; +import { Button } from '@workspace/ui/components/button'; +import { Badge } from '@workspace/ui/components/badge'; + +interface DashboardClientProps { + companies: any[]; // TODO: replace with proper types from @workspace/db + totalStudents: number; + success: boolean; + error?: string; +} + +export default function DashboardClient({ + companies: data, + totalStudents, + success, + error, +}: DashboardClientProps) { + // Calculate stats + const totalActiveJobs = data.reduce( + (acc, company) => acc + company.jobs.filter((job: any) => job.active).length, + 0, + ); + const featuredCompanies = data.slice(0, 3); + const recentJobs = data + .flatMap((company) => company.jobs.slice(0, 2).map((job: any) => ({ ...job, company }))) + .slice(0, 6); + + return ( +
+ {/* Hero Section */} +
+
+
+ + + Discover Your Dream Career + + + Explore opportunities from top companies and find the perfect role that matches your skills and + aspirations + + + + + + + + + + + + + + +
+
+ + {/* Stats Section */} +
+
+ {!success && error && ( + + + Using demo data: {error} + + )} + + {[ + { icon: Building2, color: 'blue', value: data.length, label: 'Active Companies' }, + { icon: Briefcase, color: 'green', value: totalActiveJobs, label: 'Open Positions' }, + { icon: Users, color: 'purple', value: totalStudents, label: 'Students' }, + { icon: TrendingUp, color: 'orange', value: '95%', label: 'Success Rate' }, + ].map((stat, index) => { + const IconComponent = stat.icon; + return ( + + + + +

{stat.value}

+

{stat.label}

+
+ ); + })} +
+
+
+ + {/* Featured Companies Section */} +
+
+
+

Featured Companies

+

Top companies actively hiring talented students like you

+
+ + {featuredCompanies.length > 0 ? ( + + {featuredCompanies.map((company, index) => ( + + +
+
+ +
+
+ + + Featured + +
+
+ + +
+
+

{company.name}

+

{company.email}

+
+ + {company.jobs.length} jobs + +
+ + {company.description && company.description !== 'N/A' && ( +

{company.description}

+ )} + +
+ {company.jobs.slice(0, 2).map((job: any) => ( +
+
+

{job.title}

+
+ {job.location && job.location !== 'N/A' && ( + + + {job.location} + + )} + {job.salary && job.salary !== 'N/A' && ( + + + {job.salary} + + )} +
+
+ +
+ ))} +
+ +
+ + + + +
+
+
+
+ ))} +
+ ) : ( + + + +

No featured companies yet

+

Check back later for exciting opportunities

+ + + +
+
+ )} +
+
+ + {/* Recent Job Opportunities */} +
+
+
+
+

Recent Opportunities

+

Latest job postings from top companies

+
+ + + +
+ + {recentJobs.length > 0 ? ( +
+ {recentJobs.map((job: any) => ( + + +
+
+
+ +
+
+

{job.title}

+

{job.company.name}

+
+
+
+ + + Active + + +
+
+ +
+ {job.location && job.location !== 'N/A' && ( +
+ + {job.location} +
+ )} + {job.salary && job.salary !== 'N/A' && ( +
+ + {job.salary} +
+ )} +
+ + Deadline: {new Date(job.applicationDeadline).toLocaleDateString()} +
+
+ + Min CGPA: {job.minCGPA} +
+
+ + {job.description && job.description !== 'N/A' && ( +

{job.description}

+ )} + +
+
+ + + + {job.link && ( + + )} +
+ +
+
+
+ ))} +
+ ) : ( + + + +

No recent opportunities

+

Check back later for new job postings

+ + + +
+
+ )} + +
+ + + +
+
+
+ + {/* Call to Action */} +
+
+

Ready to Start Your Career Journey?

+

Join thousands of students who have found their dream jobs through NextPlacement

+
+ + + + + + +
+
+
+
+ ); +} \ No newline at end of file diff --git a/apps/student/app/(main)/profile/page.tsx b/apps/student/app/(main)/profile/page.tsx index 10e3cb4..3a6a51a 100644 --- a/apps/student/app/(main)/profile/page.tsx +++ b/apps/student/app/(main)/profile/page.tsx @@ -1,6 +1,8 @@ 'use client'; +/* eslint-disable @typescript-eslint/no-explicit-any */ import { useState, useEffect } from 'react'; +import { useSession } from 'next-auth/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" @@ -8,6 +10,7 @@ import { Separator } from "@workspace/ui/components/separator" import { Input } from "@workspace/ui/components/input" import { Label } from "@workspace/ui/components/label" import { Textarea } from "@workspace/ui/components/textarea" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@workspace/ui/components/select" import { User, Mail, @@ -27,54 +30,53 @@ import { Upload, CheckCircle, AlertCircle, + Briefcase, + Plus, + Trash2, + ExternalLink, X } from "lucide-react" import { getStudentProfile, updateStudentProfile } from "../actions" -// Mock student data as fallback -const mockStudent = { - id: 1, - firstName: "John", - middleName: "Michael", - lastName: "Doe", - email: "john.doe@college.edu", - rollNumber: "2021CS001", - phoneNumber: "+91 98765 43210", - address: "123 College Street, Mumbai, Maharashtra", - gender: "Male", - dob: "2000-05-15", - degree: "Bachelor of Technology", - branch: "Computer Science", - year: "4th Year", - skills: ["JavaScript", "React", "Node.js", "Python", "SQL", "Git"], - linkedin: "https://linkedin.com/in/johndoe", - github: "https://github.com/johndoe", - ssc: 9.2, - hsc: 8.8, - isDiploma: false, - verified: true, - markedOut: false, - profilePicture: null, - resumes: [ - { id: 1, title: "Resume_v2.pdf", link: "/resumes/resume_v2.pdf" } - ] -} +// Optional fallback student object to avoid UI crashes during development +const mockStudent = null; export default function ProfilePage() { - const [student, setStudent] = useState(mockStudent); + const [student, setStudent] = useState(mockStudent); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [editingSection, setEditingSection] = useState(null); const [editData, setEditData] = useState({}); const [isSaving, setIsSaving] = useState(false); + const [editingSkills, setEditingSkills] = useState([]); + const [newSkill, setNewSkill] = useState(''); + const [editingGrades, setEditingGrades] = useState([]); + const [editingInternships, setEditingInternships] = useState([]); + const [editingResumes, setEditingResumes] = useState([]); + const [newInternship, setNewInternship] = useState({ + title: '', + company: '', + location: '', + startDate: '', + endDate: '', + description: '' + }); + const [newResume, setNewResume] = useState({ + title: '', + link: '' + }); + const [isDragOver, setIsDragOver] = useState(false); + const { data: session, status } = useSession(); useEffect(() => { - loadStudentProfile(); - }, []); + if (status === 'authenticated' && session?.user?.studentId) { + loadStudentProfile(session.user.studentId); + } + }, [status, session]); - const loadStudentProfile = async () => { + const loadStudentProfile = async (id: number) => { try { - const result = await getStudentProfile(1); // Using student ID 1 for demo + const result = await getStudentProfile(id); if (result.success && result.student) { setStudent(result.student as any); } else { @@ -102,7 +104,7 @@ export default function ProfilePage() { try { const result = await updateStudentProfile(student.id, editData); if (result.success) { - setStudent(prev => ({ ...prev, ...editData })); + setStudent((prev: Record | null) => ({ ...(prev || {}), ...editData })); setEditingSection(null); setEditData({}); } else { @@ -116,10 +118,201 @@ export default function ProfilePage() { }; const handleInputChange = (field: string, value: string) => { - setEditData((prev: any) => ({ ...prev, [field]: value })); + setEditData((prev: Record) => ({ ...prev, [field]: value })); }; - if (isLoading) { + const handleInputChangeWithType = (field: string, value: string, type: 'string' | 'number' | 'boolean' = 'string') => { + let processedValue: any = value; + if (type === 'number') { + processedValue = parseFloat(value) || 0; + } else if (type === 'boolean') { + processedValue = value === 'true'; + } + setEditData((prev: Record) => ({ ...prev, [field]: processedValue })); + }; + + const addSkill = () => { + if (newSkill.trim() && !editingSkills.includes(newSkill.trim())) { + setEditingSkills([...editingSkills, newSkill.trim()]); + setNewSkill(''); + } + }; + + const removeSkill = (skillToRemove: string) => { + setEditingSkills(editingSkills.filter(skill => skill !== skillToRemove)); + }; + + const saveSkills = async () => { + setIsSaving(true); + try { + const result = await updateStudentProfile(student.id, { skills: editingSkills }); + if (result.success) { + setStudent((prev: Record | null) => ({ ...(prev || {}), skills: editingSkills })); + setEditingSection(null); + } else { + setError(result.error || 'Failed to update skills'); + } + } catch (err) { + setError('Failed to update skills'); + } finally { + setIsSaving(false); + } + }; + + const handleEditSkills = () => { + setEditingSkills(student.skills || []); + setEditingSection('skills'); + }; + + const handleEditGrades = () => { + setEditingGrades(student.grades || []); + setEditingSection('grades'); + }; + + const handleEditInternships = () => { + setEditingInternships(student.internships || []); + setEditingSection('internships'); + }; + + const handleEditResumes = () => { + setEditingResumes(student.resumes || []); + setEditingSection('resumes'); + }; + + const updateGrade = (sem: number, field: string, value: any) => { + setEditingGrades(prev => + prev.map(grade => + grade.sem === sem ? { ...grade, [field]: value } : grade + ) + ); + }; + + const addInternship = () => { + if (newInternship.title && newInternship.company) { + setEditingInternships([...editingInternships, { ...newInternship, id: Date.now() }]); + setNewInternship({ + title: '', + company: '', + location: '', + startDate: '', + endDate: '', + description: '' + }); + } + }; + + const removeInternship = (id: number) => { + setEditingInternships(editingInternships.filter(intern => intern.id !== id)); + }; + + const addResume = () => { + if (newResume.title && newResume.link) { + setEditingResumes([...editingResumes, { ...newResume, id: Date.now() }]); + setNewResume({ title: '', link: '' }); + } + }; + + const removeResume = (id: number) => { + setEditingResumes(editingResumes.filter(resume => resume.id !== id)); + }; + + const saveGrades = async () => { + setIsSaving(true); + try { + const result = await updateStudentProfile(student.id, { grades: editingGrades }); + if (result.success) { + setStudent((prev: Record | null) => ({ ...(prev || {}), grades: editingGrades })); + setEditingSection(null); + } else { + setError(result.error || 'Failed to update grades'); + } + } catch (err) { + setError('Failed to update grades'); + } finally { + setIsSaving(false); + } + }; + + const saveInternships = async () => { + setIsSaving(true); + try { + const result = await updateStudentProfile(student.id, { internships: editingInternships }); + if (result.success) { + setStudent((prev: Record | null) => ({ ...(prev || {}), internships: editingInternships })); + setEditingSection(null); + } else { + setError(result.error || 'Failed to update internships'); + } + } catch (err) { + setError('Failed to update internships'); + } finally { + setIsSaving(false); + } + }; + + const saveResumes = async () => { + setIsSaving(true); + try { + const result = await updateStudentProfile(student.id, { resumes: editingResumes }); + if (result.success) { + setStudent((prev: Record | null) => ({ ...(prev || {}), resumes: editingResumes })); + setEditingSection(null); + } else { + setError(result.error || 'Failed to update resumes'); + } + } catch (err) { + setError('Failed to update resumes'); + } finally { + setIsSaving(false); + } + }; + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(true); + }; + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + + const files = Array.from(e.dataTransfer.files); + files.forEach(file => { + if (file.type === 'application/pdf' || file.name.endsWith('.pdf')) { + // In a real app, you'd upload to cloud storage and get a URL + // For now, we'll create a mock URL + const mockUrl = URL.createObjectURL(file); + const newResumeItem = { + id: Date.now(), + title: file.name, + link: mockUrl + }; + setEditingResumes(prev => [...prev, newResumeItem]); + } + }); + }; + + const handleFileSelect = (e: React.ChangeEvent) => { + const files = Array.from(e.target.files || []); + files.forEach(file => { + if (file.type === 'application/pdf' || file.name.endsWith('.pdf')) { + const mockUrl = URL.createObjectURL(file); + const newResumeItem = { + id: Date.now(), + title: file.name, + link: mockUrl + }; + setEditingResumes(prev => [...prev, newResumeItem]); + } + }); + }; + + if (isLoading || status === 'loading') { return (
@@ -130,6 +323,14 @@ export default function ProfilePage() { ); } + if (!student) { + return ( +
+

No profile data found.

+
+ ); + } + return (
@@ -216,14 +417,33 @@ export default function ProfilePage() {

Social Links

- - + {student.linkedin && ( + + )} + {student.github && ( + + )} + {(!student.linkedin && !student.github) && ( +

No social links added yet

+ )}
@@ -297,6 +517,30 @@ export default function ProfilePage() { )}
+
+ + {editingSection === 'personal' ? ( + handleInputChange('mothersName', e.target.value)} + /> + ) : ( + + )} +
+
+ + {editingSection === 'personal' ? ( + handleInputChange('personalGmail', e.target.value)} + /> + ) : ( + + )} +
@@ -349,6 +593,32 @@ export default function ProfilePage() {