// Careers page — job listings, per-job application form, open application const { useState: useCareersState, useEffect: useCareersEffect, useRef: useCareersRef } = React; // ── Paste your Google Apps Script Web App URL here after deploying // Follow the setup guide in careers_apps_script.md to deploy your script const CAREERS_WEBHOOK_URL = 'https://script.google.com/macros/s/AKfycbxnqVfYpcNNFlXw-40W8Z7skRuOMkn81rQlqwD3fNwFUp1H8VJu6FTdycBGGww8hpYl2g/exec'; // ── Fetch jobs from backend // Note: Actual fetching happens in CareersPage component // ── Hero const CareersHero = () => (
Join the Team · AMG Careers

Build something
that lasts.

We don't just build landmarks — we build careers. Join a 40-year legacy of trust, craft, and community across Punjab's real estate landscape.

{/* Stats strip */}
{[ { v: '40+', k: 'Years of Legacy' }, { v: '10K+', k: 'Investor Families' }, { v: '8+', k: 'Active Projects' }, ].map(s => (
{s.v}
{s.k}
))}
); // ── Stable field wrapper — defined OUTSIDE modal so React never remounts it on re-render const CareersField = ({ label, error, children }) => ( ); // ── Application form modal — rendered via portal to escape Framer Motion transform context function ApplicationModal({ job, onClose }) { const [form, setForm] = useCareersState({ name: '', email: '', phone: '', message: '', openPosition: '', openDepartment: '', openLocation: '' }); const [resume, setResume] = useCareersState(null); const [errors, setErrors] = useCareersState({}); const [status, setStatus] = useCareersState('idle'); // idle | submitting | success | error const fileRef = useCareersRef(); const isOpen = job?.title === '__open__'; // Lock body scroll while open useCareersEffect(() => { const prev = document.body.style.overflow; document.body.style.overflow = 'hidden'; if (window.lenis) window.lenis.stop(); return () => { document.body.style.overflow = prev; if (window.lenis) window.lenis.start(); }; }, []); // ── Validators const validateEmail = (v) => { const s = v.trim().toLowerCase(); if (!s) return 'Email address is required'; if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(s)) return 'Enter a valid email address'; const domain = s.split('@')[1] || ''; if (!domain.includes('.')) return 'Enter a valid email address'; return null; }; const validatePhone = (v) => { const digits = v.replace(/\D/g, ''); if (!digits) return 'Phone number is required'; if (digits.length !== 10) return 'Enter a valid 10-digit Indian mobile number'; if (!/^[6-9]/.test(digits)) return 'Indian numbers start with 6, 7, 8 or 9'; if (/^(.)\1+$/.test(digits)) return 'Enter a real mobile number'; return null; }; const validate = () => { const e = {}; if (!form.name.trim() || form.name.trim().length < 2) e.name = 'Please enter your full name'; const emailErr = validateEmail(form.email); if (emailErr) e.email = emailErr; const phoneErr = validatePhone(form.phone); if (phoneErr) e.phone = phoneErr; if (isOpen) { if (!form.openDepartment.trim()) e.openDepartment = 'Please specify a department'; if (!form.openPosition.trim()) e.openPosition = 'Please specify a position'; if (!form.openLocation.trim()) e.openLocation = 'Please specify a location'; } if (!resume) e.resume = 'Please attach your resume (PDF or DOCX)'; return e; }; // ── File handler — validate by extension (MIME type is unreliable on Windows) const handleFile = (e) => { const f = e.target.files[0]; if (!f) return; const name = f.name.toLowerCase(); const allowed = ['.pdf', '.doc', '.docx']; if (!allowed.some(ext => name.endsWith(ext))) { setErrors(prev => ({ ...prev, resume: 'Only PDF or DOCX files are accepted' })); e.target.value = ''; // reset so same file can be picked again return; } if (f.size > 5 * 1024 * 1024) { setErrors(prev => ({ ...prev, resume: 'File must be under 5 MB' })); e.target.value = ''; return; } setResume(f); setErrors(prev => { const n = {...prev}; delete n.resume; return n; }); }; // ── Strip non-digits from phone field const handlePhone = (e) => { const digits = e.target.value.replace(/\D/g, '').slice(0, 10); setForm(p => ({...p, phone: digits})); if (errors.phone) setErrors(prev => { const n={...prev}; delete n.phone; return n; }); }; // ── Convert file to base64 helper const toBase64 = (file) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result.split(',')[1]); // strip data:...;base64, prefix reader.onerror = reject; reader.readAsDataURL(file); }); const handleSubmit = async (ev) => { ev.preventDefault(); const e = validate(); if (Object.keys(e).length) { setErrors(e); return; } setStatus('submitting'); try { // Convert resume to base64 so Apps Script can save it to Google Drive let resumeBase64 = null; if (resume) { resumeBase64 = await toBase64(resume); } const payload = { name: form.name.trim(), email: form.email.trim().toLowerCase(), phone: form.phone.trim(), coverNote: form.message.trim(), position: isOpen ? form.openPosition.trim() : (job?.title || ''), department: isOpen ? form.openDepartment.trim() : (job?.department || '—'), location: isOpen ? form.openLocation.trim() : (job?.location || '—'), resumeName: resume ? resume.name : '', resumeBase64: resumeBase64 || '', submittedAt: new Date().toISOString(), }; if (CAREERS_WEBHOOK_URL && CAREERS_WEBHOOK_URL !== 'PASTE_YOUR_APPS_SCRIPT_URL_HERE') { await fetch(CAREERS_WEBHOOK_URL, { method: 'POST', mode: 'no-cors', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); } setStatus('success'); } catch (err) { setStatus('error'); } }; const modalContent = (
{ if (e.target === e.currentTarget) onClose(); }} >
e.stopPropagation()} > {/* Header */}
{isOpen ? 'Open Application' : (job?.department || 'Position')}

{isOpen ? 'Apply for Future Roles' : job?.title}

{status === 'success' ? (

Application received!

Thank you, {form.name.split(' ')[0] || 'there'}. Your application and resume have been sent to our HR team. We'll be in touch if there's a fit.

) : status === 'error' ? (

Submission failed

Could not reach the server. Please email us directly at hr@amgrealty.in

) : (
{!isOpen ? (
Applying for: {job?.title}
) : (

Role Preferences

setForm(p => ({...p, openPosition: e.target.value}))} />
setForm(p => ({...p, openLocation: e.target.value}))} />
)} setForm(p => ({...p, name: e.target.value}))} />
setForm(p => ({...p, email: e.target.value}))} />
+91