initial checkin

This commit is contained in:
2026-04-10 21:48:23 -05:00
parent 28fe3bfa48
commit fdc05f8048
49 changed files with 10162 additions and 1 deletions

View File

@@ -0,0 +1,97 @@
import { useEffect, useState } from "react";
import { Link, useNavigate, useParams } from "react-router-dom";
import { formatApiMessage } from "../api/client";
import * as api from "../api/services";
import { useAuth } from "../auth/AuthContext";
import {
attributionParamsFromSearch,
hasAttributionParams,
persistMarketingClickId,
readMarketingClickId,
stripAttributionFromUrl,
} from "../marketing/attribution";
import type { EquipmentItemDetail } from "../api/types";
export function EquipmentBookPage() {
const { publicId } = useParams<{ publicId: string }>();
const navigate = useNavigate();
const { user } = useAuth();
const [item, setItem] = useState<EquipmentItemDetail | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!publicId) return;
const pathnameAtStart = window.location.pathname;
const searchAtStart = window.location.search;
const attribution = attributionParamsFromSearch(searchAtStart);
let cancelled = false;
(async () => {
try {
const data = await api.getEquipmentItem(
publicId,
hasAttributionParams(attribution) ? attribution : undefined,
);
if (!cancelled) {
setItem(data);
if (hasAttributionParams(attribution)) {
persistMarketingClickId("equipment", publicId, data.marketing_click_id);
stripAttributionFromUrl(navigate, pathnameAtStart, searchAtStart);
}
}
} catch (e) {
if (!cancelled) setError(formatApiMessage(e));
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => {
cancelled = true;
};
}, [publicId, navigate]);
if (loading) {
return (
<div className="page">
<p className="muted">Loading</p>
</div>
);
}
if (error || !item) {
return (
<div className="page">
<p className="lede">{error ?? "Listing not found."}</p>
<Link to="/boats" className="text-link">
Back to boat rentals
</Link>
</div>
);
}
const marketingClickIdForConversion =
readMarketingClickId("equipment", item.public_id) ?? item.marketing_click_id;
return (
<div className="page booking-request-page">
<Link to={`/boats/item/${item.public_id}`} className="text-link">
Back to listing
</Link>
<h1>Request this rental</h1>
<p className="lede">
You are signed in as <strong>{user?.email}</strong>. Confirm details below; a full booking form can submit to
the WaterTrekk booking API next.
</p>
<article className="booking-request-summary">
<h2>{item.title}</h2>
<p className="muted">
{item.vendor_business_name} · {item.location} · ${item.price_per_day} / day
</p>
<p className="muted">
<code>marketing_click_id</code> {marketingClickIdForConversion}{" "}
<span className="muted">(send this on the booking request for attribution)</span>
</p>
</article>
</div>
);
}