282 lines
12 KiB
TypeScript
282 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState, useEffect } from "react";
|
|
import ReCAPTCHA from "react-google-recaptcha";
|
|
import axios from "axios";
|
|
import Link from 'next/link';
|
|
|
|
const slides = [
|
|
{
|
|
id: 0,
|
|
eyebrow: "Kitchener · Waterloo · Ontario · 250km delivery radius",
|
|
title: <>Ontario's B2B<br /><em>Fence Supply</em><br /><br />Partner</>,
|
|
sub: <>Supplying contractors, builders, and property managers across Ontario with <strong>chain link, ornamental, composite, glass railing, aluminum railing, Expert Stain & Seal, Fence Armor, and temporary fence rental</strong> — with scheduled job site delivery across a 250km radius from KWC.</>,
|
|
productValue: "",
|
|
},
|
|
{
|
|
id: 1,
|
|
eyebrow: "Premium Aluminum Railing Systems",
|
|
title: <>Durable & Stylish<br /><em>Aluminum</em><br />Railing</>,
|
|
sub: <>High-quality, easy-to-install aluminum railing systems for residential and commercial projects. Available in multiple styles and colors with fast job site delivery across our service area.</>,
|
|
productValue: "Aluminum railing",
|
|
},
|
|
{
|
|
id: 2,
|
|
eyebrow: "Low Maintenance Composite Fencing",
|
|
title: <>Modern & Lasting<br /><em>Composite</em><br />Fencing</>,
|
|
sub: <>Provide your clients with beautiful, weather-resistant composite fences. Easy installation, long lifespan, and premium aesthetics for any property.</>,
|
|
productValue: "Composite fence",
|
|
}
|
|
];
|
|
|
|
export default function Hero() {
|
|
const [formData, setFormData] = useState({
|
|
company: "",
|
|
name: "",
|
|
phone: "",
|
|
email: "",
|
|
product: "",
|
|
city: "",
|
|
quantity: ""
|
|
});
|
|
|
|
const [formErrors, setFormErrors] = useState<any>({});
|
|
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
|
|
const [alert, setAlert] = useState({ show: false, type: "", message: "" });
|
|
|
|
const [currentSlide, setCurrentSlide] = useState(0);
|
|
const [userSelectedProduct, setUserSelectedProduct] = useState(false);
|
|
|
|
useEffect(() => {
|
|
const timer = setInterval(() => {
|
|
setCurrentSlide((prev) => (prev + 1) % slides.length);
|
|
}, 6000);
|
|
return () => clearInterval(timer);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!userSelectedProduct) {
|
|
setFormData((prev) => ({ ...prev, product: slides[currentSlide].productValue }));
|
|
}
|
|
}, [currentSlide, userSelectedProduct]);
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
|
const { name, value } = e.target;
|
|
if (name === "product") {
|
|
setUserSelectedProduct(true);
|
|
}
|
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
|
};
|
|
|
|
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.phone.trim()) errors.phone = "Phone is required.";
|
|
if (!formData.email.trim()) errors.email = "Email is required.";
|
|
if (!formData.product.trim()) errors.product = "Please select a product.";
|
|
if (!captchaToken) errors.captcha = "Please verify the CAPTCHA.";
|
|
|
|
setFormErrors(errors);
|
|
if (Object.keys(errors).length > 0) return;
|
|
|
|
const emailData = {
|
|
name: formData.name,
|
|
email: formData.email,
|
|
phone: formData.phone,
|
|
message: `
|
|
<b>Company:</b> ${formData.company}<br />
|
|
<b>Product:</b> ${formData.product}<br />
|
|
<b>Job Site City:</b> ${formData.city}<br />
|
|
<b>Quantity:</b> ${formData.quantity}
|
|
`,
|
|
to: "info@vgfenceproducts.com",
|
|
senderName: "VG Fence Hero Form",
|
|
recaptchaToken: captchaToken,
|
|
};
|
|
|
|
setAlert({ show: true, type: "info", message: "Sending your request..." });
|
|
|
|
try {
|
|
await axios.post("https://mailserver.metatronnest.com/send", emailData, {
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
|
|
setAlert({
|
|
show: true,
|
|
type: "success",
|
|
message: "Thank you! Your quote request has been sent successfully.",
|
|
});
|
|
|
|
setFormData({
|
|
company: "",
|
|
name: "",
|
|
phone: "",
|
|
email: "",
|
|
product: "",
|
|
city: "",
|
|
quantity: ""
|
|
});
|
|
setCaptchaToken(null);
|
|
setFormErrors({});
|
|
} catch (error) {
|
|
console.error("❌ Error sending email:", error);
|
|
setAlert({
|
|
show: true,
|
|
type: "danger",
|
|
message: "Failed to send request. Please try again later.",
|
|
});
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (alert.show && alert.type !== "info") {
|
|
const timer = setTimeout(() => {
|
|
setAlert((prev) => ({ ...prev, show: false }));
|
|
}, 5000);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [alert.show, alert.type]);
|
|
|
|
return (
|
|
<section className="hero" id="home">
|
|
<div className="hero-grid-bg"></div>
|
|
<div className="hero-fence-deco" aria-hidden="true">
|
|
<div className="hero-fence-rail" style={{ top: '32%' }}></div>
|
|
<div className="hero-fence-rail" style={{ top: '67%' }}></div>
|
|
<div className="hero-fence-post" style={{ left: '5%' }}></div>
|
|
<div className="hero-fence-post" style={{ left: '16%' }}></div>
|
|
<div className="hero-fence-post" style={{ left: '27%' }}></div>
|
|
<div className="hero-fence-post" style={{ left: '38%' }}></div>
|
|
<div className="hero-fence-post" style={{ left: '49%' }}></div>
|
|
<div className="hero-fence-post" style={{ left: '60%' }}></div>
|
|
<div className="hero-fence-post" style={{ left: '71%' }}></div>
|
|
<div className="hero-fence-post" style={{ left: '82%' }}></div>
|
|
<div className="hero-fence-post" style={{ left: '93%' }}></div>
|
|
</div>
|
|
<div className="hero-accent"></div>
|
|
<div className="hero-accent2"></div>
|
|
|
|
<div className="hero-left">
|
|
<div key={currentSlide} style={{ animation: "fadeUp 0.6s ease-out forwards" }}>
|
|
<div className="hero-eyebrow">{slides[currentSlide].eyebrow}</div>
|
|
<h1 className="hero-h1">{slides[currentSlide].title}</h1>
|
|
<p className="hero-sub">{slides[currentSlide].sub}</p>
|
|
<div className="hero-btns">
|
|
<Link href="/contact" className="btn-primary">Request contractor pricing</Link>
|
|
<Link href="#products" className="btn-secondary">View all products</Link>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="hero-slider-dots" style={{ display: 'flex', gap: '8px', marginTop: '10px' }}>
|
|
{slides.map((_, i) => (
|
|
<button
|
|
type="button"
|
|
key={i}
|
|
onClick={() => setCurrentSlide(i)}
|
|
className={`hero-slider-dot ${currentSlide === i ? 'active' : ''}`}
|
|
aria-label={`Go to slide ${i + 1}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="hero-right" id="quote">
|
|
<div className="quote-card">
|
|
<div className="qc-title">Request a quote</div>
|
|
<div className="qc-sub">Response within 2 business hours · Contractor pricing available</div>
|
|
|
|
{alert.show && (
|
|
<div className={`hero-alert hero-alert-${alert.type === 'danger' ? 'danger' : (alert.type === 'info' ? 'info' : 'success')}`}>
|
|
{alert.message}
|
|
</div>
|
|
)}
|
|
|
|
<form className="quote-form" onSubmit={handleSubmit}>
|
|
<div className="frow">
|
|
<div>
|
|
<label className="fl">Company name</label>
|
|
<input className="fi" type="text" name="company" placeholder="ABC Fence Co." value={formData.company} onChange={handleChange} />
|
|
</div>
|
|
<div>
|
|
<label className="fl">Your name</label>
|
|
<input className={`fi ${formErrors.name ? 'border-danger' : ''}`} type="text" name="name" placeholder="John Smith" value={formData.name} onChange={handleChange} />
|
|
</div>
|
|
</div>
|
|
<div className="frow">
|
|
<div>
|
|
<label className="fl">Phone</label>
|
|
<input className={`fi ${formErrors.phone ? 'border-danger' : ''}`} type="tel" name="phone" placeholder="519-xxx-xxxx" value={formData.phone} onChange={handleChange} />
|
|
</div>
|
|
<div>
|
|
<label className="fl">Email</label>
|
|
<input className={`fi ${formErrors.email ? 'border-danger' : ''}`} type="email" name="email" placeholder="you@company.com" value={formData.email} onChange={handleChange} />
|
|
</div>
|
|
</div>
|
|
<label className="fl">Product needed</label>
|
|
<select className={`fi ${formErrors.product ? 'border-danger' : ''}`} name="product" value={formData.product} onChange={handleChange}>
|
|
<option value="">Select a product or service...</option>
|
|
<optgroup label="── Products ──">
|
|
<option>Aluminum railing</option>
|
|
<option>Chain link fence — commercial</option>
|
|
<option>Chain link fence — residential</option>
|
|
<option>Composite fence</option>
|
|
<option>Expert Stain & Seal products</option>
|
|
<option>Fence Armor (post caps / guards)</option>
|
|
<option>Glass railing</option>
|
|
<option>Ornamental / iron fence</option>
|
|
</optgroup>
|
|
<optgroup label="── Services ──">
|
|
<option>Temporary fence rental — construction</option>
|
|
<option>Temporary fence rental — event</option>
|
|
<option>Wood staining service</option>
|
|
<option>2D fence drawing services</option>
|
|
<option>Contractor account enquiry</option>
|
|
<option>Multiple products</option>
|
|
</optgroup>
|
|
</select>
|
|
<div className="frow">
|
|
<div>
|
|
<label className="fl">Job site city</label>
|
|
<input className="fi" type="text" name="city" placeholder="Kitchener, Guelph..." value={formData.city} onChange={handleChange} />
|
|
</div>
|
|
<div>
|
|
<label className="fl">Approx. quantity</label>
|
|
<input className="fi" type="text" name="quantity" placeholder="e.g. 200 linear ft" value={formData.quantity} onChange={handleChange} />
|
|
</div>
|
|
</div>
|
|
<div className="tc-row" id="tc-row-home">
|
|
<input type="checkbox" className="tc-check" id="tc-home" required />
|
|
<label className="tc-label" htmlFor="tc-home">
|
|
I have read and agree to VG Fence Products'
|
|
<a href="/legal#t1" target="_blank"> Terms & Conditions</a>,
|
|
<a href="/legal#t3" target="_blank"> Delivery Policy</a>, and
|
|
<a href="/legal#t7" target="_blank"> Photo Verification (24hr) Policy</a>.
|
|
I understand that damage claims require photos submitted within 24 hours of delivery.
|
|
</label>
|
|
</div>
|
|
|
|
<div className="form-group" style={{ marginTop: '16px' }}>
|
|
<ReCAPTCHA
|
|
sitekey="6LekfpwrAAAAAOTwuP1d2gg-Fv9UEsAjE2gjOQJl"
|
|
onChange={handleCaptchaChange}
|
|
/>
|
|
{formErrors.captcha && <small className="text-danger" style={{ fontSize: '11px', marginTop: '4px', display: 'block' }}>{formErrors.captcha}</small>}
|
|
</div>
|
|
|
|
<button type="submit" className="form-submit" disabled={alert.type === "info"}>
|
|
{alert.type === "info" ? "Sending..." : "Send quote request →"}
|
|
</button>
|
|
<div className="form-note">Or email us · info@vgfenceproducts.com</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|