update
This commit is contained in:
973
gnx-react/components/pages/career/JobApplicationForm.tsx
Normal file
973
gnx-react/components/pages/career/JobApplicationForm.tsx
Normal file
@@ -0,0 +1,973 @@
|
||||
"use client";
|
||||
|
||||
import { useState, FormEvent, ChangeEvent } from "react";
|
||||
import { JobPosition, JobApplication, careerService } from "@/lib/api/careerService";
|
||||
|
||||
interface JobApplicationFormProps {
|
||||
job: JobPosition;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
const inputStyle = {
|
||||
padding: '10px 12px',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #e0e0e0',
|
||||
fontSize: '14px',
|
||||
transition: 'all 0.2s',
|
||||
width: '100%'
|
||||
};
|
||||
|
||||
const labelStyle = {
|
||||
fontWeight: '500',
|
||||
color: '#555',
|
||||
marginBottom: '6px',
|
||||
display: 'block',
|
||||
fontSize: '14px'
|
||||
};
|
||||
|
||||
const sectionStyle = {
|
||||
backgroundColor: '#ffffff',
|
||||
padding: 'clamp(16px, 3vw, 20px)',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #e8e8e8',
|
||||
marginBottom: '16px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.04)'
|
||||
};
|
||||
|
||||
const sectionHeaderStyle = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: '14px',
|
||||
paddingBottom: '10px',
|
||||
borderBottom: '1px solid #f0f0f0'
|
||||
};
|
||||
|
||||
const JobApplicationForm = ({ job, onClose }: JobApplicationFormProps) => {
|
||||
const [formData, setFormData] = useState({
|
||||
first_name: "",
|
||||
last_name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
current_position: "",
|
||||
current_company: "",
|
||||
years_of_experience: "",
|
||||
cover_letter: "",
|
||||
portfolio_url: "",
|
||||
linkedin_url: "",
|
||||
github_url: "",
|
||||
website_url: "",
|
||||
available_from: "",
|
||||
notice_period: "",
|
||||
expected_salary: "",
|
||||
salary_currency: "USD",
|
||||
consent: false,
|
||||
});
|
||||
|
||||
const [resume, setResume] = useState<File | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [submitStatus, setSubmitStatus] = useState<{
|
||||
type: "success" | "error" | null;
|
||||
message: string;
|
||||
}>({ type: null, message: "" });
|
||||
|
||||
const handleInputChange = (
|
||||
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
||||
) => {
|
||||
const { name, value, type } = e.target;
|
||||
|
||||
if (type === "checkbox") {
|
||||
const checked = (e.target as HTMLInputElement).checked;
|
||||
setFormData((prev) => ({ ...prev, [name]: checked }));
|
||||
} else {
|
||||
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
// Validate file type
|
||||
const allowedTypes = [
|
||||
"application/pdf",
|
||||
"application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
];
|
||||
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
setSubmitStatus({
|
||||
type: "error",
|
||||
message: "Please upload a PDF, DOC, or DOCX file",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file size (5MB)
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
setSubmitStatus({
|
||||
type: "error",
|
||||
message: "Resume file size must be less than 5MB",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setResume(file);
|
||||
setSubmitStatus({ type: null, message: "" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
setSubmitStatus({ type: null, message: "" });
|
||||
|
||||
// Validation
|
||||
if (!resume) {
|
||||
setSubmitStatus({
|
||||
type: "error",
|
||||
message: "Please upload your resume",
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.consent) {
|
||||
setSubmitStatus({
|
||||
type: "error",
|
||||
message: "You must consent to data processing to apply",
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const applicationData: JobApplication = {
|
||||
job: job.id,
|
||||
first_name: formData.first_name,
|
||||
last_name: formData.last_name,
|
||||
email: formData.email,
|
||||
phone: formData.phone || undefined,
|
||||
current_position: formData.current_position || undefined,
|
||||
current_company: formData.current_company || undefined,
|
||||
years_of_experience: formData.years_of_experience || undefined,
|
||||
cover_letter: formData.cover_letter || undefined,
|
||||
resume: resume,
|
||||
portfolio_url: formData.portfolio_url || undefined,
|
||||
linkedin_url: formData.linkedin_url || undefined,
|
||||
github_url: formData.github_url || undefined,
|
||||
website_url: formData.website_url || undefined,
|
||||
available_from: formData.available_from || undefined,
|
||||
notice_period: formData.notice_period || undefined,
|
||||
expected_salary: formData.expected_salary ? parseFloat(formData.expected_salary) : undefined,
|
||||
salary_currency: formData.salary_currency || undefined,
|
||||
consent: formData.consent,
|
||||
};
|
||||
|
||||
await careerService.submitApplication(applicationData);
|
||||
|
||||
setSubmitStatus({
|
||||
type: "success",
|
||||
message: "Application submitted successfully! We'll be in touch soon.",
|
||||
});
|
||||
|
||||
// Reset form
|
||||
setFormData({
|
||||
first_name: "",
|
||||
last_name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
current_position: "",
|
||||
current_company: "",
|
||||
years_of_experience: "",
|
||||
cover_letter: "",
|
||||
portfolio_url: "",
|
||||
linkedin_url: "",
|
||||
github_url: "",
|
||||
website_url: "",
|
||||
available_from: "",
|
||||
notice_period: "",
|
||||
expected_salary: "",
|
||||
salary_currency: "USD",
|
||||
consent: false,
|
||||
});
|
||||
setResume(null);
|
||||
|
||||
// Reset file input
|
||||
const fileInput = document.getElementById('resume') as HTMLInputElement;
|
||||
if (fileInput) fileInput.value = '';
|
||||
|
||||
} catch (error) {
|
||||
setSubmitStatus({
|
||||
type: "error",
|
||||
message: error instanceof Error ? error.message : "Failed to submit application. Please try again.",
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="job-application-form" style={{
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
maxHeight: '90vh'
|
||||
}}>
|
||||
{/* Header Section with Gradient */}
|
||||
<div style={{
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
padding: 'clamp(24px, 4vw, 32px)',
|
||||
borderRadius: '16px 16px 0 0',
|
||||
position: 'relative',
|
||||
flexShrink: 0
|
||||
}}>
|
||||
{/* Close Button */}
|
||||
{onClose && (
|
||||
<button
|
||||
onClick={onClose}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '16px',
|
||||
right: '16px',
|
||||
background: 'rgba(255,255,255,0.2)',
|
||||
border: 'none',
|
||||
color: 'white',
|
||||
cursor: 'pointer',
|
||||
fontSize: '24px',
|
||||
padding: '8px',
|
||||
lineHeight: '1',
|
||||
borderRadius: '50%',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: 'all 0.2s',
|
||||
zIndex: 10
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'rgba(220, 53, 69, 0.9)';
|
||||
e.currentTarget.style.transform = 'scale(1.1)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.2)';
|
||||
e.currentTarget.style.transform = 'scale(1)';
|
||||
}}
|
||||
title="Close"
|
||||
>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: 'inherit' }}>close</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="intro" style={{ textAlign: 'center', color: 'white' }}>
|
||||
<div style={{
|
||||
width: '60px',
|
||||
height: '60px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'rgba(255,255,255,0.2)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: '0 auto 16px',
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<span className="material-symbols-outlined" style={{
|
||||
fontSize: '32px',
|
||||
color: 'white'
|
||||
}}>work_outline</span>
|
||||
</div>
|
||||
<h3 id="application-form-title" className="fw-7" style={{
|
||||
color: 'white',
|
||||
fontSize: 'clamp(20px, 3vw, 24px)',
|
||||
marginBottom: '8px'
|
||||
}}>
|
||||
Apply for {job.title}
|
||||
</h3>
|
||||
<p style={{
|
||||
color: 'rgba(255,255,255,0.9)',
|
||||
fontSize: 'clamp(13px, 2vw, 14px)',
|
||||
margin: '0'
|
||||
}}>
|
||||
Join our team and make an impact
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form Content - Scrollable Area */}
|
||||
<form onSubmit={handleSubmit} style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: '1 1 auto',
|
||||
minHeight: 0,
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
<div
|
||||
className="form-scrollable-content"
|
||||
style={{
|
||||
padding: 'clamp(20px, 4vw, 32px)',
|
||||
overflowY: 'scroll',
|
||||
overflowX: 'hidden',
|
||||
flex: '1 1 auto',
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
touchAction: 'pan-y',
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: '#667eea #f0f0f0'
|
||||
}}
|
||||
>
|
||||
|
||||
{submitStatus.type && (
|
||||
<div
|
||||
className={`alert mb-24`}
|
||||
style={{
|
||||
padding: "16px 20px",
|
||||
borderRadius: "8px",
|
||||
backgroundColor: submitStatus.type === "success" ? "#d4edda" : "#f8d7da",
|
||||
color: submitStatus.type === "success" ? "#155724" : "#721c24",
|
||||
border: `2px solid ${submitStatus.type === "success" ? "#28a745" : "#dc3545"}`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
||||
}}
|
||||
>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: '20px' }}>
|
||||
{submitStatus.type === "success" ? "check_circle" : "error"}
|
||||
</span>
|
||||
<span style={{ fontSize: '14px', fontWeight: '500' }}>{submitStatus.message}</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Personal Information */}
|
||||
<div className="section" style={sectionStyle}>
|
||||
<div style={sectionHeaderStyle}>
|
||||
<span className="material-symbols-outlined me-2" style={{ color: '#667eea', fontSize: 'clamp(20px, 3vw, 22px)' }}>person</span>
|
||||
<h4 className="fw-6 mb-0" style={{ color: '#333', fontSize: 'clamp(15px, 2.5vw, 16px)' }}>Personal Information</h4>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-6 mb-3">
|
||||
<label htmlFor="first_name" className="form-label" style={labelStyle}>
|
||||
First Name <span style={{ color: '#dc3545' }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="first_name"
|
||||
name="first_name"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
value={formData.first_name}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-3">
|
||||
<label htmlFor="last_name" className="form-label" style={labelStyle}>
|
||||
Last Name <span style={{ color: '#dc3545' }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="last_name"
|
||||
name="last_name"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
value={formData.last_name}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-3">
|
||||
<label htmlFor="email" className="form-label" style={labelStyle}>
|
||||
Email Address <span style={{ color: '#dc3545' }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
value={formData.email}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-3">
|
||||
<label htmlFor="phone" className="form-label" style={labelStyle}>
|
||||
Phone Number
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
id="phone"
|
||||
name="phone"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
value={formData.phone}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Professional Information */}
|
||||
<div className="section" style={sectionStyle}>
|
||||
<div style={sectionHeaderStyle}>
|
||||
<span className="material-symbols-outlined me-2" style={{ color: '#667eea', fontSize: '22px' }}>work</span>
|
||||
<h4 className="fw-6 mb-0" style={{ color: '#333', fontSize: '16px' }}>Professional Info</h4>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-6 mb-24">
|
||||
<label htmlFor="current_position" className="form-label" style={labelStyle}>
|
||||
Current Position
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="current_position"
|
||||
name="current_position"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
value={formData.current_position}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-24">
|
||||
<label htmlFor="current_company" className="form-label" style={labelStyle}>
|
||||
Current Company
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="current_company"
|
||||
name="current_company"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
value={formData.current_company}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 mb-24">
|
||||
<label htmlFor="years_of_experience" className="form-label" style={labelStyle}>
|
||||
Years of Experience
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="years_of_experience"
|
||||
name="years_of_experience"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
placeholder="e.g., 3-5 years"
|
||||
value={formData.years_of_experience}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Resume and Cover Letter */}
|
||||
<div className="section" style={sectionStyle}>
|
||||
<div style={sectionHeaderStyle}>
|
||||
<span className="material-symbols-outlined me-2" style={{ color: '#667eea', fontSize: '22px' }}>upload_file</span>
|
||||
<h4 className="fw-6 mb-0" style={{ color: '#333', fontSize: '16px' }}>Documents</h4>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12 mb-24">
|
||||
<label htmlFor="resume" className="form-label" style={labelStyle}>
|
||||
Resume (PDF, DOC, DOCX - Max 5MB) <span style={{ color: '#dc3545' }}>*</span>
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="resume"
|
||||
name="resume"
|
||||
className="form-control"
|
||||
style={{...inputStyle, padding: '10px 16px'}}
|
||||
accept=".pdf,.doc,.docx"
|
||||
onChange={handleFileChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
required
|
||||
/>
|
||||
{resume && <small style={{ color: '#28a745', fontWeight: '500', display: 'block', marginTop: '8px' }}>✓ Selected: {resume.name}</small>}
|
||||
</div>
|
||||
<div className="col-12 mb-3">
|
||||
<label htmlFor="cover_letter" className="form-label" style={labelStyle}>
|
||||
Cover Letter / Message
|
||||
</label>
|
||||
<textarea
|
||||
id="cover_letter"
|
||||
name="cover_letter"
|
||||
className="form-control"
|
||||
style={{...inputStyle, minHeight: '120px', resize: 'vertical'}}
|
||||
rows={5}
|
||||
placeholder="Tell us why you're interested in this position..."
|
||||
value={formData.cover_letter}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Links */}
|
||||
<div className="section" style={sectionStyle}>
|
||||
<div style={sectionHeaderStyle}>
|
||||
<span className="material-symbols-outlined me-2" style={{ color: '#667eea', fontSize: '22px' }}>link</span>
|
||||
<h4 className="fw-6 mb-0" style={{ color: '#333', fontSize: '16px' }}>Links (Optional)</h4>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-6 mb-24">
|
||||
<label htmlFor="portfolio_url" className="form-label" style={labelStyle}>
|
||||
Portfolio / Website
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
id="portfolio_url"
|
||||
name="portfolio_url"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
placeholder="https://"
|
||||
value={formData.portfolio_url}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-24">
|
||||
<label htmlFor="linkedin_url" className="form-label" style={labelStyle}>
|
||||
LinkedIn Profile
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
id="linkedin_url"
|
||||
name="linkedin_url"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
placeholder="https://linkedin.com/in/..."
|
||||
value={formData.linkedin_url}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-24">
|
||||
<label htmlFor="github_url" className="form-label" style={labelStyle}>
|
||||
GitHub Profile
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
id="github_url"
|
||||
name="github_url"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
placeholder="https://github.com/..."
|
||||
value={formData.github_url}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-24">
|
||||
<label htmlFor="website_url" className="form-label" style={labelStyle}>
|
||||
Personal Website
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
id="website_url"
|
||||
name="website_url"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
placeholder="https://"
|
||||
value={formData.website_url}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Availability & Salary */}
|
||||
<div className="section" style={sectionStyle}>
|
||||
<div style={sectionHeaderStyle}>
|
||||
<span className="material-symbols-outlined me-2" style={{ color: '#667eea', fontSize: '22px' }}>event_available</span>
|
||||
<h4 className="fw-6 mb-0" style={{ color: '#333', fontSize: '16px' }}>Availability</h4>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-6 mb-24">
|
||||
<label htmlFor="available_from" className="form-label" style={labelStyle}>
|
||||
Available From
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
id="available_from"
|
||||
name="available_from"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
value={formData.available_from}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-24">
|
||||
<label htmlFor="notice_period" className="form-label" style={labelStyle}>
|
||||
Notice Period
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="notice_period"
|
||||
name="notice_period"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
placeholder="e.g., 2 weeks, 1 month"
|
||||
value={formData.notice_period}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-24">
|
||||
<label htmlFor="expected_salary" className="form-label" style={labelStyle}>
|
||||
Expected Salary
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="expected_salary"
|
||||
name="expected_salary"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
placeholder="Amount"
|
||||
value={formData.expected_salary}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 mb-24">
|
||||
<label htmlFor="salary_currency" className="form-label" style={labelStyle}>
|
||||
Currency
|
||||
</label>
|
||||
<select
|
||||
id="salary_currency"
|
||||
name="salary_currency"
|
||||
className="form-control"
|
||||
style={inputStyle}
|
||||
value={formData.salary_currency}
|
||||
onChange={handleInputChange}
|
||||
onFocus={(e) => {
|
||||
e.target.style.borderColor = '#667eea';
|
||||
e.target.style.boxShadow = '0 0 0 3px rgba(102, 126, 234, 0.1)';
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.target.style.borderColor = '#e0e0e0';
|
||||
e.target.style.boxShadow = 'none';
|
||||
}}
|
||||
>
|
||||
<option value="USD">USD</option>
|
||||
<option value="EUR">EUR</option>
|
||||
<option value="GBP">GBP</option>
|
||||
<option value="INR">INR</option>
|
||||
<option value="AUD">AUD</option>
|
||||
<option value="CAD">CAD</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Consent */}
|
||||
<div className="section" style={{
|
||||
background: 'linear-gradient(135deg, #fff9e6 0%, #fffbf0 100%)',
|
||||
padding: 'clamp(14px, 3vw, 16px)',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid #ffd700',
|
||||
marginBottom: '16px'
|
||||
}}>
|
||||
<div className="form-check d-flex align-items-start">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="consent"
|
||||
name="consent"
|
||||
className="form-check-input"
|
||||
style={{
|
||||
width: '18px',
|
||||
height: '18px',
|
||||
marginTop: '2px',
|
||||
marginRight: '10px',
|
||||
cursor: 'pointer',
|
||||
flexShrink: 0,
|
||||
accentColor: '#667eea'
|
||||
}}
|
||||
checked={formData.consent}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
/>
|
||||
<label htmlFor="consent" className="form-check-label" style={{
|
||||
fontSize: 'clamp(12px, 2vw, 13px)',
|
||||
color: '#555',
|
||||
lineHeight: '1.5',
|
||||
cursor: 'pointer'
|
||||
}}>
|
||||
I consent to data processing for recruitment purposes. <span style={{ color: '#dc3545', fontWeight: '600' }}>*</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Submit and Cancel Buttons - Fixed Footer */}
|
||||
<div className="text-center" style={{
|
||||
paddingTop: '16px',
|
||||
paddingBottom: '16px',
|
||||
borderTop: '2px solid #e8e8e8',
|
||||
backgroundColor: 'white',
|
||||
padding: '16px clamp(20px, 4vw, 32px)',
|
||||
borderRadius: '0 0 16px 16px',
|
||||
boxShadow: '0 -4px 12px rgba(0,0,0,0.05)',
|
||||
flexShrink: 0
|
||||
}}>
|
||||
<div style={{ display: 'flex', gap: '12px', justifyContent: 'center', flexWrap: 'wrap' }}>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn"
|
||||
disabled={isSubmitting}
|
||||
style={{
|
||||
minWidth: "160px",
|
||||
backgroundColor: isSubmitting ? '#ccc' : 'white',
|
||||
color: '#333',
|
||||
border: '2px solid #667eea',
|
||||
padding: '12px 32px',
|
||||
fontSize: '15px',
|
||||
fontWeight: '600',
|
||||
borderRadius: '6px',
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: isSubmitting ? 'not-allowed' : 'pointer',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '8px'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!isSubmitting) {
|
||||
e.currentTarget.style.backgroundColor = '#FFD700';
|
||||
e.currentTarget.style.borderColor = '#FFD700';
|
||||
e.currentTarget.style.transform = 'translateY(-2px)';
|
||||
e.currentTarget.style.boxShadow = '0 8px 16px rgba(0,0,0,0.15)';
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (!isSubmitting) {
|
||||
e.currentTarget.style.backgroundColor = 'white';
|
||||
e.currentTarget.style.borderColor = '#667eea';
|
||||
e.currentTarget.style.transform = 'translateY(0)';
|
||||
e.currentTarget.style.boxShadow = 'none';
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: '20px', animation: 'spin 1s linear infinite' }}>progress_activity</span>
|
||||
Submitting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: '20px' }}>send</span>
|
||||
Submit Application
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{onClose && !isSubmitting && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="btn"
|
||||
style={{
|
||||
minWidth: "120px",
|
||||
backgroundColor: 'transparent',
|
||||
color: '#666',
|
||||
border: '2px solid #e0e0e0',
|
||||
padding: '12px 24px',
|
||||
fontSize: '15px',
|
||||
fontWeight: '600',
|
||||
borderRadius: '6px',
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: 'pointer',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '8px'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f5f5f5';
|
||||
e.currentTarget.style.borderColor = '#999';
|
||||
e.currentTarget.style.color = '#333';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = 'transparent';
|
||||
e.currentTarget.style.borderColor = '#e0e0e0';
|
||||
e.currentTarget.style.color = '#666';
|
||||
}}
|
||||
>
|
||||
<span className="material-symbols-outlined" style={{ fontSize: '18px' }}>close</span>
|
||||
Cancel
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<p style={{
|
||||
marginTop: '10px',
|
||||
color: '#999',
|
||||
fontSize: 'clamp(11px, 2vw, 12px)'
|
||||
}}>
|
||||
By submitting, you agree to our terms
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<style>{`
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Custom scrollbar styling */
|
||||
.form-scrollable-content::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.form-scrollable-content::-webkit-scrollbar-track {
|
||||
background: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.form-scrollable-content::-webkit-scrollbar-thumb {
|
||||
background: #667eea;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.form-scrollable-content::-webkit-scrollbar-thumb:hover {
|
||||
background: #5568d3;
|
||||
}
|
||||
|
||||
/* Ensure smooth scrolling on touch devices */
|
||||
.form-scrollable-content {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
/* Better mobile input styling */
|
||||
@media (max-width: 768px) {
|
||||
.form-control {
|
||||
font-size: 16px !important; /* Prevents zoom on iOS */
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobApplicationForm;
|
||||
|
||||
Reference in New Issue
Block a user