Merge version_3 into main #5
333
src/app/page.tsx
333
src/app/page.tsx
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Clock, Mail, MapPin, Phone } from "lucide-react";
|
||||
import { useState, useRef } from "react";
|
||||
import { Clock, Mail, MapPin, Phone, Plus, Minus } from "lucide-react";
|
||||
import { ThemeProvider } from "@/providers/themeProvider/ThemeProvider";
|
||||
import NavbarStyleApple from "@/components/navbar/NavbarStyleApple/NavbarStyleApple";
|
||||
import HeroBillboard from "@/components/sections/hero/HeroBillboard";
|
||||
@@ -12,7 +13,64 @@ import AboutMetric from "@/components/sections/about/AboutMetric";
|
||||
import ContactSplit from "@/components/sections/contact/ContactSplit";
|
||||
import FooterLogoEmphasis from "@/components/sections/footer/FooterLogoEmphasis";
|
||||
|
||||
interface PricingCalculatorState {
|
||||
serviceType: string;
|
||||
squareFootage: string;
|
||||
priceRange: { min: number; max: number } | null;
|
||||
}
|
||||
|
||||
export default function LandingPage() {
|
||||
const [calculator, setCalculator] = useState<PricingCalculatorState>({
|
||||
serviceType: "interior-painting", squareFootage: "", priceRange: null,
|
||||
});
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: "", phone: "", email: "", serviceType: "interior-painting", squareFootage: "", address: "", timeline: "asap", notes: ""});
|
||||
|
||||
const calculatorRef = useRef<HTMLDivElement>(null);
|
||||
const formRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const servicePrices: Record<string, { min: number; max: number }> = {
|
||||
"interior-painting": { min: 3.5, max: 5.5 },
|
||||
"exterior-painting": { min: 4.5, max: 7.0 },
|
||||
"flooring-installation": { min: 6.0, max: 12.0 },
|
||||
"small-renovations": { min: 5.0, max: 15.0 },
|
||||
};
|
||||
|
||||
const calculatePrice = () => {
|
||||
const sqft = parseFloat(calculator.squareFootage);
|
||||
if (!isNaN(sqft) && sqft > 0) {
|
||||
const prices = servicePrices[calculator.serviceType];
|
||||
if (prices) {
|
||||
const minPrice = sqft * prices.min;
|
||||
const maxPrice = sqft * prices.max;
|
||||
setCalculator(prev => ({
|
||||
...prev,
|
||||
priceRange: { min: Math.round(minPrice), max: Math.round(maxPrice) },
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToSection = (ref: React.RefObject<HTMLDivElement>) => {
|
||||
ref.current?.scrollIntoView({ behavior: "smooth" });
|
||||
};
|
||||
|
||||
const handleServiceCardCTA = (section: "calculator" | "form") => {
|
||||
if (section === "calculator") {
|
||||
scrollToSection(calculatorRef);
|
||||
} else {
|
||||
scrollToSection(formRef);
|
||||
}
|
||||
};
|
||||
|
||||
const serviceCardButtons = [
|
||||
{
|
||||
text: "Get Instant Estimate", onClick: () => handleServiceCardCTA("calculator"),
|
||||
},
|
||||
{ text: "Request a Quote", onClick: () => handleServiceCardCTA("form") },
|
||||
];
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="elastic-effect"
|
||||
@@ -133,7 +191,278 @@ export default function LandingPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="cta-section" data-section="cta-section">
|
||||
{/* Pricing Calculator Section */}
|
||||
<div
|
||||
id="calculator"
|
||||
ref={calculatorRef}
|
||||
data-section="calculator"
|
||||
className="py-20 px-4 md:px-8 bg-background/50"
|
||||
>
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-foreground mb-2 text-center">
|
||||
Instant Price Calculator
|
||||
</h2>
|
||||
<p className="text-foreground/70 text-center mb-12">
|
||||
Get an estimated price range for your project
|
||||
</p>
|
||||
|
||||
<div className="bg-card rounded-lg p-8 border border-accent/30">
|
||||
<div className="space-y-6">
|
||||
{/* Service Type Dropdown */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-foreground mb-3">
|
||||
Service Type
|
||||
</label>
|
||||
<select
|
||||
value={calculator.serviceType}
|
||||
onChange={(e) => {
|
||||
setCalculator((prev) => ({
|
||||
...prev,
|
||||
serviceType: e.target.value,
|
||||
priceRange: null,
|
||||
}));
|
||||
}}
|
||||
className="w-full bg-background border border-accent/50 rounded-md px-4 py-3 text-foreground focus:outline-none focus:border-primary-cta transition-colors"
|
||||
>
|
||||
<option value="interior-painting">Interior Painting</option>
|
||||
<option value="exterior-painting">Exterior Painting</option>
|
||||
<option value="flooring-installation">Flooring Installation</option>
|
||||
<option value="small-renovations">Small Renovations</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Square Footage Input */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-foreground mb-3">
|
||||
Square Footage
|
||||
</label>
|
||||
<div className="flex gap-3">
|
||||
<input
|
||||
type="number"
|
||||
value={calculator.squareFootage}
|
||||
onChange={(e) => {
|
||||
setCalculator((prev) => ({
|
||||
...prev,
|
||||
squareFootage: e.target.value,
|
||||
priceRange: null,
|
||||
}));
|
||||
}}
|
||||
placeholder="Enter square footage"
|
||||
className="flex-1 bg-background border border-accent/50 rounded-md px-4 py-3 text-foreground placeholder-foreground/40 focus:outline-none focus:border-primary-cta transition-colors"
|
||||
/>
|
||||
<button
|
||||
onClick={calculatePrice}
|
||||
className="bg-primary-cta text-primary-cta-text px-6 py-3 rounded-md font-semibold hover:opacity-90 transition-opacity"
|
||||
>
|
||||
Calculate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Price Display */}
|
||||
{calculator.priceRange && (
|
||||
<div className="bg-background/50 rounded-md p-6 border border-primary-cta/30">
|
||||
<p className="text-foreground/70 text-sm mb-2">Estimated Price Range</p>
|
||||
<p className="text-2xl md:text-3xl font-bold text-primary-cta">
|
||||
${calculator.priceRange.min.toLocaleString()} - $
|
||||
{calculator.priceRange.max.toLocaleString()}
|
||||
</p>
|
||||
<p className="text-foreground/60 text-sm mt-3">
|
||||
*This is an estimate. Final price may vary based on project complexity and site conditions.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quote Request Form Section */}
|
||||
<div
|
||||
id="form"
|
||||
ref={formRef}
|
||||
data-section="form"
|
||||
className="py-20 px-4 md:px-8 bg-background"
|
||||
>
|
||||
<div className="max-w-2xl mx-auto">
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-foreground mb-2 text-center">
|
||||
Request Your Free Quote
|
||||
</h2>
|
||||
<p className="text-foreground/70 text-center mb-12">
|
||||
Fill out the form below and our team will contact you within 24 hours
|
||||
</p>
|
||||
|
||||
<form className="bg-card rounded-lg p-8 border border-accent/30 space-y-6">
|
||||
{/* Name */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-foreground mb-2">
|
||||
Full Name *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.name}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, name: e.target.value })
|
||||
}
|
||||
placeholder="Your full name"
|
||||
required
|
||||
className="w-full bg-background border border-accent/50 rounded-md px-4 py-3 text-foreground placeholder-foreground/40 focus:outline-none focus:border-primary-cta transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Phone */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-foreground mb-2">
|
||||
Phone Number *
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
value={formData.phone}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, phone: e.target.value })
|
||||
}
|
||||
placeholder="(555) 123-4567"
|
||||
required
|
||||
className="w-full bg-background border border-accent/50 rounded-md px-4 py-3 text-foreground placeholder-foreground/40 focus:outline-none focus:border-primary-cta transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-foreground mb-2">
|
||||
Email Address *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={formData.email}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, email: e.target.value })
|
||||
}
|
||||
placeholder="your@email.com"
|
||||
required
|
||||
className="w-full bg-background border border-accent/50 rounded-md px-4 py-3 text-foreground placeholder-foreground/40 focus:outline-none focus:border-primary-cta transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Service Type */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-foreground mb-2">
|
||||
Service Type *
|
||||
</label>
|
||||
<select
|
||||
value={formData.serviceType}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, serviceType: e.target.value })
|
||||
}
|
||||
required
|
||||
className="w-full bg-background border border-accent/50 rounded-md px-4 py-3 text-foreground focus:outline-none focus:border-primary-cta transition-colors"
|
||||
>
|
||||
<option value="interior-painting">Interior Painting</option>
|
||||
<option value="exterior-painting">Exterior Painting</option>
|
||||
<option value="flooring-installation">Flooring Installation</option>
|
||||
<option value="small-renovations">Small Renovations</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Square Footage */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-foreground mb-2">
|
||||
Square Footage *
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.squareFootage}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, squareFootage: e.target.value })
|
||||
}
|
||||
placeholder="Enter square footage"
|
||||
required
|
||||
className="w-full bg-background border border-accent/50 rounded-md px-4 py-3 text-foreground placeholder-foreground/40 focus:outline-none focus:border-primary-cta transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Project Address */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-foreground mb-2">
|
||||
Project Address *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.address}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, address: e.target.value })
|
||||
}
|
||||
placeholder="123 Main St, City, Province"
|
||||
required
|
||||
className="w-full bg-background border border-accent/50 rounded-md px-4 py-3 text-foreground placeholder-foreground/40 focus:outline-none focus:border-primary-cta transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Preferred Timeline */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-foreground mb-2">
|
||||
Preferred Timeline *
|
||||
</label>
|
||||
<select
|
||||
value={formData.timeline}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, timeline: e.target.value })
|
||||
}
|
||||
required
|
||||
className="w-full bg-background border border-accent/50 rounded-md px-4 py-3 text-foreground focus:outline-none focus:border-primary-cta transition-colors"
|
||||
>
|
||||
<option value="asap">ASAP</option>
|
||||
<option value="1-2-weeks">1-2 weeks</option>
|
||||
<option value="1-month">1 month</option>
|
||||
<option value="flexible">Flexible</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Notes */}
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-foreground mb-2">
|
||||
Additional Notes (Optional)
|
||||
</label>
|
||||
<textarea
|
||||
value={formData.notes}
|
||||
onChange={(e) =>
|
||||
setFormData({ ...formData, notes: e.target.value })
|
||||
}
|
||||
placeholder="Tell us more about your project..."
|
||||
rows={4}
|
||||
className="w-full bg-background border border-accent/50 rounded-md px-4 py-3 text-foreground placeholder-foreground/40 focus:outline-none focus:border-primary-cta transition-colors resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Submit Button */}
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-primary-cta text-primary-cta-text font-semibold py-4 rounded-md hover:opacity-90 transition-opacity"
|
||||
>
|
||||
Request Quote
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sticky Mobile CTA */}
|
||||
<div className="fixed bottom-0 left-0 right-0 md:hidden bg-card border-t border-accent/30 p-4 flex gap-3 z-40">
|
||||
<button
|
||||
onClick={() => scrollToSection(calculatorRef)}
|
||||
className="flex-1 bg-primary-cta text-primary-cta-text font-semibold py-3 rounded-md hover:opacity-90 transition-opacity"
|
||||
>
|
||||
Estimate
|
||||
</button>
|
||||
<button
|
||||
onClick={() => window.location.href = "tel:(555)123-4567"}
|
||||
className="flex-1 bg-secondary-cta text-secondary-cta-text font-semibold py-3 rounded-md border border-primary-cta/30 hover:opacity-90 transition-opacity flex items-center justify-center gap-2"
|
||||
>
|
||||
<Phone size={18} />
|
||||
Call
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="cta-section" data-section="cta-section" className="mb-20 md:mb-0">
|
||||
<AboutMetric
|
||||
title="Ready to Transform Your Space?"
|
||||
metrics={[
|
||||
|
||||
Reference in New Issue
Block a user