// careers-roles-form-faq-cta.jsx // Careers page — C03 Open Roles, C04 Application Form, C05 FAQ, C06 Closing CTA // Light-led. Sage accent. No amber. // ============================================================ // Role data — FALLBACK ONLY. // // Open roles are now managed in WordPress (the `careers` CPT) and fetched at // runtime from /wp-json/claude-pages/v1/careers (see the page App in // pages/careers/inline/app.html). This array is used only if that request // FAILS (network/REST error) so the page never renders empty due to a // transient issue. On a successful (even empty) response the live CPT data is // authoritative. // ============================================================ // Tags are rendered as discrete chips on each role card. // Order in `tags` controls order shown. Keep 2–4 per role. // region: 'Canada only' · mode: 'Remote' / 'Hybrid' / 'On-site' // commitment: 'Full-time' / 'Part-time' / 'Contract' const CAREERS_ROLES = [ { id: 'sales-rep-ca', title: 'Commercial Energy Sales Representative', tags: [ { label: 'Canada only', kind: 'region' }, { label: 'Full-time', kind: 'commitment' }, { label: 'Remote', kind: 'mode' } ], posted: 'Posted May 2026', summary: 'Develop and manage relationships with commercial and industrial facility owners across Canada — qualify opportunities, present energy assessments, and manage prospects through to project close.', bullet: 'Requires an understanding of how commercial energy projects are financially evaluated — not just the technical side.', doing: [ 'Identify and qualify C&I prospects across manufacturing, food & beverage, warehousing, and agriculture', 'Present GI’s integrated energy services to facility owners, CFOs, and operations directors', 'Work with the project team to develop proposals and financial models', 'Manage a pipeline and report activity accurately through CRM', 'Represent GI at trade shows and industry events as required — travel within Canada' ], looking: [ '3+ years of B2B sales experience — energy, industrial, or facilities-related preferred', 'Comfortable discussing financial outcomes: ROI, payback, incentive stacking', 'Organised and self-directed — this is a fully remote role with territory ownership', 'Based in Canada with a valid driver’s licence', 'Experience with CRM tools (Zoho or equivalent)' ] }, { id: 'project-manager-ca', title: 'Project Manager — Solar & Energy', tags: [ { label: 'Canada only', kind: 'region' }, { label: 'Full-time', kind: 'commitment' }, { label: 'Hybrid', kind: 'mode' } ], posted: 'Posted May 2026', summary: 'Oversee solar and energy projects from design through commissioning — coordinate across engineering, procurement, and installation, manage client communication, and ensure projects close on time and on budget.', bullet: 'A hands-on role. Site visits across Canada are a regular part of the job.', doing: [ 'Manage project schedules, scopes, and budgets across solar and efficiency projects', 'Coordinate with engineering, electrical contractors, and equipment suppliers', 'Serve as the primary client contact during project delivery', 'Track permit and utility interconnection timelines — flag delays early', 'Conduct site inspections and support commissioning across Canadian project sites' ], looking: [ '3+ years of project management experience in construction, electrical, or energy', 'Familiarity with solar PV systems, electrical permitting, or utility interconnection is an asset', 'PMP designation or equivalent is an asset — not required', 'Comfortable managing multiple projects simultaneously, with travel within Canada', 'Based in Canada with a valid driver’s licence' ] }, { id: 'admin-coordinator-ca', title: 'Operations & Administrative Coordinator', tags: [ { label: 'Canada only', kind: 'region' }, { label: 'Part-time', kind: 'commitment' }, { label: 'Remote', kind: 'mode' } ], posted: 'Posted May 2026', summary: 'Support the sales and project teams with day-to-day administrative work — proposal preparation, incentive paperwork, scheduling, and client correspondence. Approximately 20–25 hours per week.', bullet: 'Suited to someone who is precise, organised, and comfortable working independently in a remote setup.', doing: [ 'Prepare and format proposals, contracts, and incentive application packages', 'Maintain client records and project documentation in CRM and shared drives', 'Coordinate calendars and meetings across the team and with external partners', 'Track deadlines on incentive applications and project deliverables', 'Handle inbound client correspondence and route requests to the right team member' ], looking: [ '2+ years in an administrative, operations, or coordinator role', 'Strong written communication — most work happens in writing', 'Comfortable with cloud tools (Google Workspace, CRM, project management software)', 'Self-directed — this is a fully remote, part-time role', 'Based in Canada' ] } ]; // ============================================================ // C03 · OPEN ROLES — White. Expandable role cards. // ============================================================ const CareersOpenRoles = ({ onApplyClick, roles }) => { const [openId, setOpenId] = React.useState(null); const didInit = React.useRef(false); // Open the first role once data arrives (without re-opening if the user // later collapses everything). React.useEffect(() => { if (!didInit.current && Array.isArray(roles) && roles.length > 0) { setOpenId(roles[0].id); didInit.current = true; } }, [roles]); const loading = roles === null || roles === undefined; const list = Array.isArray(roles) ? roles : []; const hasRoles = list.length > 0; return (
{loading ? (
Loading open roles…
) : hasRoles ? (
{list.map((role) => ( setOpenId(openId === role.id ? null : role.id)} onApply={() => onApplyClick(role.id)} /> ))}
) : ( )}
); }; // Expandable role card. Header always visible — list expands on click. const RoleCard = ({ role, isOpen, onToggle, onApply }) => (
{/* Header — clickable */} {/* Expanded body */} {isOpen && (
{role.bullet && (

{role.bullet}

)}
{Array.isArray(role.doing) && role.doing.length > 0 && ( )} {Array.isArray(role.looking) && role.looking.length > 0 && ( )}
Ready when you are
)}
); // Compact pill rendering a role condition. `kind` controls fill. // region = filled sage · mode = sage-tint · commitment = neutral outline const RoleTag = ({ tag }) => { const variants = { region: { background: CAR_SAGE, color: '#fff', border: `1px solid ${CAR_SAGE}` }, mode: { background: CAR_SAGE_TINT, color: '#2E4A39', border: `1px solid ${CAR_SAGE_LINE}` }, commitment: { background: '#fff', color: 'var(--gi-deep-ink)', border: '1px solid rgba(15,33,51,0.22)' } }; const v = variants[tag.kind] || variants.commitment; return ( {tag.kind === 'mode' && tag.label === 'Remote' && ( )} {tag.kind === 'region' && ( )} {tag.label} ); }; const RoleList = ({ label, items }) => (
{label}
); // Holding message when CAREERS_ROLES is empty const HoldingMessage = ({ onApplyClick }) => (
No roles currently open

We are not actively recruiting right now.

You are welcome to submit a general expression of interest using the form below. We keep it on file and reach out when a relevant role opens.

); // ============================================================ // C04 · APPLICATION FORM — Paper Cream. Quiet, focused. // Name · Email · Phone · Role · Cover note · CV upload // ============================================================ const CareersApplyForm = React.forwardRef(({ initialRole, roles }, ref) => { const roleOptions = Array.isArray(roles) ? roles : []; const [submitted, setSubmitted] = React.useState(false); const [values, setValues] = React.useState({ name: '', email: '', phone: '', role: initialRole || '', cover: '' }); const [file, setFile] = React.useState(null); const [errors, setErrors] = React.useState({}); const [sending, setSending] = React.useState(false); const [submitError, setSubmitError] = React.useState(null); // Zoho Forms submit endpoint — taken verbatim from the form's "Source Code" // embed. NOTE: this formperma differs from the public/iframe share link; the // /htmlRecords/submit URL below is the one that actually records entries. const ZOHO_ACTION = 'https://forms.zohopublic.com/greenintegrations/form/Applynow/formperma/LgthEWG-2-OjjJ8Zt2sBMI3etStvcjIt5c7DqFTrocM/htmlRecords/submit'; React.useEffect(() => { if (initialRole) setValues((v) => ({ ...v, role: initialRole })); }, [initialRole]); const update = (k) => (e) => { setValues((v) => ({ ...v, [k]: e.target.value })); if (errors[k]) setErrors((er) => ({ ...er, [k]: null })); }; const onFileChange = (e) => { const f = e.target.files?.[0]; if (!f) return; if (f.size > 5 * 1024 * 1024) { setErrors((er) => ({ ...er, file: 'File must be 5MB or smaller' })); return; } const ok = /\.(pdf|doc|docx)$/i.test(f.name); if (!ok) { setErrors((er) => ({ ...er, file: 'Please upload a PDF or Word document' })); return; } setFile(f); if (errors.file) setErrors((er) => ({ ...er, file: null })); }; const onSubmit = (e) => { e.preventDefault(); if (sending) return; const next = {}; if (!values.name.trim()) next.name = 'Please enter your full name'; if (!values.email.trim()) next.email = 'Email required'; else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(values.email)) next.email = 'Please enter a valid email'; if (!values.phone.trim()) next.phone = 'Phone number required'; if (!values.role) next.role = 'Please select a role'; if (!file) next.file = 'Please attach your CV (PDF or Word)'; setErrors(next); if (Object.keys(next).length > 0) return; // Zoho's Name field is composite (First / Last). Split the single "Full // name" input on the first space: first token → First, remainder → Last. const nameParts = values.name.trim().split(/\s+/); const firstName = nameParts.shift() || ''; const lastName = nameParts.join(' '); // The role dropdown is WordPress-driven, but Zoho only accepts its own // fixed option list. So we always submit "General application" as the Zoho // dropdown value and record the role the applicant actually chose at the // top of the cover note (agreed mapping). const selected = roleOptions.find((r) => r.id === values.role); const roleTitle = selected ? selected.title : (values.role === 'general' ? 'General application' : values.role); const note = values.cover.trim(); const coverNote = 'Applying for: ' + roleTitle + (note ? '\n\n' + note : ''); // Field names are taken verbatim from the Zoho "Source Code" embed — // renaming or dropping them silently empties the value on submission. const fd = new FormData(); fd.append('zf_referrer_name', ''); fd.append('zf_redirect_url', ''); fd.append('zc_gad', ''); fd.append('Name_First', firstName); fd.append('Name_Last', lastName); fd.append('Email', values.email.trim()); fd.append('PhoneNumber_countrycode', values.phone.trim()); fd.append('Dropdown', 'General application'); fd.append('MultiLine', coverNote); fd.append('FileUpload', file, file.name); setSending(true); setSubmitError(null); // multipart/form-data is a CORS-safelisted content type, so this counts as // a "simple" request: it reaches Zoho with no preflight. The response is // opaque (mode:'no-cors') so we can't read success — confirm optimistically // and surface only a network-level failure. fetch(ZOHO_ACTION, { method: 'POST', body: fd, mode: 'no-cors' }) .then(() => { setSending(false); setSubmitted(true); }) .catch(() => { setSending(false); setSubmitError('Something went wrong sending your application. Please try again, or email us directly.'); }); }; if (submitted) return ; return (
{/* Left column — header + reassurance */}
Apply now

Apply now.

Tell us about your experience and what kind of work you are looking to be part of.

    {[ ['Direct impact', ''], ['Real commercial projects', ''], ['Build for long-term growth', ''] ].map((row, i) => (
  • {row[0]} · {row[1]}
  • ))}
{/* Right column — form card */}
Application
CV required · PDF or Word
{/* Name */}
01 · Full name {errors.name && {errors.name}}
{/* Email */}
02 · Email {errors.email && {errors.email}}
{/* Phone */}
03 · Phone {errors.phone && {errors.phone}}
{/* Role */}
04 · Role of interest {roleOptions.map((r) => ( ))} {errors.role && {errors.role}}
{/* Cover note */}
05 · Short cover note
{/* CV upload */}
06 · CV upload {errors.file && {errors.file}}
{/* Submit */} {submitError && (
{submitError}
)}

We respect your privacy. Your application stays with the hiring team — never shared externally.

); }); // ---------- Form field primitives ---------- const CareerFieldLabel = ({ children, hint, htmlFor }) => ( ); const careerFieldBase = { fontFamily: 'var(--ff-sans)', fontSize: 14.5, color: 'var(--gi-deep-ink)', padding: '14px 0', border: 'none', borderBottom: '1.5px solid rgba(15,33,51,0.14)', background: 'transparent', width: '100%', outline: 'none', transition: 'border-color 200ms cubic-bezier(.2,0,0,1)', appearance: 'none', WebkitAppearance: 'none' }; const CareerInput = ({ error, ...props }) => { const [focused, setFocused] = React.useState(false); return ( setFocused(true)} onBlur={() => setFocused(false)} style={{ ...careerFieldBase, borderBottomColor: error ? '#B33A3A' : focused ? 'var(--gi-soft-green)' : 'rgba(15,33,51,0.14)' }} {...props} /> ); }; const CareerSelect = ({ id, name, value, onChange, error, children, required, placeholder }) => { const [focused, setFocused] = React.useState(false); return (
); }; const CareerTextarea = ({ id, name, value, onChange, placeholder, maxLength }) => { const [focused, setFocused] = React.useState(false); return (