Seed: real-estate template (18 files)
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Bed, Bath, Maximize, Car, MapPin, Home, Check, Phone, Mail,
|
||||
Calendar, ChevronLeft, ZoomIn, Heart
|
||||
} from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import Header from "@/components/Header";
|
||||
import Footer from "@/components/Footer";
|
||||
import { useSaasMutation } from "@/hooks/use-tygarun";
|
||||
|
||||
const features = [
|
||||
"Hardwood Floors", "Central Air Conditioning", "Gourmet Kitchen", "Walk-in Closets",
|
||||
"Smart Home System", "Wine Cellar", "Home Theater", "Heated Pool",
|
||||
"3-Car Garage", "Landscaped Gardens", "Security System", "Solar Panels",
|
||||
];
|
||||
|
||||
const similar = [
|
||||
{ name: "Sunset Terrace Villa", price: "$3,200,000", beds: "3", baths: "2", sqft: "2,100", gradient: "from-emerald-800/40 to-stone-700/40" },
|
||||
{ name: "Palm Canyon Retreat", price: "$4,100,000", beds: "4", baths: "3", sqft: "3,400", gradient: "from-stone-700/40 to-amber-800/40" },
|
||||
{ name: "Brentwood Modern", price: "$5,500,000", beds: "5", baths: "4", sqft: "4,800", gradient: "from-amber-800/40 to-emerald-900/40" },
|
||||
];
|
||||
|
||||
export default function PropertyDetailPage() {
|
||||
// toast from sonner (imported above)
|
||||
const [mainImg, setMainImg] = useState(0);
|
||||
const [viewingOpen, setViewingOpen] = useState(false);
|
||||
const { mutate: bookViewing, loading: booking } = useSaasMutation("scheduling", "/bookings");
|
||||
|
||||
const handleBookViewing = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const fd = new FormData(e.currentTarget);
|
||||
try {
|
||||
await bookViewing({
|
||||
name: fd.get("name"),
|
||||
email: fd.get("email"),
|
||||
date: fd.get("date"),
|
||||
property: "The Belmont Estate",
|
||||
});
|
||||
toast.success("Viewing booked! An agent will confirm by email.");
|
||||
setViewingOpen(false);
|
||||
e.currentTarget.reset();
|
||||
} catch {
|
||||
toast.error("Could not book viewing. Please try again.");
|
||||
}
|
||||
};
|
||||
const thumbGrads = [
|
||||
"from-emerald-800/40 to-stone-700/40",
|
||||
"from-stone-700/40 to-amber-800/40",
|
||||
"from-amber-800/40 to-emerald-900/40",
|
||||
"from-emerald-900/40 to-stone-800/40",
|
||||
"from-stone-800/40 to-emerald-800/40",
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background text-foreground antialiased">
|
||||
<Header />
|
||||
|
||||
<section className="pt-28 pb-16 bg-background">
|
||||
<div className="mx-auto max-w-7xl px-6">
|
||||
{/* Back */}
|
||||
<Link to="/properties" className="inline-flex items-center gap-2 text-sm text-stone-500 hover:text-emerald-600 transition-colors mb-6">
|
||||
<ChevronLeft className="h-4 w-4" />Back to Properties
|
||||
</Link>
|
||||
|
||||
{/* ── IMAGE GALLERY ── */}
|
||||
<div className="grid lg:grid-cols-[1fr_auto] gap-4 mb-12 animate-fade-up">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<div className={`relative h-[400px] lg:h-[500px] bg-gradient-to-br \${thumbGrads[mainImg]} rounded-2xl flex items-center justify-center cursor-pointer group overflow-hidden`}>
|
||||
<Home className="h-24 w-24 text-white/20" />
|
||||
<div className="absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors flex items-center justify-center">
|
||||
<ZoomIn className="h-8 w-8 text-white opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</div>
|
||||
<Badge className="absolute top-4 left-4 bg-emerald-600 text-white border-0">For Sale</Badge>
|
||||
<button className="absolute top-4 right-4 p-2 rounded-full bg-white/10 backdrop-blur-sm text-white/60 hover:text-red-400 transition-colors">
|
||||
<Heart className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-4xl p-0 bg-transparent border-0">
|
||||
<div className={`h-[70vh] bg-gradient-to-br \${thumbGrads[mainImg]} rounded-2xl flex items-center justify-center`}>
|
||||
<Home className="h-32 w-32 text-white/20" />
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<div className="flex lg:flex-col gap-3">
|
||||
{thumbGrads.map((g, i) => (
|
||||
<button key={i} onClick={() => setMainImg(i)} className={`w-20 h-20 lg:w-24 lg:h-24 rounded-xl bg-gradient-to-br \${g} flex items-center justify-center border-2 transition-all \${mainImg === i ? "border-emerald-500 scale-105" : "border-transparent opacity-70 hover:opacity-100"}`}>
|
||||
<Home className="h-6 w-6 text-white/30" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-[1fr_380px] gap-12">
|
||||
{/* ── LEFT: Property Info ── */}
|
||||
<div className="animate-fade-up">
|
||||
<div className="flex flex-wrap items-start gap-4 mb-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-stone-900 dark:text-white">The Belmont Estate</h1>
|
||||
<div className="flex items-center gap-2 text-stone-500 dark:text-stone-400 mt-1"><MapPin className="h-4 w-4" />1240 Belmont Drive, Beverly Hills, CA 90210</div>
|
||||
</div>
|
||||
<Badge className="bg-emerald-600/10 text-emerald-600 border-emerald-600/30 ml-auto">Single Family Home</Badge>
|
||||
</div>
|
||||
|
||||
<div className="text-4xl font-bold text-emerald-600 mb-8">$4,750,000</div>
|
||||
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4 mb-8">
|
||||
{[
|
||||
{ icon: Bed, val: "6", label: "Bedrooms" },
|
||||
{ icon: Bath, val: "5", label: "Bathrooms" },
|
||||
{ icon: Maximize, val: "6,800", label: "Square Feet" },
|
||||
{ icon: Car, val: "3-Car", label: "Garage" },
|
||||
].map((s, i) => (
|
||||
<div key={i} className="p-4 rounded-xl bg-stone-50 dark:bg-stone-900/50 border border-stone-200 dark:border-stone-800 text-center">
|
||||
<s.icon className="h-5 w-5 text-emerald-600 mx-auto mb-2" />
|
||||
<div className="text-lg font-bold text-stone-900 dark:text-white">{s.val}</div>
|
||||
<div className="text-xs text-stone-500 dark:text-stone-400">{s.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-bold text-stone-900 dark:text-white mb-4">About This Property</h2>
|
||||
<p className="text-stone-600 dark:text-stone-400 leading-relaxed mb-4">Welcome to The Belmont Estate, a stunning architectural masterpiece nestled in the prestigious hills of Beverly Hills. This extraordinary residence offers an unparalleled blend of luxury, sophistication, and modern comfort across nearly 7,000 square feet of meticulously designed living space.</p>
|
||||
<p className="text-stone-600 dark:text-stone-400 leading-relaxed">The open-concept floor plan seamlessly connects the gourmet chef's kitchen, formal dining room, and expansive living areas, all bathed in natural light from floor-to-ceiling windows. Step outside to discover the resort-style backyard featuring a heated infinity pool, outdoor kitchen, and lush landscaped gardens with panoramic city views.</p>
|
||||
</div>
|
||||
|
||||
{/* Features */}
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-bold text-stone-900 dark:text-white mb-4">Features & Amenities</h2>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{features.map((f) => (
|
||||
<div key={f} className="flex items-center gap-2 text-sm text-stone-600 dark:text-stone-400">
|
||||
<Check className="h-4 w-4 text-emerald-500 shrink-0" />{f}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Map Placeholder */}
|
||||
<div className="mb-8">
|
||||
<h2 className="text-xl font-bold text-stone-900 dark:text-white mb-4">Location</h2>
|
||||
<div className="h-64 rounded-2xl bg-stone-100 dark:bg-stone-900 border border-stone-200 dark:border-stone-800 flex items-center justify-center">
|
||||
<div className="text-center text-stone-400">
|
||||
<MapPin className="h-8 w-8 mx-auto mb-2" />
|
||||
<p className="text-sm">Interactive Map</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── RIGHT: Agent Card ── */}
|
||||
<div className="animate-fade-up" style={{ animationDelay: "150ms" }}>
|
||||
<Card className="sticky top-28 bg-white dark:bg-stone-900/70 backdrop-blur-xl border-stone-200 dark:border-stone-800 rounded-2xl shadow-lg">
|
||||
<CardContent className="p-6">
|
||||
<div className="text-center mb-6">
|
||||
<div className="w-20 h-20 rounded-full bg-gradient-to-br from-emerald-400 to-emerald-700 flex items-center justify-center text-white text-2xl font-bold mx-auto mb-3">JR</div>
|
||||
<h3 className="text-lg font-bold text-stone-900 dark:text-white">Jennifer Rodriguez</h3>
|
||||
<p className="text-sm text-stone-500 dark:text-stone-400">Senior Listing Agent</p>
|
||||
</div>
|
||||
<div className="space-y-3 mb-6">
|
||||
<div className="flex items-center gap-3 text-sm text-stone-600 dark:text-stone-400">
|
||||
<Phone className="h-4 w-4 text-emerald-500" />+1 (310) 555-0147
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm text-stone-600 dark:text-stone-400">
|
||||
<Mail className="h-4 w-4 text-emerald-500" />jennifer@prestigerealty.com
|
||||
</div>
|
||||
</div>
|
||||
<Dialog open={viewingOpen} onOpenChange={setViewingOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="w-full bg-emerald-600 hover:bg-emerald-700 text-white rounded-full shadow-lg shadow-emerald-600/20 active:scale-[0.97] transition-all mb-3">
|
||||
<Calendar className="h-4 w-4 mr-2" />Schedule Viewing
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Book a Viewing</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleBookViewing} className="space-y-4">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="v-name">Full Name</Label>
|
||||
<Input id="v-name" name="name" required placeholder="Jane Doe" />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="v-email">Email</Label>
|
||||
<Input id="v-email" name="email" type="email" required placeholder="jane@example.com" />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="v-date">Preferred Date</Label>
|
||||
<Input id="v-date" name="date" type="date" required />
|
||||
</div>
|
||||
<Button type="submit" disabled={booking} className="w-full bg-emerald-600 hover:bg-emerald-700 text-white rounded-full">
|
||||
{booking ? "Booking..." : "Confirm Viewing"}
|
||||
</Button>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<Button variant="outline" className="w-full rounded-full border-stone-300 dark:border-stone-700">
|
||||
<Phone className="h-4 w-4 mr-2" />Call Agent
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── SIMILAR ── */}
|
||||
<div className="mt-20">
|
||||
<h2 className="text-2xl font-bold text-stone-900 dark:text-white mb-8">Similar Properties</h2>
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{similar.map((p, i) => (
|
||||
<Link key={i} to="/properties/1" className="group animate-fade-up" style={{ animationDelay: `\${i * 100}ms` }}>
|
||||
<Card className="overflow-hidden border-stone-200 dark:border-stone-800 bg-white dark:bg-stone-900/50 rounded-2xl shadow-md hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
|
||||
<div className={`h-44 bg-gradient-to-br \${p.gradient} flex items-center justify-center`}>
|
||||
<Home className="h-10 w-10 text-white/20" />
|
||||
</div>
|
||||
<CardContent className="p-4">
|
||||
<div className="text-xl font-bold text-emerald-600">{p.price}</div>
|
||||
<div className="font-semibold text-stone-800 dark:text-white mt-1">{p.name}</div>
|
||||
<div className="flex items-center gap-4 mt-2 text-sm text-stone-500 dark:text-stone-400">
|
||||
<span className="flex items-center gap-1"><Bed className="h-3.5 w-3.5" />{p.beds}</span>
|
||||
<span className="flex items-center gap-1"><Bath className="h-3.5 w-3.5" />{p.baths}</span>
|
||||
<span className="flex items-center gap-1"><Maximize className="h-3.5 w-3.5" />{p.sqft}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user