+
-
- {/* NAVBAR */}
+
- {/* HEADINGS */}
- {/* QUERY TEXT */}
-
-
- {searchTerm
- ? `Listing results for '${searchTerm}'`
- : categoriesSelected.length
- ? 'Listing results for selected categories'
- : 'Listing all job ads.'}
-
-
-
-
- {/* FILTER CHIPS */}
- {categoriesSelected.length > 0 && (
-
- {categoriesSelected.map(cat => (
-
- {cat}
-
- ))}
-
-
- )}
-
- {/* JOB GRID */}
- {jobAds.map(ad => (
-
- ))}
-
-
- {/* PAGINATION */}
-
- {Array.from({ length: Math.max(totalPages, 2) }, (_, i) => (
-
- ))}
+ }
+
+ if (jobAds.length > 0) {
+ return jobAds.map(ad => {
+ if (!ad || !ad._id) return null;
+ const jobTitle = ad.title || 'Untitled Position';
+ const jobCategory = ad.category || 'General';
+ const jobImageUrl = getCategoryImage(jobCategory);
+
+ return (
+
{
+ try {
+ if (history && typeof history.push === 'function') {
+ history.push({
+ pathname: '/job-application',
+ state: {
+ jobId: ad._id,
+ jobTitle: jobTitle,
+ jobDescription: ad.description || '',
+ requirements: Array.isArray(ad.requirements) ? ad.requirements : [],
+ category: jobCategory,
+ },
+ });
+ } else {
+ window.location.href = `/job-application`;
+ }
+ } catch (error) {
+ console.error('Error navigating to job application:', error);
+ toast.error('Error opening job application');
+ }
+ }}
+ onKeyDown={e => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ try {
+ if (history && typeof history.push === 'function') {
+ history.push({
+ pathname: '/job-application',
+ state: {
+ jobId: ad._id,
+ jobTitle: jobTitle,
+ jobDescription: ad.description || '',
+ requirements: Array.isArray(ad.requirements) ? ad.requirements : [],
+ category: jobCategory,
+ },
+ });
+ } else {
+ window.location.href = `/job-application`;
+ }
+ } catch (error) {
+ console.error('Error navigating to job application:', error);
+ toast.error('Error opening job application');
+ }
+ }
+ }}
+ role="button"
+ tabIndex={0}
+ style={{ cursor: 'pointer' }}
+ >
+

{
+ e.currentTarget.onerror = null;
+ e.currentTarget.src =
+ 'https://images.unsplash.com/photo-1497366754035-f200968a6e72?w=640&h=480&fit=crop&q=80';
+ }}
+ />
+
+ {jobTitle} - {jobCategory}
+
+
+ );
+ });
+ }
+
+ return
No matching jobs found.
;
+ })()}
-
- {/* MODAL */}
- {selectedJob && (
-
-
-
-
-
{selectedJob.title}
-
- Category: {selectedJob.category}
-
-
{selectedJob.description}
-
-
-
- )}
+ )}
+
);
}
diff --git a/src/components/Collaboration/Collaboration.module.css b/src/components/Collaboration/Collaboration.module.css
index c78bd9e040..88a285678e 100644
--- a/src/components/Collaboration/Collaboration.module.css
+++ b/src/components/Collaboration/Collaboration.module.css
@@ -1,363 +1,662 @@
-/* ======================================================
- BASE LAYOUT
- ====================================================== */
-
-.jobLanding {
- width: 100%;
- min-height: 100%;
+.header a {
+ display: block;
+ padding: 0;
+ margin: 0 auto;
+ width: fit-content;
}
-.userCollaborationContainer {
- width: 90%;
+.responsiveImg {
+ width: 60%;
+ max-width: 500px;
+ height: auto;
+ display: block;
margin: 0 auto;
- background-color: #ffffff;
- border-radius: 10px;
- padding-bottom: 24px;
}
-/* ================= DARK MODE – BASE ================= */
-
-.dark .userCollaborationContainer {
- background-color: #0f172a; /* deep slate */
- color: #e5e7eb;
+.jobLanding {
+ min-height: 100vh;
+ background-color: transparent;
}
-/* ======================================================
- HEADER
- ====================================================== */
+.collabContainer {
+ width: 100%;
+ margin: 0;
+}
-.jobHeader {
+.navbar {
+ width: 100%;
display: flex;
- justify-content: center;
+ justify-content: space-between;
align-items: center;
- padding: 16px 0;
+ margin: 0;
+ margin-bottom: 0;
+ background-color: #9c0;
+ padding: 20px;
+ flex-flow: row nowrap;
+ box-sizing: border-box;
+ flex-wrap: wrap;
+ z-index: 0;
+ border-radius: 0;
}
-.jobHeader img {
- width: clamp(160px, 30vw, 600px);
- height: auto;
+.navbarLeft input,
+.navbarLeft select {
+ display: flex;
+ padding: 8px;
+ margin-right: 10px;
+ align-items: center;
}
-/* ======================================================
- NAVBAR
- ====================================================== */
-
-.navbar {
- position: relative;
+.navbarLeft,
+.navbarRight,
+.searchForm {
display: flex;
- justify-content: space-between;
align-items: center;
- background-color: #9c0;
- padding: 12px;
+ padding: 4px;
gap: 12px;
}
-.searchForm {
- display: flex;
- gap: 8px;
+.searchForm input {
+ margin-right: 10px;
+ height: 38px;
+ padding: 0 12px;
+ line-height: 38px;
+ box-sizing: border-box;
+ border: 1px solid #ccc;
+ border-radius: 6px;
+ min-width: 240px;
}
-/* ================= DARK MODE – NAVBAR ================= */
+@media (max-width: 380px) {
+ .searchForm input {
+ width: 100%;
+ max-width: 100%;
+ min-width: 0;
+ margin: 0;
+ box-sizing: border-box;
+ flex: 1 1 auto;
+ }
+}
-.dark .navbar {
- background-color: #1e293b;
+.searchButton,
+.resetButton,
+.showSummaries {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ height: 38px;
+ padding: 0 16px;
+ background-color: #1778f2;
+ color: #fff;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 16px;
+ font-weight: 600;
+ white-space: nowrap;
+ line-height: normal;
+ vertical-align: middle;
+ transition: filter .15s ease, transform .02s ease;
}
-.dark .searchForm input {
- background-color: #0f172a;
- color: #e5e7eb;
- border: 1px solid #334155;
+.searchButton:hover,
+.resetButton:hover,
+.showSummaries:hover {
+ filter: brightness(0.95);
}
-.dark .searchForm input::placeholder {
- color: #94a3b8;
+.searchButton:active,
+.resetButton:active,
+.showSummaries:active {
+ transform: translateY(1px);
}
-/* ======================================================
- CATEGORY DROPDOWN
- ====================================================== */
+.showSummaries {
+ padding: 0 22px;
+}
-.jobSelect {
- position: absolute;
- top: 100%;
- right: 12px;
- margin-top: 6px;
- background-color: #ffffff;
- padding: 8px 12px;
- border-radius: 6px;
+.navbarRight select {
+ padding: 10px 14px;
border: 1px solid #ccc;
- z-index: 1500;
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.15);
- animation: fadeInDropdown 0.12s ease-in-out;
+ border-radius: 8px;
+ background: #fff;
}
-@keyframes fadeInDropdown {
- from {
- opacity: 0;
- transform: translateY(-3px);
+/* --- Headings --- */
+.headings {
+ margin-bottom: 10px;
+ text-align: center;
+}
+
+.headings h1,
+.headings p {
+ color: #111827;
+}
+
+.mainHeading {
+ font-size: 1.8rem;
+ font-weight: 700;
+ color: #6b7280;
+ margin: 10px 0;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+@media (max-width: 768px) {
+ .mainHeading {
+ font-size: 1.4rem;
}
- to {
- opacity: 1;
- transform: translateY(0);
+}
+
+/* --- Jobs grid --- */
+.jobList {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 20px;
+ width: 100%;
+ margin-top: 0;
+ justify-content: center;
+ align-items: stretch;
+}
+
+@media (min-width: 480px) {
+ .jobList {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (min-width: 768px) {
+ .jobList {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+}
+
+@media (min-width: 1017px) {
+ .jobList {
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ }
+}
+
+@media (min-width: 1300px) {
+ .jobList {
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ }
+}
+
+@media (min-width: 1600px) {
+ .jobList {
+ grid-template-columns: repeat(6, minmax(0, 1fr));
}
}
-.dropdownItem {
+.jobList:has(.noJobads) {
+ grid-template-columns: 1fr;
+ place-items: center;
+}
+
+/* --- Job card --- */
+.jobAd {
+ border: 1px solid #ddd;
+ border-radius: 10px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+ overflow: hidden;
+ background: #fff;
+ transition: transform .3s, box-shadow .3s;
+ padding-bottom: 8px;
display: flex;
- align-items: center;
- gap: 10px;
- padding: 6px 4px;
+ flex-direction: column;
cursor: pointer;
- font-size: 14px;
+}
+
+.jobAd img {
+ width: 100%;
+ height: 200px;
+ object-fit: cover;
+ background-color: #ffffff;
+ padding: 0;
+ display: block;
+ margin: 0;
+}
+
+.jobAd h3 {
+ margin-top: 10px;
+ font-size: 1.05rem;
+ font-weight: 700;
color: #333;
- border-radius: 4px;
+ margin: 0;
+ padding: 12px;
+ transition: color .3s;
+ text-align: center;
}
-.dropdownItem:hover {
- background-color: #f0f0f0;
+.categoryTitle {
+ font-size: 1.2rem;
+ font-weight: 700;
+ color: #2b6ee8 !important;
+ margin: 0;
+ padding: 12px;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
}
-/* ================= DARK MODE – DROPDOWN ================= */
+.jobAd:hover {
+ transform: translateY(-5px);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
+}
-.dark .jobSelect {
- background-color: #020617;
- color: #e5e7eb;
- border: 1px solid #334155;
+.adminRequirementsSection {
+ margin-top: auto;
+ padding: 15px;
+ border-top: 1px solid #ddd;
+ background-color: #f9f9f9;
}
-.dark .dropdownItem {
- color: #e5e7eb;
+.requirementsTitle {
+ font-size: 0.9rem;
+ font-weight: 600;
+ margin: 0 0 12px 0;
+ color: #333;
}
-.dark .dropdownItem:hover {
- background-color: #1e293b;
+.requirementsList {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
}
-/* ======================================================
- HEADINGS
- ====================================================== */
+.requirementItem {
+ display: flex;
+ align-items: flex-start;
+}
-.headings {
- text-align: center;
- margin: 20px 0;
+.requirementCheckbox {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: default;
+ margin: 0;
+ font-size: 0.85rem;
+ line-height: 1.4;
+}
+
+.requirementCheckboxInput {
+ position: absolute;
+ opacity: 0;
+ pointer-events: none;
}
-.jobHead {
- color: #07471e;
- font-weight: 800;
+.requirementCheckboxCustom {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+ min-width: 18px;
+ border: 2px solid #ddd;
+ border-radius: 4px;
+ background-color: #fff;
+ transition: all 0.2s ease;
+ flex-shrink: 0;
}
-/* ================= DARK MODE – HEADINGS ================= */
+.requirementCheckboxCustom svg {
+ display: none;
+}
-.dark .jobHead {
- color: #e5e7eb;
+.requirementCheckboxInput:checked + .requirementCheckboxCustom {
+ background-color: #8cc63f;
+ border-color: #8cc63f;
}
-/* ======================================================
- JOB GRID
- ====================================================== */
+.requirementCheckboxInput:checked + .requirementCheckboxCustom svg {
+ display: block;
+}
-.jobList {
- display: grid;
- grid-template-columns: repeat(6, 1fr);
- gap: 20px;
- padding: 20px;
+.requirementCheckbox span:last-child {
+ color: #333;
}
-.jobAd {
- background: #ffffff;
- border-radius: 12px;
- border: 1px solid #ddd;
- padding: 14px;
+.summariesList {
+ width: min(1100px, 100%);
+ margin: 24px auto;
+ padding-inline: clamp(12px, 4vw, 32px);
text-align: center;
- cursor: pointer;
- transition: transform 0.25s ease, box-shadow 0.25s ease;
+ background: transparent;
}
-.jobAd:hover {
- transform: translateY(-5px);
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
+.summariesList h1 {
+ margin: 0 0 24px;
}
-.jobAd img {
- width: 100%;
+.summariesItem {
+ background: none !important;
+ border: 0 !important;
+ border-radius: 0 !important;
+ box-shadow: none !important;
+ padding: 16px 0 !important;
+ margin: 0 !important;
+ text-align: left;
+ border-bottom: 1px solid rgba(0, 0, 0, .08);
}
-.jobAd h3 {
- margin-top: 10px;
- font-size: 1.05rem;
+.summariesItem:last-child {
+ border-bottom: 0;
+}
+
+.summariesItem h3,
+.summariesItem h3 a {
+ position: static !important;
+ transform: none !important;
+ margin: 0 0 8px !important;
+ display: block;
+ text-align: left;
font-weight: 700;
- color: #183a9b;
+ color: #2b6ee8;
+ text-decoration: none;
+ line-height: 1.3;
+ font-size: 1.85rem;
+ word-break: break-word;
}
-/* ================= DARK MODE – JOB GRID ================= */
+.summariesItem h3 a:hover {
+ text-decoration: underline;
+}
-.dark .jobAd {
- background-color: #020617;
- border: 1px solid #334155;
+.summariesItem p {
+ margin: 0 0 10px;
+ color: #1f2937;
+ font-size: 1rem;
+ line-height: 1.6;
+ text-align: left;
}
-.dark .jobAd h3 {
- color: #93c5fd;
+.date {
+ margin-top: 6px;
+ color: #6b7280;
+ font-size: 0.95rem;
}
-/* ======================================================
- PAGINATION
- ====================================================== */
+@media (max-width: 640px) {
+ .summariesList {
+ padding: 0 12px;
+ }
+
+ .summariesItem {
+ margin-bottom: 22px !important;
+ }
+
+ .summariesItem h3,
+ .summariesItem h3 a {
+ font-size: 1.4rem;
+ line-height: 1.3;
+ }
+
+ .summariesItem p {
+ font-size: 0.98rem;
+ line-height: 1.5;
+ }
+}
.pagination {
+ margin: 20px 0;
display: flex;
justify-content: center;
gap: 8px;
- margin: 20px 0;
+ flex-wrap: wrap;
}
-.paginationButton {
- padding: 6px 12px;
- border-radius: 6px;
- border: 1px solid #ccc;
- background-color: #ffffff;
+.pagination button {
+ margin: 0 5px;
+ padding: 6px 10px;
cursor: pointer;
+ border-radius: 8px;
+ border: 1px solid #ddd;
+ background: #fff;
}
-.paginationButtonActive {
- background-color: #9c0;
- border-color: #9c0;
- font-weight: 700;
+.pagination button:hover {
+ background-color: #4a99ee;
+ color: #fff;
}
-/* ================= DARK MODE – PAGINATION ================= */
+.pagination button:disabled {
+ background-color: #ccc;
+ color: #333;
+ cursor: default;
+}
-.dark .paginationButton {
- background-color: #020617;
- color: #e5e7eb;
- border: 1px solid #334155;
+.noJobads {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ font-size: 1.1em;
+ margin: 20px auto;
+ max-width: 400px;
+ padding: 16px 20px;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ background-color: #f9f9f9;
}
-.dark .paginationButtonActive {
- background-color: #65a30d;
- border-color: #65a30d;
- color: #020617;
+@media (max-width: 800px) {
+ .navbar {
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .navbarLeft,
+ .navbarRight {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 10px;
+ }
+
+ .searchForm {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 10px;
+ }
+
+ .searchForm input,
+ .searchForm button {
+ width: 90%;
+ max-width: 360px;
+ margin: 0 auto;
+ }
+
+ .navbarRight select {
+ margin-top: -5px;
+ width: 90%;
+ max-width: 360px;
+ }
+
+ .summariesList {
+ padding: 0 12px;
+ }
+
+ .requirementsList {
+ grid-template-columns: 1fr;
+ }
}
-/* ======================================================
- SELECTED FILTER CHIPS
- ====================================================== */
+@media (max-width: 350px) {
+ .navbarRight {
+ width: 100%;
+ }
-.jobQueries {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-top: 8px;
+ .navbarRight select {
+ width: 100%;
+ max-width: 100%;
+ min-width: 0;
+ box-sizing: border-box;
+ margin: 0;
+ font-size: 0.9rem;
+ }
}
-.chip {
- background-color: #9c0;
+@media (max-width: 480px) {
+ .navbar {
+ padding: 5px;
+ text-align: center;
+ }
+
+ .pagination button {
+ padding: 8px;
+ width: auto;
+ }
+}
+
+.jobLandingDark {
+ background-color: #1c2541;
+}
+
+.jobLandingDark .header {
+ background: #1c2541;
+ padding: 8px 0;
+}
+
+.jobLandingDark .collabContainer {
+ background-color: #2e3b55;
+ color: #f1f1f1;
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
+ border-radius: 6px;
+ padding: 0;
+}
+
+.jobLandingDark .navbar {
+ background-color: #1c2541 !important;
+ color: #fff;
+ border: 1px solid #444;
+ border-radius: 6px;
+ padding: 1rem;
+}
+
+.jobLandingDark .navbar input,
+.jobLandingDark .navbar select {
+ background-color: #3f4a58 !important;
+ color: #fff !important;
+ border: none !important;
+}
+
+.jobLandingDark .searchButton,
+.jobLandingDark .resetButton,
+.jobLandingDark .showSummaries,
+.jobLandingDark .pagination button {
+ background-color: #1778f2 !important;
+ color: #fff !important;
+ border: none !important;
+ border-radius: 8px;
padding: 6px 12px;
- border-radius: 16px;
- font-size: 13px;
font-weight: 600;
}
-.clearAllButton {
- border: 1px solid #333;
- background: transparent;
- padding: 6px 12px;
- border-radius: 16px;
- cursor: pointer;
+.jobLandingDark .pagination button:hover {
+ background-color: #4a99ee !important;
+ color: #fff !important;
+}
+
+.jobLandingDark .jobAd {
+ background: none !important;
+ background-color: #1c2541 !important;
+ color: #fff !important;
+ border-radius: 6px;
+ box-shadow: 0 2px 6px rgba(255, 255, 255, 0.05);
+ border: 1px solid #444;
+ padding: 1rem;
+ transition: background-color .3s ease;
+}
+
+.jobLandingDark .jobAd:hover {
+ background-color: #3f4a58;
}
-/* ================= DARK MODE – CHIPS ================= */
+.jobLandingDark .jobAd h3 {
+ color: #f1f1f1 !important;
+}
-.dark .chip {
- background-color: #65a30d;
- color: #020617;
+.jobLandingDark .headings h1 {
+ color: #ffffff;
}
-.dark .clearAllButton {
- border-color: #94a3b8;
+.jobLandingDark .headings p {
color: #e5e7eb;
}
-/* ======================================================
- MODAL
- ====================================================== */
+.jobLandingDark a {
+ color: #7db4ff;
+}
-.dark {
- background-color: #1b2a41;
- color: #f1f1f1;
+.jobLandingDark a:hover {
+ color: #a6ceff;
+ text-decoration: underline;
}
-.dark .userCollaborationContainer {
- background-color: #1b2a41;
+.jobLandingDark .summariesList h1 {
+ color: #ffffff;
}
-.modalOverlay {
- position: fixed;
- inset: 0;
- background: rgba(0, 0, 0, 0.55);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 2000;
+.jobLandingDark .summariesItem {
+ border-bottom: 1px solid rgba(255, 255, 255, .12);
}
-.modal {
- position: relative;
- background-color: #ffffff;
- border-radius: 10px;
- padding: 28px;
- max-width: 520px;
- width: 90%;
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
+.jobLandingDark .summariesItem h3 a {
+ color: #f8fafc;
}
-/* ================= DARK MODE – MODAL ================= */
+.jobLandingDark .summariesItem h3 a:hover {
+ color: #ffffff;
+}
-.dark .jobAd {
- background: linear-gradient(135deg, #23272a, #181a1b);
- border-color: #333;
+.jobLandingDark .summariesItem p {
+ color: #e5e7eb;
}
-.dark .jobAd h3 {
- color: #e0e0e0;
+.jobLandingDark .date {
+ color: #cbd5e1;
}
-/* ======================================================
- MODAL – CLOSE BUTTON
- ====================================================== */
+.jobLandingDark .noJobads {
+ background-color: #1c2541;
+ border: 1px solid #555;
+ color: #fff;
+}
-.closeButton {
- position: absolute;
- top: 14px;
- right: 16px;
- background: transparent;
- border: none;
- font-size: 22px;
- cursor: pointer;
- line-height: 1;
- color: #333;
+.jobLandingDark .adminRequirementsSection {
+ background-color: #1c2541;
+ border-top-color: #444;
}
-.closeButton:hover {
- opacity: 0.75;
+.jobLandingDark .requirementsTitle {
+ color: #e0e0e0;
}
-.dark .closeButton {
- color: #e5e7eb;
+.jobLandingDark .requirementCheckboxCustom {
+ border-color: #555;
+ background-color: #2a2a2a;
}
-/* ======================================================
- MODAL – ACTIONS
- ====================================================== */
+.jobLandingDark .requirementCheckbox span:last-child {
+ color: #e0e0e0;
+}
-.modalActions {
- display: flex;
- justify-content: flex-end;
- gap: 16px;
- margin-top: 28px;
+.jobLandingDark .summariesList {
+ width: min(1100px, 100%);
+ margin: 24px auto;
+ padding-inline: clamp(12px, 4vw, 32px);
+ background: transparent;
}
-.modalActions button {
- min-width: 120px;
+.jobLandingDark .pagination button:disabled {
+ background-color: #ffffff !important;
+ color: black !important;
+ border: none !important;
+ box-shadow: 0 0 0 2px rgba(14, 94, 215, 0.35) inset;
+ cursor: default;
+ opacity: 1;
}
diff --git a/src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx b/src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx
index 92bb9090b8..6f76be7bdf 100644
--- a/src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx
+++ b/src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx
@@ -1,5 +1,6 @@
// ...existing code...
import React, { useState, useEffect, useRef } from 'react';
+import { useLocation } from 'react-router-dom';
import styles from './JobApplicationForm.module.css';
import OneCommunityImage from '../../../assets/images/logo2.png';
import axios from 'axios';
@@ -9,6 +10,7 @@ import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
function JobApplicationForm() {
+ const location = useLocation();
const [forms, setForms] = useState([]);
const [selectedJob, setSelectedJob] = useState('');
const [answers, setAnswers] = useState([]);
@@ -23,8 +25,110 @@ function JobApplicationForm() {
const [websiteSocial, setWebsiteSocial] = useState('');
const [resumeFile, setResumeFile] = useState(null);
const resumeInputRef = useRef(null);
+ const [jobDataFromRedirect, setJobDataFromRedirect] = useState(null);
+ // Additional fields for requirements checking
+ const [fullTimeYears, setFullTimeYears] = useState('');
+ const [monthsVolunteer, setMonthsVolunteer] = useState('');
+ const [hoursPerWeek, setHoursPerWeek] = useState('');
+ const [roleSkills, setRoleSkills] = useState('');
+ // User questionnaire data from referral link
+ const [userQuestionnaireData, setUserQuestionnaireData] = useState(null);
const darkMode = useSelector(state => state.theme?.darkMode);
+ const isAdmin = useSelector(state => {
+ try {
+ const user = state?.auth?.user;
+ const role = user?.role;
+ return (
+ role === 'Administrator' ||
+ role === 'Owner' ||
+ role === 'admin' ||
+ role === 'ADMINISTRATOR' ||
+ role === 'OWNER'
+ );
+ } catch (error) {
+ return false;
+ }
+ });
+
+ // Validate ID parameter to prevent injection attacks
+ const isValidId = id => {
+ if (!id || typeof id !== 'string') return false;
+ // Allow only alphanumeric characters, hyphens, and underscores (MongoDB ObjectId format)
+ return /^[a-zA-Z0-9_-]+$/.test(id) && id.length <= 100;
+ };
+
+ // Get job data from redirect or URL parameters
+ useEffect(() => {
+ // Check for referral link parameters from URL query string
+ const searchParams = new URLSearchParams(location.search);
+ const referralId = searchParams.get('ref') || searchParams.get('referral');
+ const jobIdParam = searchParams.get('jobId');
+ const pathJobId = location.pathname.split('/').pop();
+ const jobId = jobIdParam || (pathJobId && pathJobId !== 'job-application' ? pathJobId : null);
+
+ // If we have a valid referral ID, fetch user's questionnaire data
+ if (referralId && isValidId(referralId)) {
+ fetchUserQuestionnaireData(referralId);
+ }
+
+ // Get job data from location state or URL
+ if (location.state) {
+ setJobDataFromRedirect(location.state);
+ if (location.state.jobTitle) {
+ setJobTitleInput(location.state.jobTitle);
+ }
+ } else if (jobId && isValidId(jobId)) {
+ // Fetch job data from API if we have a valid jobId
+ fetchJobData(jobId);
+ }
+ }, [location.state, location.search, location.pathname]);
+
+ // Fetch user's prior questionnaire data from referral link
+ const fetchUserQuestionnaireData = async referralId => {
+ try {
+ // This endpoint should return the user's questionnaire responses
+ // Adjust the endpoint based on your API structure
+ const response = await axios.get(`${ENDPOINTS.GET_USER_QUESTIONNAIRE}/${referralId}`);
+ if (response.data) {
+ setUserQuestionnaireData(response.data);
+ // Pre-fill form fields from questionnaire data
+ if (response.data.name) setApplicantName(response.data.name);
+ if (response.data.email) setApplicantEmail(response.data.email);
+ if (response.data.locationTimezone) setLocationTimezone(response.data.locationTimezone);
+ if (response.data.phone) setPhone(response.data.phone);
+ if (response.data.fullTimeYears) setFullTimeYears(response.data.fullTimeYears);
+ if (response.data.monthsVolunteer) setMonthsVolunteer(response.data.monthsVolunteer);
+ if (response.data.hoursPerWeek) setHoursPerWeek(response.data.hoursPerWeek);
+ if (response.data.roleSkills) setRoleSkills(response.data.roleSkills);
+ }
+ } catch (error) {
+ console.error('Error fetching user questionnaire data:', error);
+ // If referral data fetch fails, continue without it
+ }
+ };
+
+ // Fetch job data by ID
+ const fetchJobData = async jobId => {
+ try {
+ const response = await axios.get(`${ENDPOINTS.GET_JOB}/${jobId}`);
+ if (response.data) {
+ setJobDataFromRedirect({
+ jobId: response.data._id,
+ jobTitle: response.data.title,
+ jobDescription: response.data.description || '',
+ requirements: response.data.requirements || [],
+ category: response.data.category || 'General',
+ });
+ if (response.data.title) {
+ setJobTitleInput(response.data.title);
+ }
+ }
+ } catch (error) {
+ console.error('Error fetching job data:', error);
+ toast.error('Failed to load job details');
+ }
+ };
useEffect(() => {
async function fetchForms() {
@@ -32,6 +136,20 @@ function JobApplicationForm() {
const res = await axios.get(ENDPOINTS.GET_ALL_JOB_FORMS);
const formsArr = Array.isArray(res.data.forms) ? res.data.forms : [];
setForms(formsArr);
+
+ // If we have job data from redirect, try to match it
+ if (jobDataFromRedirect?.jobTitle) {
+ const matchedForm = formsArr.find(
+ f => f.title?.toLowerCase() === jobDataFromRedirect.jobTitle?.toLowerCase(),
+ );
+ if (matchedForm) {
+ setSelectedJob(matchedForm.title);
+ setFilteredForm(matchedForm);
+ setAnswers(new Array((matchedForm.questions ?? []).length).fill(''));
+ return;
+ }
+ }
+
const firstWithQuestions = formsArr.find(f => f.questions && f.questions.length > 0);
if (firstWithQuestions) {
setSelectedJob(firstWithQuestions.title);
@@ -51,7 +169,7 @@ function JobApplicationForm() {
}
}
fetchForms();
- }, []);
+ }, [jobDataFromRedirect]);
useEffect(() => {
if (!selectedJob) return;
@@ -97,6 +215,69 @@ function JobApplicationForm() {
setResumeFile(f);
};
+ // Shared function to check requirements based on provided data
+ const evaluateRequirements = (data = {}) => {
+ const {
+ fullTimeYears: years = '',
+ monthsVolunteer: months = '',
+ hoursPerWeek: hours = '',
+ roleSkills: skills = '',
+ locationTimezone: timezone = '',
+ } = data;
+
+ const reactKeywords = ['react', 'reactjs', 'react.js'];
+ const skillsLower = (skills || '').toLowerCase();
+ const yearsNum = years ? parseFloat(years) : 0;
+ const monthsNum = months ? parseFloat(months) : 0;
+ const hoursNum = hours ? parseFloat(hours) : 0;
+
+ return {
+ reactExperience:
+ yearsNum >= 1 || reactKeywords.some(keyword => skillsLower.includes(keyword)),
+ twoMonthsCommitment: monthsNum >= 2,
+ javascriptExperience: yearsNum >= 1,
+ timeZoneLocation: !!(timezone && timezone.trim()),
+ tenHoursPerWeek: hoursNum >= 10,
+ };
+ };
+
+ // Check if requirements are satisfied (for admin view) - matching reference implementation
+ const checkRequirements = () => {
+ return evaluateRequirements({
+ fullTimeYears,
+ monthsVolunteer,
+ hoursPerWeek,
+ roleSkills,
+ locationTimezone,
+ });
+ };
+
+ // Check user requirements based on their prior questionnaire data (for user view)
+ const checkUserRequirements = () => {
+ return evaluateRequirements({
+ fullTimeYears: fullTimeYears || userQuestionnaireData?.fullTimeYears || '',
+ monthsVolunteer: monthsVolunteer || userQuestionnaireData?.monthsVolunteer || '',
+ hoursPerWeek: hoursPerWeek || userQuestionnaireData?.hoursPerWeek || '',
+ roleSkills: roleSkills || userQuestionnaireData?.roleSkills || '',
+ locationTimezone: locationTimezone || userQuestionnaireData?.locationTimezone || '',
+ });
+ };
+
+ // Helper function to strip HTML tags and clean text
+ const stripHtml = html => {
+ if (!html) return '';
+ try {
+ const doc = new DOMParser().parseFromString(html, 'text/html');
+ const text = doc.body.textContent || doc.body.innerText || '';
+ return text.replace(/\s+/g, ' ').trim();
+ } catch (error) {
+ return html
+ .replace(/<[^>]*>/g, '')
+ .replace(/\s+/g, ' ')
+ .trim();
+ }
+ };
+
const validateBeforeSubmit = () => {
const missing = [];
if (!applicantName.trim()) missing.push('Name');
@@ -173,12 +354,16 @@ function JobApplicationForm() {