import { useState, useRef, useCallback } from "react";
const PEAS_GREEN = "#0B3D2C";
const PEAS_LIGHT = "#E8F5E9";
const PEAS_ACCENT = "#4CAF50";
const PEAS_DARK = "#1a1a1a";
const PEAS_BORDER = "#d0d0d0";
const defaultBooking = {
contractorName: "",
contractorEntity: "",
workerPhone: "",
workerEmail: "",
companyName: "",
bookedBy: "",
reportToName: "",
reportToEmail: "",
reportToPhone: "",
jobTitle: "",
workingAddress: "",
workingHours: "",
confirmedDates: "",
reservedDates: "",
ir35: "",
weekdayRateContractor: "",
weekdayRateClient: "",
satSunRate: "check with Peas agent before engaging in O/T.",
satSunRateClient: "check with Peas agent before engaging in O/T.",
overtime: "check with Peas agent before engaging in O/T.",
overtimeClient: "check with Peas agent before engaging in O/T.",
workerPayTerms: "14 days (from Monday eop deadline)",
peasAgent: "",
timesheetLink: "https://www.peas.london/timesheet",
paymentPolicyLink: "https://www.peas.london/payment-policy",
invoiceTemplateLink: "https://www.peas.london/invoice-template",
};
const sampleBooking = {
contractorName: "Lisander Qorri",
contractorEntity: "LISANDERQORRI LIMITED & 11849834",
workerPhone: "07762 363249 whatsapp call",
workerEmail: "lisanderqorri@gmail.com",
companyName: "Motus-Creative",
bookedBy: "Richard Allen, Director",
reportToName: "Richard",
reportToEmail: "rich@motus-creative.com, lulu@motus-creative.com",
reportToPhone: "",
jobTitle: "CGI Artist",
workingAddress: "remote",
workingHours: "9a, to 6pm UK",
confirmedDates: "Project fee commencing Wednesday 11 March 2026 (7-8 Days)",
reservedDates: "n/a",
ir35: "End client SME",
weekdayRateContractor: "300",
weekdayRateClient: "360",
satSunRate: "check with Peas agent before engaging in O/T.",
satSunRateClient: "check with Peas agent before engaging in O/T.",
overtime: "check with Peas agent before engaging in O/T.",
overtimeClient: "check with Peas agent before engaging in O/T.",
workerPayTerms: "14 days (from Monday eop deadline)",
peasAgent: "PM/CC",
timesheetLink: "https://www.peas.london/timesheet",
paymentPolicyLink: "https://www.peas.london/payment-policy",
invoiceTemplateLink: "https://www.peas.london/invoice-template",
};
/* ── Reusable form field ── */
function Field({ label, name, value, onChange, placeholder, type = "text", half = false }) {
return (
{type === "textarea" ? (
) : (
)}
);
}
function FormSectionHeader({ children }) {
return (
);
}
/* ── Table row for email preview ── */
function TableRow({ label, value }) {
return (
| {label} |
{value || ""} |
);
}
/* ── shared inline style tokens (Helvetica Neue, 9pt) ── */
const F = `font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 9pt;`;
/* Label cell: no border, sits outside the box */
const LBL = `padding: 5px 10px 5px 0; ${F} color: #1a1a1a; vertical-align: middle; width: 160px; border: none;`;
/* Value cell: bordered box */
const VAL = `padding: 5px 10px; ${F} color: #1a1a1a; border: 1px solid #000;`;
/* Outer border wrapper */
const OUTER = `border: 2px solid #000; padding: 20px 24px; margin-top: 0;`;
/* Helper: one row = label (no border) | value (bordered box) */
function row(label, value) {
return `
| ${label} | ${value} |
`;
}
/* ── Generate HTML for Contractor email ── */
function generateContractorHTML(b) {
return `
CONTRACTOR COPY
Hi ${b.contractorName.split(" ")[0]}
Confirming your booking details as follows;
Contractor:
${row("Contractor entity:", b.contractorEntity)}
${row("Worker:", b.contractorName)}
${row("Phone & Email:", `${b.workerEmail} (${b.workerPhone})`)}
End Client
${row("Company name", b.companyName)}
${row("Booking made by:", b.bookedBy)}
${row("Report to:", b.reportToName)}
${row("Phone & email:", [b.reportToEmail, b.reportToPhone].filter(Boolean).join(", "))}
Booking:
${row("Job title/Capacity:", b.jobTitle)}
${row("Working address:", b.workingAddress || "TBC")}
${row("Working hours:", b.workingHours || "TBC")}
${row("Confirmed dates", b.confirmedDates)}
${row("Reserved dates:", b.reservedDates || "n/a")}
${row("IR35 determinant:", b.ir35 || "TBC")}
${row("Weekday rate:", b.weekdayRateContractor)}
${row("Sat & Sun rate:", b.satSunRate)}
${row("Overtime:", b.overtime)}
${row("Worker pay terms", b.workerPayTerms)}
Note:
Submit authorised timesheet & invoices to weekly, to accounts@wearepeas.com
Multiple weeks\u2019 timesheets submittals will be paid week by week until cleared
Timesheet, Invoice template and billing information attached.
Fulfilled by: ${b.peasAgent}
`;
}
/* ── Generate HTML for Client email ── */
function generateClientHTML(b) {
return `
CLIENT COPY
NOTE: Please share PO / Job code / IR35 CEST or SDS where applicable
Hi ${b.reportToName.split(" ")[0]}
Confirming your booking details as follows;
Contractor:
${row("Contractor entity:", b.contractorEntity)}
${row("Worker:", b.contractorName)}
${row("Phone & Email:", `${b.workerEmail} (${b.workerPhone})`)}
End Client
${row("Company name", b.companyName)}
${row("Booking made by:", b.bookedBy)}
${row("Report to:", b.reportToName)}
${row("Phone & email:", [b.reportToEmail, b.reportToPhone].filter(Boolean).join(", "))}
Booking:
${row("Job title/Capacity:", b.jobTitle)}
${row("Working address:", b.workingAddress || "TBC")}
${row("Working hours:", b.workingHours || "TBC")}
${row("Confirmed dates", b.confirmedDates)}
${row("Reserved dates:", b.reservedDates || "n/a")}
${row("IR35 determinant:", b.ir35 || "TBC")}
${row("Weekday rate:", b.weekdayRateClient)}
${row("Sat & Sun rate:", b.satSunRateClient)}
${row("Overtime:", b.overtimeClient)}
Note:
We pay freelancers in advance to keep them happy
Clients receive a weekly invoice, with 30 day pay terms.
Contractor has been fully briefed in on this task, and selected by the client.
Cancelation fees apply once booking is confirmed.
Please highlight any health & safety concerns prior to the booking commencing.
Fulfilled by: ${b.peasAgent}
`;
}
/* ── Styled email preview with live HTML render ── */
function EmailPreview({ title, htmlContent, subjectLine, recipientEmail, recipientLabel, bannerColor, booking }) {
const [copied, setCopied] = useState(false);
const [subjectCopied, setSubjectCopied] = useState(false);
const previewRef = useRef(null);
const copyAsRichText = useCallback(() => {
try {
// Create an off-screen container with the HTML content
const tempDiv = document.createElement("div");
tempDiv.innerHTML = htmlContent;
tempDiv.style.position = "fixed";
tempDiv.style.left = "-9999px";
tempDiv.style.top = "0";
tempDiv.style.opacity = "0";
document.body.appendChild(tempDiv);
// Select the content
const range = document.createRange();
range.selectNodeContents(tempDiv);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
// Copy as rich text (this preserves HTML for Outlook)
document.execCommand("copy");
// Clean up
selection.removeAllRanges();
document.body.removeChild(tempDiv);
setCopied(true);
setTimeout(() => setCopied(false), 2500);
} catch (err) {
// Final fallback: copy the rendered text from the preview
const text = previewRef.current?.innerText || "";
navigator.clipboard.writeText(text).then(() => {
setCopied(true);
setTimeout(() => setCopied(false), 2500);
});
}
}, [htmlContent]);
const copySubject = () => {
navigator.clipboard.writeText(subjectLine);
setSubjectCopied(true);
setTimeout(() => setSubjectCopied(false), 2000);
};
const openMailto = () => {
const plainText = previewRef.current?.innerText || "";
window.open(`mailto:${recipientEmail || ""}?subject=${encodeURIComponent(subjectLine)}&body=${encodeURIComponent(plainText)}`, "_blank");
};
return (
{/* Top bar */}
{title}
{/* Subject */}
Subject:
{subjectLine}
{/* To */}
{recipientEmail && (
To:
{recipientLabel || recipientEmail}
)}
{/* Rendered HTML preview */}
);
}
/* ── Main App ── */
export default function PeasBookingApp() {
const [booking, setBooking] = useState(defaultBooking);
const [view, setView] = useState("form");
const handleChange = (e) => {
setBooking((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
const loadSample = () => setBooking(sampleBooking);
const clearForm = () => setBooking(defaultBooking);
const subjectLine = `Booking Schedule: ${booking.companyName || "CLIENT"} & ${booking.contractorName || "CANDIDATE"}`;
const contractorHTML = generateContractorHTML(booking);
const clientHTML = generateClientHTML(booking);
const hasMinimumData = booking.contractorName && booking.companyName;
return (
{/* Header */}
P
Peas Booking Confirmation
Generate contractor & client booking schedule emails
{/* Tabs */}
{["form", "preview"].map((tab) => (
))}
{/* ═══ FORM VIEW ═══ */}
{view === "form" && (
Contractor Details
Client Details
Report To
Booking
Rates — Contractor
Rates — Client
Peas Internal
)}
{/* ═══ PREVIEW VIEW ═══ */}
{view === "preview" && (
`}
bannerColor={PEAS_GREEN}
booking={booking}
/>
`}
bannerColor="#2C5282"
booking={booking}
/>
"Copy for Outlook" copies rich HTML — paste directly into a new Outlook email and the tables & formatting will be preserved.
)}
);
}