career form updated
This commit is contained in:
parent
eb7c190e80
commit
7061fcff3e
@ -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",
|
||||
@ -32,6 +33,7 @@ export default function CareersPage() {
|
||||
<AboutThree />
|
||||
<WhyChooseUs />
|
||||
<ProjectsSection />
|
||||
<CareersForm />
|
||||
</main>
|
||||
<Footer1 />
|
||||
</>
|
||||
|
||||
338
src/components/careers/CareersForm.tsx
Normal file
338
src/components/careers/CareersForm.tsx
Normal 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;
|
||||
Loading…
x
Reference in New Issue
Block a user