# Saddle Up AI Interview & Training System — Source Files for Migration

Feature: interview + training opt-in system co-branded Winters × Saddle Up AI.
Original stack: Base44 (React + Vite + Tailwind + react-router-dom). Entity SDK calls (`base44.entities.LumenaryProsSignup.*`) and `@/api/base44Client` imports must be replaced with the new data layer (Google Sheets).

## Data record (LumenaryProsSignup)
```json
{
  "first_name": "string (required)",
  "last_name": "string (required)",
  "phone": "string (required)",
  "email": "string (required)",
  "selected_date": "YYYY-MM-DD (required)",
  "selected_slot": "HH:MM Central, e.g. 11:00",
  "notes": "string",
  "confirmation_sent": "boolean, default false",
  "company": "saddleup_ai | saddleup_training"
}
```
Built-in fields on every record: id, created_date, updated_date.

## Routes
- /saddleupai and /saddleup-opt-in -> SaddleUpAI (public interview signup)
- /saddleup-training -> SaddleUpTraining (public training signup)
- /interview-opt -> InterviewOpt (admin dashboard, interviews)
- /training-opt -> TrainingOpt (admin dashboard, training)

---

## FILE: src/lib/companies.js
```js
export const COMPANIES = [
  {
    slug: "saddleup_ai",
    name: "Saddle Up AI",
    days: [1, 2, 3, 4, 5],
    mode: "interview",
    sessionLabel: "interview",
    meetLink: "https://meet.google.com/mfw-vpxw-cny",
    partnerName: "Saddle Up AI",
    partnerLogo: "https://media.base44.com/images/public/69a5100f045ec588f04d3789/cb0c78510_image.png",
    coBrand: "Winters × Saddle Up AI",
    eventTitle: "Sales Team Interview — Winters × Saddle Up AI",
    eventDetails:
      "Interview for the Winters / Saddle Up AI sales team. Join via Google Meet: meet.google.com/mfw-vpxw-cny. Please join a few minutes early.",
    adminTag: "SaddleUpAI",
    pageTitle: "Winters × Saddle Up AI — Sales Team Interview",
    footerText: "Sales team interviews · Monday–Friday · 30-min slots · 8 AM to 5 PM Central time (9:30–10:30 reserved for SaddleUp training)",
  },
  {
    slug: "saddleup_training",
    name: "Saddle Up AI Training",
    days: [1, 2, 3, 4, 5],
    mode: "training",
    sessionLabel: "training",
    meetLink: "https://meet.google.com/tes-eius-ufh",
    partnerName: "Saddle Up AI",
    partnerLogo: "https://media.base44.com/images/public/69a5100f045ec588f04d3789/cb0c78510_image.png",
    coBrand: "Winters × Saddle Up AI",
    eventTitle: "SaddleUp Training — Winters × Saddle Up AI",
    eventDetails:
      "SaddleUp training session with Winters × Saddle Up AI. Join via Google Meet: meet.google.com/tes-eius-ufh. Please join a few minutes early.",
    adminTag: "SaddleUpTraining",
    pageTitle: "Winters × Saddle Up AI — SaddleUp Training",
    footerText: "SaddleUp training · Monday–Friday · 9:30 AM Central time · 1 hour",
  },
];

export const DEFAULT_COMPANY = COMPANIES[0].slug;

export const getCompany = (slug) =>
  COMPANIES.find((c) => c.slug === slug) || COMPANIES[0];
```

---

## FILE: src/lib/interviewSlots.js
```js
// Sessions run Monday–Friday only. Interview slots are 30-minute blocks,
// 8 AM – 5 PM Central, lunch (12–1 PM) excluded. 9:30–10:30 AM is reserved for
// SaddleUp training. Training is a single 1-hour block at 9:30 AM Central.

export function getNextSlots(count = 2, { includeToday = true, days = [1, 2, 3, 4, 5] } = {}) {
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const wanted = [...new Set(days)].filter((d) => d >= 0 && d <= 6);
  if (wanted.length === 0) return [];

  const cur = new Date(today);
  if (!includeToday) cur.setDate(cur.getDate() + 1);

  const out = [];
  let guard = 0;
  while (out.length < count && guard < 400) {
    if (wanted.includes(cur.getDay())) out.push(new Date(cur));
    cur.setDate(cur.getDate() + 1);
    guard++;
  }
  return out;
}

export function getNextWeekdays(days = [1, 2, 3, 4, 5], { includeToday = true } = {}) {
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const out = days.map((wd) => {
    const d = new Date(today);
    let diff = (wd - today.getDay() + 7) % 7;
    if (diff === 0 && !includeToday) diff = 7;
    d.setDate(today.getDate() + diff);
    return d;
  });
  out.sort((a, b) => a.getTime() - b.getTime());
  return out;
}

export function toDateKey(d) {
  const y = d.getFullYear();
  const m = String(d.getMonth() + 1).padStart(2, "0");
  const day = String(d.getDate()).padStart(2, "0");
  return `${y}-${m}-${day}`;
}

export function formatLong(d) {
  return d.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric" });
}

export function formatShort(d) {
  return d.toLocaleDateString("en-US", { weekday: "long", month: "short", day: "numeric" });
}

export const ALL_SLOT_TIMES = [
  "08:00", "08:30", "09:00", "09:30", "10:00", "10:30", "11:00", "11:30",
  "13:00", "13:30", "14:00", "14:30", "15:00", "15:30", "16:00", "16:30",
];

export const BLOCKED_SLOTS = ["09:30", "10:00"];
export const BLOCKED_LABEL = "SaddleUp training · 9:30 – 10:30 AM (Central)";

export function isBlocked(slot) {
  return BLOCKED_SLOTS.includes(slot);
}

export const SLOT_TIMES = ALL_SLOT_TIMES.filter((s) => !isBlocked(s));

export const TRAINING_SLOT = "09:30";
export const SLOT_DURATION_MIN = 30;
export const TRAINING_DURATION_MIN = 60;

const SRC_TZ = "America/Chicago";

export function parseSlot(slot) {
  if (!slot) return null;
  const parts = String(slot).split(":");
  const h = parseInt(parts[0], 10);
  const m = parts.length > 1 ? parseInt(parts[1], 10) : 0;
  if (Number.isNaN(h) || Number.isNaN(m)) return null;
  return { h, m: Number.isNaN(m) ? 0 : m };
}

export function slotToHour(slot) {
  const p = parseSlot(slot);
  return p ? p.h : null;
}

function clockLabel(h, m) {
  const ap = h < 12 ? "AM" : "PM";
  let d = h % 12;
  if (d === 0) d = 12;
  return m === 0 ? `${d} ${ap}` : `${d}:${String(m).padStart(2, "0")} ${ap}`;
}

export function slotShort(slot) {
  const p = parseSlot(slot);
  return p ? clockLabel(p.h, p.m) : "";
}

export function slotRange(slot, mins = SLOT_DURATION_MIN) {
  const p = parseSlot(slot);
  if (!p) return "";
  const endMins = (p.h * 60 + p.m + mins) % (24 * 60);
  const eh = Math.floor(endMins / 60);
  const em = endMins % 60;
  return `${clockLabel(p.h, p.m)} – ${clockLabel(eh, em)}`;
}
export function slotLabel(slot, mins = SLOT_DURATION_MIN) {
  const r = slotRange(slot, mins);
  return r ? `${r} (Central time)` : "";
}

function tzOffsetMinutes(tz, date) {
  const dtf = new Intl.DateTimeFormat("en-US", {
    timeZone: tz, hourCycle: "h23",
    year: "numeric", month: "2-digit", day: "2-digit",
    hour: "2-digit", minute: "2-digit", second: "2-digit",
  });
  const map = {};
  for (const p of dtf.formatToParts(date)) map[p.type] = p.value;
  const asUtc = Date.UTC(+map.year, +map.month - 1, +map.day, +map.hour, +map.minute, +map.second);
  return Math.round((asUtc - date.getTime()) / 60000);
}

// UTC Date for a given date + slot (slot interpreted as Central time).
export function slotInstant(dateKey, slot) {
  const p = parseSlot(slot);
  if (!p || !dateKey) return null;
  const base = Date.UTC(+dateKey.slice(0, 4), +dateKey.slice(5, 7) - 1, +dateKey.slice(8, 10), p.h, p.m, 0);
  let inst = base - tzOffsetMinutes(SRC_TZ, new Date(base)) * 60000;
  inst = base - tzOffsetMinutes(SRC_TZ, new Date(inst)) * 60000;
  return new Date(inst);
}

function localTime(d, opts) {
  return d.toLocaleTimeString("en-US", opts);
}

export function slotLocalShort(dateKey, slot) {
  const start = slotInstant(dateKey, slot);
  return start ? localTime(start, { hour: "numeric" }) : "";
}

export function slotLocalRange(dateKey, slot, mins = SLOT_DURATION_MIN) {
  const start = slotInstant(dateKey, slot);
  if (!start) return "";
  const end = new Date(start.getTime() + mins * 60000);
  return `${localTime(start, { hour: "numeric", minute: "2-digit" })} – ${localTime(end, { hour: "numeric", minute: "2-digit" })}`;
}

export function slotLocalLabel(dateKey, slot, mins = SLOT_DURATION_MIN) {
  const r = slotLocalRange(dateKey, slot, mins);
  return r ? `${r} (your time)` : "";
}

export function viewerTzLabel() {
  try {
    const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
    return tz ? tz.replace(/_/g, " ") : "your time";
  } catch {
    return "your time";
  }
}
```

---

## FILE: src/pages/SaddleUpAI.jsx
```jsx
import React, { useEffect } from "react";
import SignupForm from "@/components/lumenarypros/SignupForm";
import { getCompany } from "@/lib/companies";

const WINTERS_LOGO =
  "https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/69a5100f045ec588f04d3789/696df1c10_WintersWinters.png";

const company = getCompany("saddleup_ai");

export default function SaddleUpAI() {
  useEffect(() => {
    document.title = company.pageTitle;
  }, []);

  return (
    <div
      className="w-full min-h-screen flex flex-col bg-white text-[#111]"
      style={{ fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif' }}
    >
      <header className="bg-[#111] relative flex justify-center items-center gap-[26px] min-h-[88px] px-6 py-4">
        <img className="h-16 w-auto block brightness-0 invert" src={WINTERS_LOGO} alt="Winters" />
        <span className="w-px h-[52px] bg-white/35" />
        {company.partnerLogo ? (
          <img className="h-[52px] max-w-[320px] object-contain" src={company.partnerLogo} alt={company.partnerName} />
        ) : (
          <span className="text-white text-[1.55rem] font-semibold tracking-tight leading-none">{company.partnerName}</span>
        )}
      </header>

      <section className="flex-1 px-6 pt-12 pb-20">
        <div className="max-w-[640px] mx-auto mb-6 text-center">
          <a
            href="/saddleup-training"
            className="text-[0.8rem] font-medium tracking-wide text-[#888] hover:text-[#C4A86A] transition-colors"
          >
            Looking for SaddleUp training (Mon–Fri, 9:30 AM Central)? Sign up here →
          </a>
        </div>
        <SignupForm companySlug="saddleup_ai" />
      </section>

      <footer className="bg-[#111] text-[#8a8a8a] text-center py-10 px-6">
        <p className="text-[0.85rem] m-0">{company.footerText}</p>
      </footer>
    </div>
  );
}
```

---

## FILE: src/pages/SaddleUpTraining.jsx
```jsx
import React, { useEffect } from "react";
import SignupForm from "@/components/lumenarypros/SignupForm";
import { getCompany } from "@/lib/companies";

const WINTERS_LOGO =
  "https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/69a5100f045ec588f04d3789/696df1c10_WintersWinters.png";

const company = getCompany("saddleup_training");

export default function SaddleUpTraining() {
  useEffect(() => {
    document.title = company.pageTitle;
  }, []);

  return (
    <div
      className="w-full min-h-screen flex flex-col bg-white text-[#111]"
      style={{ fontFamily: '-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif' }}
    >
      <header className="bg-[#111] relative flex justify-center items-center gap-[26px] min-h-[88px] px-6 py-4">
        <img className="h-16 w-auto block brightness-0 invert" src={WINTERS_LOGO} alt="Winters" />
        <span className="w-px h-[52px] bg-white/35" />
        {company.partnerLogo ? (
          <img className="h-[52px] max-w-[320px] object-contain" src={company.partnerLogo} alt={company.partnerName} />
        ) : (
          <span className="text-white text-[1.55rem] font-semibold tracking-tight leading-none">{company.partnerName}</span>
        )}
      </header>

      <section className="flex-1 px-6 pt-12 pb-20">
        <SignupForm companySlug="saddleup_training" />
      </section>

      <footer className="bg-[#111] text-[#8a8a8a] text-center py-10 px-6">
        <p className="text-[0.85rem] m-0">{company.footerText}</p>
      </footer>
    </div>
  );
}
```

---

## FILE: src/pages/InterviewOpt.jsx
```jsx
import React from "react";
import InterviewOptDashboard from "@/components/interview/InterviewOptDashboard";

export default function InterviewOpt() {
  return (
    <InterviewOptDashboard
      companySlug="saddleup_ai"
      otherPath="/training-opt"
      otherLabel="View training signups"
    />
  );
}
```

---

## FILE: src/pages/TrainingOpt.jsx
```jsx
import React from "react";
import InterviewOptDashboard from "@/components/interview/InterviewOptDashboard";

export default function TrainingOpt() {
  return (
    <InterviewOptDashboard
      companySlug="saddleup_training"
      otherPath="/interview-opt"
      otherLabel="View interview opt-ins"
    />
  );
}
```

---

## FILE: src/components/lumenarypros/SignupForm.jsx
```jsx
import React, { useMemo, useState } from "react";
import { base44 } from "@/api/base44Client";
import CalendarSave from "@/components/lumenarypros/CalendarSave";
import SlotGrid from "@/components/lumenarypros/SlotGrid";
import { formatShort, slotLocalLabel, slotRange, SLOT_DURATION_MIN, TRAINING_DURATION_MIN } from "@/lib/interviewSlots";
import { getCompany } from "@/lib/companies";

const FIELDS = [
  { name: "first_name", label: "First Name", type: "text", autoComplete: "given-name" },
  { name: "last_name", label: "Last Name", type: "text", autoComplete: "family-name" },
  { name: "phone", label: "Phone Number", type: "tel", autoComplete: "tel" },
  { name: "email", label: "Email", type: "email", autoComplete: "email" },
];

const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const PHONE_RE = /^[\d()+\-\s.]{7,}$/;

const CSS = `
.lrp-wrap{max-width:640px;margin:0 auto;background:#fff;border:1px solid #e7e7e5;border-radius:14px;padding:36px 32px;box-shadow:0 1px 3px rgba(0,0,0,.04)}
.lrp-title{font-size:1.35rem;font-weight:600;color:#111;margin:0 0 6px}
.lrp-sub{font-size:.92rem;color:#6b6b6b;margin:0 0 26px}
.lrp-form{display:flex;flex-direction:column;gap:18px}
.lrp-field{display:flex;flex-direction:column;gap:7px}
.lrp-label{font-size:.72rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#555}
.lrp-input{width:100%;padding:13px 15px;border:1px solid #d8d8d5;border-radius:9px;font-size:1rem;font-family:inherit;color:#111;background:#fff;transition:border-color .2s,box-shadow .2s;box-sizing:border-box}
.lrp-input:focus{outline:none;border-color:#C4A86A;box-shadow:0 0 0 3px rgba(196,168,106,.15)}
.lrp-input.lrp-error{border-color:#c0392b}
.lrp-msg{font-size:.78rem;color:#c0392b;margin:0}
.lrp-btn{margin-top:6px;padding:14px 28px;background:#111;color:#fff;border:none;border-radius:9px;font-size:.85rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;cursor:pointer;transition:background .2s,border-color .2s,color .2s;font-family:inherit}
.lrp-btn:hover{background:#C4A86A;color:#111}
.lrp-btn:disabled{opacity:.6;cursor:not-allowed;background:#999}
.lrp-confirm{text-align:center;padding:18px 8px}
.lrp-check{width:58px;height:58px;border-radius:50%;background:#C4A86A;margin:0 auto 20px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:1.8rem}
.lrp-confirm-title{font-size:1.25rem;font-weight:600;color:#111;margin:0 0 14px}
.lrp-confirm-text{font-size:.95rem;color:#444;line-height:1.6;margin:0}
.lrp-confirm-text a{color:#111;font-weight:600;text-decoration:underline}
.lrp-confirm-text .lrp-link{color:#111;font-weight:600;word-break:break-all}
.lrp-link-row{display:flex;align-items:center;gap:8px;justify-content:center;margin:14px 0 6px}
.lrp-link-chip{background:#f4f4f2;border:1px solid #e2e2df;border-radius:999px;padding:8px 16px;font-size:.85rem;color:#222;font-family:inherit;word-break:break-all}
.lrp-copy-btn{background:#111;color:#fff;border:none;border-radius:8px;padding:9px 16px;font-size:.74rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;cursor:pointer;font-family:inherit}
.lrp-copy-btn:hover{background:#C4A86A;color:#111}
.lrp-mail-btn{margin-top:10px;display:inline-block;width:100%;text-align:center;background:transparent;border:1px solid #d8d8d5;color:#222;border-radius:9px;padding:13px 18px;font-size:.82rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;text-decoration:none;cursor:pointer;font-family:inherit}
.lrp-mail-btn:hover{border-color:#C4A86A;color:#111}
.lrp-copied{font-size:.74rem;color:#3a9a52;margin:6px 0 0;font-weight:600}
.lrp-slot-summary{margin-top:2px;font-size:.85rem;color:#C4A86A;font-weight:600}
@media(max-width:480px){.lrp-wrap{padding:26px 20px;border-radius:12px}.lrp-title{font-size:1.2rem}}
`;

function valid(v) {
  const errs = {};
  if (!v.first_name.trim()) errs.first_name = "First name is required.";
  if (!v.last_name.trim()) errs.last_name = "Last name is required.";
  if (!v.phone.trim()) errs.phone = "Phone number is required.";
  else if (!PHONE_RE.test(v.phone.trim())) errs.phone = "Enter a valid phone number.";
  if (!v.email.trim()) errs.email = "Email is required.";
  else if (!EMAIL_RE.test(v.email.trim())) errs.email = "Enter a valid email address.";
  if (!v.selected_date) errs.selected_date = "Please choose a time block.";
  else if (!v.selected_slot) errs.selected_date = "Please choose a time block.";
  return errs;
}

export default function SignupForm({ companySlug = "saddleup_ai" }) {
  const company = getCompany(companySlug);
  const isTraining = company.mode === "training";
  const durationMin = isTraining ? TRAINING_DURATION_MIN : SLOT_DURATION_MIN;
  const sessionLabel = company.sessionLabel || "interview";
  const [values, setValues] = useState({
    first_name: "", last_name: "", phone: "", email: "", selected_date: "", selected_slot: "",
  });
  const [errors, setErrors] = useState({});
  const [submitting, setSubmitting] = useState(false);
  const [done, setDone] = useState(false);
  const [serverError, setServerError] = useState("");
  const [copied, setCopied] = useState(false);

  const meetLink = company.meetLink;
  const copyLink = async () => {
    try {
      await navigator.clipboard.writeText(meetLink);
      setCopied(true);
      setTimeout(() => setCopied(false), 2200);
    } catch { /* ignore */ }
  };
  const emailMyself = () => {
    const subject = `Your ${company.coBrand} ${isTraining ? "SaddleUp Training" : "Interview"}`;
    const lines = [
      `Here are the details for your upcoming ${isTraining ? "SaddleUp training session" : "sales team interview"}:`,
      "",
      `Date: ${chosenLabel}`,
      `Time: ${slotLocalLabel(values.selected_date, values.selected_slot, durationMin)} · Central ${slotRange(values.selected_slot, durationMin)}`,
      `Google Meet link: ${meetLink}`,
      "",
      "Please join a few minutes early.",
      "",
      "Save it to your calendar by visiting the signup page or use the Meet link above at the scheduled time.",
    ];
    const body = lines.join("\n");
    window.location.href = `mailto:${encodeURIComponent(values.email)}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
  };

  const onChange = (e) => {
    const { name, value } = e.target;
    setValues((s) => ({ ...s, [name]: value }));
    if (errors[name]) setErrors((er) => ({ ...er, [name]: undefined }));
    setServerError("");
  };

  const onSlot = ({ date, slot }) => {
    setValues((s) => ({ ...s, selected_date: date, selected_slot: slot }));
    if (errors.selected_date) setErrors((er) => ({ ...er, selected_date: undefined }));
    setServerError("");
  };

  const onSubmit = async (e) => {
    e.preventDefault();
    const errs = valid(values);
    setErrors(errs);
    if (Object.keys(errs).length) return;
    setSubmitting(true);
    setServerError("");
    try {
      const first_name = values.first_name.trim();
      const last_name = values.last_name.trim();
      const phone = values.phone.trim();
      const email = values.email.trim();
      const selected_date = values.selected_date;
      const selected_slot = values.selected_slot;
      // De-duplicate by email: if the email already exists, update that record
      // instead of creating a second one.
      const all = await base44.entities.LumenaryProsSignup.list("-created_date", 500);
      const existing = (all || []).find(
        (r) => (r.email || "").toLowerCase().trim() === email.toLowerCase().trim()
      );
      let isNew = true;
      if (existing) {
        await base44.entities.LumenaryProsSignup.update(existing.id, {
          first_name, last_name, phone, selected_date, selected_slot, company: company.slug,
        });
        isNew = false;
      } else {
        await base44.entities.LumenaryProsSignup.create({
          first_name, last_name, phone, email, selected_date, selected_slot, company: company.slug,
        });
      }
      setDone(true);
      // Notify the admin
      base44.integrations.Core.SendEmail({
        to: "Harley@winters-company.com",
        subject: isNew ? `New ${sessionLabel} signup — ${company.adminTag}` : `Reschedule (duplicate updated) — ${company.adminTag}`,
        body:
          (isNew ? `A new person signed up for a ${sessionLabel}.` : "A returning email rescheduled (duplicate merged).") + "\n\n" +
          `Date: ${formatShort(new Date(selected_date + "T00:00:00"))}\n` +
          `Time: ${slotLocalLabel(selected_date, selected_slot, durationMin)} · Central ${slotRange(selected_slot, durationMin)}\n` +
          `Name: ${first_name} ${last_name}\n` +
          `Phone: ${phone}\n` +
          `Email: ${email}\n` +
          `Submitted: ${new Date().toLocaleString("en-US", { timeZone: "America/Chicago" })} (Central)\n\n` +
          "View all opt-ins on the /interview-opt page.",
      }).catch(() => {});
    } catch (err) {
      setServerError("Something went wrong. Please try again.");
    } finally {
      setSubmitting(false);
    }
  };

  const reset = () => {
    setValues({ first_name: "", last_name: "", phone: "", email: "", selected_date: "", selected_slot: "" });
    setErrors({});
    setServerError("");
    setDone(false);
  };

  const chosenLabel = values.selected_date
    ? formatShort(new Date(values.selected_date + "T00:00:00"))
    : "";

  return (
    <>
      <style>{CSS}</style>
      {done ? (
        <div className="lrp-wrap">
          <div className="lrp-confirm">
            <div className="lrp-check">✓</div>
            <h3 className="lrp-confirm-title">Thanks for signing up!</h3>
            <p className="lrp-confirm-text">
              Your {sessionLabel} is on {chosenLabel} at {slotLocalLabel(values.selected_date, values.selected_slot, durationMin)} via Google Meet. Please join a few minutes early.
            </p>
            <div className="lrp-link-row">
              <span className="lrp-link-chip">{meetLink.replace("https://", "")}</span>
              <button className="lrp-copy-btn" onClick={copyLink}>{copied ? "Copied!" : "Copy"}</button>
            </div>
            {copied && <p className="lrp-copied">Link copied to clipboard.</p>}
            <button className="lrp-mail-btn" onClick={emailMyself}>Email me my confirmation</button>
            <CalendarSave
              selectedDate={values.selected_date}
              selectedSlot={values.selected_slot}
              company={company}
            />
            <button className="lrp-btn lrp-again" onClick={reset} style={{ marginTop: 24, background: "transparent", border: "1px solid #d8d8d5", color: "#555" }}>
              Sign up another person
            </button>
          </div>
        </div>
      ) : (
        <div className="lrp-wrap">
          <h3 className="lrp-title">Sign up for your {sessionLabel}</h3>
          <p className="lrp-sub">{isTraining
            ? "All fields are required. Pick a day below for your 1-hour SaddleUp training at 9:30 AM Central — you'll get the Google Meet link on the next screen."
            : "All fields are required. Pick an open 30-minute block below (8 AM – 5 PM Central, 9:30–10:30 reserved for training) — you'll get the Google Meet link on the next screen."}</p>
          <form className="lrp-form" onSubmit={onSubmit} noValidate>
            {FIELDS.map((f) => (
              <div className="lrp-field" key={f.name}>
                <label className="lrp-label" htmlFor={`lrp-${f.name}`}>{f.label}</label>
                <input
                  id={`lrp-${f.name}`}
                  className={`lrp-input ${errors[f.name] ? "lrp-error" : ""}`}
                  type={f.type}
                  name={f.name}
                  value={values[f.name]}
                  onChange={onChange}
                  autoComplete={f.autoComplete}
                />
                {errors[f.name] && <p className="lrp-msg">{errors[f.name]}</p>}
              </div>
            ))}
            <div className="lrp-field">
              <label className="lrp-label">Choose Your {isTraining ? "Training" : "Interview"} Time</label>
              <SlotGrid company={company} value={{ date: values.selected_date, slot: values.selected_slot }} onChange={onSlot} />
              {values.selected_date && values.selected_slot && (
                <p className="lrp-slot-summary">{chosenLabel} · {slotLocalLabel(values.selected_date, values.selected_slot, durationMin)}</p>
              )}
              {errors.selected_date && <p className="lrp-msg">{errors.selected_date}</p>}
            </div>
            {serverError && <p className="lrp-msg">{serverError}</p>}
            <button className="lrp-btn" type="submit" disabled={submitting}>
              {submitting ? "Submitting…" : "Sign Up"}
            </button>
          </form>
        </div>
      )}
    </>
  );
}
```

---

## FILE: src/components/lumenarypros/SlotGrid.jsx
```jsx
import React, { useEffect, useMemo, useState } from "react";
import { base44 } from "@/api/base44Client";
import {
  getNextSlots, toDateKey, ALL_SLOT_TIMES, SLOT_TIMES, isBlocked, BLOCKED_LABEL,
  TRAINING_SLOT, SLOT_DURATION_MIN, TRAINING_DURATION_MIN, parseSlot,
  slotLocalShort, slotLocalRange, viewerTzLabel,
} from "@/lib/interviewSlots";

const CSS = `
.sg-wrap{margin-top:4px}
.sg-legend{display:flex;align-items:center;gap:14px;margin:0 0 12px;flex-wrap:wrap}
.sg-legend-item{display:inline-flex;align-items:center;gap:6px;font-size:.72rem;color:#888}
.sg-dot{width:11px;height:11px;border-radius:3px;display:inline-block;border:1px solid #d8d8d5}
.sg-dot.open{background:#fff}
.sg-dot.sel{background:#111;border-color:#111}
.sg-dot.blocked{background:#f0ece0;border-color:#e2d8bf}
.sg-scroll{display:flex;gap:14px;overflow-x:auto;padding:2px 2px 14px;margin:-2px -2px 4px}
.sg-day{min-width:138px;flex:1 1 138px;display:flex;flex-direction:column;gap:10px}
.sg-day-head{display:flex;flex-direction:column;align-items:flex-start;padding:0 2px}
.sg-day-wd{font-size:.7rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#999}
.sg-day-md{font-size:1.02rem;font-weight:600;color:#111}
.sg-blocks{display:flex;flex-direction:column;gap:9px}
.sg-block{display:flex;flex-direction:column;align-items:flex-start;justify-content:center;gap:2px;text-align:left;padding:13px 14px;border:1px solid #d8d8d5;border-radius:10px;background:#fff;cursor:pointer;font-family:inherit;transition:border-color .2s,background .2s,box-shadow .2s;min-height:58px}
.sg-block:hover:not(:disabled){border-color:#C4A86A;box-shadow:0 0 0 3px rgba(196,168,106,.12)}
.sg-block.sel{background:#111;border-color:#111;color:#fff}
.sg-block.blocked{background:#f4efe1;border:1px dashed #d9c8a0;color:#9a8748;cursor:not-allowed}
.sg-block:disabled{cursor:not-allowed}
.sg-time{font-size:.95rem;font-weight:600;line-height:1.1}
.sg-range{font-size:.7rem;color:#999;font-weight:500}
.sg-block.sel .sg-range{color:#fff;opacity:.7}
.sg-state{font-size:.64rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;margin-top:2px}
.sg-empty{font-size:.82rem;color:#aaa;padding:18px 4px}
.sg-tz-note{font-size:.72rem;color:#888;margin:0 0 12px}
.sg-tz-note strong{color:#555;font-weight:600}
`;

export default function SlotGrid({ company, value, onChange }) {
  const isTraining = company.mode === "training";
  const durationMin = isTraining ? TRAINING_DURATION_MIN : SLOT_DURATION_MIN;
  const displaySlots = isTraining ? [TRAINING_SLOT] : ALL_SLOT_TIMES;
  const tzNote = isTraining
    ? `Times below are in ${viewerTzLabel()}. SaddleUp training runs Monday–Friday at 9:30 AM Central time, 1 hour.`
    : `Times below are in ${viewerTzLabel()}. Schedule set in Central time, Monday–Friday (8 AM – 5 PM, lunch break, 9:30–10:30 reserved for training).`;

  const dates = useMemo(
    () => getNextSlots(4, { includeToday: false, days: company.days }),
    []
  );
  const [booked, setBooked] = useState({});
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    let alive = true;
    const load = async () => {
      try {
        const all = await base44.entities.LumenaryProsSignup.list("-created_date", 500);
        const map = {};
        for (const r of all || []) {
          if ((r.company || "saddleup_ai") !== company.slug) continue;
          if (!r.selected_date) continue;
          const k = String(r.selected_date).slice(0, 10);
          const t = r.selected_slot ? parseSlot(r.selected_slot) : null;
          if (!t) continue;
          const s = `${String(t.h).padStart(2, "0")}:${String(t.m).padStart(2, "0")}`;
          map[`${k}|${s}`] = (map[`${k}|${s}`] || 0) + 1;
        }
        if (alive) setBooked(map);
      } catch { /* ignore */ } finally {
        if (alive) setLoading(false);
      }
    };
    load();
    const unsub = base44.entities.LumenaryProsSignup.subscribe(() => load());
    return () => { alive = false; if (unsub) unsub(); };
  }, [company.slug]);

  let blockedRendered = false;

  return (
    <>
      <style>{CSS}</style>
      <div className="sg-wrap">
        <div className="sg-legend">
          <span className="sg-legend-item"><span className="sg-dot open" />Open</span>
          <span className="sg-legend-item"><span className="sg-dot sel" />Selected</span>
          {!isTraining && (
            <span className="sg-legend-item"><span className="sg-dot blocked" />Training</span>
          )}
        </div>
        <p className="sg-tz-note">{tzNote}</p>
        <div className="sg-scroll">
          {dates.map((d) => {
            const dKey = toDateKey(d);
            blockedRendered = false;
            return (
              <div className="sg-day" key={dKey}>
                <div className="sg-day-head">
                  <span className="sg-day-wd">{d.toLocaleDateString("en-US", { weekday: "short" })}</span>
                  <span className="sg-day-md">{d.toLocaleDateString("en-US", { month: "short", day: "numeric" })}</span>
                </div>
                <div className="sg-blocks">
                  {displaySlots.map((s) => {
                    if (!isTraining && isBlocked(s)) {
                      if (blockedRendered || s !== "09:30") return null;
                      blockedRendered = true;
                      return (
                        <div className="sg-block blocked" key="blocked-training">
                          <span className="sg-time" style={{ fontSize: ".78rem" }}>Training</span>
                          <span className="sg-range">9:30 – 10:30 AM (Central)</span>
                          <span className="sg-state" style={{ color: "#9a8748" }}>Reserved</span>
                        </div>
                      );
                    }
                    const isSel = value && value.date === dKey && value.slot === s;
                    return (
                      <button
                        type="button"
                        key={s}
                        className={`sg-block ${isSel ? "sel" : ""}`}
                        disabled={loading}
                        onClick={() => onChange({ date: dKey, slot: s })}
                      >
                        <span className="sg-time">{slotLocalShort(dKey, s)}</span>
                        <span className="sg-range">{slotLocalRange(dKey, s, durationMin)}</span>
                        <span className="sg-state">{isSel ? "Selected" : loading ? "…" : "Open"}</span>
                      </button>
                    );
                  })}
                </div>
              </div>
            );
          })}
        </div>
        {dates.length === 0 && <div className="sg-empty">No upcoming slots.</div>}
      </div>
    </>
  );
}
```

---

## FILE: src/components/lumenarypros/CalendarSave.jsx
```jsx
import React, { useMemo, useState } from "react";
import { getNextSlots, toDateKey, formatShort, slotInstant, slotLocalLabel, SLOT_DURATION_MIN, TRAINING_DURATION_MIN, TRAINING_SLOT } from "@/lib/interviewSlots";

const CSS = `
.lrp-cal{margin-top:26px;padding-top:24px;border-top:1px solid #ececec;text-align:left}
.lrp-cal-title{font-size:.72rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#555;margin:0 0 6px;text-align:center}
.lrp-cal-hint{font-size:.82rem;color:#888;margin:0 0 18px;text-align:center}
.lrp-cal-row{display:flex;flex-direction:column;gap:10px;margin-bottom:20px}
.lrp-cal-label{font-size:.68rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:#999}
.lrp-sel{width:100%;padding:12px 14px;border:1px solid #d8d8d5;border-radius:9px;font-size:.95rem;font-family:inherit;color:#111;background:#fff;box-sizing:border-box;cursor:pointer;-webkit-appearance:none;appearance:none;background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path fill='%23555' d='M6 8L0 0h12z'/></svg>");background-repeat:no-repeat;background-position:right 14px center}
.lrp-sel:focus{outline:none;border-color:#C4A86A;box-shadow:0 0 0 3px rgba(196,168,106,.15)}
.lrp-cal-btns{display:flex;flex-direction:column;gap:10px}
.lrp-cal-btn{display:flex;align-items:center;justify-content:center;gap:8px;padding:13px 22px;border-radius:9px;font-size:.82rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;text-decoration:none;cursor:pointer;font-family:inherit;transition:all .2s;border:1px solid transparent}
.lrp-cal-google{background:#111;color:#fff}
.lrp-cal-google:hover{background:#C4A86A;color:#111}
.lrp-cal-ics{background:#fff;border:1px solid #d8d8d5;color:#555}
.lrp-cal-ics:hover{border-color:#C4A86A;color:#111}
@media(max-width:480px){.lrp-sel{padding:11px 12px}}
`;

function fmtUtc(d) {
  const pad = (n) => String(n).padStart(2, "0");
  return `${d.getUTCFullYear()}${pad(d.getUTCMonth() + 1)}${pad(d.getUTCDate())}T${pad(d.getUTCHours())}${pad(d.getUTCMinutes())}${pad(d.getUTCSeconds())}Z`;
}

function buildLinks(dateKey, { meet, title, details, prodId, slot, durationMin }) {
  if (!dateKey) return { google: "", ics: "" };
  const start = slotInstant(dateKey, slot || "09:30");
  if (!start) return { google: "", ics: "" };
  const end = new Date(start.getTime() + durationMin * 60000);
  const startStr = fmtUtc(start);
  const endStr = fmtUtc(end);
  const google =
    "https://calendar.google.com/calendar/render?action=TEMPLATE" +
    `&text=${encodeURIComponent(title)}` +
    `&dates=${startStr}/${endStr}` +
    `&details=${encodeURIComponent(details)}` +
    `&location=${encodeURIComponent(meet)}`;
  const ics =
    "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//Winters Family Office//" + prodId + "//EN\n" +
    "BEGIN:VEVENT\n" +
    `UID:${Date.now()}@winters-company.com\n` +
    `DTSTAMP:${fmtUtc(new Date())}\n` +
    `DTSTART:${startStr}\n` +
    `DTEND:${endStr}\n` +
    `SUMMARY:${title}\n` +
    `DESCRIPTION:${details}\n` +
    `LOCATION:${meet}\n` +
    "END:VEVENT\nEND:VCALENDAR";
  return { google, ics };
}

export default function CalendarSave({ selectedDate, selectedSlot, company } = {}) {
  const isTraining = company.mode === "training";
  const durationMin = isTraining ? TRAINING_DURATION_MIN : SLOT_DURATION_MIN;
  const defaultSlot = isTraining ? TRAINING_SLOT : "08:00";
  const meet = company.meetLink.replace("https://", "");
  const title = company.eventTitle;
  const details = company.eventDetails;
  const prodId = company.slug;

  const dates = useMemo(() => getNextSlots(2, { includeToday: false, days: company.days }), []);
  const [dateKey, setDateKey] = useState(selectedDate || "");
  const [slot, setSlot] = useState(selectedSlot || defaultSlot);

  const links = buildLinks(dateKey, { meet, title, details, prodId, slot, durationMin });
  const ready = !!dateKey && !!slot;

  const onDownloadICS = () => {
    if (!ready) return;
    const blob = new Blob([links.ics], { type: "text/calendar;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "interview.ics";
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  };

  const hint = selectedDate
    ? `Your ${isTraining ? "training" : "interview"} is set for ${formatShort(new Date(selectedDate + "T00:00:00"))}${slot ? " · " + slotLocalLabel(selectedDate, slot, durationMin) : ""}.`
    : isTraining
      ? "SaddleUp training runs daily at 9:30 AM Central time for one hour."
      : "Pick an available 30-minute block — interviews run 8 AM to 5 PM Central time (9:30–10:30 reserved for training).";

  return (
    <>
      <style>{CSS}</style>
      <div className="lrp-cal">
        <p className="lrp-cal-title">Save it to your calendar</p>
        <p className="lrp-cal-hint">{hint}</p>
        {!selectedDate && (
          <div className="lrp-cal-row">
            <span className="lrp-cal-label">Choose a day</span>
            <select className="lrp-sel" value={dateKey} onChange={(e) => setDateKey(e.target.value)}>
              <option value="">Select a date…</option>
              {dates.map((d) => {
                const key = toDateKey(d);
                return <option key={key} value={key}>{formatShort(d)}</option>;
              })}
            </select>
          </div>
        )}
        <div className="lrp-cal-btns">
          <a
            className="lrp-cal-btn lrp-cal-google"
            href={ready ? links.google : undefined}
            target="_blank"
            rel="noopener noreferrer"
            onClick={(e) => { if (!ready) e.preventDefault(); }}
            aria-disabled={!ready}
            style={ready ? undefined : { opacity: 0.5, pointerEvents: "none" }}
          >
            Add to Google Calendar
          </a>
          <button className="lrp-cal-btn lrp-cal-ics" onClick={onDownloadICS} disabled={!ready}>
            Download .ics (Apple / Outlook)
          </button>
        </div>
      </div>
    </>
  );
}
```

---

## FILE: src/components/interview/ApplicantRow.jsx
```jsx
import React, { useState, useMemo } from "react";
import { base44 } from "@/api/base44Client";
import { getNextSlots, toDateKey, formatShort, slotRange, slotLocalLabel, slotLocalRange, slotShort, SLOT_TIMES, TRAINING_SLOT, TRAINING_DURATION_MIN } from "@/lib/interviewSlots";
import { getCompany } from "@/lib/companies";

export default function ApplicantRow({ person }) {
  const [notes, setNotes] = useState(person.notes || "");
  const [open, setOpen] = useState(false);
  const [saving, setSaving] = useState(false);
  const [dateOpen, setDateOpen] = useState(false);
  const [picked, setPicked] = useState("");
  const [pickedSlot, setPickedSlot] = useState("");
  const [dateSaving, setDateSaving] = useState(false);
  const [status, setStatus] = useState("");

  const company = getCompany(person.company || "saddleup_ai");
  const slots = useMemo(() => getNextSlots(8, { includeToday: true, days: company.days }), []);
  const noDate = !person.selected_date;

  const meetLink = company.meetLink;
  const dateLabel = person.selected_date
    ? formatShort(new Date(String(person.selected_date).slice(0, 10) + "T00:00:00"))
    : "";
  const dateKey = String(person.selected_date || "").slice(0, 10);
  const slotText = person.selected_slot ? slotLocalLabel(dateKey, person.selected_slot) : "";

  const buildHtml = () => {
    const greeting = `Hi ${person.first_name},`;
    const isTraining = company.mode === "training";
    const sessionNoun = isTraining ? "SaddleUp training session" : "sales team interview";
    const joinLabel = isTraining ? "Training" : "Interview";
    const noDateBody = `Thanks for signing up for the ${company.coBrand} ${isTraining ? "SaddleUp training" : "sales interview"}. We'll follow up shortly with your scheduled date and the Google Meet link.`;
    const scheduledBody = `This is a reminder for your upcoming ${sessionNoun}.`;
    const noDateBlock = "";
    const scheduledBlock = `
      <p style="margin:0 0 6px;font-size:.72rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#555;">Date</p>
      <p style="margin:0 0 22px;font-size:1rem;color:#111;">${dateLabel}${person.selected_slot ? " · " + slotRange(person.selected_slot) + " (Central time)" : ""}</p>
      <a href="${meetLink}" style="display:inline-block;background:#111;color:#fff;border-radius:9px;padding:14px 30px;font-size:.85rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;text-decoration:none;">Join the ${joinLabel}</a>
      <p style="margin:18px 0 0;font-size:.78rem;color:#6b6b6b;">Please join a few minutes early.</p>`;
    return `<!doctype html><html><body style="margin:0;padding:0;background:#f5f5f5;font-family:Inter,Arial,sans-serif;color:#111;">
<div style="max-width:560px;margin:0 auto;padding:40px 20px;">
<div style="background:#fff;border:1px solid #e7e7e5;border-radius:14px;padding:36px 32px;box-shadow:0 1px 3px rgba(0,0,0,.04);">
<p style="margin:0 0 6px;font-size:.72rem;font-weight:600;letter-spacing:.18em;text-transform:uppercase;color:#C4A86A;">${company.coBrand}</p>
<h1 style="margin:0 0 10px;font-size:1.35rem;font-weight:600;color:#111;letter-spacing:-.01em;">${noDate ? "Thanks for signing up" : "Your Interview is Scheduled"}</h1>
<p style="margin:0 0 22px;font-size:.95rem;line-height:1.6;color:#444;">${greeting}</p>
<p style="margin:0 0 24px;font-size:.95rem;line-height:1.6;color:#444;">${noDate ? noDateBody : scheduledBody}</p>
${noDate ? noDateBlock : scheduledBlock}
</div>
<p style="margin:14px 0 0;font-size:.7rem;color:#9a9a9a;letter-spacing:.04em;text-align:center;">— ${company.coBrand}</p>
</div>
</body></html>`;
  };

  const buildPlain = () => {
    const isTraining = company.mode === "training";
    const sessionNoun = isTraining ? "SaddleUp training session" : "sales team interview";
    const joinLabel = isTraining ? "training" : "interview";
    const fallbackTime = isTraining ? "9:30 AM (Central time)" : "8 AM – 5 PM (Central time)";
    return noDate
      ? `Hi ${person.first_name},\n\nThanks for signing up for the ${company.coBrand} ${isTraining ? "SaddleUp training" : "sales interview"}. We'll follow up shortly with your scheduled date and the Google Meet link.\n\n— ${company.coBrand}`
      : `Hi ${person.first_name},\n\nThis is a reminder for your upcoming ${sessionNoun}.\n\nDate: ${dateLabel}\nTime: ${person.selected_slot ? slotRange(person.selected_slot) + " (Central time)" : fallbackTime}\n\nJoin the ${joinLabel} here:\n${meetLink}\n\nPlease join a few minutes early.\n\n— ${company.coBrand}`;
  };

  // NOTE: In the Base44 app this copies the styled email to the clipboard and
  // opens Gmail compose. In the new stack, replace this with a server call to
  // the sendInterviewEmail endpoint (see below) and then mark confirmation_sent.
  const sendConfirmation = async () => {
    setStatus("sending");
    const subject = noDate
      ? `Your ${company.coBrand} Interview`
      : `Your ${company.coBrand} Interview — ${dateLabel}`;
    const html = buildHtml();
    const plain = buildPlain();
    try {
      if (navigator.clipboard && window.ClipboardItem) {
        await navigator.clipboard.write([
          new ClipboardItem({
            "text/html": new Blob([html], { type: "text/html" }),
            "text/plain": new Blob([plain], { type: "text/plain" }),
          }),
        ]);
        setStatus("copied");
      } else {
        await navigator.clipboard.writeText(plain);
        setStatus("plain");
      }
    } catch {
      setStatus("fail");
    }
    base44.entities.LumenaryProsSignup.update(person.id, { confirmation_sent: true }).catch(() => {});
    const gmail = `https://mail.google.com/mail/?view=cm&fs=1&to=${encodeURIComponent(person.email)}&su=${encodeURIComponent(subject)}`;
    window.open(gmail, "_blank", "noopener,noreferrer");
  };

  const save = async () => {
    setSaving(true);
    try {
      await base44.entities.LumenaryProsSignup.update(person.id, { notes });
      setOpen(false);
    } finally {
      setSaving(false);
    }
  };

  const assignDate = async () => {
    if (!picked) return;
    setDateSaving(true);
    try {
      await base44.entities.LumenaryProsSignup.update(person.id, {
        selected_date: picked,
        selected_slot: pickedSlot,
      });
      setDateOpen(false);
    } finally {
      setDateSaving(false);
    }
  };

  return (
    <li className="opt-person" key={person.id}>
      <p className="opt-person-name">{person.first_name} {person.last_name}{(status === "copied" || person.confirmation_sent) && <span className="opt-sent-badge">Sent ✓</span>}</p>
      <p className="opt-person-meta">{person.phone} · {person.email}{dateLabel && ` · ${dateLabel}${slotText ? " · " + slotText : ""}`}</p>

      <div className="opt-row">
        <button className="opt-btn-opt" onClick={() => { setPicked(noDate ? "" : String(person.selected_date).slice(0, 10)); setPickedSlot(noDate ? "" : (person.selected_slot || "")); setDateOpen((v) => !v); }}>
          {noDate ? "+ Assign date" : "Reschedule"}
        </button>
        <button className="opt-btn-send" onClick={sendConfirmation} disabled={status === "sending"}>
          {status === "sending" ? "Copying…" : "Send confirmation"}
        </button>
      </div>
      {status === "copied" && <p className="opt-toast opt-toast-ok">Styled email copied — paste into Gmail (Ctrl/Cmd+V).</p>}
      {status === "plain" && <p className="opt-toast opt-toast-err" onClick={() => setStatus("")}>Couldn't copy styled HTML — plain text copied instead. Tap to dismiss.</p>}
      {status === "fail" && <p className="opt-toast opt-toast-err" onClick={() => setStatus("")}>Couldn't copy to clipboard. Tap to dismiss.</p>}

      {dateOpen && (
        <div className="opt-assign">
          <select className="opt-assign-select" value={picked} onChange={(e) => setPicked(e.target.value)}>
            <option value="">Choose a date…</option>
            {slots.map((d) => (
              <option key={toDateKey(d)} value={toDateKey(d)}>{formatShort(d)}</option>
            ))}
          </select>
          <select className="opt-assign-select" value={pickedSlot} onChange={(e) => setPickedSlot(e.target.value)}>
            <option value="">Choose a time…</option>
            {(company.mode === "training" ? [TRAINING_SLOT] : SLOT_TIMES).map((s) => (
              <option key={s} value={s}>
                {picked ? `${slotLocalRange(picked, s, company.mode === "training" ? TRAINING_DURATION_MIN : undefined)} (CT ${slotShort(s)})` : `${slotShort(s)} (Central)`}
              </option>
            ))}
          </select>
          <div className="opt-note-actions">
            <button className="opt-note-save" onClick={assignDate} disabled={!picked || dateSaving}>
              {dateSaving ? "Saving…" : "Save date"}
            </button>
            <button className="opt-note-cancel" onClick={() => { setPicked(""); setDateOpen(false); }} disabled={dateSaving}>
              Cancel
            </button>
          </div>
        </div>
      )}

      <div className="opt-notes">
        {open ? (
          <>
            <textarea
              className="opt-note-input"
              placeholder="Add a note about this applicant…"
              value={notes}
              onChange={(e) => setNotes(e.target.value)}
              rows={3}
            />
            <div className="opt-note-actions">
              <button className="opt-note-save" onClick={save} disabled={saving}>
                {saving ? "Saving…" : "Save"}
              </button>
              <button className="opt-note-cancel" onClick={() => { setNotes(person.notes || ""); setOpen(false); }} disabled={saving}>
                Cancel
              </button>
            </div>
          </>
        ) : (
          <button className="opt-note-toggle" onClick={() => setOpen(true)}>
            {person.notes ? "Edit note" : "+ Add note"}
          </button>
        )}
        {person.notes && !open && <p className="opt-note-text">{person.notes}</p>}
      </div>
    </li>
  );
}
```

---

## FILE: src/components/lumenarypros/ApplicantsModal.jsx
```jsx
import React, { useEffect, useState } from "react";
import { base44 } from "@/api/base44Client";
import { X, Trash2 } from "lucide-react";
import { formatShort } from "@/lib/interviewSlots";

export default function ApplicantsModal({ onClose, companySlug }) {
  const [rows, setRows] = useState(null);
  const [err, setErr] = useState("");

  useEffect(() => {
    let live = true;
    base44.entities.LumenaryProsSignup.list("-created_date", 200)
      .then((data) => {
        if (!live) return;
        const all = Array.isArray(data) ? data : data?.items ?? [];
        const filtered = companySlug
          ? all.filter((r) => (r.company || "saddleup_ai") === companySlug)
          : all;
        setRows(filtered);
      })
      .catch((e) => live && setErr(e?.message || "Unable to load applicants."));
    return () => { live = false; };
  }, [companySlug]);

  const remove = async (r) => {
    if (!window.confirm(`Delete ${r.first_name} ${r.last_name}?`)) return;
    await base44.entities.LumenaryProsSignup.delete(r.id);
    setRows((prev) => prev.filter((x) => x.id !== r.id));
  };

  return (
    <div
      onClick={onClose}
      style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,.55)", zIndex: 1000, display: "flex", alignItems: "center", justifyContent: "center", padding: 20 }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{ background: "#fff", borderRadius: 14, width: "100%", maxWidth: 720, maxHeight: "82vh", display: "flex", flexDirection: "column", overflow: "hidden", boxShadow: "0 12px 40px rgba(0,0,0,.18)" }}
      >
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "18px 22px", borderBottom: "1px solid #eee" }}>
          <h3 style={{ margin: 0, fontSize: "1.05rem", fontWeight: 600, letterSpacing: "-.01em" }}>Past Applicants</h3>
          <button onClick={onClose} style={{ background: "none", border: "none", cursor: "pointer", padding: 4, color: "#666" }}><X size={18} /></button>
        </div>
        <div style={{ overflow: "auto", padding: "8px 22px 18px" }}>
          {err && <p style={{ color: "#c0392b", fontSize: ".85rem", padding: "12px 0" }}>{err}</p>}
          {rows === null && !err ? (
            <p style={{ color: "#777", fontSize: ".85rem", padding: "16px 0" }}>Loading…</p>
          ) : rows.length === 0 ? (
            <p style={{ color: "#777", fontSize: ".85rem", padding: "16px 0" }}>No applicants yet.</p>
          ) : (
            <table style={{ width: "100%", borderCollapse: "collapse", fontSize: ".85rem" }}>
              <thead>
                <tr style={{ textAlign: "left", borderBottom: "1px solid #eee", color: "#888", fontWeight: 600, fontSize: ".72rem", textTransform: "uppercase", letterSpacing: ".06em" }}>
                  <th style={{ padding: "10px 8px" }}>Name</th>
                  <th style={{ padding: "10px 8px" }}>Email</th>
                  <th style={{ padding: "10px 8px" }}>Phone</th>
                  <th style={{ padding: "10px 8px" }}>Date</th>
                  <th style={{ padding: "10px 8px" }}>Sent</th>
                  <th style={{ padding: "10px 8px" }}></th>
                </tr>
              </thead>
              <tbody>
                {rows.map((r) => (
                  <tr key={r.id} style={{ borderBottom: "1px solid #f2f2f2" }}>
                    <td style={{ padding: "10px 8px" }}>{r.first_name} {r.last_name}</td>
                    <td style={{ padding: "10px 8px" }}>{r.email}</td>
                    <td style={{ padding: "10px 8px" }}>{r.phone || "—"}</td>
                    <td style={{ padding: "10px 8px" }}>{r.selected_date ? formatShort(new Date(String(r.selected_date).slice(0,10) + "T00:00:00")) : "—"}</td>
                    <td style={{ padding: "10px 8px" }}>{r.confirmation_sent ? "Sent ✓" : "—"}</td>
                    <td style={{ padding: "10px 8px", textAlign: "right" }}>
                      <button onClick={() => remove(r)} title="Delete" style={{ background: "none", border: "none", cursor: "pointer", color: "#c0392b", padding: 4 }}><Trash2 size={16} /></button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
}
```

---

## FILE: src/components/interview/InterviewOptDashboard.jsx
Admin dashboard shared by /interview-opt and /training-opt (props: companySlug, otherPath, otherLabel).
Behavior summary (rebuild with the same logic):
- Header (dark #111): Winters logo + divider + partner logo; link to the other dashboard on the left; "All Applicants" button on the right.
- Title: "Interview Opt-Ins · {company.name}" or "Training Signups · {company.name}".
- "Remove duplicates" button: for the current company, group records by lowercased email, keep the oldest by created_date; if the keeper has no selected_date and a duplicate does, copy the date to the keeper first; delete the rest.
- One column per weekday (Mon–Fri), each the next upcoming occurrence of that weekday (today included), sorted chronologically. Column shows weekday tag, month + day, schedule text, a "Join the room »" Meet link, and an opt-in count for that date.
- Interview mode: slots listed in order from ALL_SLOT_TIMES; the 9:30 and 10:00 blocks collapse into ONE "9:30 – 10:30 AM (Central) · Training" reserved block with a "sign up" link to /saddleup-training and a "Join training meeting »" link to the training Meet room. Training mode: only the 9:30 slot.
- Each slot block shows a color swatch, "{start} – {end} (Central)", an opt-in count, and the ApplicantRow list. Applicants with a date but no valid slot go in an "Unslotted · date only" group.
- Applicants with no selected_date appear in a section below: "Unverified · No date scheduled".
- Loads LumenaryProsSignup list sorted -created_date (limit 500), filtered by company (default "saddleup_ai"), and re-loads on realtime subscribe events (replace with polling on the new stack).
- Slot colors: 08:00 #6B8E9E, 08:30 #5E8A7A, 09:00 #C4A86A, 10:30 #B07C5E, 11:00 #7A9A6B, 11:30 #9E6B6B, 13:00 #8A7AA0, 13:30 #4E7A8A, 14:00 #6B8E9E, 14:30 #5E8A7A, 15:00 #C4A86A, 15:30 #B07C5E, 16:00 #7A9A6B, 16:30 #9E6B6B.
- Page background #F7F7F6, header/footer #111, accent #C4A86A, system sans-serif font, full viewport width on desktop with a 5-column grid (2 columns under 1100px, 1 under 760px).
- Footer: "{company.coBrand} · {company.footerText}".
- Schedule text (interview): "Monday–Friday · 30-min slots · 8 AM – 5 PM Central (9:30–10:30 reserved for training) · Google Meet". Schedule text (training): "Monday–Friday · 9:30 AM Central time · 1-hour SaddleUp training · Google Meet".
- ApplicantRow CSS classes used: opt-person, opt-person-name, opt-person-meta, opt-row, opt-btn-opt, opt-btn-send, opt-sent-badge, opt-toast, opt-assign, opt-assign-select, opt-notes, opt-note-*. Style them with the same neutral look (white cards, #ececec borders, 10px radius, #111 primary buttons, #C4A86A accents).

---

## FILE: base44/functions/sendInterviewEmail/entry.ts
```ts
import { createClientFromRequest } from 'npm:@base44/sdk@0.8.40';

Deno.serve(async (req) => {
  try {
    const base44 = createClientFromRequest(req);
    const user = await base44.auth.me();
    if (!user) return Response.json({ error: "Unauthorized" }, { status: 401 });
    if (user.role !== "admin") return Response.json({ error: "Forbidden" }, { status: 403 });

    const body = await req.json().catch(() => ({}));
    const firstName = (body.first_name || "").toString().trim();
    const email = (body.email || "").toString().trim();
    const dateLabel = (body.dateLabel || "").toString().trim();
    const meetLink = (body.meetLink || "https://meet.google.com/mfw-vpxw-cny").toString().trim();
    const noDate = !dateLabel;
    // "interview" (default) or "training". Drives copy and the time line.
    const mode = body.mode === "training" ? "training" : "interview";
    const isTraining = mode === "training";
    const sessionNoun = isTraining ? "SaddleUp training session" : "sales team interview";
    const signUpNoun = isTraining ? "SaddleUp training" : "sales interview";
    const joinLabel = isTraining ? "Training" : "Interview";
    const timeLine = isTraining ? "9:30 AM Central time (1 hour)" : "8 AM – 5 PM Central time";
    const titleWord = isTraining ? "SaddleUp Training" : "Interview";

    if (!firstName || !email) {
      return Response.json({ error: "first_name and email are required" }, { status: 400 });
    }

    const subject = noDate
      ? `Your Winters × Saddle Up AI ${titleWord}`
      : `Your Winters × Saddle Up AI ${titleWord} — ${dateLabel}`;

    const html = `<!doctype html><html><body style="margin:0;padding:0;background:#f5f5f5;font-family:Inter,Arial,sans-serif;color:#111;">
<div style="max-width:560px;margin:0 auto;padding:40px 20px;">
<div style="background:#fff;border:1px solid #e7e7e5;border-radius:14px;padding:36px 32px;box-shadow:0 1px 3px rgba(0,0,0,.04);">
<p style="margin:0 0 6px;font-size:.72rem;font-weight:600;letter-spacing:.18em;text-transform:uppercase;color:#C4A86A;">Winters × Saddle Up AI</p>
<h1 style="margin:0 0 10px;font-size:1.35rem;font-weight:600;color:#111;letter-spacing:-.01em;">${noDate ? "Thanks for signing up" : `Your ${titleWord} is Scheduled`}</h1>
<p style="margin:0 0 22px;font-size:.95rem;line-height:1.6;color:#444;">Hi ${firstName},</p>
<p style="margin:0 0 24px;font-size:.95rem;line-height:1.6;color:#444;">${noDate ? `Thanks for signing up for the Winters × Saddle Up AI ${signUpNoun}. We'll follow up shortly with your scheduled date and the Google Meet link.` : `This is a reminder for your upcoming ${sessionNoun}.`}</p>
${noDate ? "" : `<p style="margin:0 0 6px;font-size:.72rem;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:#555;">Date</p>
<p style="margin:0 0 22px;font-size:1rem;color:#111;">${dateLabel} · ${timeLine}</p>
<a href="${meetLink}" style="display:inline-block;background:#111;color:#fff;border-radius:9px;padding:14px 30px;font-size:.85rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;text-decoration:none;">Join the ${joinLabel}</a>
<p style="margin:18px 0 0;font-size:.78rem;color:#6b6b6b;">Please join a few minutes early.</p>`}
</div>
<p style="margin:14px 0 0;font-size:.7rem;color:#9a9a9a;letter-spacing:.04em;text-align:center;">— Winters × Saddle Up AI</p>
</div>
</body></html>`;

    const text = noDate
      ? `Hi ${firstName},\n\nThanks for signing up for the Winters × Saddle Up AI ${signUpNoun}. We'll follow up shortly with your scheduled date and the Google Meet link.\n\n— Winters × Saddle Up AI`
      : `Hi ${firstName},\n\nThis is a reminder for your upcoming ${sessionNoun}.\n\nDate: ${dateLabel}\nTime: ${timeLine}\n\nJoin the ${joinLabel.toLowerCase()} here:\n${meetLink}\n\nPlease join a few minutes early.\n\n— Winters × Saddle Up AI`;

    const apiKey = Deno.env.get("RESEND_API_KEY");
    const fromEmail = Deno.env.get("RESEND_FROM_EMAIL");
    if (!apiKey || !fromEmail) {
      return Response.json({ error: "Email provider not configured" }, { status: 500 });
    }

    const res = await fetch("https://api.resend.com/emails", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ from: fromEmail, to: email, subject, html, text }),
    });

    if (!res.ok) {
      const errText = await res.text();
      return Response.json({ error: `Resend error: ${errText}` }, { status: 502 });
    }

    const data = await res.json();
    return Response.json({ ok: true, id: data.id });
  } catch (error) {
    return Response.json({ error: error.message }, { status: 500 });
  }
});
```
