This commit is contained in:
akash 2026-03-14 19:31:10 +05:30
parent 5959b20dab
commit 763c0df3c1
4 changed files with 1566 additions and 15 deletions

View File

@ -33,7 +33,7 @@ export default function CareersPage() {
<AboutThree /> <AboutThree />
<WhyChooseUs /> <WhyChooseUs />
<ProjectsSection /> <ProjectsSection />
<CareersForm /> {/* <CareersForm /> */}
</main> </main>
<Footer1 /> <Footer1 />
</> </>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,361 @@
"use client";
import React, { useEffect, useState, useRef } from "react";
import { createPortal } from "react-dom";
import ReCAPTCHA from "react-google-recaptcha";
import axios from "axios";
interface CareersContactPopupProps {
isOpen: boolean;
onClose: () => void;
defaultPosition?: string;
}
const positions = [
{ value: "Frontend Developer", label: "Website Development" },
{ value: "Backend Developer", label: "Social Media Marketing" },
{ value: "Full Stack Developer", label: "Search Engine Optimization" },
{ value: "UI/UX Designer", label: "Mobile App Development" },
{ value: "SEO Specialist", label: "UI/UX Designing" },
{ value: "Digital Marketing Executive", label: "Graphic Designing" },
{ value: "Other", label: "Other" },
];
const CareersContactPopup: React.FC<CareersContactPopupProps> = ({
isOpen,
onClose,
defaultPosition = "",
}) => {
const [mounted, setMounted] = useState(false);
const [formData, setFormData] = useState({
name: "",
email: "",
phone: "",
position: defaultPosition,
message: "",
});
const [resumeFile, setResumeFile] = useState<File | null>(null);
const [resumeBase64, setResumeBase64] = useState<string | null>(null);
const [formErrors, setFormErrors] = useState<any>({});
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
const [alert, setAlert] = useState({ show: false, type: "", message: "" });
const [isSubmitting, setIsSubmitting] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
setMounted(true);
}, []);
// Sync position when defaultPosition changes (new card clicked)
useEffect(() => {
setFormData((prev) => ({ ...prev, position: defaultPosition }));
}, [defaultPosition]);
// Close on ESC
useEffect(() => {
const handleEsc = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
if (isOpen) {
window.addEventListener("keydown", handleEsc);
document.body.style.overflow = "hidden";
}
return () => {
window.removeEventListener("keydown", handleEsc);
document.body.style.overflow = "";
};
}, [isOpen, onClose]);
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
const file = e.target.files[0];
if (file.size > 5 * 1024 * 1024) {
setFormErrors((prev: any) => ({ ...prev, resume: "File size should be less than 5MB." }));
setResumeFile(null);
setResumeBase64(null);
return;
}
const allowedTypes = [
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
];
if (!allowedTypes.includes(file.type)) {
setFormErrors((prev: any) => ({ ...prev, resume: "Only PDF and Word documents are allowed." }));
setResumeFile(null);
setResumeBase64(null);
return;
}
setResumeFile(file);
setFormErrors((prev: any) => ({ ...prev, resume: undefined }));
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
setResumeBase64(reader.result as string);
};
}
};
const handleCaptchaChange = (token: string | null) => {
setCaptchaToken(token);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const errors: any = {};
if (!formData.name.trim()) errors.name = "Name is required.";
if (!formData.email.trim()) errors.email = "Email is required.";
if (!formData.phone.trim()) errors.phone = "Phone is required.";
if (!formData.position.trim()) errors.position = "Please select a position.";
if (!captchaToken) errors.captcha = "Please verify the CAPTCHA.";
// Resume is OPTIONAL — no validation
setFormErrors(errors);
if (Object.keys(errors).length > 0) return;
setIsSubmitting(true);
const emailData: any = {
name: formData.name,
email: formData.email,
phone: formData.phone,
position: formData.position,
message: `Position: ${formData.position}<br /><br />Phone: ${formData.phone}<br /><br />Message: ${formData.message || "No additional message."}`,
to: "info@metatroncubesolutions.com",
senderName: "Metatroncube Careers Application",
recaptchaToken: captchaToken,
};
if (resumeBase64 && resumeFile) {
emailData.attachments = [
{
filename: resumeFile.name,
path: resumeBase64,
},
];
}
try {
const res = await axios.post("https://mailserver.metatronnest.com/send", emailData, {
headers: { "Content-Type": "application/json" },
});
setAlert({
show: true,
type: "success",
message: res?.data?.message || "Application submitted successfully!",
});
setFormData({ name: "", email: "", phone: "", position: defaultPosition, message: "" });
setResumeFile(null);
setResumeBase64(null);
if (fileInputRef.current) fileInputRef.current.value = "";
setCaptchaToken(null);
setFormErrors({});
} catch (error) {
console.error("Error sending application:", error);
setAlert({
show: true,
type: "danger",
message: "Failed to submit application. Please try again later.",
});
} finally {
setIsSubmitting(false);
}
};
// Auto-hide alert
useEffect(() => {
if (alert.show) {
const t = setTimeout(() => setAlert((p) => ({ ...p, show: false })), 5000);
return () => clearTimeout(t);
}
}, [alert.show]);
const handleReset = () => {
setFormData({ name: "", email: "", phone: "", position: defaultPosition, message: "" });
setResumeFile(null);
setResumeBase64(null);
if (fileInputRef.current) fileInputRef.current.value = "";
setCaptchaToken(null);
setFormErrors({});
setAlert({ show: false, type: "", message: "" });
};
const handleClose = () => {
handleReset();
onClose();
};
if (!mounted || !isOpen) return null;
const modalContent = (
<div className={`contact-popup-overlay ${isOpen ? 'active' : ''}`} onClick={handleClose}>
<div className="contact-popup-content careers-popup-optimized" onClick={(e) => e.stopPropagation()}>
{/* ── CLOSE BUTTON ── */}
<button className="close-btn" onClick={handleClose} aria-label="Close popup" suppressHydrationWarning>
<i className="fa-solid fa-xmark"></i>
</button>
{/* ── FORM PANEL ── */}
<div className="form-panel">
<div className="form-header">
<h3 className="form-title">Apply Now</h3>
</div>
{alert.show && (
<div className={`cp-alert cp-alert--${alert.type}`}>
<i className={`fa-solid ${alert.type === "success" ? "fa-circle-check" : "fa-circle-xmark"}`}></i>
{alert.message}
</div>
)}
<form onSubmit={handleSubmit} noValidate className="contact-form">
<div className="row g-3">
<div className="col-sm-6">
<label className="form-label-custom">Full Name <span>*</span></label>
<input
type="text"
name="name"
placeholder="Full Name"
value={formData.name}
onChange={handleChange}
className={`form-input ${formErrors.name ? "input-error" : ""}`}
disabled={isSubmitting}
suppressHydrationWarning
/>
{formErrors.name && <small className="text-danger">{formErrors.name}</small>}
</div>
<div className="col-sm-6">
<label className="form-label-custom">Email Address <span>*</span></label>
<input
type="email"
name="email"
placeholder="Email Address"
value={formData.email}
onChange={handleChange}
className={`form-input ${formErrors.email ? "input-error" : ""}`}
disabled={isSubmitting}
suppressHydrationWarning
/>
{formErrors.email && <small className="text-danger">{formErrors.email}</small>}
</div>
</div>
<div className="row g-3 mt-1">
<div className="col-sm-6">
<label className="form-label-custom">Phone Number <span>*</span></label>
<input
type="tel"
name="phone"
placeholder="Phone Number"
value={formData.phone}
onChange={handleChange}
className={`form-input ${formErrors.phone ? "input-error" : ""}`}
disabled={isSubmitting}
suppressHydrationWarning
/>
{formErrors.phone && <small className="text-danger">{formErrors.phone}</small>}
</div>
<div className="col-sm-6">
<label className="form-label-custom">Select Position <span>*</span></label>
<select
name="position"
value={formData.position}
onChange={handleChange}
className={`form-input form-select ${formErrors.position ? "input-error" : ""}`}
disabled={isSubmitting}
suppressHydrationWarning
>
<option value="">Select Position</option>
{positions
.filter(pos => !defaultPosition || pos.value === defaultPosition)
.map((pos) => (
<option key={pos.value} value={pos.value}>
{pos.label}
</option>
))
}
</select>
{formErrors.position && <small className="text-danger">{formErrors.position}</small>}
</div>
</div>
<div className="form-field-wrapper mt-3">
<label className="form-label-custom">Upload Resume (PDF, DOC, DOCX) <span className="optional-label"> Optional</span></label>
<div className="file-upload-styled">
<input
ref={fileInputRef}
id="resume-upload-popup"
type="file"
name="resume"
accept=".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
onChange={handleFileChange}
className="file-hidden-input"
disabled={isSubmitting}
suppressHydrationWarning
/>
<label htmlFor="resume-upload-popup" className="file-upload-label">
<div className="file-content">
<i className="fa-solid fa-cloud-arrow-up"></i>
<span className="file-name-text">
{resumeFile ? resumeFile.name : "Choose your resume"}
</span>
</div>
<div className="file-browse-btn">Browse</div>
</label>
</div>
{formErrors.resume && <small className="text-danger">{formErrors.resume}</small>}
</div>
<div className="form-field-wrapper mt-3">
<label className="form-label-custom">Cover Letter / Message <span className="optional-label"> Optional</span></label>
<textarea
name="message"
placeholder="Write your message here..."
rows={2}
value={formData.message}
onChange={handleChange}
className="form-textarea"
disabled={isSubmitting}
suppressHydrationWarning
/>
</div>
<div className="mt-3 g-recaptcha-container text-center">
<ReCAPTCHA
sitekey="6LekfpwrAAAAAOTwuP1d2gg-Fv9UEsAjE2gjOQJl"
onChange={handleCaptchaChange}
/>
{formErrors.captcha && <small className="text-danger d-block mt-1">{formErrors.captcha}</small>}
</div>
<div className="mt-3">
<button
type="submit"
className="vl-btn1 submit-btn w-100"
disabled={isSubmitting}
suppressHydrationWarning
>
<span>{isSubmitting ? "Submitting Application..." : "Submit Application"}</span>
{!isSubmitting && <i className="fa-solid fa-arrow-right"></i>}
</button>
</div>
</form>
</div>
</div>
</div>
);
return typeof document !== "undefined"
? createPortal(modalContent, document.body)
: null;
};
export default CareersContactPopup;

View File

@ -30,6 +30,8 @@ const projectsData = [
} }
]; ];
import CareersContactPopup from '@/components/careers/CareersContactPopup';
const categories = [ const categories = [
{ label: "All Items", filter: "all" }, { label: "All Items", filter: "all" },
{ label: "Website Development", filter: "web" }, { label: "Website Development", filter: "web" },
@ -43,11 +45,26 @@ const categories = [
const ProjectsSection = () => { const ProjectsSection = () => {
const [hasMounted, setHasMounted] = useState(false); const [hasMounted, setHasMounted] = useState(false);
const [activeFilter, setActiveFilter] = useState("all"); const [activeFilter, setActiveFilter] = useState("all");
const [isPopupOpen, setIsPopupOpen] = useState(false);
const [selectedPosition, setSelectedPosition] = useState("");
useEffect(() => { useEffect(() => {
setHasMounted(true); setHasMounted(true);
}, []); }, []);
const openPopup = (positionTitle: string, e?: React.MouseEvent) => {
if (e) {
e.preventDefault();
e.stopPropagation();
}
setSelectedPosition(positionTitle);
setIsPopupOpen(true);
};
const closePopup = () => {
setIsPopupOpen(false);
};
const filteredProjects = projectsData.filter(project => const filteredProjects = projectsData.filter(project =>
activeFilter === "all" || project.category === activeFilter activeFilter === "all" || project.category === activeFilter
); );
@ -109,19 +126,30 @@ const ProjectsSection = () => {
{filteredProjects.map((project) => ( {filteredProjects.map((project) => (
<div key={project.id} className="project-item"> <div key={project.id} className="project-item">
<div className="projects-one__card"> <div className="projects-one__card" onClick={(e) => openPopup(project.title, e)}>
<img src={project.image} alt={project.title} /> <img src={project.image} alt={project.title} />
<div className="projects-one__card__hover d-flex"> <div className="projects-one__card__hover d-flex">
<div className="projects-one__card__hover-mask"></div> <div className="projects-one__card__hover-mask"></div>
<div className="projects-one__card__hover-content-inner"> <div className="projects-one__card__hover-content-inner">
<div className="projects-one__card__hover-text"> <div className="projects-one__card__hover-text">
<h3><Link href={project.link}>{project.title}</Link></h3> <h3>
<a
href="#"
onClick={(e) => openPopup(project.title, e)}
>
{project.title}
</a>
</h3>
<p>{project.description}</p> <p>{project.description}</p>
</div> </div>
<div className="projects-one__card__hover-item"></div> <div className="projects-one__card__hover-item"></div>
<Link href="/contact" className="projects-one__card__hover-link"> <a
href="#"
className="projects-one__card__hover-link"
onClick={(e) => openPopup(project.title, e)}
>
<i className="fa fa-arrow-right"></i> <i className="fa fa-arrow-right"></i>
</Link> </a>
</div> </div>
</div> </div>
</div> </div>
@ -136,6 +164,12 @@ const ProjectsSection = () => {
</div> </div>
)} )}
</div> </div>
<CareersContactPopup
isOpen={isPopupOpen}
onClose={closePopup}
defaultPosition={selectedPosition}
/>
</section> </section>
); );
}; };