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 ;
0 commit comments