update image in hero section

This commit is contained in:
2026-06-23 11:45:23 +05:30
parent 1b9ceb4a52
commit 9ea89c317f
11 changed files with 1155 additions and 630 deletions

View File

@@ -0,0 +1,252 @@
"use client";
import React, { useState } from "react";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
type FormData = {
name: string;
email: string;
message: string;
};
type FormErrors = {
name?: string;
email?: string;
message?: string;
};
export function ContactForm() {
const [formData, setFormData] = useState<FormData>({
name: "",
email: "",
message: "",
});
const [errors, setErrors] = useState<FormErrors>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [showToast, setShowToast] = useState(false);
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
// Clear error for this field as the user types
if (errors[name as keyof FormErrors]) {
setErrors((prev) => ({
...prev,
[name]: undefined,
}));
}
};
const validateForm = (): boolean => {
const tempErrors: FormErrors = {};
let isValid = true;
if (!formData.name.trim()) {
tempErrors.name = "Name is required";
isValid = false;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!formData.email.trim()) {
tempErrors.email = "Email is required";
isValid = false;
} else if (!emailRegex.test(formData.email)) {
tempErrors.email = "Please enter a valid email address";
isValid = false;
}
if (!formData.message.trim()) {
tempErrors.message = "Message is required";
isValid = false;
} else if (formData.message.trim().length < 10) {
tempErrors.message = "Message must be at least 10 characters long";
isValid = false;
}
setErrors(tempErrors);
return isValid;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) return;
setIsSubmitting(true);
// Simulate API Request delay
await new Promise((resolve) => setTimeout(resolve, 1200));
setIsSubmitting(false);
setShowToast(true);
setFormData({ name: "", email: "", message: "" });
// Auto-hide toast after 5 seconds
setTimeout(() => {
setShowToast(false);
}, 5000);
};
return (
<>
<div className="lg:col-span-7 bg-white rounded-3xl p-8 md:p-10 border border-outline-variant/25 shadow-xl relative overflow-hidden">
{/* Background Accent Mesh */}
<div className="absolute top-0 right-0 w-32 h-32 bg-primary/5 rounded-full blur-3xl" />
<div className="absolute bottom-0 left-0 w-32 h-32 bg-secondary/5 rounded-full blur-3xl" />
<div className="relative z-10">
<span className="text-label-sm font-black text-primary uppercase tracking-widest">
Write to us
</span>
<h2 className="font-display text-2xl md:text-3xl font-black text-brand-dark mt-2 mb-8">
Send Message
</h2>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Name field */}
<div className="flex flex-col">
<label htmlFor="field-name" className="text-label-sm text-on-surface-variant font-bold mb-2">
Your Name
</label>
<div className="relative">
<span className="absolute inset-y-0 left-0 flex items-center pl-4 text-on-surface-variant/40">
<MaterialIcon icon="person" size={20} />
</span>
<input
type="text"
id="field-name"
name="name"
value={formData.name}
onChange={handleInputChange}
placeholder="e.g. John Doe"
className={`w-full pl-11 pr-4 py-3.5 bg-surface rounded-xl border ${
errors.name ? "border-error" : "border-outline-variant/30"
} text-body-md text-on-surface placeholder:text-on-surface-variant/45 focus:outline-none focus:ring-2 ${
errors.name ? "focus:ring-error/20" : "focus:ring-primary/20"
} focus:border-primary transition-all`}
/>
</div>
{errors.name && (
<span className="text-body-sm text-error font-bold mt-1.5 flex items-center gap-1">
<MaterialIcon icon="error" size={14} />
{errors.name}
</span>
)}
</div>
{/* Email field */}
<div className="flex flex-col">
<label htmlFor="field-email" className="text-label-sm text-on-surface-variant font-bold mb-2">
Your Email
</label>
<div className="relative">
<span className="absolute inset-y-0 left-0 flex items-center pl-4 text-on-surface-variant/40">
<MaterialIcon icon="email" size={20} />
</span>
<input
type="email"
id="field-email"
name="email"
value={formData.email}
onChange={handleInputChange}
placeholder="e.g. john@example.com"
className={`w-full pl-11 pr-4 py-3.5 bg-surface rounded-xl border ${
errors.email ? "border-error" : "border-outline-variant/30"
} text-body-md text-on-surface placeholder:text-on-surface-variant/45 focus:outline-none focus:ring-2 ${
errors.email ? "focus:ring-error/20" : "focus:ring-primary/20"
} focus:border-primary transition-all`}
/>
</div>
{errors.email && (
<span className="text-body-sm text-error font-bold mt-1.5 flex items-center gap-1">
<MaterialIcon icon="error" size={14} />
{errors.email}
</span>
)}
</div>
</div>
{/* Message field */}
<div className="flex flex-col">
<label htmlFor="field-message" className="text-label-sm text-on-surface-variant font-bold mb-2">
Message
</label>
<div className="relative">
<span className="absolute top-3.5 left-4 text-on-surface-variant/40">
<MaterialIcon icon="chat_bubble" size={20} />
</span>
<textarea
id="field-message"
name="message"
rows={5}
value={formData.message}
onChange={handleInputChange}
placeholder="Describe your inquiry in detail (e.g. partnership options, tech support, etc.)"
className={`w-full pl-11 pr-4 py-3.5 bg-surface rounded-xl border ${
errors.message ? "border-error" : "border-outline-variant/30"
} text-body-md text-on-surface placeholder:text-on-surface-variant/45 focus:outline-none focus:ring-2 ${
errors.message ? "focus:ring-error/20" : "focus:ring-primary/20"
} focus:border-primary transition-all resize-none`}
/>
</div>
{errors.message && (
<span className="text-body-sm text-error font-bold mt-1.5 flex items-center gap-1">
<MaterialIcon icon="error" size={14} />
{errors.message}
</span>
)}
</div>
{/* Submit button */}
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-primary hover:bg-primary-container disabled:bg-primary-container/60 text-on-primary font-black px-8 py-4 rounded-xl text-label-md hover:shadow-lg transition-all duration-300 hover:-translate-y-0.5 active:translate-y-0 active:scale-[0.98] flex items-center justify-center gap-2 cursor-pointer"
>
{isSubmitting ? (
<>
<span className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
<span>Sending Message...</span>
</>
) : (
<>
<MaterialIcon icon="send" size={18} />
<span>Submit Message</span>
</>
)}
</button>
</form>
</div>
</div>
{/* ─── Success Toast Notification ─── */}
{showToast && (
<div className="fixed bottom-8 right-8 z-50 animate-bounce-subtle max-w-sm w-full bg-brand-dark-surface border border-brand-accent/30 rounded-2xl shadow-2xl p-4 flex gap-4 items-start">
<div className="w-10 h-10 rounded-xl bg-secondary text-white flex items-center justify-center shrink-0">
<MaterialIcon icon="check_circle" size={24} />
</div>
<div className="flex-1">
<h4 className="text-body-md font-black text-white">Message Sent!</h4>
<p className="text-body-sm text-white/70 mt-1 leading-normal">
Thank you for reaching out. We will get back to you within 24 hours.
</p>
</div>
<button
onClick={() => setShowToast(false)}
className="text-white/40 hover:text-white transition-colors cursor-pointer shrink-0"
aria-label="Close Toast"
>
<MaterialIcon icon="close" size={18} />
</button>
</div>
)}
</>
);
}

View File

@@ -116,7 +116,7 @@ export function Hero() {
Express Services
</span>
<h1 className="text-5xl lg:text-[92px] lg:leading-[0.95] font-display font-black tracking-tight mb-8">
<h1 className="text-4xl sm:text-5xl lg:text-[64px] xl:text-[78px] 2xl:text-[92px] lg:leading-[0.95] font-display font-black tracking-tight mb-8">
Express
<br />
<span className="text-brand-accent drop-shadow-md">

View File

@@ -122,9 +122,9 @@ export function Footer() {
};
return (
<footer className="relative z-10 bg-brand-dark text-white border-t border-white/5 pt-24 pb-6 w-full mt-24">
<footer className="footer relative z-10 bg-brand-dark text-white border-t border-white/5 w-full mt-24">
{/* Overlapping Contact Helpdesk Card */}
<div className="absolute -top-20 md:-top-14 left-0 right-0 z-20 max-w-[1380px] mx-auto px-6 md:px-12 lg:px-20 xl:px-24">
<div className="footer-helpdesk-wrap">
<div className="bg-brand-accent p-6 md:p-8 rounded-[2.5rem] flex flex-col md:flex-row justify-between items-center gap-6 shadow-2xl border border-brand-accent/20">
<div className="flex items-center gap-4 text-brand-dark">
<div className="w-12 h-12 rounded-full bg-brand-dark text-brand-accent flex items-center justify-center shadow-lg">
@@ -132,14 +132,14 @@ export function Footer() {
</div>
<div>
<h4 className="text-headline-md font-black">24/7 Neighborhood Helpdesk</h4>
<p className="text-[11px] font-bold text-brand-dark/80 mt-0.5">
<p className="helpdesk-subtitle mt-0.5">
Need immediate assistance with an order or local service?
</p>
</div>
</div>
<a
href="tel:+918049123456"
className="flex items-center gap-3 bg-brand-dark hover:bg-brand-dark/90 text-brand-accent font-black px-8 py-3.5 rounded-full text-label-md shadow-2xl active:scale-95 duration-200 transition-all"
className="helpdesk-btn"
>
<MaterialIcon icon="phone" size={20} className="animate-pulse-subtle" />
<span>+91 80 4912 3456</span>
@@ -147,196 +147,196 @@ export function Footer() {
</div>
</div>
<div className="max-w-[1380px] mx-auto px-6 md:px-12 lg:px-20 xl:px-24">
{/* Main content grid: aligned on the same top baseline */}
<div className={`grid grid-cols-1 md:grid-cols-2 lg:items-start gap-y-10 gap-x-6 lg:gap-x-12 mb-12 ${
isContactPage
? "lg:grid-cols-[220px_170px_190px_150px]"
: "lg:grid-cols-[220px_170px_190px_150px_minmax(360px,480px)]"
}`}>
{/* Brand Column */}
<div className="col-span-1 md:col-span-2 lg:col-span-1">
<Link
href="/"
className="hover:scale-[1.02] transition-transform active:scale-95 duration-200 mb-4 inline-block"
>
<Image
src="/logo-v2.png"
alt="Nearle"
width={220}
height={36}
style={{ height: "auto" }}
className="h-9 w-auto object-contain"
/>
</Link>
<p className="text-body-sm text-white/60 max-w-xs mb-4 leading-relaxed">
India&apos;s leading hyperlocal neighborhood network, empowering communities and verified local businesses within 2km.
</p>
{/* Social Links */}
<div className="flex gap-4">
<a
href="#"
className="w-10 h-10 rounded-full bg-white/5 border border-white/10 flex items-center justify-center hover:bg-brand-accent hover:text-brand-dark hover:scale-105 transition-all"
aria-label="Share"
<div className="footer-container">
{/* Main content wrapper */}
<div className="footer-main">
{/* Main content grid: aligned on the same top baseline */}
<div className={`${
isContactPage
? "footer-grid-contact-page"
: "footer-grid"
}`}>
{/* Brand Column */}
<div className="footer-brand">
<Link
href="/"
className="mb-4 inline-block"
>
<MaterialIcon icon="share" size={18} />
</a>
<a
href="#"
className="w-10 h-10 rounded-full bg-white/5 border border-white/10 flex items-center justify-center hover:bg-brand-accent hover:text-brand-dark hover:scale-105 transition-all"
aria-label="Website"
>
<MaterialIcon icon="public" size={18} />
</a>
</div>
</div>
{/* Company Links */}
<div className="col-span-1">
<FooterCol title="Company" links={COMPANY} />
</div>
{/* Partnerships Links */}
<div className="col-span-1">
<FooterCol title="Partnerships" links={PARTNERSHIPS} />
</div>
{/* Legal Links */}
<div className="col-span-1">
<FooterCol title="Legal" links={LEGAL} />
</div>
{/* Contact Form Card */}
{!isContactPage && (
<div className="col-span-1 md:col-span-2 lg:col-span-1 bg-white text-brand-dark rounded-3xl p-5 md:p-6 shadow-2xl border border-outline-variant/15 relative overflow-hidden w-full lg:max-w-none lg:-translate-x-16 lg:translate-y-2">
{/* Background Glow Decors */}
<div className="absolute top-0 right-0 w-24 h-24 bg-primary/5 rounded-full blur-2xl" />
<div className="absolute bottom-0 left-0 w-24 h-24 bg-secondary/5 rounded-full blur-2xl" />
<div className="relative z-10 text-center lg:text-left">
<h3 className="text-xl font-display font-black text-brand-dark tracking-tight">
Send us a Message
</h3>
<p className="text-[11px] text-on-surface-variant mt-1 mb-6">
We shall guide you through our Neighbourhood Experts.
</p>
<form onSubmit={handleSubmit} className="space-y-4">
{submitSuccess && (
<div className="p-3 bg-secondary/10 border border-secondary/20 text-secondary text-body-sm font-bold rounded-xl flex items-center justify-center gap-1.5 animate-bounce-subtle">
<MaterialIcon icon="check_circle" size={16} />
<span>Message sent successfully!</span>
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2 gap-4">
<div className="flex flex-col text-left">
<input
type="text"
name="name"
value={formData.name}
onChange={handleInputChange}
placeholder="Your Name..."
className={`w-full bg-surface border ${
errors.name ? "border-error focus:ring-error/20" : "border-outline-variant/30 focus:ring-primary/20"
} text-body-sm text-on-surface placeholder:text-on-surface-variant/45 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:border-primary transition-all`}
/>
{errors.name && (
<span className="text-[9px] text-error font-bold mt-1 pl-1 flex items-center gap-0.5">
<MaterialIcon icon="error" size={10} />
{errors.name}
</span>
)}
</div>
<div className="flex flex-col text-left">
<input
type="text"
name="phone"
value={formData.phone}
onChange={handleInputChange}
placeholder="Your Phone..."
className={`w-full bg-surface border ${
errors.phone ? "border-error focus:ring-error/20" : "border-outline-variant/30 focus:ring-primary/20"
} text-body-sm text-on-surface placeholder:text-on-surface-variant/45 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:border-primary transition-all`}
/>
{errors.phone && (
<span className="text-[9px] text-error font-bold mt-1 pl-1 flex items-center gap-0.5">
<MaterialIcon icon="error" size={10} />
{errors.phone}
</span>
)}
</div>
</div>
<div className="flex flex-col text-left">
<input
type="email"
name="email"
value={formData.email}
onChange={handleInputChange}
placeholder="Your Email Address..."
className={`w-full bg-surface border ${
errors.email ? "border-error focus:ring-error/20" : "border-outline-variant/30 focus:ring-primary/20"
} text-body-sm text-on-surface placeholder:text-on-surface-variant/45 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:border-primary transition-all`}
/>
{errors.email && (
<span className="text-[9px] text-error font-bold mt-1 pl-1 flex items-center gap-0.5">
<MaterialIcon icon="error" size={10} />
{errors.email}
</span>
)}
</div>
<div className="flex flex-col text-left">
<textarea
name="message"
value={formData.message}
onChange={handleInputChange}
placeholder="Your Message..."
rows={3}
className={`w-full bg-surface border ${
errors.message ? "border-error focus:ring-error/20" : "border-outline-variant/30 focus:ring-primary/20"
} text-body-sm text-on-surface placeholder:text-on-surface-variant/45 rounded-xl px-4 py-2.5 focus:outline-none focus:ring-2 focus:border-primary transition-all resize-none`}
/>
{errors.message && (
<span className="text-[9px] text-error font-bold mt-1 pl-1 flex items-center gap-0.5">
<MaterialIcon icon="error" size={10} />
{errors.message}
</span>
)}
</div>
<button
type="submit"
disabled={isSubmitting}
className="w-full bg-primary hover:bg-primary-container disabled:bg-primary/50 text-on-primary font-black px-6 py-3 rounded-xl text-label-md shadow-md active:scale-95 duration-200 transition-all flex items-center justify-center gap-2 cursor-pointer"
>
{isSubmitting ? (
<>
<span className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
<span>SUBMITTING...</span>
</>
) : (
<span>SUBMIT</span>
)}
</button>
</form>
<Image
src="/logo-v2.png"
alt="Nearle"
width={220}
height={36}
style={{ width: "210px", height: "auto" }}
className="object-contain"
/>
</Link>
<p className="footer-desc mb-4">
India&apos;s leading hyperlocal neighborhood network, empowering communities and verified local businesses within 2km.
</p>
{/* Social Links */}
<div className="flex gap-4">
<a
href="#"
className="footer-social-link"
aria-label="Share"
>
<MaterialIcon icon="share" size={24} />
</a>
<a
href="#"
className="footer-social-link"
aria-label="Website"
>
<MaterialIcon icon="public" size={24} />
</a>
</div>
</div>
)}
{/* Company Links */}
<div className="footer-company">
<FooterCol title="Company" links={COMPANY} />
</div>
{/* Partnerships Links */}
<div className="footer-partnerships">
<FooterCol title="Partnerships" links={PARTNERSHIPS} />
</div>
{/* Legal Links */}
<div className="footer-legal">
<FooterCol title="Legal" links={LEGAL} />
</div>
{/* Contact Form Card */}
{!isContactPage && (
<div className="footer-contact-wrap">
<div className="footer-contact-card bg-white text-brand-dark shadow-2xl border border-outline-variant/15 relative overflow-hidden">
{/* Background Glow Decors */}
<div className="absolute top-0 right-0 w-24 h-24 bg-primary/5 rounded-full blur-2xl" />
<div className="absolute bottom-0 left-0 w-24 h-24 bg-secondary/5 rounded-full blur-2xl" />
<div className="relative z-10 text-center lg:text-left">
<h3 className="footer-contact-title font-display text-brand-dark tracking-tight">
Send us a Message
</h3>
<p className="footer-contact-subtitle">
We shall guide you through our Neighbourhood Experts.
</p>
<form onSubmit={handleSubmit} className="space-y-4">
{submitSuccess && (
<div className="p-3 bg-secondary/10 border border-secondary/20 text-secondary text-body-sm font-bold rounded-xl flex items-center justify-center gap-1.5 animate-bounce-subtle">
<MaterialIcon icon="check_circle" size={16} />
<span>Message sent successfully!</span>
</div>
)}
<div className="footer-form-row">
<div className="flex flex-col text-left">
<input
type="text"
name="name"
value={formData.name}
onChange={handleInputChange}
placeholder="Your Name..."
className={`footer-input ${errors.name ? "has-error" : ""}`}
/>
{errors.name && (
<span className="footer-error-text">
<MaterialIcon icon="error" size={16} />
{errors.name}
</span>
)}
</div>
<div className="flex flex-col text-left">
<input
type="text"
name="phone"
value={formData.phone}
onChange={handleInputChange}
placeholder="Your Phone..."
className={`footer-input ${errors.phone ? "has-error" : ""}`}
/>
{errors.phone && (
<span className="footer-error-text">
<MaterialIcon icon="error" size={16} />
{errors.phone}
</span>
)}
</div>
</div>
<div className="flex flex-col text-left">
<input
type="email"
name="email"
value={formData.email}
onChange={handleInputChange}
placeholder="Your Email Address..."
className={`footer-input ${errors.email ? "has-error" : ""}`}
/>
{errors.email && (
<span className="footer-error-text">
<MaterialIcon icon="error" size={16} />
{errors.email}
</span>
)}
</div>
<div className="flex flex-col text-left">
<textarea
name="message"
value={formData.message}
onChange={handleInputChange}
placeholder="Your Message..."
className={`footer-textarea ${errors.message ? "has-error" : ""}`}
/>
{errors.message && (
<span className="footer-error-text">
<MaterialIcon icon="error" size={16} />
{errors.message}
</span>
)}
</div>
<button
type="submit"
disabled={isSubmitting}
className="footer-submit-btn"
>
{isSubmitting ? (
<>
<span className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
<span>SUBMITTING...</span>
</>
) : (
<span>SUBMIT</span>
)}
</button>
</form>
</div>
</div>
</div>
)}
</div>
</div>
{/* Divider */}
<hr className="footer-divider" />
{/* Bottom Bar */}
<div className="border-t border-white/10 pt-7 flex flex-col md:flex-row justify-between items-center gap-4">
<p className="text-body-sm text-white/40 lg:pl-20">
© {new Date().getFullYear()} Nearle. All rights reserved.
</p>
<div className="flex items-center gap-2 text-white/40 text-body-sm">
<span>Designed for neighborhoods in</span>
<span className="flex items-center gap-1 font-bold text-white/80">
India 🇮🇳
</span>
<div className="footer-bottom">
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
<p className="footer-bottom-text">
© {new Date().getFullYear()} Nearle. All rights reserved.
</p>
<div className="flex items-center gap-2 footer-bottom-text">
<span>Designed for neighborhoods in</span>
<span className="flex items-center gap-1 font-bold text-white/80">
India 🇮🇳
</span>
</div>
</div>
</div>
</div>
@@ -344,6 +344,10 @@ export function Footer() {
);
}
// Ensure default export is also available for Next.js app router client components if needed
export default Footer;
function FooterCol({
title,
links,
@@ -353,15 +357,15 @@ function FooterCol({
}) {
return (
<div>
<h5 className="text-label-sm font-black text-brand-accent uppercase tracking-widest mb-6">
<h5 className="footer-column-title">
{title}
</h5>
<ul className="space-y-3">
<ul className="footer-links-list">
{links.map((l) => (
<li key={l.label}>
<Link
href={l.href}
className="text-body-sm text-white/60 hover:text-brand-accent hover:translate-x-1.5 transition-all duration-300 inline-block"
className="footer-link inline-block"
>
{l.label}
</Link>