Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 132 additions & 33 deletions src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@
const [jobTitleInput, setJobTitleInput] = useState('');
const [filteredForm, setFilteredForm] = useState(null);
const [showDescription, setShowDescription] = useState(false);
const [applicantName, setApplicantName] = useState('');
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fieldErrors, setFieldErrors] = useState({});
const [applicantEmail, setApplicantEmail] = useState('');
const [locationTimezone, setLocationTimezone] = useState('');
const [phone, setPhone] = useState('');
Expand All @@ -149,10 +151,20 @@

const darkMode = useSelector(state => state.theme?.darkMode);

const visibleQuestions = useMemo(
() => (filteredForm?.questions ?? []).filter(q => q.visible !== false),
[filteredForm],
);
const visibleQuestions = useMemo(() => {
const seen = new Set();

return (filteredForm?.questions ?? [])
.filter(q => q.visible !== false)
.filter(q => {
const key = normalizeTitleKey(q.label || q.questionText);

if (seen.has(key)) return false;

seen.add(key);
return true;
});
}, [filteredForm]);

useEffect(() => {
let cancelled = false;
Expand Down Expand Up @@ -241,10 +253,30 @@
}
};

const handleAnswerChange = (idx, value) => {
const handleAnswerChange = (idx, value, label) => {
const newAnswers = [...answers];
newAnswers[idx] = value;
setAnswers(newAnswers);

const labelLower = String(label || '').toLowerCase();

const isHoursQuestion =
labelLower.includes('hour') && (labelLower.includes('week') || labelLower.includes('weekly'));

if (isHoursQuestion) {
if (value && !/^\d*$/.test(value)) {
setFieldErrors(prev => ({
...prev,
[idx]: 'Only numbers are allowed',
}));
} else {
setFieldErrors(prev => {
const copy = { ...prev };
delete copy[idx];
return copy;
});
}
}
};

const handleShowDescription = e => {
Expand All @@ -271,15 +303,38 @@
setResumeFile(f);
};

const validateBeforeSubmit = () => {

Check failure on line 306 in src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 25 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ31_GqHsJq2vT_Q_Ouq&open=AZ31_GqHsJq2vT_Q_Ouq&pullRequest=5228
const missing = [];
if (!applicantName.trim()) missing.push('Name');

if (!firstName.trim()) missing.push('First Name');
if (!lastName.trim()) missing.push('Last Name');
if (!applicantEmail.trim()) missing.push('Email');

if (visibleQuestions.length) {
for (const [idx, q] of visibleQuestions.entries()) {
if (isQuestionRequired(q) && !String(answers[idx] ?? '').trim()) {
missing.push(getQuestionLabel(q, idx));
const answer = String(answers[idx] ?? '').trim();
const label = getQuestionLabel(q, idx);
const labelLower = label.toLowerCase();

// Required validation
if (isQuestionRequired(q) && !answer) {
missing.push(label);
}

// HOURS/WEEK VALIDATION
const isHoursQuestion =
labelLower.includes('hour') &&
(labelLower.includes('week') || labelLower.includes('weekly'));

if (isHoursQuestion && answer) {
if (!/^\d+$/.test(answer)) {

Check warning on line 330 in src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ31_GqHsJq2vT_Q_Our&open=AZ31_GqHsJq2vT_Q_Our&pullRequest=5228
missing.push(`${label} (must be a number)`);
} else {
const num = Number(answer);
if (num <= 0 || num > 168) {
missing.push(`${label} (must be between 1 and 168)`);
}
}
}
}
}
Expand All @@ -298,7 +353,8 @@

toast.success('Application submitted. A copy will be sent to your email.');

setApplicantName('');
setFirstName('');
setLastName('');
setApplicantEmail('');
setLocationTimezone('');
setPhone('');
Expand Down Expand Up @@ -402,10 +458,18 @@
<div className={styles.formProfileDetailGroup}>
<input
type="text"
placeholder="Name"
placeholder="First Name *"
className={styles.inputField}
value={applicantName}
onChange={e => setApplicantName(e.target.value)}
value={firstName}
onChange={e => setFirstName(e.target.value)}
/>

<input
type="text"
placeholder="Last Name *"
className={styles.inputField}
value={lastName}
onChange={e => setLastName(e.target.value)}
/>
<input
type="email"
Expand Down Expand Up @@ -455,6 +519,9 @@
{visibleQuestions.map((q, idx) => {
const qt = getQuestionType(q);
const label = getQuestionLabel(q, idx);
const labelLower = label.toLowerCase();
const isIndividualOrgQuestion =
labelLower.includes('individual') && labelLower.includes('organization');
const formKey = filteredForm?._id
? `${filteredForm._id}-q-${idx}`
: `q-${idx}-${label.slice(0, 24)}`;
Expand All @@ -464,28 +531,50 @@
<h2>
{idx + 1}. {label}
</h2>
{['textbox', 'text'].includes(qt) && (
<input
type="text"
placeholder={q.placeholder || 'Type your response here'}
value={answers[idx] || ''}
onChange={e => handleAnswerChange(idx, e.target.value)}
/>
{['textbox', 'text'].includes(qt) && !isIndividualOrgQuestion && (
<>
<input
type="text"
placeholder={q.placeholder || 'Type your response here'}
value={answers[idx] || ''}
onChange={e => handleAnswerChange(idx, e.target.value, label)}
style={{
border: fieldErrors[idx] ? '1px solid red' : undefined,
}}
/>
{fieldErrors[idx] && (
<div style={{ color: 'red', fontSize: '12px', marginTop: '4px' }}>
{fieldErrors[idx]}
</div>
)}
</>
)}
{qt === 'textarea' && (
<textarea
placeholder={q.placeholder || 'Type your response here'}
value={answers[idx] || ''}
onChange={e => handleAnswerChange(idx, e.target.value)}
rows={5}
/>

{qt === 'textarea' && !isIndividualOrgQuestion && (
<>
<textarea
placeholder={q.placeholder || 'Type your response here'}
value={answers[idx] || ''}
onChange={e => handleAnswerChange(idx, e.target.value, label)}
rows={5}
style={{
border: fieldErrors[idx] ? '1px solid red' : undefined,
}}
/>
{fieldErrors[idx] && (
<div style={{ color: 'red', fontSize: '12px', marginTop: '4px' }}>
{fieldErrors[idx]}
</div>
)}
</>
)}
{qt === 'date' && (
{(qt === 'date' ||
(labelLower.includes('start') && labelLower.includes('date'))) && (
<input
type="date"
className={styles.dateInput}
value={answers[idx] || ''}
onChange={e => handleAnswerChange(idx, e.target.value)}
onChange={e => handleAnswerChange(idx, e.target.value, label)}
/>
)}
{['checkbox', 'radio'].includes(qt) && q.options && q.options.length > 0 && (
Expand All @@ -504,20 +593,30 @@
))}
</div>
)}
{qt === 'dropdown' && (
{isIndividualOrgQuestion ? (
<select
className={styles.selectField}
value={answers[idx] || ''}
onChange={e => handleAnswerChange(idx, e.target.value, label)}
>
<option value="">Select an option</option>
<option value="Individual">Individual</option>
<option value="Organization">Organization</option>
</select>
) : qt === 'dropdown' ? (
<select
className={styles.selectField}
value={answers[idx] || ''}
onChange={e => handleAnswerChange(idx, e.target.value)}
onChange={e => handleAnswerChange(idx, e.target.value, label)}
>
<option value="">Select an option</option>
{(q.options || []).map(opt => (
<option key={opt} value={opt}>
{opt}
</option>
))}
</select>
)}
) : null}

Check warning on line 619 in src/components/Collaboration/JobApplicationForm/JobApplicationForm.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=OneCommunityGlobal_HighestGoodNetworkApp&issues=AZ31_GqHsJq2vT_Q_Ous&open=AZ31_GqHsJq2vT_Q_Ous&pullRequest=5228
{![
'textbox',
'text',
Expand All @@ -531,7 +630,7 @@
type="text"
placeholder="Type your response here"
value={answers[idx] || ''}
onChange={e => handleAnswerChange(idx, e.target.value)}
onChange={e => handleAnswerChange(idx, e.target.value, label)}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,19 @@
}

.jobSelect {
padding: 6px 8px;
padding: 6px 36px 6px 8px;
border: 1px solid #ccc;
border-radius: 5px;
width: 100%;

appearance: none;
-webkit-appearance: none;
-moz-appearance: none;

background-image: url("data:image/svg+xml;utf8,<svg fill='%23666' height='20' viewBox='0 0 20 20' width='20' xmlns='http://www.w3.org/2000/svg'><path d='M7 10l5 5 5-5z'/></svg>");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 16px;
}

.goButton {
Expand Down Expand Up @@ -339,6 +348,12 @@
background: #155dc0;
}

.logo img {
max-width: 220px;
height: auto;
object-fit: contain;
}

/* Dark mode: native date input — white calendar icon (WebKit/Blink) */
.darkMode input[type='date'],
.darkMode .dateInput {
Expand Down Expand Up @@ -558,3 +573,4 @@
color: #cfe7ff;
border-color: rgb(255 255 255 / 5%);
}

Loading