import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { formatApiMessage } from "../api/client"; import * as api from "../api/services"; import type { AdventureOffering } from "../api/types"; export function ToursPage() { const [offerings, setOfferings] = useState([]); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { let cancelled = false; (async () => { try { const data = await api.listAdventureOfferings(); if (!cancelled) setOfferings(data); } catch (e) { if (!cancelled) setError(formatApiMessage(e)); } finally { if (!cancelled) setLoading(false); } })(); return () => { cancelled = true; }; }, []); return (

Tours & adventures

Guided experiences from GET /api/v1/adventrues/offerings/. Open a listing for marketing attribution IDs used at booking time.

{loading &&

Loading…

} {error &&

{error}

}
    {offerings.map((o) => { const img = o.images.find((i) => i.is_primary)?.image_url ?? o.images[0]?.image_url; return (
  • {o.title}

    {o.vendor_business_name}

    {o.meeting_point} · {o.duration_minutes} min · up to {o.capacity} guests

    ${o.price_per_person} / person

    View details
  • ); })}
{!loading && !error && offerings.length === 0 && (

No adventures published yet.

)}
); }