Seed: travel template (16 files)
This commit is contained in:
@@ -0,0 +1,182 @@
|
|||||||
|
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 { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||||
|
import { Check, Star, Quote, ArrowLeft, X, ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { useSaasMutation } from "@/hooks/use-tygarun";
|
||||||
|
import Header from "@/components/Header";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
|
||||||
|
const gallery = [
|
||||||
|
{ gradient: "from-blue-600/50 to-cyan-400/30", label: "Scenic coastline" },
|
||||||
|
{ gradient: "from-emerald-600/50 to-teal-400/30", label: "Lush landscapes" },
|
||||||
|
{ gradient: "from-amber-600/50 to-orange-400/30", label: "Cultural heritage" },
|
||||||
|
{ gradient: "from-purple-600/50 to-violet-400/30", label: "Local cuisine" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const highlights = [
|
||||||
|
"Sacred temple visits with local guides", "Rice terrace trekking in Tegallalang", "Snorkeling in crystal-clear waters",
|
||||||
|
"Traditional Balinese cooking class", "Sunset at Tanah Lot Temple", "Spa and wellness retreat day",
|
||||||
|
];
|
||||||
|
|
||||||
|
const itinerary = [
|
||||||
|
{ day: "Day 1", title: "Arrival & Seminyak Beach", desc: "Arrive at Ngurah Rai Airport, private transfer to your luxury beachfront villa. Evening welcome dinner with traditional Balinese dance performance." },
|
||||||
|
{ day: "Day 2", title: "Ubud Cultural Exploration", desc: "Visit the Sacred Monkey Forest, explore Ubud Palace, walk through Tegallalang rice terraces, and enjoy a Balinese cooking class with a local family." },
|
||||||
|
{ day: "Day 3", title: "Temple Tour & Waterfalls", desc: "Discover the majestic Tirta Empul water temple, hike to Tegenungan waterfall, and experience a traditional purification ceremony." },
|
||||||
|
{ day: "Day 4", title: "Nusa Penida Island Adventure", desc: "Speed boat to Nusa Penida. Visit Kelingking Beach, snorkel with manta rays at Crystal Bay, and explore the stunning Angel Billabong natural pool." },
|
||||||
|
{ day: "Day 5", title: "Uluwatu & Farewell Dinner", desc: "Morning spa treatment, afternoon visit to Uluwatu Temple perched on dramatic sea cliffs. Farewell seafood dinner at Jimbaran Bay as the sun sets." },
|
||||||
|
];
|
||||||
|
|
||||||
|
const reviews = [
|
||||||
|
{ name: "Emma Rodriguez", text: "The itinerary was perfectly balanced between adventure and relaxation. Our guide was incredibly knowledgeable about local customs and hidden gems.", rating: 5 },
|
||||||
|
{ name: "David Chen", text: "Exceeded every expectation. The Nusa Penida day trip was the highlight of our trip. Swimming with manta rays was absolutely unforgettable.", rating: 5 },
|
||||||
|
{ name: "Lisa Andersson", text: "Great value for the price. Hotels were beautiful, food was amazing, and the whole trip was seamlessly organized from start to finish.", rating: 4 },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function DestinationDetailPage() {
|
||||||
|
const [lightbox, setLightbox] = useState<number | null>(null);
|
||||||
|
const { mutate: createBooking, loading: booking } = useSaasMutation("scheduling", "/bookings");
|
||||||
|
|
||||||
|
const handleBook = async () => {
|
||||||
|
try {
|
||||||
|
await createBooking({ destination: "Bali", price: "\$899", type: "trip" });
|
||||||
|
toast.success("Booking request submitted! Our team will contact you within 24 hours.");
|
||||||
|
} catch (err) {
|
||||||
|
toast.error("Could not submit booking. Please try again.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background text-foreground antialiased">
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
{/* ── HERO ── */}
|
||||||
|
<section className="relative pt-24 pb-16 bg-gradient-to-br from-slate-950 via-blue-950/40 to-cyan-950/30 overflow-hidden">
|
||||||
|
<div className="absolute inset-0 pointer-events-none">
|
||||||
|
<div className="absolute top-1/3 left-1/2 -translate-x-1/2 w-[600px] h-[600px] rounded-full bg-blue-500 opacity-10 blur-[160px]" />
|
||||||
|
</div>
|
||||||
|
<div className="relative z-10 mx-auto max-w-7xl px-6 pt-8">
|
||||||
|
<Button asChild variant="ghost" className="mb-6 text-slate-400 hover:text-white">
|
||||||
|
<Link to="/destinations"><ArrowLeft className="mr-2 h-4 w-4" /> Back to Destinations</Link>
|
||||||
|
</Button>
|
||||||
|
<Badge className="mb-3 bg-blue-600/20 text-blue-300 border-blue-700/30">Indonesia</Badge>
|
||||||
|
<h1 className="text-4xl md:text-5xl font-bold text-white animate-fade-up">Bali</h1>
|
||||||
|
<p className="mt-3 text-slate-400 max-w-2xl">Tropical paradise where ancient temples meet pristine beaches and vibrant culture.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── GALLERY ── */}
|
||||||
|
<section className="py-12 bg-slate-900">
|
||||||
|
<div className="mx-auto max-w-7xl px-6">
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
|
{gallery.map((g, i) => (
|
||||||
|
<button key={i} onClick={() => setLightbox(i)} className={`relative h-48 md:h-56 rounded-xl bg-gradient-to-br \${g.gradient} overflow-hidden group cursor-pointer`}>
|
||||||
|
<div className="absolute inset-0 bg-black/30 group-hover:bg-black/10 transition-colors flex items-end p-4">
|
||||||
|
<span className="text-xs text-white/80 font-medium">{g.label}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Lightbox */}
|
||||||
|
{lightbox !== null && (
|
||||||
|
<div className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center">
|
||||||
|
<button onClick={() => setLightbox(null)} className="absolute top-6 right-6 text-white/60 hover:text-white"><X className="h-6 w-6" /></button>
|
||||||
|
<button onClick={() => setLightbox((lightbox - 1 + gallery.length) % gallery.length)} className="absolute left-6 text-white/60 hover:text-white"><ChevronLeft className="h-8 w-8" /></button>
|
||||||
|
<div className={`w-[80vw] h-[60vh] rounded-2xl bg-gradient-to-br \${gallery[lightbox].gradient} flex items-center justify-center`}>
|
||||||
|
<span className="text-white/60 text-lg">{gallery[lightbox].label}</span>
|
||||||
|
</div>
|
||||||
|
<button onClick={() => setLightbox((lightbox + 1) % gallery.length)} className="absolute right-6 text-white/60 hover:text-white"><ChevronRight className="h-8 w-8" /></button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-7xl px-6 py-16">
|
||||||
|
<div className="grid lg:grid-cols-3 gap-12">
|
||||||
|
<div className="lg:col-span-2 space-y-12">
|
||||||
|
{/* ── OVERVIEW ── */}
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-white mb-4 animate-fade-up">Overview</h2>
|
||||||
|
<p className="text-slate-400 leading-relaxed mb-6">Bali captivates visitors with its lush rice terraces, sacred temples, world-class surfing, and warm Balinese hospitality. From the cultural heart of Ubud to the stunning cliffs of Uluwatu, every corner of this island offers something extraordinary. Whether you seek adventure, relaxation, or spiritual renewal, Bali delivers an unforgettable experience.</p>
|
||||||
|
<h3 className="text-lg font-semibold text-white mb-3">Highlights</h3>
|
||||||
|
<div className="grid sm:grid-cols-2 gap-3">
|
||||||
|
{highlights.map((h, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-2 text-sm text-slate-300">
|
||||||
|
<Check className="h-4 w-4 text-cyan-400 shrink-0" />
|
||||||
|
{h}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── ITINERARY ── */}
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-white mb-4 animate-fade-up">Sample Itinerary</h2>
|
||||||
|
<Accordion type="single" collapsible className="space-y-2 stagger">
|
||||||
|
{itinerary.map((item, i) => (
|
||||||
|
<AccordionItem key={i} value={`day-\${i}`} className="border border-slate-800 rounded-lg overflow-hidden bg-slate-950/50 px-4">
|
||||||
|
<AccordionTrigger className="text-white hover:text-cyan-400 hover:no-underline py-4">
|
||||||
|
<span className="flex items-center gap-3">
|
||||||
|
<Badge className="bg-blue-600/20 text-blue-300 border-blue-700/30 text-xs">{item.day}</Badge>
|
||||||
|
{item.title}
|
||||||
|
</span>
|
||||||
|
</AccordionTrigger>
|
||||||
|
<AccordionContent className="text-slate-400 pb-4">{item.desc}</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── REVIEWS ── */}
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-white mb-4 animate-fade-up">Traveler Reviews</h2>
|
||||||
|
<div className="space-y-4 stagger">
|
||||||
|
{reviews.map((r, i) => (
|
||||||
|
<Card key={i} className="border-slate-800 backdrop-blur-sm bg-card/80">
|
||||||
|
<CardContent className="p-5">
|
||||||
|
<div className="flex items-center gap-1 text-amber-400 mb-2">
|
||||||
|
{[...Array(r.rating)].map((_, j) => <Star key={j} className="h-3.5 w-3.5 fill-current" />)}
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-300 text-sm leading-relaxed mb-2">{r.text}</p>
|
||||||
|
<div className="text-sm font-semibold text-white">{r.name}</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* ── PRICING CARD ── */}
|
||||||
|
<div>
|
||||||
|
<Card className="border-slate-800 backdrop-blur-sm bg-card/80 sticky top-24">
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="text-sm text-slate-500 mb-1">Starting from</div>
|
||||||
|
<div className="text-3xl font-bold text-white mb-1">\$899</div>
|
||||||
|
<div className="text-sm text-slate-500 mb-6">per person</div>
|
||||||
|
<h4 className="text-sm font-semibold text-white mb-3">Package Includes</h4>
|
||||||
|
<ul className="space-y-2 mb-6">
|
||||||
|
{["5 nights luxury accommodation", "Daily breakfast and 3 dinners", "All transfers and transportation", "Professional English-speaking guide", "All entrance fees and activities"].map((item, i) => (
|
||||||
|
<li key={i} className="flex items-center gap-2 text-sm text-slate-300">
|
||||||
|
<Check className="h-4 w-4 text-cyan-400 shrink-0" />
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<Button onClick={handleBook} disabled={booking} className="w-full bg-blue-600 hover:bg-blue-500 text-white rounded-full font-semibold active:scale-[0.98] shadow-lg shadow-blue-600/20">
|
||||||
|
{booking ? "Booking..." : "Book Now"}
|
||||||
|
</Button>
|
||||||
|
<p className="mt-3 text-center text-xs text-slate-500">Free cancellation up to 48 hours</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user