Skip to content

Commit 73c5ce6

Browse files
Merge pull request #4 from Learnathon-By-Geeky-Solutions/ismail
ui components created
2 parents fdeb40b + 63d3c7e commit 73c5ce6

File tree

4 files changed

+534
-0
lines changed

4 files changed

+534
-0
lines changed

frontend/src/components/AppDrawer.jsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Drawer, List, ListItem, ListItemText } from '@mui/material';
2+
import { useNavigate } from 'react-router-dom';
3+
4+
const AppDrawer = ({ drawerOpen, toggleDrawer, logout }) => {
5+
const navigate = useNavigate();
6+
7+
return (
8+
<Drawer anchor="right" open={drawerOpen} onClose={() => toggleDrawer(false)} onClick={toggleDrawer(false)}>
9+
<div className="w-64 p-4" onKeyDown={() => toggleDrawer(false)} >
10+
<h2 className="text-xl font-bold mb-4">Profile Options</h2>
11+
<List>
12+
<ListItem button onClick={() => navigate('/profile')}>
13+
<ListItemText primary="Profile" />
14+
</ListItem>
15+
<ListItem button>
16+
<ListItemText primary="Option 2" />
17+
</ListItem>
18+
<ListItem button onClick={logout}>
19+
<ListItemText primary="Logout" />
20+
</ListItem>
21+
</List>
22+
</div>
23+
</Drawer>
24+
);
25+
};
26+
27+
export default AppDrawer;
+311
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
import { useAuth } from '../context/AuthContext';
2+
import { useNavigate } from 'react-router-dom';
3+
import { useState } from 'react';
4+
import {
5+
Download,
6+
MessageCircle,
7+
CheckCircle,
8+
Clock,
9+
XCircle,
10+
Calendar,
11+
MapPin,
12+
User,
13+
Briefcase
14+
} from 'lucide-react';
15+
16+
const ApplicationCard = ({ app, onStatusChange }) => {
17+
const navigate = useNavigate();
18+
const { user } = useAuth();
19+
const [status, setStatus] = useState(app.status);
20+
const [modalState, setModalState] = useState({
21+
statusChangeModal: false,
22+
confirmationModal: false,
23+
selectedStatus: null
24+
});
25+
const cvPath = app.cvPath.split("/").pop();
26+
27+
const handleChat = async (receiverId) => {
28+
29+
try {
30+
31+
const senderId = user.userId;
32+
33+
const res = await fetch("http://localhost:3500/conversation/createConversation", {
34+
35+
method: "POST",
36+
37+
body: JSON.stringify({ senderId, receiverId }),
38+
39+
headers: { "Content-Type": "application/json" }
40+
41+
});
42+
43+
if (!res.ok) {
44+
45+
throw new Error(`HTTP error! status: ${res.status}`);
46+
47+
}
48+
49+
50+
51+
const res2 = await fetch(`http://localhost:3500/conversation/getCOnversations/${senderId}`);
52+
53+
const data = await res2.json();
54+
55+
const selectedConv = data.users.find((conv)=> conv.id===receiverId);
56+
57+
setSelectedConversation(selectedConv);
58+
59+
60+
61+
} catch (error) {
62+
63+
console.error(error.message);
64+
65+
}
66+
navigate('/chats');
67+
68+
};
69+
70+
const handleStatusChange = async (newStatus) => {
71+
try {
72+
const res = await fetch(`http://localhost:3500/apply/updateStatus/${app.applicationId}`, {
73+
method: "PUT",
74+
headers: { "Content-Type": "application/json" },
75+
body: JSON.stringify({ status: newStatus }),
76+
});
77+
if (res.ok) {
78+
setStatus(newStatus);
79+
onStatusChange(app.applicationId, newStatus);
80+
81+
// Reset modal state
82+
setModalState({
83+
statusChangeModal: false,
84+
confirmationModal: false,
85+
selectedStatus: null
86+
});
87+
88+
if (newStatus === "Accepted") {
89+
const offerRes = await fetch(`http://localhost:3500/offer/sendOffer`, {
90+
method: "POST",
91+
headers: { "Content-Type": "application/json" },
92+
body: JSON.stringify({
93+
jobSeekerId: app.userId,
94+
companyId: user.userId,
95+
status: "Pending",
96+
applicationId: app.applicationId,
97+
}),
98+
});
99+
const offerData = await offerRes.json();
100+
if (!offerRes.ok) {
101+
console.error("Failed to send offer letter:", offerData.message);
102+
} else {
103+
console.log("Offer letter sent successfully", offerData);
104+
}
105+
}
106+
}
107+
} catch (error) {
108+
console.error("Failed to update status", error);
109+
}
110+
};
111+
112+
const getStatusStyle = (currentStatus) => {
113+
const statusStyles = {
114+
'Pending': {
115+
icon: <Clock className="inline-block mr-2 text-sky-500" />,
116+
bgColor: 'bg-sky-50',
117+
textColor: 'text-sky-800'
118+
},
119+
'Accepted': {
120+
icon: <CheckCircle className="inline-block mr-2 text-emerald-500" />,
121+
bgColor: 'bg-emerald-50',
122+
textColor: 'text-emerald-800'
123+
},
124+
'Interview': {
125+
icon: <Calendar className="inline-block mr-2 text-amber-500" />,
126+
bgColor: 'bg-amber-50',
127+
textColor: 'text-amber-800'
128+
},
129+
'Rejected': {
130+
icon: <XCircle className="inline-block mr-2 text-rose-500" />,
131+
bgColor: 'bg-rose-50',
132+
textColor: 'text-rose-800'
133+
}
134+
};
135+
return statusStyles[currentStatus];
136+
};
137+
138+
const statusStyle = getStatusStyle(status);
139+
140+
// Status Change Modal Component
141+
const StatusChangeModal = () => {
142+
if (!modalState.statusChangeModal) return null;
143+
144+
return (
145+
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex justify-center items-center">
146+
<div className="bg-white rounded-3xl shadow-2xl max-w-md w-full p-6">
147+
<div className="bg-gradient-to-r from-indigo-500 to-purple-600 text-white p-4 -mx-6 -mt-6 mb-4 rounded-t-3xl">
148+
<h2 className="text-xl font-bold text-center">Change Application Status</h2>
149+
</div>
150+
151+
<div className="flex flex-col space-y-4">
152+
<button
153+
onClick={() => {
154+
handleStatusChange('Interview');
155+
}}
156+
className="w-full px-6 py-3 rounded-lg bg-amber-500 text-white hover:bg-amber-600 transition-all duration-300 flex items-center justify-center"
157+
>
158+
<Calendar className="mr-2" /> Schedule Interview
159+
</button>
160+
<button
161+
onClick={() => setModalState({
162+
statusChangeModal: false,
163+
confirmationModal: true,
164+
selectedStatus: 'Accepted'
165+
})}
166+
className="w-full px-6 py-3 rounded-lg bg-green-500 text-white hover:bg-green-600 transition-all duration-300 flex items-center justify-center"
167+
>
168+
<CheckCircle className="mr-2" /> Accept Application
169+
</button>
170+
<button
171+
onClick={() => setModalState({
172+
statusChangeModal: false,
173+
confirmationModal: true,
174+
selectedStatus: 'Rejected'
175+
})}
176+
className="w-full px-6 py-3 rounded-lg bg-red-500 text-white hover:bg-red-600 transition-all duration-300 flex items-center justify-center"
177+
>
178+
<XCircle className="mr-2" /> Reject Application
179+
</button>
180+
<button
181+
onClick={() => setModalState({
182+
statusChangeModal: false,
183+
confirmationModal: false,
184+
selectedStatus: null
185+
})}
186+
className="w-full px-6 py-3 rounded-lg bg-gray-100 text-gray-600 hover:bg-gray-200 transition-all duration-300"
187+
>
188+
Cancel
189+
</button>
190+
</div>
191+
</div>
192+
</div>
193+
);
194+
};
195+
196+
// Confirmation Modal Component
197+
const ConfirmationModal = () => {
198+
if (!modalState.confirmationModal) return null;
199+
200+
return (
201+
<div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex justify-center items-center">
202+
<div className="bg-white rounded-3xl shadow-2xl max-w-md w-full p-6">
203+
<div className="bg-gradient-to-r from-indigo-500 to-purple-600 text-white p-4 -mx-6 -mt-6 mb-4 rounded-t-3xl">
204+
<h2 className="text-xl font-bold text-center">Confirm Status Change</h2>
205+
</div>
206+
207+
<p className="text-center text-gray-600 mb-6">
208+
Are you sure you want to mark this application as <span className="font-bold text-indigo-600">{modalState.selectedStatus}</span>?
209+
</p>
210+
211+
<div className="flex justify-center space-x-4">
212+
<button
213+
onClick={() => setModalState({
214+
statusChangeModal: true,
215+
confirmationModal: false,
216+
selectedStatus: null
217+
})}
218+
className="px-6 py-2 bg-gray-100 text-gray-600 rounded-full hover:bg-gray-200 transition-all duration-300"
219+
>
220+
Cancel
221+
</button>
222+
<button
223+
onClick={() => handleStatusChange(modalState.selectedStatus)}
224+
className={`
225+
px-6 py-2 rounded-full text-white transition-all duration-300
226+
${modalState.selectedStatus === 'Accepted'
227+
? 'bg-green-500 hover:bg-green-600'
228+
: 'bg-red-500 hover:bg-red-600'}
229+
`}
230+
>
231+
Confirm
232+
</button>
233+
</div>
234+
</div>
235+
</div>
236+
);
237+
};
238+
239+
return (
240+
<>
241+
<StatusChangeModal />
242+
<ConfirmationModal />
243+
<div className="bg-white rounded-3xl shadow-xl overflow-hidden transition-all duration-300 hover:shadow-2xl">
244+
{/* Header */}
245+
<div className="bg-gradient-to-r from-indigo-100 to-purple-100 p-5 border-b border-gray-100">
246+
<div className="flex justify-between items-center">
247+
<div className="flex items-center">
248+
<User className="mr-3 text-indigo-500" size={24} />
249+
<div>
250+
<h4 className="text-xl font-semibold text-gray-800 tracking-wide">
251+
{user?.userType==='JobSeeker' ? app.jobPost.user.name : app.userName}
252+
</h4>
253+
<p className="text-sm text-gray-600">{app.jobPost.position}</p>
254+
</div>
255+
</div>
256+
<span className={`px-3 py-1 rounded-full text-xs font-medium ${statusStyle.bgColor} ${statusStyle.textColor}`}>
257+
{statusStyle.icon}
258+
{status}
259+
</span>
260+
</div>
261+
</div>
262+
263+
{/* Body */}
264+
<div className="p-5">
265+
{/* Location */}
266+
<div className="flex items-center mb-4 text-gray-600">
267+
<MapPin className="mr-2 text-violet-500" size={20} />
268+
<span className="text-sm">{app.jobPost.location}</span>
269+
</div>
270+
271+
{/* Action Buttons */}
272+
<div className={`flex ${user.userType === 'JobSeeker' ? "justify-center" : "justify-between"} space-x-2`}>
273+
<a
274+
href={`http://localhost:3500/apply/download/${cvPath}`}
275+
download
276+
className="flex items-center justify-center px-4 py-2 rounded-lg bg-gradient-to-r from-indigo-400 to-purple-500 text-white hover:opacity-90 transition-all duration-300"
277+
>
278+
<Download className="mr-2" />
279+
View CV
280+
</a>
281+
282+
{user.userType === 'Company' && (status === 'Pending' || status === 'Interview') && (
283+
<button
284+
onClick={() => setModalState({
285+
statusChangeModal: true,
286+
confirmationModal: false,
287+
selectedStatus: null
288+
})}
289+
className="flex items-center justify-center px-4 py-2 rounded-lg bg-gradient-to-r from-indigo-400 to-purple-500 text-white hover:opacity-90 transition-all duration-300"
290+
>
291+
Change Status
292+
</button>
293+
)}
294+
295+
{user.userType === 'Company' && (
296+
<button
297+
onClick={() => handleChat(app.userId)}
298+
className="flex items-center justify-center px-4 py-2 rounded-lg bg-gradient-to-r from-indigo-400 to-purple-500 text-white hover:opacity-90 transition-all duration-300"
299+
>
300+
<MessageCircle className="mr-2" />
301+
Chat
302+
</button>
303+
)}
304+
</div>
305+
</div>
306+
</div>
307+
</>
308+
);
309+
};
310+
311+
export default ApplicationCard;

frontend/src/components/Hero.jsx

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Link } from 'react-router-dom';
2+
import { delay, motion } from 'framer-motion';
3+
4+
const Hero = () => {
5+
const slideInLeft = {
6+
hidden: { opacity: 0, x: -100 },
7+
visible: { opacity: 1, x: 0, transition: { duration: 1, ease: 'easeInOut' ,delay: 0.1} }
8+
};
9+
10+
const slideInRight = {
11+
hidden: { opacity: 0, x: 100 },
12+
visible: { opacity: 1, x: 0, transition: { duration: 1, ease: 'easeInOut' , delay: 0.1} }
13+
};
14+
15+
return (
16+
<section className="container mx-auto py-12 flex flex-col md:flex-row items-center justify-between">
17+
<motion.div
18+
className="md:w-1/2"
19+
variants={slideInLeft}
20+
initial="hidden"
21+
animate="visible"
22+
>
23+
<h2 className="text-4xl font-bold mb-4">
24+
Your Career, Just a <strong className="text-customm">Node</strong> Away
25+
</h2>
26+
<p className="mb-6 text-gray-600">
27+
JobNode connects job seekers with employers quickly and easily. It’s a platform designed to make finding jobs or hiring talent simple and straightforward. Whether you're looking for work or hiring, JobNode helps you get it done.
28+
</p>
29+
<Link
30+
to="/posts"
31+
className="bg-customm text-white py-3 px-6 rounded-md hover:bg-[rgba(62,7,181,1)]"
32+
>
33+
Explore Posts
34+
</Link>
35+
</motion.div>
36+
<motion.div
37+
className="md:w-1/3"
38+
variants={slideInRight}
39+
initial="hidden"
40+
animate="visible"
41+
>
42+
<img
43+
src="./imageHome.jpg"
44+
alt="Innovative Technology"
45+
className="rounded-md shadow-md"
46+
/>
47+
</motion.div>
48+
</section>
49+
);
50+
};
51+
52+
export default Hero;

0 commit comments

Comments
 (0)