initial checkin
This commit is contained in:
74
src/pages/ToursPage.tsx
Normal file
74
src/pages/ToursPage.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
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<AdventureOffering[]>([]);
|
||||
const [error, setError] = useState<string | null>(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 (
|
||||
<div className="page tours-catalog">
|
||||
<h1>Tours & adventures</h1>
|
||||
<p className="lede">
|
||||
Guided experiences from GET <code>/api/v1/adventrues/offerings/</code>. Open a listing for marketing
|
||||
attribution IDs used at booking time.
|
||||
</p>
|
||||
{loading && <p className="muted">Loading…</p>}
|
||||
{error && <p className="muted">{error}</p>}
|
||||
<ul className="card-list">
|
||||
{offerings.map((o) => {
|
||||
const img =
|
||||
o.images.find((i) => i.is_primary)?.image_url ?? o.images[0]?.image_url;
|
||||
return (
|
||||
<li key={o.id}>
|
||||
<article className="card">
|
||||
<div
|
||||
className="tours-card-image"
|
||||
style={
|
||||
img
|
||||
? { backgroundImage: `url(${img})` }
|
||||
: { background: "linear-gradient(135deg, #fef9c3, #facc15)" }
|
||||
}
|
||||
/>
|
||||
<h2>{o.title}</h2>
|
||||
<p>{o.vendor_business_name}</p>
|
||||
<p className="muted">
|
||||
{o.meeting_point} · {o.duration_minutes} min · up to {o.capacity} guests
|
||||
</p>
|
||||
<p>
|
||||
<strong>${o.price_per_person}</strong> / person
|
||||
</p>
|
||||
<Link to={`/tours/${o.public_id}`} className="text-link">
|
||||
View details
|
||||
</Link>
|
||||
</article>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{!loading && !error && offerings.length === 0 && (
|
||||
<p className="muted">No adventures published yet.</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user