"use client"; import { useState, ChangeEvent, FormEvent, useEffect } from "react"; import Footer from "@/components/Footer"; import PropertyGallery from "@/components/PropertyGallery"; import PropertyNav from "@/components/PropertyNav"; import InnerBanner from "@/components/InnerBanner"; import { Property } from "@/data/properties"; import axios from "axios"; import { useCompare } from "@/context/CompareContext"; import dynamic from 'next/dynamic'; import Link from 'next/link'; import { motion } from "framer-motion"; const AnimateSection = ({ children, className, id, direction = "left" }: { children: React.ReactNode, className?: string, id?: string, direction?: "left" | "right" | "up" | "down" }) => { const variants = { hidden: { opacity: 0, scale: direction === "left" || direction === "right" ? 0.95 : 1, y: direction === "up" ? 30 : direction === "down" ? -30 : 0 }, visible: { opacity: 1, scale: 1, y: 0 } }; return ( {children} ); }; const getOverviewIcon = (type: string) => { switch (type) { case "land": return ( ); case "metro": return ( ); case "road": return ( ); case "openArea": return ( ); case "density": return ( ); case "clubhouse": return ( ); default: return null; } }; const ConnectivityMap = dynamic(() => import('./ConnectivityMap'), { ssr: false, loading: () =>
}); interface FormData { name: string; email: string; phone: string; message: string; } interface FormErrors { name?: string; email?: string; phone?: string; } const sections = [ { id: "overview", label: "Overview" }, { id: "pricing", label: "Pricing" }, { id: "connectivity", label: "Connectivity" }, { id: "master-plan", label: "Master Plan" }, { id: "floor-plans", label: "Floor Plans" }, { id: "amenities", label: "Amenities" }, // { id: "challenges", label: "Challenges" }, { id: "approvals", label: "Approvals" }, { id: "builder", label: "Builder" }, { id: "locality", label: "Locality" }, { id: "faq", label: "FAQ's" }, ]; export default function PropertyDetailClient({ property }: { property: Property }) { const [formData, setFormData] = useState({ name: "", email: "", phone: "", message: "", }); const [formErrors, setFormErrors] = useState({}); const [alert, setAlert] = useState<{ show: boolean; type: string; message: string }>({ show: false, type: "", message: "", }); const [isSubmitting, setIsSubmitting] = useState(false); const [isExpanded, setIsExpanded] = useState(false); const [isConnectivityExpanded, setIsConnectivityExpanded] = useState(false); const [activeMapTab, setActiveMapTab] = useState("Transit"); const [isMapEnabled, setIsMapEnabled] = useState(false); const [isLightboxOpen, setIsLightboxOpen] = useState(false); const [isAmenitiesModalOpen, setIsAmenitiesModalOpen] = useState(false); const [isAmenitiesLoading, setIsAmenitiesLoading] = useState(false); const [activeFaqTab, setActiveFaqTab] = useState('Services'); const [expandedFaq, setExpandedFaq] = useState(null); const { addToCompare, removeFromCompare, isInCompare } = useCompare(); const isCompared = isInCompare(property.id); const [isWishlisted, setIsWishlisted] = useState(false); const handleShare = async () => { if (navigator.share) { try { await navigator.share({ title: property.title, text: `Check out this property: ${property.title} in ${property.location}`, url: window.location.href, }); } catch (error) { console.log('Error sharing:', error); } } else { // Fallback to clipboard navigator.clipboard.writeText(window.location.href); setAlert({ show: true, type: 'success', message: 'Link copied to clipboard!' }); } }; const handleWishlist = () => { setIsWishlisted(!isWishlisted); setAlert({ show: true, type: 'success', message: !isWishlisted ? 'Added to your wishlist!' : 'Removed from your wishlist' }); }; const handlePrint = () => { window.print(); }; const handleCompareToggle = () => { if (isCompared) { removeFromCompare(property.id); setAlert({ show: true, type: 'success', message: 'Removed from compare list' }); } else { addToCompare(property); setAlert({ show: true, type: 'success', message: 'Added to compare list' }); } }; // Helper for icons const getAmenityIcon = (name: string) => { switch (name) { case "Gym - Indoor": return ; case "Gym - Outdoor": case "Running Track": return ; case "Kids Play Area": return ; case "Amphitheatre": return ; case "Cafe/Restaurant": return ; case "Badminton": return ; case "Basketball": return ; case "Cricket Ground": return ; case "Cricket Pitch": return ; default: return ; } }; const faqData = property.faq || {}; const floorPlans = property.floorPlans || []; // Initialize activeFloorPlan with the first plan's ID if available const [activeFloorPlan, setActiveFloorPlan] = useState(""); useEffect(() => { if (floorPlans.length > 0 && !activeFloorPlan) { setActiveFloorPlan(floorPlans[0].id); } }, [floorPlans, activeFloorPlan]); const activePlanDetails = floorPlans.find(plan => plan.id === activeFloorPlan) || (floorPlans.length > 0 ? floorPlans[0] : null); const handleSeeAllAmenities = () => { setIsAmenitiesLoading(true); setTimeout(() => { setIsAmenitiesLoading(false); setIsAmenitiesModalOpen(true); }, 500); }; const handleChange = (e: ChangeEvent) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); // Clear error when user types if (formErrors[name as keyof FormErrors]) { setFormErrors(prev => ({ ...prev, [name]: undefined })); } }; const handleSubmit = async (e: FormEvent) => { e.preventDefault(); const errors: FormErrors = {}; if (!formData.name.trim()) errors.name = "Name is required."; if (!formData.phone.trim()) errors.phone = "Phone number is required."; if (!formData.email.trim()) errors.email = "Email is required."; setFormErrors(errors); if (Object.keys(errors).length > 0) return; setIsSubmitting(true); const emailData = { name: formData.name, phone: formData.phone, email: formData.email, subject: `Property Inquiry: ${property?.title} from ${formData.name}`, message: `Property Inquiry

Property: ${property?.title}
Location: ${property?.location}
Price: ${property?.price}

Customer Details:
Name: ${formData.name}
Phone: ${formData.phone}
Email: ${formData.email}
Message: ${formData.message || 'N/A'}`, to: "hello@skyandsoil.com", senderName: "Sky and Soil Property Inquiry", }; try { const res = await axios.post("https://mailserver.metatronnest.com/send", emailData, { headers: { "Content-Type": "application/json" }, }); setAlert({ show: true, type: "success", message: res?.data?.message || "Request sent successfully! We will contact you soon.", }); setFormData({ name: "", email: "", phone: "", message: "" }); setFormErrors({}); } catch (error) { setAlert({ show: true, type: "danger", message: "Failed to send request. Please try again later.", }); } finally { setIsSubmitting(false); } }; useEffect(() => { if (alert.show) { const timer = setTimeout(() => { setAlert(prev => ({ ...prev, show: false })); }, 5000); return () => clearTimeout(timer); } }, [alert.show]); return (
{/* Sticky Navigation */}
{/* Image Gallery */}
{/* Main Content */}
{/* Property Header */}
{/* Title and Price Row */}
{/* Title */}

{property.title}

{/* Location and Price */}
{property.location}
{property.price}
{/* Stats Row with Icons */}
{/* Possession */}
Possession: {property.overview.possession}
{/* Area/Plot */}
{property.category === "Plots" ? "Plot" : property.overview.bhk} - {property.overview.size}
{/* Sky&Soil Score */}
Sky&Soil Score:
{[1, 2, 3, 4, 5].map((star) => { const score = property.skyAndSoilScore || 4; const isFilled = star <= Math.floor(score); const isHalf = !isFilled && star === Math.ceil(score) && score % 1 !== 0; return ( {isHalf ? ( <> ) : ( )} ); })}
{/* Overview Section */} {/* Header Row: Title Left, Badges Right, Subtitle Right */}
{/* Left Side: Title and Builder */}

Overview

{property.title} by {property.builder?.name || "Builder"}

{/* Right Side: Badges and Subtitle */}
{/* Badges Row */}
{property.overview.badges?.map((badge, index) => ( {badge} ))}
{/* Subtitle Text */}

Average is based on comparable projects in {property.locality?.name || "this area"}

{/* Info Grid - 2 rows x 3 columns */}
{property.overview.stats?.map((stat, index) => { const getBgColor = (index: number) => { const colors = [ "bg-green-50 dark:bg-green-900/10 border-green-100 dark:border-green-800/30 text-green-600 dark:text-green-400", "bg-blue-50 dark:bg-blue-900/10 border-blue-100 dark:border-blue-800/30 text-blue-600 dark:text-blue-400", "bg-blue-50 dark:bg-blue-900/10 border-blue-100 dark:border-blue-800/30 text-blue-600 dark:text-blue-400", "bg-red-50 dark:bg-red-900/10 border-red-100 dark:border-red-800/30 text-red-600 dark:text-red-400", "bg-blue-50 dark:bg-blue-900/10 border-blue-100 dark:border-blue-800/30 text-blue-600 dark:text-blue-400", "bg-blue-50 dark:bg-blue-900/10 border-blue-100 dark:border-blue-800/30 text-blue-600 dark:text-blue-400" ]; return colors[index % colors.length]; }; const bgColorClass = getBgColor(index).split(' ').slice(0, 2).join(' '); // Extract bg classes const borderColorClass = getBgColor(index).split(' ').slice(2, 4).join(' '); // Extract border classes const textColorClass = getBgColor(index).split(' ').slice(4).join(' '); // Extract text classes // Simplified color logic to avoid complex parsing, just use alternating or specific based on type if needed. // For now, let's use a simpler approach based on the original code let bgClass = "bg-blue-50 dark:bg-blue-900/10 border border-blue-100 dark:border-blue-800/30"; let textClass = "text-blue-600 dark:text-blue-400"; if (stat.icon === 'land') { bgClass = "bg-green-50 dark:bg-green-900/10 border border-green-100 dark:border-green-800/30"; textClass = "text-blue-600 dark:text-blue-400"; } else if (stat.icon === 'openArea') { bgClass = "bg-red-50 dark:bg-red-900/10 border border-red-100 dark:border-red-800/30"; textClass = "text-red-600 dark:text-red-400"; } return (
{getOverviewIcon(stat.icon)}
{stat.value}
{stat.label}
{stat.subtext}
); })}

{property.overview.description || property.connectivity?.description || `${property.title} is located in ${property.location}.`}

{!isConnectivityExpanded && (
)}
{/* Info Cards Grid */} {/* RERA Card */}

{property.title} is {property.reraInfo?.isApproved ? 'approved by RERA' : 'pending RERA approval'}

{property.reraInfo?.isApproved && `It was approved on ${property.reraInfo.approvalDate}`}

{/* Brochure Card */}

{property.brochureCard?.title || `See ${property.title} brochure`}

{property.brochureCard?.description || "Download the detailed brochure"}

{/* Pros & Cons Card */}

{property.prosConsCard?.title || "See the truth beyond the brochures"}

{property.prosConsCard?.description || "See every risk & potential clearly with our experts"}

{/* Pricing Section - Moved and Redesigned */}

Pricing

Last updated on {property.pricingUpdateDate || "Recently"}

Total Range
{property.pricingDetails?.totalRange || property.price}
Monthly EMI
{property.pricingDetails?.emi || "N/A"}
Project's Avg.
{property.pricingDetails?.projectAvg || "N/A"}
Market Avg.
{property.pricingDetails?.marketAvg || "N/A"}
{/* Connectivity Section (Formerly Location) */}

Connectivity

{property.title}

{property.connectivity?.description || `${property.title} is located in ${property.location}.`}

{!isConnectivityExpanded && (
)}
{/* Map Interface */}
{/* Master Plan Section */}

Master Plan

{property.masterPlan?.description || "Master plan details unavailable."}

{/* Master Plan Image */}
setIsLightboxOpen(true)} > Master Plan
Click to zoom
{/* Stats Grid */}
Total Units
{property.masterPlan?.totalUnits || "N/A"}
Water Source
{property.masterPlan?.waterSource || "N/A"} +1
Park Area
{property.masterPlan?.parkArea || "N/A"}
Land type
{property.masterPlan?.landType || "N/A"}
{/* Sky & Soil Clarity Engine Section */}

The Sky & Soil Clarity Engine

300+ families found safer homes with Sky & Soil. You could be next

{/* Card 1: Compare */}
{/* Decorative Circle */}
{/* House Icon Placeholder - Using SVG for 3D feel */}

Like this project?

Compare it with the rest

Compare 2 properties side by side on 40+ parameters like connectivity, layout, price, specs and more

{/* Card 2: Negotiate (Green) */}
{/* Trusted Badge */}
{property.skyandsoilClarity?.familiesHelped}
{/* Sparkles */}
{/* Trophy Icon */}

Pay less. Get more.

Let us negotiate for you.

Get a free Peace of Mind report, solid negotiations & a loyalty reward up to ₹3.29 Lakhs when you work with us

{/* Card 3: Report (Purple) */}
{/* Decorative Triangle */}
{/* Report Icon */}

Almost convinced?

See what we uncovered

Ideal if you've already visited and close to deciding. Our Peace of Mind report shows you what builders won't.

{/* Floor Plans Section */}

Floor Plans

{property.floorPlans && property.floorPlans.length > 0 ? `${property.title} has configurations ranging from ${property.floorPlans[0].area} to ${property.floorPlans[property.floorPlans.length - 1].area}.` : "Floor plans details coming soon."}

{/* Tabs */}
{floorPlans.map((plan) => ( ))}
{/* Active Plan Details Card */} {activePlanDetails && (

{activePlanDetails.id}

{activePlanDetails.price}
Saleable Area
{activePlanDetails.area}
Direction(s)
{activePlanDetails.direction}
)}
{/* Amenities Section */}

Amenities

{property.title}

{/* Lifestyle */}

Lifestyle Amenities

    {property.detailedAmenities?.Lifestyle?.map((item, idx) => (
  • {/* Icon rendering logic can be improved here if needed, for now assuming icons are handled or passed */} {getOverviewIcon(item.name.toLowerCase().replace(" ", "")) || ( )}
    {item.name}
  • ))}
{/* Sports */}

Sports Amenities

    {property.detailedAmenities?.Sports?.map((item, idx) => (
  • {/* Icon rendering logic can be improved here if needed, for now assuming icons are handled or passed */} {getOverviewIcon(item.name.toLowerCase().replace(" ", "")) || ( )}
    {item.name}
  • ))}
{/* Natural */}

Natural Amenities

    {property.detailedAmenities?.Natural?.map((item, idx) => (
  • {getOverviewIcon(item.name.toLowerCase().replace(" ", "")) || ( )}
    {item.name}
  • ))}
{/* Approvals Section */}

Approvals

{property.title} by Modern Spaaces has received 4 out of 4 important approvals as per RERA

{/* Approvals List - Two Column Grid */}
    {property.approvals?.map((approval, index) => (
  • {approval.name}
  • ))}
{/* Documents Subsection */}

Documents

Curated documents from various sources for {property.title} by {property.builder?.name || "Builder"}

{property.documents?.map((doc, index) => (

{doc.title}

{doc.description}

See Document
))}
{/* About the Builder Section */}

About the builder

{property.builder?.name || "Builder"}

{property.builder?.description}

See all properties by {property.builder?.name || "this builder"}

Established On

{property.builder?.establishedYear || "N/A"}

Completed Projects

{property.builder?.completedProjects || "N/A"}

{/* Locality Section */}

Locality

{property.locality?.name || "Bangalore"}

{/* Large Image */}
{`Map

{property.locality?.name || "Locality"}

{property.locality?.description}

{/* Gauges */}
{/* Developing */}

Developing

Living Experience

{/* Medium */}

Medium

Investment Potential

{/* Moderate */}

Moderate

Price Range

{/* Checkout Nearby Properties Section */}

Checkout Nearby Properties

See properties similar to {property.title} by {property.builder?.name || "Builder"} in the same neighbourhood

{/* Property 1 */}
Nearby property Prestige Raintree Park

Prestige Raintree Park

3.07 Crores - 5.67 Crores

Varthur

{/* Property 2 */}
Nearby property Adarsh Park Heights

Adarsh Park Heights

1.63 Crores - 2.32 Crores

Varthur

{/* Property 3 */}
Nearby property Tru Aquapolis

Tru Aquapolis

2.22 Crores - 3.51 Crores

Varthur

See all properties in Varthur
{/* FAQ Section */}

Frequently Asked Questions

99% of your queries should get answered here, for others, you can always talk to us

{/* Tabs */}
{Object.keys(faqData).map((tab) => ( ))}
{/* Accordion List */}
{faqData[activeFaqTab]?.map((item, index) => (
{expandedFaq === index && (
{item.answer}
)}
))}
{/* Sidebar */}
{/* Contact Form */}

Get in Touch

{alert.show && (
{alert.message}
)}
{formErrors.name && {formErrors.name}}
{formErrors.email && {formErrors.email}}
{formErrors.phone && {formErrors.phone}}