husseinelsaadi commited on
Commit
4741385
·
1 Parent(s): a4c7637
Files changed (1) hide show
  1. backend/templates/apply.html +108 -420
backend/templates/apply.html CHANGED
@@ -22,467 +22,155 @@
22
  <li>Apply</li>
23
  </ul>
24
 
25
- <div class="application-container">
26
- <div class="card application-card">
27
- <div class="card-header">
28
- <h2>Submit Your Application</h2>
29
- <p>Please upload your resume and fill in the required information below.</p>
30
- </div>
31
-
32
- <div class="card-body">
33
- <form method="POST" enctype="multipart/form-data" id="application-form">
34
- <!-- Resume Upload Section -->
35
- <div class="upload-section">
36
- <div class="form-group">
37
- <label for="resume">
38
- <i class="upload-icon">📄</i>
39
- Upload Resume
40
- </label>
41
- <div class="file-input-wrapper">
42
- <input type="file" name="resume" id="resume" class="file-input" required accept=".pdf,.doc,.docx">
43
- <div class="file-input-display">
44
- <span class="file-placeholder">Choose file... (PDF, DOCX)</span>
45
- <span class="file-name"></span>
46
- </div>
47
- </div>
48
- <button type="button" id="parse-resume" class="btn btn-secondary mt-2">
49
- <span class="btn-icon">🔍</span> Parse Resume
50
- </button>
51
- </div>
52
- </div>
53
-
54
- <!-- Personal Information -->
55
- <div class="form-section">
56
- <h3 class="section-title">Personal Information</h3>
57
- <div class="form-group">
58
- <label for="full-name">Full Name</label>
59
- <input type="text" name="full_name" id="full-name" class="form-control" placeholder="e.g. Jane Doe" required>
60
- </div>
61
- </div>
62
 
63
- <!-- Professional Details -->
64
- <div class="form-section">
65
- <h3 class="section-title">Professional Details</h3>
66
-
67
- <div class="form-group">
68
- <label for="skills">Skills</label>
69
- <textarea name="skills" id="skills" class="form-control" rows="3"
70
- placeholder="e.g. Python, Data Analysis, Project Management" required></textarea>
71
- <small class="form-text">Separate skills with commas</small>
72
- </div>
 
 
73
 
74
- <div class="form-group">
75
- <label for="experience">Experience</label>
76
- <textarea name="experience" id="experience" class="form-control" rows="4"
77
- placeholder="e.g. 3 years at TechCorp as a Backend Developer" required></textarea>
78
- <small class="form-text">List your relevant work experience</small>
79
- </div>
80
 
81
- <div class="form-group">
82
- <label for="education">Education</label>
83
- <textarea name="education" id="education" class="form-control" rows="3"
84
- placeholder="e.g. B.Sc. in Computer Science, M.Sc. in Data Science" required></textarea>
85
- <small class="form-text">Include degrees, certifications, and relevant coursework</small>
86
- </div>
87
- </div>
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- <!-- Submit Section -->
90
- <div class="application-actions">
91
- <button type="submit" class="btn btn-primary btn-lg">
92
- <span class="btn-icon">✓</span> Submit Application
93
- </button>
94
- <a href="{{ url_for('job_detail', job_id=job.id) }}" class="btn btn-outline">
95
- <span class="btn-icon">←</span> Back to Job Details
96
- </a>
97
- </div>
98
- </form>
99
- </div>
100
- </div>
101
 
102
- <!-- Job Summary Sidebar -->
103
- <div class="job-summary-sidebar">
104
- <div class="card">
105
- <div class="card-header">
106
- <h3>Job Summary</h3>
107
- </div>
108
- <div class="card-body">
109
- <div class="summary-item">
110
- <strong>Role:</strong> {{ job.role }}
111
- </div>
112
- <div class="summary-item">
113
- <strong>Company:</strong> {{ job.company }}
114
- </div>
115
- {% if job.seniority %}
116
- <div class="summary-item">
117
- <strong>Seniority:</strong> {{ job.seniority }}
118
- </div>
119
- {% endif %}
120
- {% if job.location %}
121
- <div class="summary-item">
122
- <strong>Location:</strong> {{ job.location }}
123
- </div>
124
- {% endif %}
125
- {% if job.salary %}
126
- <div class="summary-item">
127
- <strong>Salary:</strong> {{ job.salary }}
128
- </div>
129
- {% endif %}
130
  </div>
 
 
 
 
131
  </div>
132
  </div>
133
  </div>
134
  </section>
135
 
136
  <style>
137
- /* Application Container */
138
- .application-container {
139
- display: grid;
140
- grid-template-columns: 1fr;
141
- gap: 2rem;
142
- }
143
-
144
- @media (min-width: 992px) {
145
- .application-container {
146
- grid-template-columns: 2fr 1fr;
147
- }
148
- }
149
-
150
- /* Card Enhancements */
151
- .application-card {
152
- background: white;
153
- border-radius: 12px;
154
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
155
- overflow: hidden;
156
- }
157
-
158
- .application-card .card-header {
159
- background: linear-gradient(135deg, var(--primary), var(--secondary));
160
- color: white;
161
- padding: 2rem;
162
- }
163
-
164
- .application-card .card-header h2 {
165
- margin: 0 0 0.5rem 0;
166
- font-size: 1.75rem;
167
- }
168
-
169
- .application-card .card-header p {
170
- margin: 0;
171
- opacity: 0.9;
172
- }
173
-
174
- .application-card .card-body {
175
- padding: 2rem;
176
- }
177
-
178
- /* Form Sections */
179
- .form-section {
180
- margin-bottom: 2.5rem;
181
- }
182
-
183
- .section-title {
184
- font-size: 1.25rem;
185
- color: var(--primary);
186
- margin-bottom: 1.5rem;
187
- padding-bottom: 0.5rem;
188
- border-bottom: 2px solid #e9ecef;
189
- }
190
-
191
- /* Form Elements */
192
- .form-group {
193
- margin-bottom: 1.5rem;
194
- }
195
-
196
  .form-group label {
197
  font-weight: 600;
198
- color: var(--dark);
199
  margin-bottom: 0.5rem;
200
- display: flex;
201
- align-items: center;
202
- gap: 0.5rem;
203
  }
204
 
205
  .form-control {
206
  width: 100%;
207
- padding: 0.875rem;
208
  font-size: 1rem;
209
- border-radius: 8px;
210
- border: 2px solid #e9ecef;
211
- transition: all 0.3s ease;
212
- background-color: #f8f9fa;
213
- }
214
-
215
- .form-control:focus {
216
- outline: none;
217
- border-color: var(--primary);
218
- background-color: white;
219
- box-shadow: 0 0 0 4px rgba(67, 97, 238, 0.1);
220
  }
221
 
222
- .form-text {
223
- display: block;
224
- margin-top: 0.5rem;
225
- color: #6c757d;
226
- font-size: 0.875rem;
227
- }
228
-
229
- /* File Upload Styling */
230
- .upload-section {
231
- background-color: #f8f9fa;
232
- padding: 1.5rem;
233
- border-radius: 8px;
234
- margin-bottom: 2rem;
235
- }
236
-
237
- .upload-icon {
238
- font-size: 1.5rem;
239
- }
240
-
241
- .file-input-wrapper {
242
- position: relative;
243
- overflow: hidden;
244
- display: inline-block;
245
- width: 100%;
246
- }
247
-
248
- .file-input {
249
- position: absolute;
250
- left: -9999px;
251
- }
252
-
253
- .file-input-display {
254
- display: block;
255
- padding: 0.875rem;
256
- border: 2px dashed var(--primary);
257
- border-radius: 8px;
258
- background-color: white;
259
- cursor: pointer;
260
- transition: all 0.3s ease;
261
  text-align: center;
262
  }
263
 
264
- .file-input-wrapper:hover .file-input-display {
265
- border-color: var(--secondary);
266
- background-color: #f8f9fa;
267
- }
268
-
269
- .file-placeholder {
270
- color: #6c757d;
271
- }
272
-
273
- .file-name {
274
- color: var(--primary);
275
- font-weight: 500;
276
- }
277
-
278
- /* Button Enhancements */
279
- .btn {
280
- display: inline-flex;
281
- align-items: center;
282
- gap: 0.5rem;
283
  padding: 0.75rem 1.5rem;
284
  font-weight: 500;
285
- border-radius: 8px;
286
- transition: all 0.3s ease;
 
287
  }
288
 
289
- .btn-lg {
290
- padding: 1rem 2rem;
291
- font-size: 1.1rem;
292
  }
293
 
 
294
  .btn-secondary {
295
- background: #6c757d;
296
  color: white;
 
 
297
  border: none;
 
 
298
  }
299
-
300
  .btn-secondary:hover {
301
- background: #5a6268;
302
- transform: translateY(-2px);
303
- }
304
-
305
- .btn-icon {
306
- font-size: 1.2rem;
307
- }
308
-
309
- /* Application Actions */
310
- .application-actions {
311
- display: flex;
312
- gap: 1rem;
313
- margin-top: 2rem;
314
- padding-top: 2rem;
315
- border-top: 2px solid #e9ecef;
316
- flex-wrap: wrap;
317
- }
318
-
319
- /* Job Summary Sidebar */
320
- .job-summary-sidebar .card {
321
- position: sticky;
322
- top: 2rem;
323
- }
324
-
325
- .job-summary-sidebar .card-header {
326
- background-color: #f8f9fa;
327
- padding: 1rem 1.5rem;
328
- }
329
-
330
- .job-summary-sidebar .card-header h3 {
331
- margin: 0;
332
- font-size: 1.2rem;
333
- color: var(--primary);
334
- }
335
-
336
- .summary-item {
337
- padding: 0.75rem 0;
338
- border-bottom: 1px solid #e9ecef;
339
- }
340
-
341
- .summary-item:last-child {
342
- border-bottom: none;
343
- }
344
-
345
- /* Responsive Adjustments */
346
- @media (max-width: 768px) {
347
- .application-card .card-header {
348
- padding: 1.5rem;
349
- }
350
-
351
- .application-card .card-body {
352
- padding: 1.5rem;
353
- }
354
-
355
- .form-section {
356
- margin-bottom: 2rem;
357
- }
358
-
359
- .application-actions {
360
- flex-direction: column;
361
- }
362
-
363
- .application-actions .btn {
364
- width: 100%;
365
- justify-content: center;
366
- }
367
-
368
- .job-summary-sidebar {
369
- order: -1;
370
- }
371
- }
372
-
373
- /* Loading State */
374
- .btn:disabled {
375
- opacity: 0.6;
376
- cursor: not-allowed;
377
- }
378
-
379
- /* Success Animation */
380
- @keyframes successPulse {
381
- 0% {
382
- box-shadow: 0 0 0 0 rgba(46, 204, 113, 0.4);
383
- }
384
- 70% {
385
- box-shadow: 0 0 0 10px rgba(46, 204, 113, 0);
386
- }
387
- 100% {
388
- box-shadow: 0 0 0 0 rgba(46, 204, 113, 0);
389
- }
390
- }
391
-
392
- .application-success {
393
- animation: successPulse 2s infinite;
394
  }
395
  </style>
396
 
 
 
 
397
  <script>
398
  document.addEventListener('DOMContentLoaded', function() {
399
- // File input handling
400
- const fileInput = document.getElementById('resume');
401
- const fileDisplay = document.querySelector('.file-input-display');
402
- const filePlaceholder = document.querySelector('.file-placeholder');
403
- const fileName = document.querySelector('.file-name');
404
-
405
- fileInput.addEventListener('change', function() {
406
- if (this.files && this.files[0]) {
407
- filePlaceholder.style.display = 'none';
408
- fileName.textContent = this.files[0].name;
409
- fileName.style.display = 'block';
410
- fileDisplay.style.borderStyle = 'solid';
411
- fileDisplay.style.borderColor = 'var(--success)';
412
- }
413
- });
414
-
415
- // Parse resume functionality
416
- const parseBtn = document.getElementById('parse-resume');
417
- if (parseBtn) {
418
- parseBtn.addEventListener('click', function() {
419
- const resumeInput = document.getElementById('resume');
420
- if (!resumeInput || !resumeInput.files || resumeInput.files.length === 0) {
421
- alert('Please upload your resume before parsing.');
422
- return;
423
- }
424
-
425
- // Show loading state
426
- parseBtn.disabled = true;
427
- parseBtn.innerHTML = '<span class="btn-icon">⏳</span> Parsing...';
428
-
429
- const formData = new FormData();
430
- formData.append('resume', resumeInput.files[0]);
431
-
432
- fetch('/parse_resume', {
433
- method: 'POST',
434
- body: formData
435
- })
436
- .then(resp => resp.json())
437
- .then(data => {
438
- if (data) {
439
- // Populate fields with animation
440
- const fields = [
441
- { id: 'full-name', value: data.name },
442
- { id: 'skills', value: data.skills },
443
- { id: 'education', value: data.education },
444
- { id: 'experience', value: data.experience }
445
- ];
446
-
447
- fields.forEach((field, index) => {
448
- if (field.value && document.getElementById(field.id)) {
449
- setTimeout(() => {
450
- const element = document.getElementById(field.id);
451
- element.value = field.value;
452
- element.style.borderColor = 'var(--success)';
453
- setTimeout(() => {
454
- element.style.borderColor = '';
455
- }, 1000);
456
- }, index * 200);
457
- }
458
- });
459
-
460
- // Show success message
461
- parseBtn.innerHTML = '<span class="btn-icon">✓</span> Parsed Successfully';
462
- parseBtn.classList.add('btn-success');
463
- }
464
- })
465
- .catch(err => {
466
- console.error(err);
467
- alert('Unable to parse resume. Please fill in the fields manually.');
468
- })
469
- .finally(() => {
470
- setTimeout(() => {
471
- parseBtn.disabled = false;
472
- parseBtn.innerHTML = '<span class="btn-icon">🔍</span> Parse Resume';
473
- parseBtn.classList.remove('btn-success');
474
- }, 2000);
475
- });
476
- });
477
  }
478
-
479
- // Form validation
480
- const form = document.getElementById('application-form');
481
- form.addEventListener('submit', function(e) {
482
- const submitBtn = form.querySelector('button[type="submit"]');
483
- submitBtn.disabled = true;
484
- submitBtn.innerHTML = '<span class="btn-icon">⏳</span> Submitting...';
485
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  });
487
  </script>
488
- {% endblock %}
 
22
  <li>Apply</li>
23
  </ul>
24
 
25
+ <div class="card">
26
+ <div class="card-header">
27
+ <h2>Submit Your Application</h2>
28
+ <p>Please upload your resume (PDF, DOCX). Your file will be saved securely for recruiters to review.</p>
29
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
+ <div class="card-body">
32
+ <!-- Application Form -->
33
+ <form method="POST" enctype="multipart/form-data">
34
+ <div class="form-group">
35
+ <label for="resume">Upload Resume</label>
36
+ <!-- Resume upload remains mandatory. The file will be stored for recruiter review but is no longer parsed automatically. -->
37
+ <input type="file" name="resume" id="resume" class="form-control" required accept=".pdf,.doc,.docx">
38
+ <!-- Parse Resume button sits beside the upload to allow users to extract information from their CV. It uses a
39
+ type="button" so that clicking it does not submit the form. The actual parsing logic is defined in
40
+ the script at the bottom of this template. -->
41
+ <button type="button" id="parse-resume" class="btn btn-secondary" style="margin-top:0.5rem;">Parse Resume</button>
42
+ </div>
43
 
44
+ <!-- Name field added to capture the applicant's full name. This input can be autofilled by the resume parser,
45
+ but remains editable so applicants can correct any mistakes. -->
46
+ <div class="form-group">
47
+ <label for="full-name">Full Name</label>
48
+ <input type="text" name="full_name" id="full-name" class="form-control" placeholder="e.g. Jane Doe" required>
49
+ </div>
50
 
51
+ <!--
52
+ Collect the candidate's skills, experience and education manually.
53
+ These fields allow applicants to highlight their background even when resume
54
+ parsing is disabled. Entries can be separated by commas, semicolons or newlines;
55
+ the backend will normalise them into lists.
56
+ -->
57
+ <div class="form-group">
58
+ <label for="skills">Skills</label>
59
+ <textarea name="skills" id="skills" class="form-control" rows="3" placeholder="e.g. Python, Data Analysis, Project Management" required></textarea>
60
+ </div>
61
+ <div class="form-group">
62
+ <label for="experience">Experience</label>
63
+ <textarea name="experience" id="experience" class="form-control" rows="3" placeholder="e.g. 3 years at TechCorp as a Backend Developer" required></textarea>
64
+ </div>
65
+ <div class="form-group">
66
+ <label for="education">Education</label>
67
+ <textarea name="education" id="education" class="form-control" rows="3" placeholder="e.g. B.Sc. in Computer Science, M.Sc. in Data Science" required></textarea>
68
+ </div>
69
 
70
+ <!-- Interview guidelines removed from this page. They are now displayed on the
71
+ "My Applications" page so applicants see them before taking the interview. -->
 
 
 
 
 
 
 
 
 
 
72
 
73
+ <div class="application-actions" style="margin-top: 2rem;">
74
+ <button type="submit" class="btn btn-primary">Submit Application</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  </div>
76
+ </form>
77
+
78
+ <div style="margin-top: 1.5rem; text-align: center;">
79
+ <a href="{{ url_for('job_detail', job_id=job.id) }}" class="btn btn-outline">Back to Job Details</a>
80
  </div>
81
  </div>
82
  </div>
83
  </section>
84
 
85
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  .form-group label {
87
  font-weight: 600;
88
+ color: var(--primary);
89
  margin-bottom: 0.5rem;
90
+ display: block;
 
 
91
  }
92
 
93
  .form-control {
94
  width: 100%;
95
+ padding: 0.75rem;
96
  font-size: 1rem;
97
+ border-radius: 6px;
98
+ border: 1px solid #ccc;
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
+ .application-actions {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  text-align: center;
103
  }
104
 
105
+ .btn-primary {
106
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
107
+ color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  padding: 0.75rem 1.5rem;
109
  font-weight: 500;
110
+ border: none;
111
+ border-radius: 6px;
112
+ cursor: pointer;
113
  }
114
 
115
+ .btn-primary:hover {
116
+ opacity: 0.9;
 
117
  }
118
 
119
+ /* Secondary button styling used for the "Parse Resume" control */
120
  .btn-secondary {
121
+ background: var(--secondary);
122
  color: white;
123
+ padding: 0.75rem 1.5rem;
124
+ font-weight: 500;
125
  border: none;
126
+ border-radius: 6px;
127
+ cursor: pointer;
128
  }
 
129
  .btn-secondary:hover {
130
+ opacity: 0.9;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  }
132
  </style>
133
 
134
+ {# Resume parsing script: attaches click handler to the Parse Resume button. It performs an asynchronous
135
+ POST to a placeholder endpoint (`/parse_resume`) with the uploaded file and, upon success,
136
+ populates the corresponding form fields. Users can still edit the populated fields. #}
137
  <script>
138
  document.addEventListener('DOMContentLoaded', function() {
139
+ const parseBtn = document.getElementById('parse-resume');
140
+ if (!parseBtn) return;
141
+ parseBtn.addEventListener('click', function() {
142
+ const resumeInput = document.getElementById('resume');
143
+ if (!resumeInput || !resumeInput.files || resumeInput.files.length === 0) {
144
+ alert('Please upload your resume before parsing.');
145
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  }
147
+ const formData = new FormData();
148
+ formData.append('resume', resumeInput.files[0]);
149
+ fetch('/parse_resume', {
150
+ method: 'POST',
151
+ body: formData
152
+ }).then(resp => resp.json())
153
+ .then(data => {
154
+ if (data) {
155
+ if (data.name && document.getElementById('full-name')) {
156
+ document.getElementById('full-name').value = data.name;
157
+ }
158
+ if (data.skills && document.getElementById('skills')) {
159
+ document.getElementById('skills').value = data.skills;
160
+ }
161
+ if (data.education && document.getElementById('education')) {
162
+ document.getElementById('education').value = data.education;
163
+ }
164
+ if (data.experience && document.getElementById('experience')) {
165
+ document.getElementById('experience').value = data.experience;
166
+ }
167
+ }
168
+ })
169
+ .catch(err => {
170
+ console.error(err);
171
+ alert('Unable to parse resume. Please try again later.');
172
+ });
173
+ });
174
  });
175
  </script>
176
+ {% endblock %}