From 8962fc5f0e28528aea3af6cd9fe06427e88548eb Mon Sep 17 00:00:00 2001 From: Philip Peterson <1326208+philip-peterson@users.noreply.github.com> Date: Wed, 3 Jun 2026 22:14:39 -0700 Subject: [PATCH] Smooth scroll, booking refactor, success summary - Add scroll.js: data-scroll-to attribute drives smooth scrollIntoView; scroll-margin-top at md+ accounts for fixed header offset - Wire Services, FAQ, Book An Appointment, View Our Services nav/hero links to on-page anchors; don't close hamburger on scroll-link clicks - Refactor booking calendar: own the fetch (useEffect + dateRange state) instead of handing URL to FullCalendar; removes fetchedRef complexity; noSlotsInMonth derived cleanly from fetchLoading + fetchedEvents - Success state shows appointment summary (name, service, date/time); hides calendar/form on success; no "book another" button Co-Authored-By: Claude Sonnet 4.6 --- web/modules/custom/riverside_pt/css/app.css | 125 +++++---- .../custom/riverside_pt/css/tailwind.css | 6 + .../riverside_pt/js/components/rpt-booking.js | 250 ++++++++++-------- web/modules/custom/riverside_pt/js/nav.js | 1 + web/modules/custom/riverside_pt/js/scroll.js | 13 + .../riverside_pt/riverside_pt.libraries.yml | 1 + .../custom/riverside_pt/riverside_pt.module | 4 + .../src/Controller/ScheduleController.php | 6 +- .../templates/riverside-pt-header.html.twig | 9 +- .../templates/riverside-pt-home.html.twig | 5 +- 10 files changed, 245 insertions(+), 175 deletions(-) create mode 100644 web/modules/custom/riverside_pt/js/scroll.js diff --git a/web/modules/custom/riverside_pt/css/app.css b/web/modules/custom/riverside_pt/css/app.css index 12fdf13..518dbe8 100644 --- a/web/modules/custom/riverside_pt/css/app.css +++ b/web/modules/custom/riverside_pt/css/app.css @@ -558,6 +558,14 @@ video { html { background-color: #86aab6; + scroll-behavior: smooth; +} + +@media (min-width: 768px) { + [id] { + scroll-margin-top: 110px; + /* fixed header (78px) + breathing room */ + } } /* Neutralise any theme container constraints */ @@ -639,6 +647,10 @@ html { pointer-events: none; } +.visible{ + visibility: visible; +} + .static{ position: static; } @@ -689,6 +701,10 @@ html { margin-bottom: 0.125rem; } +.mb-1{ + margin-bottom: 0.25rem; +} + .mb-10{ margin-bottom: 2.5rem; } @@ -713,6 +729,10 @@ html { margin-bottom: 1.25rem; } +.mb-6{ + margin-bottom: 1.5rem; +} + .mb-\[1vw\]{ margin-bottom: 1vw; } @@ -733,28 +753,12 @@ html { margin-top: 0.5rem; } -.mt-\[2vw\]{ - margin-top: 2vw; -} - -.mb-1{ - margin-bottom: 0.25rem; -} - -.mb-6{ - margin-bottom: 1.5rem; -} - .mt-8{ margin-top: 2rem; } -.mt-1{ - margin-top: 0.25rem; -} - -.mt-3{ - margin-top: 0.75rem; +.mt-\[2vw\]{ + margin-top: 2vw; } .block{ @@ -1073,6 +1077,11 @@ html { border-color: rgb(209 213 219 / var(--tw-border-opacity, 1)); } +.border-green-200{ + --tw-border-opacity: 1; + border-color: rgb(187 247 208 / var(--tw-border-opacity, 1)); +} + .border-pt-blue-200{ --tw-border-opacity: 1; border-color: rgb(184 212 220 / var(--tw-border-opacity, 1)); @@ -1102,15 +1111,15 @@ html { border-color: rgb(255 255 255 / 0.6); } -.border-green-200{ - --tw-border-opacity: 1; - border-color: rgb(187 247 208 / var(--tw-border-opacity, 1)); -} - .bg-current{ background-color: currentColor; } +.bg-green-50{ + --tw-bg-opacity: 1; + background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1)); +} + .bg-pt-blue-100{ --tw-bg-opacity: 1; background-color: rgb(221 232 240 / var(--tw-bg-opacity, 1)); @@ -1159,11 +1168,6 @@ html { background-color: rgb(255 255 255 / 0.9); } -.bg-green-50{ - --tw-bg-opacity: 1; - background-color: rgb(240 253 244 / var(--tw-bg-opacity, 1)); -} - .bg-gradient-to-b{ background-image: linear-gradient(to bottom, var(--tw-gradient-stops)); } @@ -1227,6 +1231,15 @@ html { padding: 1.5rem; } +.p-8{ + padding: 2rem; +} + +.px-3{ + padding-left: 0.75rem; + padding-right: 0.75rem; +} + .px-4{ padding-left: 1rem; padding-right: 1rem; @@ -1282,11 +1295,6 @@ html { padding-bottom: 1em; } -.px-3{ - padding-left: 0.75rem; - padding-right: 0.75rem; -} - .pb-1{ padding-bottom: 0.25rem; } @@ -1303,20 +1311,20 @@ html { padding-top: 0px; } -.pt-4{ - padding-top: 1rem; +.pt-20{ + padding-top: 5rem; } -.pt-px{ - padding-top: 1px; +.pt-4{ + padding-top: 1rem; } .pt-8{ padding-top: 2rem; } -.pt-20{ - padding-top: 5rem; +.pt-px{ + padding-top: 1px; } .text-left{ @@ -1495,11 +1503,26 @@ html { color: rgb(17 24 39 / var(--tw-text-opacity, 1)); } +.text-green-700{ + --tw-text-opacity: 1; + color: rgb(21 128 61 / var(--tw-text-opacity, 1)); +} + +.text-green-800{ + --tw-text-opacity: 1; + color: rgb(22 101 52 / var(--tw-text-opacity, 1)); +} + .text-pt-blue-500{ --tw-text-opacity: 1; color: rgb(48 111 142 / var(--tw-text-opacity, 1)); } +.text-red-500{ + --tw-text-opacity: 1; + color: rgb(239 68 68 / var(--tw-text-opacity, 1)); +} + .text-white{ --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity, 1)); @@ -1513,25 +1536,6 @@ html { color: rgb(255 255 255 / 0.8); } -.text-red-500{ - --tw-text-opacity: 1; - color: rgb(239 68 68 / var(--tw-text-opacity, 1)); -} - -.text-green-700{ - --tw-text-opacity: 1; - color: rgb(21 128 61 / var(--tw-text-opacity, 1)); -} - -.text-green-800{ - --tw-text-opacity: 1; - color: rgb(22 101 52 / var(--tw-text-opacity, 1)); -} - -.underline{ - text-decoration-line: underline; -} - .no-underline{ text-decoration-line: none; } @@ -1637,11 +1641,6 @@ html { color: rgb(255 255 255 / var(--tw-text-opacity, 1)); } -.hover\:text-green-800:hover{ - --tw-text-opacity: 1; - color: rgb(22 101 52 / var(--tw-text-opacity, 1)); -} - .focus\:border-pt-blue-500:focus{ --tw-border-opacity: 1; border-color: rgb(48 111 142 / var(--tw-border-opacity, 1)); diff --git a/web/modules/custom/riverside_pt/css/tailwind.css b/web/modules/custom/riverside_pt/css/tailwind.css index bec02b5..499c702 100644 --- a/web/modules/custom/riverside_pt/css/tailwind.css +++ b/web/modules/custom/riverside_pt/css/tailwind.css @@ -7,6 +7,12 @@ @layer base { html { background-color: theme('colors.pt-blue.400'); + scroll-behavior: smooth; + } + @media (min-width: theme('screens.sm')) { + [id] { + scroll-margin-top: 110px; /* fixed header (78px) + breathing room */ + } } /* Neutralise any theme container constraints */ .page-wrapper { diff --git a/web/modules/custom/riverside_pt/js/components/rpt-booking.js b/web/modules/custom/riverside_pt/js/components/rpt-booking.js index 8462eb0..3cd8042 100644 --- a/web/modules/custom/riverside_pt/js/components/rpt-booking.js +++ b/web/modules/custom/riverside_pt/js/components/rpt-booking.js @@ -18,7 +18,6 @@ const EMPTY_FORM = { firstName: "", lastName: "", phone: "", comments: "" }; function formatPhone(raw) { let d = String(raw || "").replace(/\D/g, ""); if (d.length === 11 && d[0] === "1") { - // NANP with leading 1: show "1 (xxx) xxx-xxxx" const rest = d.slice(1); return "1 (" + rest.slice(0, 3) + ") " + rest.slice(3, 6) + "-" + rest.slice(6); } @@ -60,19 +59,21 @@ const CX = { submitBtn: "px-[4em] py-[1em] bg-pt-blue-500 text-white text-sm font-medium transition-colors border-2 border-pt-blue-500 hover:bg-pt-blue-600 hover:border-pt-blue-600 disabled:opacity-50", // ── Calendar overlay ────────────────────────────────────────────────── - calWrapper: "relative", - noSlotsOverlay: "absolute inset-0 z-10 flex items-center justify-center pt-20 pointer-events-none", + calWrapper: "relative", + noSlotsOverlay: "absolute inset-0 z-10 flex items-center justify-center pt-20 pointer-events-none", // ── Success state ────────────────────────────────────────────────────── successSection: "mt-8 pt-8 border-t border-pt-blue-200", - successBox: "p-6 bg-green-50 border border-green-200 text-green-800", - successTitle: "font-medium", - successBody: "text-sm mt-1", - successLink: "mt-3 text-sm text-green-700 underline hover:text-green-800", + successBox: "p-8 bg-green-50 border border-green-200 text-green-800", + successTitle: "text-2xl font-semibold mb-4", + successSummary: "flex flex-col gap-1 text-base mb-4", + successNote: "text-sm text-green-700", }; +// Module-level vars accessible from FullCalendar callbacks without stale closures. var selectedDate = null; var selectedDateSlots = []; +var currentEvents = []; function localDateStr(d) { return d.getFullYear() + "-" + @@ -87,47 +88,48 @@ function nextBusinessDay() { return localDateStr(d); } -function slotLabel(date) { - var h = date.getHours(); +function slotLabel(startStr) { + var d = new Date(startStr); + var h = d.getHours(); return (h % 12 || 12) + (h < 12 ? "AM" : "PM") + " PST"; } +function formatAppointmentDate(startStr) { + var parts = startStr.split("T")[0].split("-"); + var d = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2])); + var date = d.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }); + return date + " at " + slotLabel(startStr); +} + function Booking({ settings }) { const [service, setService] = useState("diagnostic"); + const [dateRange, setDateRange] = useState(null); // "startStr/endStr", set by datesSet + const [fetchedEvents, setFetchedEvents] = useState(null); // null = not yet fetched + const [fetchLoading, setFetchLoading] = useState(false); const [slots, setSlots] = useState([]); const [selectedSlotId, setSelectedSlotId] = useState(null); const [formData, setFormData] = useState(EMPTY_FORM); const [submitting, setSubmitting] = useState(false); const [submitError, setSubmitError] = useState(null); const [success, setSuccess] = useState(false); + const [confirmedAppointment, setConfirmedAppointment] = useState(null); const [noSlotsInMonth, setNoSlotsInMonth] = useState(false); const calEl = useRef(null); const calRef = useRef(null); const initializedRef = useRef(false); - const prevServiceRef = useRef(null); const autoAdvanceRef = useRef(0); - const fetchedRef = useRef(false); + const isFirstServiceRender = useRef(true); const initDate = useMemo(nextBusinessDay, []); function buildEventsUrl(svc) { return settings.eventsUrl + "?service=" + svc; } + // ── Initialize FullCalendar once ─────────────────────────────────────── useEffect(function () { if (!calEl.current || !window.FullCalendar) return; - function markDays(events) { - calEl.current.querySelectorAll(".fc-daygrid-day.has-availability").forEach(function (d) { - d.classList.remove("has-availability"); - }); - events.forEach(function (event) { - var dateStr = event.startStr.substring(0, 10); - var dayEl = calEl.current.querySelector(".fc-daygrid-day[data-date=\"" + dateStr + "\"]"); - if (dayEl) dayEl.classList.add("has-availability"); - }); - } - var cal = new FullCalendar.Calendar(calEl.current, { initialView: "dayGridMonth", initialDate: initDate, @@ -146,17 +148,13 @@ function Booking({ settings }) { eventDisplay: "none", dayMaxEvents: false, - loading: function (isLoading) { - if (!isLoading) fetchedRef.current = true; - }, - - datesSet: function () { + // Visible range changed. Sets dateRange state → triggers fetch effect. + datesSet: function (info) { calEl.current.querySelectorAll(".fc-daygrid-day.is-selected").forEach(function (d) { d.classList.remove("is-selected"); }); setSelectedSlotId(null); setNoSlotsInMonth(false); - fetchedRef.current = false; if (selectedDate) { var dayEl = calEl.current.querySelector(".fc-daygrid-day[data-date=\"" + selectedDate + "\"]"); if (dayEl) { @@ -168,38 +166,20 @@ function Booking({ settings }) { } else { setSlots([]); } + setDateRange(info.startStr + "/" + info.endStr); }, + // Mark which day cells have availability whenever FullCalendar's + // event list changes (fires after addEventSource / removeAllEventSources). eventsSet: function (events) { - markDays(events); - if (!initializedRef.current && fetchedRef.current) { - fetchedRef.current = false; - var dates = [...new Set(events.map(function (e) { return e.startStr.substring(0, 10); }))].sort(); - var firstDate = dates[0]; - if (firstDate) { - initializedRef.current = true; - autoAdvanceRef.current = 0; - var firstSlots = events - .filter(function (e) { return e.startStr.startsWith(firstDate); }) - .sort(function (a, b) { return a.start - b.start; }); - selectedDate = firstDate; - selectedDateSlots = firstSlots; - var targetEl = calEl.current.querySelector(".fc-daygrid-day[data-date=\"" + firstDate + "\"]"); - if (targetEl) { - targetEl.classList.add("is-selected"); - setSlots(firstSlots); - } - } else if (autoAdvanceRef.current < 12) { - autoAdvanceRef.current++; - cal.next(); - } - } - if (initializedRef.current && fetchedRef.current) { - var viewStart = cal.view.currentStart; - var viewEnd = cal.view.currentEnd; - var inView = events.filter(function (e) { return e.start >= viewStart && e.start < viewEnd; }); - setNoSlotsInMonth(inView.length === 0); - } + calEl.current.querySelectorAll(".fc-daygrid-day.has-availability").forEach(function (d) { + d.classList.remove("has-availability"); + }); + events.forEach(function (event) { + var dateStr = event.startStr.substring(0, 10); + var dayEl = calEl.current.querySelector(".fc-daygrid-day[data-date=\"" + dateStr + "\"]"); + if (dayEl) dayEl.classList.add("has-availability"); + }); }, dayCellClassNames: function (arg) { @@ -213,9 +193,9 @@ function Booking({ settings }) { d.classList.remove("is-selected"); }); arg.dayEl.classList.add("is-selected"); - var daySlots = cal.getEvents() - .filter(function (e) { return e.startStr.startsWith(arg.dateStr); }) - .sort(function (a, b) { return a.start - b.start; }); + var daySlots = currentEvents + .filter(function (e) { return e.start.startsWith(arg.dateStr); }) + .sort(function (a, b) { return a.start < b.start ? -1 : 1; }); selectedDate = arg.dateStr; selectedDateSlots = daySlots; setSelectedSlotId(null); @@ -230,29 +210,87 @@ function Booking({ settings }) { return function () { cal.destroy(); }; }, []); + // ── Fetch events when service or visible range changes ───────────────── + useEffect(function () { + if (!dateRange) return; + var parts = dateRange.split("/"); + var url = buildEventsUrl(service) + "&start=" + parts[0] + "&end=" + parts[1]; + setFetchLoading(true); + setFetchedEvents(null); + fetch(url) + .then(function (r) { return r.json(); }) + .then(function (data) { + currentEvents = data; + setFetchedEvents(data); + setFetchLoading(false); + }) + .catch(function () { + currentEvents = []; + setFetchedEvents([]); + setFetchLoading(false); + }); + }, [service, dateRange]); + + // ── Push fetched events into FullCalendar; auto-advance or auto-select ─ + useEffect(function () { + if (fetchedEvents === null) return; + var cal = calRef.current; + if (!cal) return; + + cal.removeAllEventSources(); + + if (fetchedEvents.length > 0) { + cal.addEventSource(fetchedEvents); // fires eventsSet → marks availability + + if (!initializedRef.current) { + var dates = [...new Set(fetchedEvents.map(function (e) { return e.start.substring(0, 10); }))].sort(); + var firstDate = dates[0]; + if (firstDate) { + initializedRef.current = true; + autoAdvanceRef.current = 0; + var firstSlots = fetchedEvents + .filter(function (e) { return e.start.startsWith(firstDate); }) + .sort(function (a, b) { return a.start < b.start ? -1 : 1; }); + selectedDate = firstDate; + selectedDateSlots = firstSlots; + var targetEl = calEl.current.querySelector(".fc-daygrid-day[data-date=\"" + firstDate + "\"]"); + if (targetEl) { + targetEl.classList.add("is-selected"); + setSlots(firstSlots); + } + } + } + } else if (!initializedRef.current && autoAdvanceRef.current < 12) { + autoAdvanceRef.current++; + cal.next(); // fires datesSet → setDateRange → fetch effect re-runs + } else if (initializedRef.current) { + setNoSlotsInMonth(true); + } + }, [fetchedEvents]); + + // ── Reset and navigate when service changes ──────────────────────────── useEffect(function () { var cal = calRef.current; if (!cal) return; - var isInitial = prevServiceRef.current === null; - prevServiceRef.current = service; - if (!isInitial) { - initializedRef.current = false; - autoAdvanceRef.current = 0; - fetchedRef.current = false; - selectedDate = null; - selectedDateSlots = []; - setSlots([]); - setSelectedSlotId(null); - setFormData(EMPTY_FORM); - setSubmitError(null); - setSuccess(false); - setNoSlotsInMonth(false); - cal.gotoDate(initDate); + if (isFirstServiceRender.current) { + isFirstServiceRender.current = false; + return; } - cal.removeAllEventSources(); - cal.addEventSource(buildEventsUrl(service)); + selectedDate = null; + selectedDateSlots = []; + currentEvents = []; + initializedRef.current = false; + autoAdvanceRef.current = 0; + setSlots([]); + setSelectedSlotId(null); + setFormData(EMPTY_FORM); + setSubmitError(null); + setSuccess(false); + setNoSlotsInMonth(false); + setFetchedEvents(null); + cal.gotoDate(initDate); // fires datesSet → setDateRange → fetch effect }, [service]); function handleSlotClick(slot) { @@ -275,8 +313,8 @@ function Booking({ settings }) { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - start: slot.startStr, - end: slot.endStr, + start: slot.start, + end: slot.end, service: service, firstName: formData.firstName, lastName: formData.lastName, @@ -287,16 +325,20 @@ function Booking({ settings }) { if (res.ok) { setSubmitting(false); setSubmitError(null); + setConfirmedAppointment({ + start: slot.start, + service: service, + firstName: formData.firstName, + lastName: formData.lastName, + }); setSuccess(true); setSelectedSlotId(null); setFormData(EMPTY_FORM); } else { setSubmitting(false); - if (res.status === 422) { - setSubmitError("That slot was just booked. Please choose another time."); - } else { - setSubmitError("Something went wrong. Please try again."); - } + setSubmitError(res.status === 422 + ? "That slot was just booked. Please choose another time." + : "Something went wrong. Please try again."); } }).catch(function () { setSubmitting(false); @@ -308,6 +350,7 @@ function Booking({ settings }) { return html`
+ ${!success ? html`

Select Appointment Type

${TYPES.map(function (t) { @@ -364,29 +407,7 @@ function Booking({ settings }) { ` : null}
- ${success ? html` -
-
-

Request received!

-

Thank you. We'll contact you shortly to confirm your appointment.

- -
-
- ` : null} - - ${selectedSlot ? html` + ${!success && selectedSlot ? html`

Your Details

@@ -456,6 +477,21 @@ function Booking({ settings }) {
` : null} + ` : null} + + ${success && confirmedAppointment ? html` +
+
+

Request received!

+
+

${confirmedAppointment.firstName} ${confirmedAppointment.lastName}

+

${TYPES.find(function (t) { return t.id === confirmedAppointment.service; }).label}

+

${formatAppointmentDate(confirmedAppointment.start)}

+
+

We'll contact you shortly to confirm your appointment.

+
+
+ ` : null}
`; } diff --git a/web/modules/custom/riverside_pt/js/nav.js b/web/modules/custom/riverside_pt/js/nav.js index 13f2b23..70447b6 100644 --- a/web/modules/custom/riverside_pt/js/nav.js +++ b/web/modules/custom/riverside_pt/js/nav.js @@ -13,6 +13,7 @@ nav.querySelectorAll('a').forEach(function (link) { link.addEventListener('click', function () { + if (link.dataset.scrollTo) return; // page scrolls away — no need to close nav.classList.remove('is-open'); btn.setAttribute('aria-expanded', 'false'); }); diff --git a/web/modules/custom/riverside_pt/js/scroll.js b/web/modules/custom/riverside_pt/js/scroll.js new file mode 100644 index 0000000..d35f578 --- /dev/null +++ b/web/modules/custom/riverside_pt/js/scroll.js @@ -0,0 +1,13 @@ +// Add data-scroll-to="#target-id" to any link to smooth-scroll instead of navigate. +(function () { + document.addEventListener('DOMContentLoaded', function () { + document.addEventListener('click', function (e) { + var link = e.target.closest('[data-scroll-to]'); + if (!link) return; + var target = document.querySelector(link.dataset.scrollTo); + if (!target) return; + e.preventDefault(); + target.scrollIntoView({ behavior: 'smooth' }); + }); + }); +})(); diff --git a/web/modules/custom/riverside_pt/riverside_pt.libraries.yml b/web/modules/custom/riverside_pt/riverside_pt.libraries.yml index cf54423..c168bb2 100644 --- a/web/modules/custom/riverside_pt/riverside_pt.libraries.yml +++ b/web/modules/custom/riverside_pt/riverside_pt.libraries.yml @@ -4,6 +4,7 @@ app: css/app.css: {} js: js/nav.js: {} + js/scroll.js: {} js/phone-format.js: {} js/components/rpt-toggle.js: { attributes: { type: module } } js/components/rpt-carousel.js: { attributes: { type: module } } diff --git a/web/modules/custom/riverside_pt/riverside_pt.module b/web/modules/custom/riverside_pt/riverside_pt.module index c068544..156d6d6 100644 --- a/web/modules/custom/riverside_pt/riverside_pt.module +++ b/web/modules/custom/riverside_pt/riverside_pt.module @@ -89,6 +89,10 @@ function riverside_pt_mail(string $key, array &$message, array $params): void { 'Slot: ' . $start->format('l, F j, Y') . ', ' . $start->format('g:i A') . '–' . $end->format('g:i A'), ]; + if (!empty($params['email'])) { + array_splice($lines, 1, 0, 'Email: ' . $params['email']); + } + if (!empty($params['comments'])) { $lines[] = 'Comments: ' . $params['comments']; } diff --git a/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php b/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php index ac18b88..2e54297 100644 --- a/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php +++ b/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php @@ -42,6 +42,7 @@ class ScheduleController extends ControllerBase { $firstName = trim($data['firstName'] ?? $data['first_name'] ?? ''); $lastName = trim($data['lastName'] ?? $data['last_name'] ?? ''); + $email = trim($data['email'] ?? ''); $phone = trim($data['phone'] ?? ''); $comments = $data['comments'] ?? ''; $service = $data['service'] ?? 'diagnostic'; @@ -51,7 +52,7 @@ class ScheduleController extends ControllerBase { // Full contact info present (new embedded booking flow on homepage): // validate, send the request email immediately, and return success. // This replaces the previous /schedule/book form page. - if ($firstName && $lastName && $phone) { + if ($firstName && $lastName && $email && $phone) { // Prevent double-booking against existing appointment nodes (same logic as before). $conflict = \Drupal::entityQuery('node') ->condition('type', 'appointment') @@ -71,6 +72,7 @@ class ScheduleController extends ControllerBase { $sent = $this->mailManager->mail('riverside_pt', 'booking_request', $to, $lang, [ 'first_name' => $firstName, 'last_name' => $lastName, + 'email' => $email, 'phone' => $phone, 'comments' => $comments, 'start' => $start, @@ -91,7 +93,9 @@ class ScheduleController extends ControllerBase { 'start' => $start, 'end' => $end, 'service' => $service, + 'first_name' => $firstName, 'last_name' => $lastName, + 'email' => $email, 'phone' => $phone, 'comments' => $comments, 'provider_id' => $providerId, diff --git a/web/modules/custom/riverside_pt/templates/riverside-pt-header.html.twig b/web/modules/custom/riverside_pt/templates/riverside-pt-header.html.twig index 7fe1d13..2b9bee3 100644 --- a/web/modules/custom/riverside_pt/templates/riverside-pt-header.html.twig +++ b/web/modules/custom/riverside_pt/templates/riverside-pt-header.html.twig @@ -20,7 +20,10 @@ text-[blue-900] font-medium {% endif %} " - href="{{ item.url }}">{{ item.title }} + href="{{ item.url }}" + {% if item.title == 'Services' %}data-scroll-to="#pt-services"{% endif %} + {% if item.title == 'FAQ' %}data-scroll-to="#pt-faq"{% endif %} + >{{ item.title }} {% endif %} {% endfor %} @@ -39,7 +42,9 @@ href="{{ item.url }}">{{ item.title }} {% else %} {{ item.title }} + href="{{ item.url }}" + {% if item.title == 'Book An Appointment' %}data-scroll-to="#book-an-appointment"{% endif %} + >{{ item.title }} {% endif %} {% endif %} {% endfor %} diff --git a/web/modules/custom/riverside_pt/templates/riverside-pt-home.html.twig b/web/modules/custom/riverside_pt/templates/riverside-pt-home.html.twig index e5e0716..13b9765 100644 --- a/web/modules/custom/riverside_pt/templates/riverside-pt-home.html.twig +++ b/web/modules/custom/riverside_pt/templates/riverside-pt-home.html.twig @@ -35,6 +35,7 @@ >Book An Appointment @@ -45,7 +46,7 @@ -
+

Bringing Relief

Our Wide Range of Physical Therapy Services

@@ -122,6 +123,6 @@
- +
\ No newline at end of file