initial checkin
This commit is contained in:
97
src/pages/EquipmentBookPage.tsx
Normal file
97
src/pages/EquipmentBookPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user