// Hotel Walks — the Walk Letter document.
// Three copies (Guest / Hotel / Emailed). First print is clean; subsequent
// prints are watermarked DUPLICATE COPY. Includes:
// - sending hotel letterhead
// - guest details + receiving hotel
// - negotiated rate (on the emailed copy only)
// - serial number [arrival]-[guest initial]-[sending]-[receiving]-[global #]
// - agent + authorized signatures
//
// LettersList is a thin index that lists the walks that have letters.
(function() {
const { useState, useEffect } = React;
const { Caps, Btn, Pill, Hr, money } = window.UI;
const { SmartKey, Lockup } = window.HW;
const { SENT, HOTEL_BY_ID, ME } = window.HW_DATA;
const COPY_LABELS = {
guest: "Guest copy",
hotel: "Hotel copy",
emailed: "Emailed copy",
};
// Only the emailed copy explicitly includes the negotiated rate.
const showsRate = (kind) => kind === "emailed" || kind === "hotel";
/* ───────── Letters list ───────── */
function LettersList() {
return (
Walk letters
Documents generated.
One serial per walk. Reprints are watermarked DUPLICATE COPY and the certificate cannot be redeemed twice.
);
}
/* ───────── The document ───────── */
function WalkLetter({ id }) {
const walk = SENT.find(w => w.id === id) || SENT[1]; // fallback to accepted demo
const [copyKind, setCopyKind] = useState("guest");
// Per-walk print count → triggers DUPLICATE watermark after first print.
const printKey = "hw_letter_prints_" + walk.id;
const [printCount, setPrintCount] = useState(() => Number(localStorage.getItem(printKey) || 0));
const isDuplicate = printCount > 0;
const onPrint = () => {
const n = printCount + 1;
setPrintCount(n);
localStorage.setItem(printKey, String(n));
setTimeout(() => window.print(), 100);
};
return (
{/* Toolbar */}
← Letters
·
{walk.id}
{isDuplicate &&
Duplicate · {printCount}× }
{Object.entries(COPY_LABELS).map(([k, l]) => (
setCopyKind(k)} style={{
background: copyKind === k ? "var(--ink)" : "transparent",
color: copyKind === k ? "var(--bone)" : "var(--ink)",
border: "1px solid var(--ink)",
padding: "9px 14px", fontSize: 11, cursor: "pointer",
fontFamily: "'JetBrains Mono', monospace", letterSpacing: "0.12em", textTransform: "uppercase",
whiteSpace: "nowrap",
}}>{l}
))}
Guest view ↗
{ localStorage.removeItem(printKey); setPrintCount(0); }}>Reset
Print →
{/* Document */}
);
}
function Letter({ walk, copyKind, isDuplicate }) {
const { guest, to: receiver, serial, rate, rateOffered } = walk;
const me = ME.property;
return (
{/* DUPLICATE watermark */}
{isDuplicate && (
)}
{/* Letterhead */}
{/* Salutation */}
For the guest of
{guest.name}
{/* Body copy */}
{copyKind === "guest" && (
On behalf of {me.name}, please accept our apology for the inconvenience.
Due to circumstances beyond our control we are unable to honor your reservation tonight.
We have arranged for accommodations at the receiving property below.
Please present this letter on arrival.
)}
{copyKind === "hotel" && (
Internal record of relocation. This certificate represents a single, non-transferable
obligation. {me.name} will reimburse the receiving property per the rate negotiated below.
)}
{copyKind === "emailed" && (
To the front office at {receiver?.name || "the receiving property"} — please honor the
accommodations described below for the named guest. The negotiated nightly rate is
included for billing reference. Please return a confirmation number to the sending property.
)}
{/* Details grid */}
1?"s":""}`]} right={["Sending confirmation", guest.confirmation]} />
{showsRate(copyKind) && (
)}
{!showsRate(copyKind) && (
)}
{/* Liability blurb */}
Liability
Hotel Walks operates strictly as a communication layer between properties. Contract of
service and liabilities rest between the providing entity / property and the sending entity / property.
{/* Signatures */}
{/* Serial */}
);
}
function Row({ left, right }) {
return (
|
|
);
}
function Cell({ k, v }) {
if (!k && !v) return
;
return (
);
}
function Signature({ label, name, sub }) {
return (
);
}
const responsiveCSS = `
@media (max-width: 700px) {
.letters-row { grid-template-columns: 1fr 1fr !important; gap: 8px !important; }
.letters-row > *:nth-child(n+3) { grid-column: span 2; }
.letter-toolbar { padding: 14px 18px !important; }
.letter-toolbar-actions { width: 100%; justify-content: flex-start; }
.letter-stage { padding: 28px 0 !important; }
.letter-stage article { padding: 36px 24px 32px !important; }
.letter-stage article header { flex-direction: column !important; align-items: flex-start !important; gap: 16px; }
.letter-stage article header > div:last-child { text-align: left !important; }
.letter-stage article section[style*="grid-template-columns: 1fr 1fr"] { grid-template-columns: 1fr !important; gap: 24px !important; }
}
@media print {
.letter-stage { background: white !important; padding: 0 !important; }
.letter-stage article { box-shadow: none !important; width: 100% !important; padding: 24px 32px !important; }
}
`;
function WrappedLetter(props) {
return (<> >);
}
function WrappedList() {
return (<> >);
}
window.Screens = window.Screens || {};
window.Screens.WalkLetter = WrappedLetter;
window.Screens.LettersList = WrappedList;
})();