career form updated

This commit is contained in:
Selvi 2026-03-11 10:22:41 +05:30
parent eb7c190e80
commit 7061fcff3e
2 changed files with 341 additions and 1 deletions

View File

@ -8,6 +8,7 @@ import AboutService from "@/components/services-digital-solutions/AboutService";
import AboutThree from "@/components/home/AboutThree";
import ProjectsSection from "@/components/home/ProjectsSection";
import MetatronInitializer from "@/components/MetatronInitializer";
import CareersForm from "@/components/careers/CareersForm";
export const metadata: Metadata = {
title: "Careers - Metatroncube Software Solutions | Innovative & User-Centric Tech Services in Waterloo",
@ -19,7 +20,7 @@ export const metadata: Metadata = {
export default function CareersPage() {
return (
<>
<>
<MetatronInitializer />
<Header1 />
<main>
@ -32,6 +33,7 @@ export default function CareersPage() {
<AboutThree />
<WhyChooseUs />
<ProjectsSection />
<CareersForm />
</main>
<Footer1 />
</>

View File

@ -0,0 +1,338 @@
"use client";
import React, { useState, useEffect, useRef } from "react";
import ReCAPTCHA from "react-google-recaptcha";
import axios from "axios";
const CareersForm = () => {
const sectionRef = useRef<HTMLDivElement>(null);
const [formData, setFormData] = useState({
name: "",
email: "",
phone: "",
position: "",
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 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];
// Check file size (e.g., max 5MB)
if (file.size > 5 * 1024 * 1024) {
setFormErrors((prev: any) => ({ ...prev, resume: "File size should be less than 5MB." }));
setResumeFile(null);
setResumeBase64(null);
return;
}
// Check file type
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 }));
// Convert to Base64 for email attachment
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
setResumeBase64(reader.result as string);
};
reader.onerror = (error) => {
console.error("Error reading file:", error);
setFormErrors((prev: any) => ({ ...prev, resume: "Error reading file." }));
};
}
};
const handleCaptchaChange = (token: string | null) => {
setCaptchaToken(token);
};
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const elements = entry.target.querySelectorAll(".wow");
elements.forEach((el) => {
const htmlEl = el as HTMLElement;
const delay = htmlEl.dataset.wowDelay || "0ms";
setTimeout(() => {
htmlEl.classList.add("animated");
if (htmlEl.classList.contains("fadeInLeft")) {
htmlEl.classList.add("fadeInLeft");
} else if (htmlEl.classList.contains("fadeInRight")) {
htmlEl.classList.add("fadeInRight");
} else {
htmlEl.classList.add("fadeInUp");
}
htmlEl.style.visibility = "visible";
}, parseInt(delay));
});
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1 }
);
if (sectionRef.current) {
observer.observe(sectionRef.current);
}
return () => observer.disconnect();
}, []);
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.";
if (!resumeFile) errors.resume = "Please upload your resume.";
setFormErrors(errors);
if (Object.keys(errors).length > 0) return;
setIsSubmitting(true);
const emailData: any = {
...formData,
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 the backend endpoint supports attachments via Nodemailer's "attachments" array matching to data URI paths
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: "",
message: "",
});
setResumeFile(null);
setResumeBase64(null);
// Reset file input value manually
const fileInput = document.getElementById("resume-upload") as HTMLInputElement;
if (fileInput) fileInput.value = "";
setCaptchaToken(null);
setFormErrors({});
} catch (error) {
console.error("❌ Error sending email:", error);
setAlert({
show: true,
type: "danger",
message: "Failed to submit application. Please try again later.",
});
} finally {
setIsSubmitting(false);
}
};
useEffect(() => {
if (alert.show) {
const timer = setTimeout(() => {
setAlert((prev) => ({ ...prev, show: false }));
}, 5000);
return () => clearTimeout(timer);
}
}, [alert.show]);
return (
<section ref={sectionRef} className="contact-one section-space-bottom" id="careers-form">
<div className="container contact-one__container wow fadeInUp" data-wow-delay="100ms">
<div
className="contact-one__wrapper"
style={{ backgroundImage: "url(/assets/images/about/7/element-bottom-right.webp)" }}
>
<div className="row">
<div className="col-lg-6">
<div className="contact-one__image-two">
<img src="/assets/images/about/7/4-left-img.webp" alt="Career Growth" />
</div>
</div>
<div className="col-lg-6">
<div className="contact-one__content">
<div className="sec-title text-left">
<h6 className="sec-title__tagline">
<span className="sec-title__tagline__left"></span>
Join Our Team
<span className="sec-title__tagline__right"></span>
</h6>
<h3 className="sec-title__title" style={{ color: "#fff" }}>
Apply for your dream position today.
</h3>
</div>
{alert.show && (
<div className={`alert alert-${alert.type === 'danger' ? 'danger' : 'success'} mb-4`} style={{ padding: '15px', color: '#fff', backgroundColor: alert.type === 'danger' ? 'rgba(255, 107, 107, 0.2)' : 'rgba(76, 175, 80, 0.2)', border: alert.type === 'danger' ? '1px solid #ff6b6b' : '1px solid #4CAF50', borderRadius: '8px' }}>
{alert.message}
</div>
)}
<form className="contact-one__form form-one" onSubmit={handleSubmit}>
<div className="form-one__group">
<div className="mb-20">
<label className="form-label">Full Name *</label>
<input
type="text"
name="name"
placeholder="Full Name"
value={formData.name}
onChange={handleChange}
className="form-input"
suppressHydrationWarning={true}
/>
{formErrors.name && <small className="text-danger">{formErrors.name}</small>}
</div>
<div className="mb-20">
<label className="form-label">Email Address *</label>
<input
type="email"
name="email"
placeholder="Email Address"
value={formData.email}
onChange={handleChange}
className="form-input"
suppressHydrationWarning={true}
/>
{formErrors.email && <small className="text-danger">{formErrors.email}</small>}
</div>
<div className="mb-20">
<label className="form-label">Phone Number *</label>
<input
type="tel"
name="phone"
placeholder="Phone Number"
value={formData.phone}
onChange={handleChange}
className="form-input"
suppressHydrationWarning={true}
/>
{formErrors.phone && <small className="text-danger">{formErrors.phone}</small>}
</div>
<div className="mb-20">
<label className="form-label">Select Position *</label>
<select
name="position"
value={formData.position}
onChange={handleChange}
className="form-select-custom"
suppressHydrationWarning={true}
>
<option value="">Select Position</option>
<option value="Frontend Developer">Website Development</option>
<option value="Backend Developer">Social Media Marketing</option>
<option value="Full Stack Developer">Search Engine Optimization</option>
<option value="UI/UX Designer">Mobile App Development</option>
<option value="SEO Specialist">UI/UX Designing</option>
<option value="Digital Marketing Executive">Graphic Designing</option>
<option value="Other">Other</option>
</select>
{formErrors.position && <small className="text-danger">{formErrors.position}</small>}
</div>
<div className="form-one__control--full mb-20 mt-2">
<label className="form-label">Upload Resume (PDF, DOC, DOCX) *</label>
<input
id="resume-upload"
type="file"
name="resume"
accept=".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
onChange={handleFileChange}
className="form-input p-2"
style={{ background: 'transparent', color: '#fff', border: '1px solid rgba(255, 255, 255, 0.2)' }}
suppressHydrationWarning={true}
/>
{formErrors.resume && <small className="text-danger">{formErrors.resume}</small>}
{resumeFile && !formErrors.resume && <small className="text-success d-block mt-1">File selected: {resumeFile.name}</small>}
</div>
<div className="form-one__control--full mb-20">
<label className="form-label">Cover Letter / Message (Optional)</label>
<textarea
name="message"
placeholder="Write your message here..."
rows={3}
value={formData.message}
onChange={handleChange}
className="form-textarea"
suppressHydrationWarning={true}
></textarea>
</div>
{/* <div className="form-one__control--full mb-3 mt-3">
<ReCAPTCHA
sitekey="6LekfpwrAAAAAOTwuP1d2gg-Fv9UEsAjE2gjOQJl"
onChange={handleCaptchaChange}
/>
{formErrors.captcha && <small className="text-danger">{formErrors.captcha}</small>}
</div> */}
<div className="form-one__control--full">
<button type="submit" className="submit-btn tolak-btn w-100" disabled={isSubmitting}>
<b>{isSubmitting ? "SUBMITTING..." : "SUBMIT APPLICATION"}</b>
<span></span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default CareersForm;