This commit is contained in:
Iliyan Angelov
2025-10-07 22:10:27 +03:00
parent 3f5bcfad68
commit d48c54e2c5
3221 changed files with 40187 additions and 92575 deletions

View 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;

View File

@@ -1,152 +1,680 @@
import Link from "next/link";
"use client";
import { useState, useEffect } from "react";
import { JobPosition } from "@/lib/api/careerService";
import JobApplicationForm from "./JobApplicationForm";
interface JobSingleProps {
job: JobPosition;
}
const JobSingle = ({ job }: JobSingleProps) => {
const [showApplicationForm, setShowApplicationForm] = useState(false);
// Prevent body scroll when modal is open
useEffect(() => {
if (showApplicationForm) {
// Get scrollbar width to prevent layout shift
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
// Save current scroll position
const scrollY = window.scrollY;
// Prevent background scroll
document.body.style.position = 'fixed';
document.body.style.top = `-${scrollY}px`;
document.body.style.left = '0';
document.body.style.right = '0';
document.body.style.overflow = 'hidden';
if (scrollbarWidth > 0) {
document.body.style.paddingRight = `${scrollbarWidth}px`;
}
} else {
// Get the scroll position from body top
const scrollY = parseInt(document.body.style.top || '0') * -1;
// Restore scroll
document.body.style.position = '';
document.body.style.top = '';
document.body.style.left = '';
document.body.style.right = '';
document.body.style.overflow = '';
document.body.style.paddingRight = '';
// Restore scroll position
window.scrollTo(0, scrollY);
}
// Cleanup on unmount
return () => {
const scrollY = parseInt(document.body.style.top || '0') * -1;
document.body.style.position = '';
document.body.style.top = '';
document.body.style.left = '';
document.body.style.right = '';
document.body.style.overflow = '';
document.body.style.paddingRight = '';
if (scrollY > 0) {
window.scrollTo(0, scrollY);
}
};
}, [showApplicationForm]);
const formatSalary = () => {
if (job.salary_min && job.salary_max) {
return `${job.salary_currency} ${job.salary_min}-${job.salary_max} ${job.salary_period}`;
} else if (job.salary_min) {
return `From ${job.salary_currency} ${job.salary_min} ${job.salary_period}`;
} else if (job.salary_max) {
return `Up to ${job.salary_currency} ${job.salary_max} ${job.salary_period}`;
}
return "Competitive";
};
const scrollToForm = () => {
setShowApplicationForm(true);
setTimeout(() => {
const formElement = document.getElementById('application-form');
if (formElement) {
formElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}, 100);
};
const JobSingle = () => {
return (
<section className="job-single fix-top pb-120 sticky-wrapper">
<div className="container">
<div className="row vertical-column-gap">
<div className="col-12 col-lg-7">
<div className="j-d-content sticky-item">
<div className="intro">
<h2 className="mt-8 text-secondary fw-7 title-anim mb-24">
UI/UX Design
</h2>
<p>
Position: <span className="position mb-12">(02)</span>
</p>
<p>
Location: <span className="location">(Remote)</span>
</p>
</div>
<div className="group pt-120">
<h4 className="mt-8 text-secondary title-anim fw-6 mb-24">
Who we are
</h4>
<p className="cur-lg">
Lorem ipsum dolor sit amet consectetur. Augue morbi sapien
malesuada augue massa vivamus pharetra. Pellentesque velit
lectus dui convallis posuere viverra enim mauris. Pulvinar
quam vitae ut viverra. Vitae quis cursus magna sit amet neque
ultricies lectus massa. Sem mauris tincidunt risus enim
adipiscing viverra. Interdum lectus interdum diam ultricies
molestie. In et ullamcorper semper odio enim.
</p>
</div>
<div className="group mt-60">
<h4 className="mt-8 text-secondary title-anim fw-6 mb-24">
What you want
</h4>
<ul>
<li>
you have at least three years of commercial experience
</li>
<li>
you have a strong web/UI portfolio including published
projects
</li>
<li>fluent English in verbal and written communication</li>
<li>
you are passionate about user interface and web design
</li>
<li>issues are challenges not show-stoppers for you</li>
<li>you are a trend seeker</li>
<li>you bring a lot of attention to details</li>
<li>
you plan upfront, think ahead, and are ready to be surprised
</li>
<li>you think about the full picture</li>
<li>
you are familiar with any UI design tool, i.e., Sketch,
Figma or Adobe XD
</li>
</ul>
</div>
<div className="group mt-60">
<h4 className="mt-8 text-secondary title-anim fw-6 mb-24">
Who we are
</h4>
<p className="cur-lg">
Lorem ipsum dolor sit amet consectetur. Augue morbi sapien
malesuada augue massa vivamus pharetra. Pellentesque velit
lectus dui convallis posuere viverra enim mauris. Pulvinar
quam vitae ut viverra. Vitae quis cursus magna sit amet neque
ultricies lectus massa. Sem mauris tincidunt risus enim
adipiscing viverra. Interdum lectus interdum diam ultricies
molestie. In et ullamcorper semper odio enim.
</p>
</div>
<div className="group mt-60">
<h4 className="mt-8 text-secondary title-anim fw-6 mb-24">
Bonus points
</h4>
<ul>
<li>you have at least three years</li>
<li>you have a strong web/UI portfolio including</li>
<li>fluent English in verbal</li>
<li>you are passionate about user interface</li>
<li>issues are challenges</li>
<li>you are a seeker</li>
</ul>
</div>
<div className="group mt-60">
<h4 className="mt-8 text-secondary title-anim fw-6 mb-24">
What you get is what you see
</h4>
<ul>
<li>you have at least three years</li>
<li>you have a strong web/UI portfolio including</li>
<li>fluent English in verbal</li>
<li>you are passionate about user interface</li>
<li>issues are challenges</li>
<li>you are a seeker</li>
<li>fluent English in verbal and written communication</li>
<li>
you are passionate about user interface and web design
</li>
<li>issues are challenges not show-stoppers for you</li>
<li>you are a trend seeker</li>
<li>you bring a lot of attention to details</li>
<li>
you plan upfront, think ahead, and are ready to be surprised
</li>
</ul>
</div>
</div>
</div>
<div className="col-12 col-lg-5 col-xxl-4 offset-xxl-1">
<div className="j-d-sidebar sticky-item">
<div className="intro">
<span className="text-uppercase mt-8 text-tertiary mb-16">
JOIN US
</span>
<h4 className="mt-8 fw-5 title-anim text-secondary mb-16">
UI/UX Design
</h4>
<p>Full-time (40hr per week)</p>
</div>
<div className="content mt-40">
<p className="mt-8 fw-5 text-xl text-secondary mb-16">
Salary-
</p>
<p className="mt-8 fw-5 text-xl text-secondary mb-16">
$1500-$2000 per month
</p>
<p className="mt-8 fw-4 text-tertiary mb-30">
+ VAT (B2B) + bonuses
</p>
<p className="mt-8 fw-4 text-tertiary mb-16">Remote / Poznań</p>
<p className="mt-8 fw-4 text-tertiary">Start: ASAP</p>
</div>
<div className="cta mt-60">
<Link href="/" className="btn">
Apply Now
</Link>
<>
{/* Job Header Banner */}
<section className="job-header pt-80 pt-md-100 pt-lg-120 pb-60 pb-md-70 pb-lg-80" style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
position: 'relative'
}}>
<div className="container">
<div className="row">
<div className="col-12">
<div className="job-header-content" style={{ color: 'white' }}>
<div className="mb-12 mb-md-16">
<span className="badge" style={{
backgroundColor: 'rgba(255,255,255,0.2)',
color: 'white',
padding: '6px 12px',
borderRadius: '20px',
fontSize: '12px',
fontWeight: '500',
textTransform: 'uppercase',
letterSpacing: '1px',
display: 'inline-block'
}}>
{job.department || 'Career Opportunity'}
</span>
</div>
<h1 className="fw-7 mb-16 mb-md-20 mb-lg-24" style={{
fontSize: 'clamp(1.75rem, 5vw, 3.5rem)',
lineHeight: '1.2',
color: 'white'
}}>
{job.title}
</h1>
<div className="job-meta d-flex flex-wrap" style={{
fontSize: 'clamp(13px, 2vw, 16px)',
gap: 'clamp(12px, 2vw, 16px)'
}}>
<div className="meta-item d-flex align-items-center">
<span className="material-symbols-outlined me-1 me-md-2" style={{ fontSize: 'clamp(18px, 3vw, 24px)' }}>location_on</span>
<span>{job.location}</span>
</div>
<div className="meta-item d-flex align-items-center">
<span className="material-symbols-outlined me-1 me-md-2" style={{ fontSize: 'clamp(18px, 3vw, 24px)' }}>work</span>
<span className="d-none d-sm-inline">{job.employment_type.replace('-', ' ').replace(/\b\w/g, l => l.toUpperCase())}</span>
<span className="d-sm-none">
{job.employment_type.split('-')[0].charAt(0).toUpperCase() + job.employment_type.split('-')[0].slice(1)}
</span>
</div>
<div className="meta-item d-flex align-items-center">
<span className="material-symbols-outlined me-1 me-md-2" style={{ fontSize: 'clamp(18px, 3vw, 24px)' }}>group</span>
<span className="d-none d-sm-inline">{job.open_positions} {job.open_positions === 1 ? 'Position' : 'Positions'}</span>
<span className="d-sm-none">{job.open_positions} {job.open_positions === 1 ? 'Pos' : 'Pos'}</span>
</div>
{job.experience_required && (
<div className="meta-item d-flex align-items-center d-none d-md-flex">
<span className="material-symbols-outlined me-2">school</span>
<span>{job.experience_required}</span>
</div>
)}
</div>
</div>
</div>
</div>
</div>
</section>
{/* Job Content Section */}
<section className="job-single pb-80 pb-md-100 pb-lg-120 sticky-wrapper" style={{ marginTop: 'clamp(-30px, -5vw, -40px)' }}>
<div className="container">
<div className="row vertical-column-gap">
<div className="col-12 col-lg-8 mb-4 mb-lg-0">
<div className="j-d-content" style={{
backgroundColor: 'white',
borderRadius: 'clamp(8px, 2vw, 12px)',
padding: 'clamp(20px, 4vw, 40px)',
boxShadow: '0 10px 40px rgba(0,0,0,0.08)'
}}>
<div className="intro" style={{
borderBottom: '2px solid #f0f0f0',
paddingBottom: 'clamp(20px, 3vw, 30px)',
marginBottom: 'clamp(20px, 3vw, 30px)'
}}>
<h3 className="fw-6 mb-12 mb-md-16 text-secondary" style={{ fontSize: 'clamp(18px, 3vw, 24px)' }}>
About This Position
</h3>
{job.short_description && (
<p style={{
color: '#666',
lineHeight: '1.8',
fontSize: 'clamp(14px, 2vw, 16px)'
}}>
{job.short_description}
</p>
)}
</div>
{job.about_role && (
<div className="group mb-32 mb-md-40">
<div className="d-flex align-items-center mb-16 mb-md-20">
<span className="material-symbols-outlined me-2" style={{
color: '#667eea',
fontSize: 'clamp(22px, 4vw, 28px)'
}}>info</span>
<h4 className="mt-8 text-secondary fw-6 mb-0" style={{ fontSize: 'clamp(16px, 3vw, 20px)' }}>
About This Role
</h4>
</div>
<p style={{
color: '#555',
lineHeight: '1.8',
fontSize: 'clamp(14px, 2vw, 16px)'
}}>{job.about_role}</p>
</div>
)}
{job.requirements && job.requirements.length > 0 && (
<div className="group mb-32 mb-md-40">
<div className="d-flex align-items-center mb-16 mb-md-20">
<span className="material-symbols-outlined me-2" style={{
color: '#667eea',
fontSize: 'clamp(22px, 4vw, 28px)'
}}>task_alt</span>
<h4 className="mt-8 text-secondary fw-6 mb-0" style={{ fontSize: 'clamp(16px, 3vw, 20px)' }}>
Requirements
</h4>
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{job.requirements.map((req, index) => (
<li key={index} className="mb-2" style={{
paddingLeft: 'clamp(20px, 4vw, 30px)',
position: 'relative',
color: '#555',
lineHeight: '1.8',
fontSize: 'clamp(14px, 2vw, 16px)'
}}>
<span style={{
position: 'absolute',
left: '0',
top: 'clamp(6px, 1.5vw, 8px)',
width: 'clamp(5px, 1vw, 6px)',
height: 'clamp(5px, 1vw, 6px)',
backgroundColor: '#667eea',
borderRadius: '50%'
}}></span>
{req}
</li>
))}
</ul>
</div>
)}
{job.responsibilities && job.responsibilities.length > 0 && (
<div className="group mb-32 mb-md-40">
<div className="d-flex align-items-center mb-16 mb-md-20">
<span className="material-symbols-outlined me-2" style={{ color: '#667eea', fontSize: 'clamp(22px, 4vw, 28px)' }}>assignment</span>
<h4 className="mt-8 text-secondary fw-6 mb-0" style={{ fontSize: 'clamp(16px, 3vw, 20px)' }}>
Key Responsibilities
</h4>
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{job.responsibilities.map((resp, index) => (
<li key={index} className="mb-2" style={{
paddingLeft: 'clamp(20px, 4vw, 30px)',
position: 'relative',
color: '#555',
lineHeight: '1.8',
fontSize: 'clamp(14px, 2vw, 16px)'
}}>
<span style={{
position: 'absolute',
left: '0',
top: 'clamp(6px, 1.5vw, 8px)',
width: 'clamp(5px, 1vw, 6px)',
height: 'clamp(5px, 1vw, 6px)',
backgroundColor: '#667eea',
borderRadius: '50%'
}}></span>
{resp}
</li>
))}
</ul>
</div>
)}
{job.qualifications && job.qualifications.length > 0 && (
<div className="group mb-32 mb-md-40">
<div className="d-flex align-items-center mb-16 mb-md-20">
<span className="material-symbols-outlined me-2" style={{ color: '#667eea', fontSize: 'clamp(22px, 4vw, 28px)' }}>workspace_premium</span>
<h4 className="mt-8 text-secondary fw-6 mb-0" style={{ fontSize: 'clamp(16px, 3vw, 20px)' }}>
Qualifications
</h4>
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{job.qualifications.map((qual, index) => (
<li key={index} className="mb-2" style={{
paddingLeft: 'clamp(20px, 4vw, 30px)',
position: 'relative',
color: '#555',
lineHeight: '1.8',
fontSize: 'clamp(14px, 2vw, 16px)'
}}>
<span style={{
position: 'absolute',
left: '0',
top: 'clamp(6px, 1.5vw, 8px)',
width: 'clamp(5px, 1vw, 6px)',
height: 'clamp(5px, 1vw, 6px)',
backgroundColor: '#667eea',
borderRadius: '50%'
}}></span>
{qual}
</li>
))}
</ul>
</div>
)}
{job.bonus_points && job.bonus_points.length > 0 && (
<div className="group mb-32 mb-md-40">
<div className="d-flex align-items-center mb-16 mb-md-20">
<span className="material-symbols-outlined me-2" style={{ color: '#667eea', fontSize: 'clamp(22px, 4vw, 28px)' }}>stars</span>
<h4 className="mt-8 text-secondary fw-6 mb-0" style={{ fontSize: 'clamp(16px, 3vw, 20px)' }}>
Nice to Have
</h4>
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{job.bonus_points.map((bonus, index) => (
<li key={index} className="mb-2" style={{
paddingLeft: 'clamp(20px, 4vw, 30px)',
position: 'relative',
color: '#555',
lineHeight: '1.8',
fontSize: 'clamp(14px, 2vw, 16px)'
}}>
<span style={{
position: 'absolute',
left: '0',
top: 'clamp(6px, 1.5vw, 8px)',
width: 'clamp(5px, 1vw, 6px)',
height: 'clamp(5px, 1vw, 6px)',
backgroundColor: '#667eea',
borderRadius: '50%'
}}></span>
{bonus}
</li>
))}
</ul>
</div>
)}
{job.benefits && job.benefits.length > 0 && (
<div className="group mb-32 mb-md-40">
<div className="d-flex align-items-center mb-16 mb-md-20">
<span className="material-symbols-outlined me-2" style={{ color: '#667eea', fontSize: 'clamp(22px, 4vw, 28px)' }}>card_giftcard</span>
<h4 className="mt-8 text-secondary fw-6 mb-0" style={{ fontSize: 'clamp(16px, 3vw, 20px)' }}>
What We Offer
</h4>
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{job.benefits.map((benefit, index) => (
<li key={index} className="mb-2" style={{
paddingLeft: 'clamp(20px, 4vw, 30px)',
position: 'relative',
color: '#555',
lineHeight: '1.8',
fontSize: 'clamp(14px, 2vw, 16px)'
}}>
<span style={{
position: 'absolute',
left: '0',
top: 'clamp(6px, 1.5vw, 8px)',
width: 'clamp(5px, 1vw, 6px)',
height: 'clamp(5px, 1vw, 6px)',
backgroundColor: '#667eea',
borderRadius: '50%'
}}></span>
{benefit}
</li>
))}
</ul>
</div>
)}
</div>
</div>
<div className="col-12 col-lg-4">
<div className="j-d-sidebar" style={{
backgroundColor: 'white',
borderRadius: 'clamp(8px, 2vw, 12px)',
padding: 'clamp(20px, 4vw, 30px)',
boxShadow: '0 10px 40px rgba(0,0,0,0.08)',
position: 'sticky',
top: '20px'
}}>
<div className="intro mb-20 mb-md-30" style={{
borderBottom: '2px solid #f0f0f0',
paddingBottom: 'clamp(16px, 3vw, 20px)'
}}>
<span className="text-uppercase" style={{
color: '#667eea',
fontSize: 'clamp(11px, 2vw, 12px)',
fontWeight: '600',
letterSpacing: '2px'
}}>
JOB DETAILS
</span>
</div>
<div className="content">
<div className="detail-item mb-16 mb-md-24">
<div className="d-flex align-items-center mb-6 mb-md-8">
<span className="material-symbols-outlined me-2" style={{
color: '#667eea',
fontSize: 'clamp(18px, 3vw, 20px)'
}}>payments</span>
<p className="fw-6 mb-0" style={{
color: '#333',
fontSize: 'clamp(14px, 2vw, 15px)'
}}>Salary Range</p>
</div>
<p className="fw-5 mb-0" style={{
color: '#667eea',
fontSize: 'clamp(16px, 3vw, 18px)'
}}>
{formatSalary()}
</p>
{job.salary_additional && (
<p className="mt-6 mt-md-8" style={{
color: '#666',
fontSize: 'clamp(12px, 2vw, 14px)'
}}>
{job.salary_additional}
</p>
)}
</div>
<div className="detail-item mb-16 mb-md-24">
<div className="d-flex align-items-center mb-6 mb-md-8">
<span className="material-symbols-outlined me-2" style={{
color: '#667eea',
fontSize: 'clamp(18px, 3vw, 20px)'
}}>work</span>
<p className="fw-6 mb-0" style={{
color: '#333',
fontSize: 'clamp(14px, 2vw, 15px)'
}}>Employment Type</p>
</div>
<p style={{
color: '#666',
fontSize: 'clamp(13px, 2vw, 14px)'
}}>
{job.employment_type.replace('-', ' ').replace(/\b\w/g, l => l.toUpperCase())}
</p>
</div>
<div className="detail-item mb-16 mb-md-24">
<div className="d-flex align-items-center mb-6 mb-md-8">
<span className="material-symbols-outlined me-2" style={{
color: '#667eea',
fontSize: 'clamp(18px, 3vw, 20px)'
}}>location_on</span>
<p className="fw-6 mb-0" style={{
color: '#333',
fontSize: 'clamp(14px, 2vw, 15px)'
}}>Location</p>
</div>
<p style={{
color: '#666',
fontSize: 'clamp(13px, 2vw, 14px)'
}}>{job.location}</p>
</div>
<div className="detail-item mb-16 mb-md-24">
<div className="d-flex align-items-center mb-6 mb-md-8">
<span className="material-symbols-outlined me-2" style={{
color: '#667eea',
fontSize: 'clamp(18px, 3vw, 20px)'
}}>event</span>
<p className="fw-6 mb-0" style={{
color: '#333',
fontSize: 'clamp(14px, 2vw, 15px)'
}}>Start Date</p>
</div>
<p style={{
color: '#666',
fontSize: 'clamp(13px, 2vw, 14px)'
}}>{job.start_date}</p>
</div>
<div className="detail-item mb-16 mb-md-24">
<div className="d-flex align-items-center mb-6 mb-md-8">
<span className="material-symbols-outlined me-2" style={{
color: '#667eea',
fontSize: 'clamp(18px, 3vw, 20px)'
}}>groups</span>
<p className="fw-6 mb-0" style={{
color: '#333',
fontSize: 'clamp(14px, 2vw, 15px)'
}}>Openings</p>
</div>
<p style={{
color: '#666',
fontSize: 'clamp(13px, 2vw, 14px)'
}}>
{job.open_positions} {job.open_positions === 1 ? 'Position' : 'Positions'} Available
</p>
</div>
</div>
<div className="cta mt-20 mt-md-30">
<button
onClick={scrollToForm}
className="btn w-100 apply-btn"
style={{
backgroundColor: 'white',
color: '#333',
border: '2px solid #667eea',
padding: 'clamp(12px, 2vw, 15px) clamp(20px, 4vw, 30px)',
fontSize: 'clamp(14px, 2vw, 16px)',
fontWeight: '600',
borderRadius: 'clamp(6px, 1.5vw, 8px)',
transition: 'all 0.3s ease',
cursor: 'pointer'
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = '#FFD700';
e.currentTarget.style.borderColor = '#FFD700';
e.currentTarget.style.color = '#333';
e.currentTarget.style.transform = 'translateY(-2px)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'white';
e.currentTarget.style.borderColor = '#667eea';
e.currentTarget.style.color = '#333';
e.currentTarget.style.transform = 'translateY(0)';
}}
>
<span className="d-none d-sm-inline">Apply for This Position</span>
<span className="d-sm-none">Apply Now</span>
</button>
<p className="text-center mt-12 mt-md-16" style={{
color: '#999',
fontSize: 'clamp(11px, 2vw, 13px)'
}}>
<span className="d-none d-sm-inline">Application takes ~5 minutes</span>
<span className="d-sm-none">~5 min</span>
</p>
<a
href="/career"
className="btn w-100 mt-12 mt-md-16"
style={{
backgroundColor: 'transparent',
color: '#667eea',
border: '2px solid #e0e0e0',
padding: 'clamp(10px, 2vw, 12px) clamp(20px, 4vw, 30px)',
fontSize: 'clamp(13px, 2vw, 14px)',
fontWeight: '500',
borderRadius: 'clamp(6px, 1.5vw, 8px)',
transition: 'all 0.3s ease',
cursor: 'pointer',
textDecoration: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 'clamp(6px, 1.5vw, 8px)'
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = '#f5f5f5';
e.currentTarget.style.borderColor = '#667eea';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'transparent';
e.currentTarget.style.borderColor = '#e0e0e0';
}}
>
<span className="material-symbols-outlined" style={{ fontSize: 'clamp(16px, 3vw, 18px)' }}>arrow_back</span>
<span className="d-none d-sm-inline">Back to Career Page</span>
<span className="d-sm-none">Back</span>
</a>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Application Form Modal/Popup */}
{showApplicationForm && (
<>
{/* Backdrop Overlay */}
<div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
zIndex: 9998,
animation: 'fadeIn 0.3s ease-in-out'
}}
onClick={() => setShowApplicationForm(false)}
aria-hidden="true"
/>
{/* Modal Container */}
<div
role="dialog"
aria-modal="true"
aria-labelledby="application-form-title"
tabIndex={-1}
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 9999,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: 'clamp(10px, 2vw, 20px)',
overflow: 'hidden',
animation: 'fadeIn 0.3s ease-in-out'
}}
onClick={(e) => {
// Close when clicking the container background
if (e.target === e.currentTarget) {
setShowApplicationForm(false);
}
}}
onKeyDown={(e) => {
// Close on ESC key
if (e.key === 'Escape') {
setShowApplicationForm(false);
}
}}
ref={(el) => {
if (el) {
setTimeout(() => el.focus(), 100);
}
}}
>
<div
style={{
backgroundColor: 'white',
borderRadius: '16px',
width: '100%',
maxWidth: '900px',
maxHeight: '90vh',
display: 'flex',
flexDirection: 'column',
boxShadow: '0 20px 60px rgba(0,0,0,0.3)',
position: 'relative',
animation: 'slideUp 0.3s ease-out',
outline: 'none',
overflow: 'hidden',
touchAction: 'none'
}}
onClick={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
onTouchMove={(e) => e.stopPropagation()}
>
<JobApplicationForm job={job} onClose={() => setShowApplicationForm(false)} />
</div>
</div>
{/* Animation Styles */}
<style>{`
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`}</style>
</>
)}
</>
);
};

View File

@@ -1,6 +1,74 @@
"use client";
import Link from "next/link";
import { useJobs } from "@/lib/hooks/useCareer";
const OpenPosition = () => {
const { jobs, loading, error } = useJobs();
if (loading) {
return (
<section className="op-position pt-120 pb-120" id="scroll-to">
<div className="container">
<div className="row">
<div className="col-12">
<div className="intro mb-60">
<h2 className="mt-8 fw-7 title-anim text-secondary">
Open Positions
</h2>
</div>
</div>
<div className="col-12 mt-60">
<p className="text-center">Loading positions...</p>
</div>
</div>
</div>
</section>
);
}
if (error) {
return (
<section className="op-position pt-120 pb-120" id="scroll-to">
<div className="container">
<div className="row">
<div className="col-12">
<div className="intro mb-60">
<h2 className="mt-8 fw-7 title-anim text-secondary">
Open Positions
</h2>
</div>
</div>
<div className="col-12 mt-60">
<p className="text-center text-danger">Error loading positions. Please try again later.</p>
</div>
</div>
</div>
</section>
);
}
if (jobs.length === 0) {
return (
<section className="op-position pt-120 pb-120" id="scroll-to">
<div className="container">
<div className="row">
<div className="col-12">
<div className="intro mb-60">
<h2 className="mt-8 fw-7 title-anim text-secondary">
Open Positions
</h2>
</div>
</div>
<div className="col-12 mt-60">
<p className="text-center">No open positions at the moment. Please check back later.</p>
</div>
</div>
</div>
</section>
);
}
return (
<section className="op-position pt-120 pb-120" id="scroll-to">
<div className="container">
@@ -13,188 +81,36 @@ const OpenPosition = () => {
</div>
</div>
<div className="col-12 mt-60">
<div className="op-position-single appear-down">
<div className="row vertical-column-gap align-items-center">
<div className="col-12 col-sm-2">
<span className="fw-7 text-xl text-tertiary">01</span>
</div>
<div className="col-12 col-sm-5">
<h4 className="fw-7">
<Link href="job-single">UI/UX Design</Link>
</h4>
</div>
<div className="col-12 col-sm-3">
<div className="roles">
<span className="text-tertiary fw-5 text-xl">
(04 Open Roles)
{jobs.map((job, index) => (
<div key={job.id} className="op-position-single appear-down">
<div className="row vertical-column-gap align-items-center">
<div className="col-12 col-sm-2">
<span className="fw-7 text-xl text-tertiary">
{String(index + 1).padStart(2, '0')}
</span>
</div>
</div>
<div className="col-12 col-sm-2">
<div className="cta text-start text-sm-end">
<Link href="job-single">
<span className="material-symbols-outlined">east</span>
</Link>
<div className="col-12 col-sm-5">
<h4 className="fw-7">
<Link href={`/career/${job.slug}`}>{job.title}</Link>
</h4>
</div>
<div className="col-12 col-sm-3">
<div className="roles">
<span className="text-tertiary fw-5 text-xl">
({job.open_positions.toString().padStart(2, '0')} Open {job.open_positions === 1 ? 'Role' : 'Roles'})
</span>
</div>
</div>
<div className="col-12 col-sm-2">
<div className="cta text-start text-sm-end">
<Link href={`/career/${job.slug}`}>
<span className="material-symbols-outlined">east</span>
</Link>
</div>
</div>
</div>
</div>
</div>
<div className="op-position-single appear-down">
<div className="row vertical-column-gap align-items-center">
<div className="col-12 col-sm-2">
<span className="fw-7 text-xl text-tertiary">02</span>
</div>
<div className="col-12 col-sm-5">
<h4 className="fw-7">
<Link href="job-single">Administrative Assistant</Link>
</h4>
</div>
<div className="col-12 col-sm-3">
<div className="roles">
<span className="text-tertiary fw-5 text-xl">
(03 Open Roles)
</span>
</div>
</div>
<div className="col-12 col-sm-2">
<div className="cta text-start text-sm-end">
<Link href="job-single">
<span className="material-symbols-outlined">east</span>
</Link>
</div>
</div>
</div>
</div>
<div className="op-position-single appear-down">
<div className="row vertical-column-gap align-items-center">
<div className="col-12 col-sm-2">
<span className="fw-7 text-xl text-tertiary">03</span>
</div>
<div className="col-12 col-sm-5">
<h4 className="fw-7">
<Link href="job-single">Software Engineer</Link>
</h4>
</div>
<div className="col-12 col-sm-3">
<div className="roles">
<span className="text-tertiary fw-5 text-xl">
(12 Open Roles)
</span>
</div>
</div>
<div className="col-12 col-sm-2">
<div className="cta text-start text-sm-end">
<Link href="job-single">
<span className="material-symbols-outlined">east</span>
</Link>
</div>
</div>
</div>
</div>
<div className="op-position-single appear-down">
<div className="row vertical-column-gap align-items-center">
<div className="col-12 col-sm-2">
<span className="fw-7 text-xl text-tertiary">04</span>
</div>
<div className="col-12 col-sm-5">
<h4 className="fw-7">
<Link href="job-single">Data Entry Clerk</Link>
</h4>
</div>
<div className="col-12 col-sm-3">
<div className="roles">
<span className="text-tertiary fw-5 text-xl">
(01 Open Roles)
</span>
</div>
</div>
<div className="col-12 col-sm-2">
<div className="cta text-start text-sm-end">
<Link href="job-single">
<span className="material-symbols-outlined">east</span>
</Link>
</div>
</div>
</div>
</div>
<div className="op-position-single appear-down">
<div className="row vertical-column-gap align-items-center">
<div className="col-12 col-sm-2">
<span className="fw-7 text-xl text-tertiary">05</span>
</div>
<div className="col-12 col-sm-5">
<h4 className="fw-7">
<Link href="job-single">Marketing Manager</Link>
</h4>
</div>
<div className="col-12 col-sm-3">
<div className="roles">
<span className="text-tertiary fw-5 text-xl">
(09 Open Roles)
</span>
</div>
</div>
<div className="col-12 col-sm-2">
<div className="cta text-start text-sm-end">
<Link href="job-single">
<span className="material-symbols-outlined">east</span>
</Link>
</div>
</div>
</div>
</div>
<div className="op-position-single appear-down">
<div className="row vertical-column-gap align-items-center">
<div className="col-12 col-sm-2">
<span className="fw-7 text-xl text-tertiary">06</span>
</div>
<div className="col-12 col-sm-5">
<h4 className="fw-7">
<Link href="job-single">Executive Assistant</Link>
</h4>
</div>
<div className="col-12 col-sm-3">
<div className="roles">
<span className="text-tertiary fw-5 text-xl">
(07 Open Roles)
</span>
</div>
</div>
<div className="col-12 col-sm-2">
<div className="cta text-start text-sm-end">
<Link href="job-single">
<span className="material-symbols-outlined">east</span>
</Link>
</div>
</div>
</div>
</div>
<div className="op-position-single appear-down">
<div className="row vertical-column-gap align-items-center">
<div className="col-12 col-sm-2">
<span className="fw-7 text-xl text-tertiary">07</span>
</div>
<div className="col-12 col-sm-5">
<h4 className="fw-7">
<Link href="job-single">Lead Product Designer</Link>
</h4>
</div>
<div className="col-12 col-sm-3">
<div className="roles">
<span className="text-tertiary fw-5 text-xl">
(03 Open Roles)
</span>
</div>
</div>
<div className="col-12 col-sm-2">
<div className="cta text-start text-sm-end">
<Link href="job-single">
<span className="material-symbols-outlined">east</span>
</Link>
</div>
</div>
</div>
</div>
))}
</div>
</div>
</div>