Skip to content

Commit 6337c5b

Browse files
Merge pull request #5 from Learnathon-By-Geeky-Solutions/ismail
post related ui components created
2 parents 73c5ce6 + 76d6aca commit 6337c5b

File tree

3 files changed

+672
-0
lines changed

3 files changed

+672
-0
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useEffect, useState } from 'react';
2+
import { motion } from 'framer-motion';
3+
import PostCard from './PostCard'; // Assuming PostCard is imported from your code
4+
import { Link } from 'react-router-dom';
5+
import { useAuth } from '../context/AuthContext';
6+
const LatestJobPosts = () => {
7+
const [latestJobs, setLatestJobs] = useState([]);
8+
const {user} = useAuth();
9+
// Define animation variants
10+
const containerVariants = {
11+
hidden: {},
12+
visible: {
13+
transition: { staggerChildren: 0.2, delayChildren: 0.4 }
14+
}
15+
};
16+
17+
const itemVariants = {
18+
hidden: { opacity: 0, y: 50 },
19+
visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: 'easeOut' } }
20+
};
21+
const userType = user?.userType;
22+
23+
24+
useEffect(() => {
25+
const fetchLatestJobs = async () => {
26+
try {
27+
const response = await fetch('http://localhost:3500/post/getAllPosts');
28+
const data = await response.json();
29+
let filteredData = data;
30+
31+
// If current user is a Company, filter to show only their own job posts
32+
if (userType === 'Company') {
33+
filteredData = data.filter(job => job.user.name === user.name);
34+
}
35+
const latest = filteredData.sort((a,b)=>new Date(b.createdAt)-new Date(a.createdAt)).slice(0,6);
36+
if (response.ok) {
37+
setLatestJobs(latest);
38+
} else {
39+
console.error('Error fetching latest job posts:', data.error);
40+
}
41+
} catch (error) {
42+
console.error('Error fetching job posts:', error);
43+
}
44+
};
45+
46+
fetchLatestJobs();
47+
}, [user]);
48+
49+
return (
50+
<section className="container mx-auto py-12">
51+
<motion.h2
52+
className="text-3xl font-semibold text-center mb-4"
53+
initial={{ opacity: 0, y: 50 }}
54+
animate={{ opacity: 1, y: 0 }}
55+
transition={{ duration: 0.8, ease: 'easeOut' }}
56+
>
57+
Latest Job Posts
58+
</motion.h2>
59+
60+
{/* Stunning HR line */}
61+
<motion.hr
62+
initial={{ width: "0%" }}
63+
animate={{ width: "19%" }}
64+
transition={{ duration: 1, ease: "easeInOut" }}
65+
className="border-t-2 border-purple-600 mx-auto mb-8"
66+
/>
67+
68+
<motion.div
69+
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
70+
variants={containerVariants}
71+
initial="hidden"
72+
animate="visible"
73+
>
74+
{latestJobs.length > 0 ? (
75+
latestJobs.map((job) => (
76+
<motion.div
77+
key={job.id}
78+
variants={itemVariants}
79+
>
80+
<PostCard
81+
title={job.title}
82+
location={job.location}
83+
position={job.position}
84+
companyName={job.user.name}
85+
salaryRange={job.salary}
86+
experience={job.experience}
87+
skills={job.requiredSkills.map((skill) => skill.skill.name).join(', ')}
88+
jobPostId={job.id}
89+
deadline={job.deadline}
90+
/>
91+
</motion.div>
92+
))
93+
) : (
94+
<p className="text-center text-gray-500">No latest job posts available.</p>
95+
)}
96+
</motion.div>
97+
98+
<motion.div
99+
className="text-center mt-12"
100+
initial={{ opacity: 0, y: 50 }}
101+
animate={{ opacity: 1, y: 0 }}
102+
transition={{ duration: 0.8, ease: 'easeOut', delay: 0.4 }}
103+
>
104+
<Link to="/posts" className="bg-[rgb(97,27,248)] text-white py-3 px-6 rounded-lg hover:bg-[rgb(62,7,181)] transition-colors">
105+
See More
106+
</Link>
107+
</motion.div>
108+
</section>
109+
);
110+
};
111+
112+
export default LatestJobPosts;
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { useAuth } from '../context/AuthContext';
2+
import { useNavigate } from 'react-router-dom';
3+
import useConversation from '../zustand/useConversation';
4+
import { useState } from 'react';
5+
import {
6+
Download,
7+
CheckCircle,
8+
XCircle,
9+
Clock,
10+
Building,
11+
User,
12+
FileText
13+
} from 'lucide-react';
14+
15+
const OfferCard = ({ offer, newStatus }) => {
16+
const navigate = useNavigate();
17+
const { user } = useAuth();
18+
const [status, setStatus] = useState(offer.status);
19+
const cvPath = offer.offerLetterPath.split("/").pop();
20+
console.log(offer)
21+
const handleStatusChange = async (newStatus) => {
22+
try {
23+
// First, update the status of the offer
24+
const res = await fetch(`http://localhost:3500/offer/updateStatus/${offer.offerId}`, {
25+
method: "PUT",
26+
headers: {
27+
"Content-Type": "application/json"
28+
},
29+
body: JSON.stringify({ status: newStatus })
30+
});
31+
32+
if (res.ok) {
33+
setStatus(newStatus);
34+
35+
// If status is "Accepted", call the hire API to create a hiring record
36+
if (newStatus === 'Accepted') {
37+
const hireRes = await fetch('http://localhost:3500/hiring/hire', {
38+
method: 'POST',
39+
headers: {
40+
'Content-Type': 'application/json',
41+
},
42+
body: JSON.stringify({
43+
jobSeekerId: offer.jobSeekerId, // assuming `user.id` is the job seeker ID
44+
companyId: offer.companyId, // assuming `offer.company.id` is the company ID
45+
}),
46+
});
47+
48+
if (!hireRes.ok) {
49+
console.error("Failed to create hiring record");
50+
}
51+
}
52+
}
53+
} catch (error) {
54+
console.error("Failed to update status", error);
55+
}
56+
};
57+
58+
const getStatusDetails = (currentStatus) => {
59+
switch (currentStatus) {
60+
case 'Pending':
61+
return {
62+
icon: <Clock className="w-5 h-5 text-amber-500" />,
63+
bgColor: 'bg-amber-50',
64+
textColor: 'text-amber-600'
65+
};
66+
case 'Accepted':
67+
return {
68+
icon: <CheckCircle className="w-5 h-5 text-emerald-500" />,
69+
bgColor: 'bg-emerald-50',
70+
textColor: 'text-emerald-600'
71+
};
72+
case 'Rejected':
73+
return {
74+
icon: <XCircle className="w-5 h-5 text-rose-500" />,
75+
bgColor: 'bg-rose-50',
76+
textColor: 'text-rose-600'
77+
};
78+
default:
79+
return {
80+
icon: null,
81+
bgColor: 'bg-gray-50',
82+
textColor: 'text-gray-600'
83+
};
84+
}
85+
};
86+
87+
const statusDetails = getStatusDetails(status);
88+
89+
return (
90+
<div className="bg-white rounded-3xl shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden ">
91+
{/* Header */}
92+
<div className="bg-gradient-to-r from-violet-100 to-indigo-100 text-gray-800 p-6">
93+
<div className="flex justify-between items-start">
94+
<div className="flex items-center space-x-4">
95+
<Building className="w-8 h-8 text-violet-500" />
96+
<div>
97+
<h4 className="text-2xl font-bold mb-1 tracking-wide">{offer.company.name}</h4>
98+
<div className="flex items-center space-x-2 text-sm text-gray-600">
99+
<User className="w-4 h-4" />
100+
<span>{offer.application.jobPost.position}</span>
101+
</div>
102+
</div>
103+
</div>
104+
<div className="flex items-center space-x-2">
105+
{statusDetails.icon}
106+
<span className={`px-3 py-1 rounded-full text-xs font-medium ${statusDetails.bgColor} ${statusDetails.textColor}`}>
107+
{status}
108+
</span>
109+
</div>
110+
</div>
111+
</div>
112+
113+
{/* Card Body */}
114+
<div className="p-6">
115+
<div className="flex flex-col sm:flex-row justify-between items-center space-y-4 sm:space-y-0 sm:space-x-4">
116+
{/* Download Offer Letter Button */}
117+
<a
118+
href={`http://localhost:3500/apply/download/${cvPath}`}
119+
download
120+
className="
121+
w-full sm:w-auto flex items-center justify-center
122+
px-6 py-3
123+
bg-violet-100
124+
text-violet-700
125+
rounded-full
126+
hover:bg-violet-200
127+
transition duration-300
128+
space-x-2
129+
group
130+
"
131+
>
132+
<FileText className="w-5 h-5" />
133+
<span>Download Offer Letter</span>
134+
</a>
135+
136+
{/* Status Change Dropdown for Job Seeker */}
137+
{user.userType === 'JobSeeker' && (
138+
<select
139+
className="
140+
w-full sm:w-auto
141+
px-6 py-3
142+
bg-violet-100
143+
text-violet-700
144+
rounded-full
145+
focus:outline-none
146+
focus:ring-2
147+
focus:ring-offset-2
148+
focus:ring-violet-300
149+
"
150+
value={status}
151+
onChange={(e) => handleStatusChange(e.target.value)}
152+
>
153+
<option value="Pending">Pending</option>
154+
<option value="Accepted">Accepted</option>
155+
<option value="Rejected">Rejected</option>
156+
</select>
157+
)}
158+
</div>
159+
</div>
160+
</div>
161+
);
162+
};
163+
164+
export default OfferCard;

0 commit comments

Comments
 (0)