diff --git a/web/modules/custom/riverside_pt/js/components/rpt-faq.js b/web/modules/custom/riverside_pt/js/components/rpt-faq.js
index 2681244..43a4930 100644
--- a/web/modules/custom/riverside_pt/js/components/rpt-faq.js
+++ b/web/modules/custom/riverside_pt/js/components/rpt-faq.js
@@ -21,7 +21,7 @@ const FAQS = [
},
{
q: "Can I book an appointment online?",
- a: "Yes. Use the booking tool on our Schedule page to pick a service type, choose an available slot, and confirm your appointment. You'll receive a confirmation email immediately.",
+ a: "Yes. Use the booking tool on this page to pick a service type, choose an available slot, and submit your request. You'll receive a confirmation email immediately.",
},
{
q: "What should I wear or bring to my session?",
diff --git a/web/modules/custom/riverside_pt/riverside_pt.install b/web/modules/custom/riverside_pt/riverside_pt.install
index f4e825c..e3701d7 100644
--- a/web/modules/custom/riverside_pt/riverside_pt.install
+++ b/web/modules/custom/riverside_pt/riverside_pt.install
@@ -223,7 +223,7 @@ function _riverside_pt_build_navigation(): void {
['title' => 'FAQ', 'uri' => 'internal:/faq', 'weight' => 3, 'class' => NULL],
['title' => 'Contact', 'uri' => 'internal:/contact', 'weight' => 4, 'class' => 'nav-cta nav-cta--primary'],
- ['title' => 'Book An Appointment', 'uri' => 'internal:/schedule', 'weight' => 5, 'class' => 'nav-cta nav-cta--primary'],
+ ['title' => 'Book An Appointment', 'uri' => 'internal:/home', 'weight' => 5, 'class' => 'nav-cta nav-cta--primary'],
];
foreach ($defs as $def) {
diff --git a/web/modules/custom/riverside_pt/riverside_pt.libraries.yml b/web/modules/custom/riverside_pt/riverside_pt.libraries.yml
index b08f972..cf54423 100644
--- a/web/modules/custom/riverside_pt/riverside_pt.libraries.yml
+++ b/web/modules/custom/riverside_pt/riverside_pt.libraries.yml
@@ -15,7 +15,6 @@ schedule:
css/calendar.css: {}
js:
js/fullcalendar.min.js: { minified: true }
- js/calendar.js: {}
js/components/rpt-booking.js: { attributes: { type: module } }
dependencies:
- core/drupalSettings
diff --git a/web/modules/custom/riverside_pt/riverside_pt.module b/web/modules/custom/riverside_pt/riverside_pt.module
index 86ee670..c068544 100644
--- a/web/modules/custom/riverside_pt/riverside_pt.module
+++ b/web/modules/custom/riverside_pt/riverside_pt.module
@@ -74,14 +74,6 @@ function riverside_pt_preprocess_riverside_pt_header(array &$variables): void {
$variables['current_path'] = \Drupal::request()->getPathInfo();
}
-function riverside_pt_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context): void {
- if ($route_match->getRouteName() === 'riverside_pt.booking') {
- $breadcrumb = new Breadcrumb();
- $breadcrumb->addLink(Link::createFromRoute('← Back', 'riverside_pt.schedule'));
- $breadcrumb->addCacheContexts(['route']);
- }
-}
-
function riverside_pt_mail(string $key, array &$message, array $params): void {
if ($key !== 'booking_request') {
return;
diff --git a/web/modules/custom/riverside_pt/riverside_pt.routing.yml b/web/modules/custom/riverside_pt/riverside_pt.routing.yml
index ebce8a2..94e1b49 100644
--- a/web/modules/custom/riverside_pt/riverside_pt.routing.yml
+++ b/web/modules/custom/riverside_pt/riverside_pt.routing.yml
@@ -14,22 +14,6 @@ riverside_pt.home:
requirements:
_permission: 'access content'
-riverside_pt.schedule:
- path: '/schedule'
- defaults:
- _controller: '\Drupal\riverside_pt\Controller\ScheduleController::page'
- _title: 'Schedule'
- requirements:
- _permission: 'access content'
-
-riverside_pt.booking:
- path: '/schedule/book'
- defaults:
- _form: '\Drupal\riverside_pt\Form\BookingForm'
- _title: 'Request Appointment'
- requirements:
- _permission: 'access content'
-
riverside_pt.booking_store_slot:
path: '/schedule/book/slot'
defaults:
diff --git a/web/modules/custom/riverside_pt/src/Controller/HomeController.php b/web/modules/custom/riverside_pt/src/Controller/HomeController.php
index 040301d..1dba4cf 100644
--- a/web/modules/custom/riverside_pt/src/Controller/HomeController.php
+++ b/web/modules/custom/riverside_pt/src/Controller/HomeController.php
@@ -21,7 +21,6 @@ class HomeController extends ControllerBase {
'drupalSettings' => [
'riversidePt' => [
'eventsUrl' => Url::fromRoute('riverside_pt.schedule_events')->toString(),
- 'bookingUrl' => Url::fromRoute('riverside_pt.booking')->toString(),
'storeSlotUrl' => Url::fromRoute('riverside_pt.booking_store_slot')->toString(),
'holidays' => $holidayMap,
],
diff --git a/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php b/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php
index 7a3c29d..068a032 100644
--- a/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php
+++ b/web/modules/custom/riverside_pt/src/Controller/ScheduleController.php
@@ -5,7 +5,8 @@ namespace Drupal\riverside_pt\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\TempStore\PrivateTempStore;
use Drupal\Core\TempStore\PrivateTempStoreFactory;
-use Drupal\Core\Url;
+use Drupal\Core\Mail\MailManagerInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -13,66 +14,23 @@ use Symfony\Component\HttpFoundation\Request;
class ScheduleController extends ControllerBase {
private PrivateTempStore $tempStore;
+ private $configFactory;
- public function __construct(PrivateTempStoreFactory $tempStoreFactory) {
+ public function __construct(
+ PrivateTempStoreFactory $tempStoreFactory,
+ private readonly MailManagerInterface $mailManager,
+ ConfigFactoryInterface $configFactory,
+ ) {
$this->tempStore = $tempStoreFactory->get('riverside_pt');
+ $this->configFactory = $configFactory;
}
public static function create(ContainerInterface $container): static {
- return new static($container->get('tempstore.private'));
- }
-
- public function page(): array {
- return [
- '#type' => 'container',
- 'intro' => [
- '#type' => 'html_tag',
- '#tag' => 'p',
- '#value' => $this->t('View provider availability below. Use the calendar to browse open appointment slots by week.'),
- ],
- 'booking_wrap' => [
- '#type' => 'html_tag',
- '#tag' => 'div',
- '#attributes' => ['class' => ['riverside-booking-wrap']],
- 'calendar' => [
- '#type' => 'html_tag',
- '#tag' => 'div',
- '#attributes' => ['id' => 'riverside-calendar'],
- '#value' => '',
- ],
- 'slots_wrap' => [
- '#type' => 'html_tag',
- '#tag' => 'div',
- '#attributes' => ['id' => 'riverside-slots-wrap', 'hidden' => TRUE],
- 'slots' => [
- '#type' => 'html_tag',
- '#tag' => 'div',
- '#attributes' => ['id' => 'riverside-booking-slots'],
- '#value' => '',
- ],
- ],
- ],
- '#attached' => [
- 'library' => ['riverside_pt/schedule'],
- 'drupalSettings' => [
- 'riversidePt' => [
- 'eventsUrl' => Url::fromRoute('riverside_pt.schedule_events')->toString(),
- 'bookingUrl' => Url::fromRoute('riverside_pt.booking')->toString(),
- 'storeSlotUrl' => Url::fromRoute('riverside_pt.booking_store_slot')->toString(),
- 'holidays' => $this->buildHolidaysMap(),
- ],
- ],
- ],
- ];
- }
-
- private function buildHolidaysMap(): array {
- $holidays = $this->config('riverside_pt.settings')->get('holidays') ?? [];
- $map = [];
- foreach ($holidays as $holiday) {
- $map[$holiday['date']] = $holiday['name'];
- }
- return $map;
+ return new static(
+ $container->get('tempstore.private'),
+ $container->get('plugin.manager.mail'),
+ $container->get('config.factory'),
+ );
}
public function storeSlot(Request $request): JsonResponse {
@@ -83,14 +41,61 @@ class ScheduleController extends ControllerBase {
return new JsonResponse(['error' => 'past'], 422);
}
+ $firstName = trim($data['firstName'] ?? $data['first_name'] ?? '');
+ $lastName = trim($data['lastName'] ?? $data['last_name'] ?? '');
+ $phone = trim($data['phone'] ?? '');
+ $comments = $data['comments'] ?? '';
+ $service = $data['service'] ?? 'diagnostic';
+ $end = $data['end'] ?? '';
+ $providerId = $data['provider_id'] ?? '';
+
+ // 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) {
+ // Prevent double-booking against existing appointment nodes (same logic as before).
+ $conflict = \Drupal::entityQuery('node')
+ ->condition('type', 'appointment')
+ ->condition('field_appointment_date', $start)
+ ->condition('field_provider', $providerId ?: 0)
+ ->accessCheck(FALSE)
+ ->count()
+ ->execute();
+
+ if ($conflict > 0) {
+ return new JsonResponse(['error' => 'conflict'], 422);
+ }
+
+ $to = $this->configFactory->get('riverside_pt.settings')->get('notification_email');
+ $lang = \Drupal::languageManager()->getDefaultLanguage()->getId();
+
+ $sent = $this->mailManager->mail('riverside_pt', 'booking_request', $to, $lang, [
+ 'first_name' => $firstName,
+ 'last_name' => $lastName,
+ 'phone' => $phone,
+ 'comments' => $comments,
+ 'start' => $start,
+ 'end' => $end,
+ ]);
+
+ $this->tempStore->delete('booking_slot');
+
+ if ($sent['result']) {
+ return new JsonResponse(['ok' => TRUE]);
+ }
+ return new JsonResponse(['error' => 'mail_failed'], 500);
+ }
+
+ // Legacy/minimal path (no contact details): just stash in tempstore (for any
+ // remaining callers that don't send full info).
$this->tempStore->set('booking_slot', [
'start' => $start,
- 'end' => $data['end'] ?? '',
- 'service' => $data['service'] ?? 'diagnostic',
- 'last_name' => $data['lastName'] ?? '',
- 'phone' => $data['phone'] ?? '',
- 'comments' => $data['comments'] ?? '',
- 'provider_id' => $data['provider_id'] ?? '',
+ 'end' => $end,
+ 'service' => $service,
+ 'last_name' => $lastName,
+ 'phone' => $phone,
+ 'comments' => $comments,
+ 'provider_id' => $providerId,
]);
return new JsonResponse(['ok' => TRUE]);
diff --git a/web/modules/custom/riverside_pt/src/Form/BookingForm.php b/web/modules/custom/riverside_pt/src/Form/BookingForm.php
deleted file mode 100644
index 86b75e1..0000000
--- a/web/modules/custom/riverside_pt/src/Form/BookingForm.php
+++ /dev/null
@@ -1,159 +0,0 @@
-configFactory = $configFactory;
- $this->tempStore = $tempStoreFactory->get('riverside_pt');
- }
-
- public static function create(ContainerInterface $container): static {
- return new static(
- $container->get('plugin.manager.mail'),
- $container->get('config.factory'),
- $container->get('tempstore.private'),
- );
- }
-
- public function getFormId(): string {
- return 'riverside_pt_booking_form';
- }
-
- public function buildForm(array $form, FormStateInterface $form_state): array {
- $slot = $this->tempStore->get('booking_slot') ?? [];
- $start = $slot['start'] ?? '';
- $end = $slot['end'] ?? '';
- $uid = $slot['provider_id'] ?? '';
-
- $slot_display = '';
- if ($start && $end) {
- $s = new \DateTime($start);
- $e = new \DateTime($end);
- $slot_display = $s->format('l, F j, Y') . ', ' . $s->format('g:i A') . '–' . $e->format('g:i A');
- }
-
- $form['#cache'] = ['max-age' => 0];
-
- $form['slot_summary'] = [
- '#type' => 'item',
- '#title' => $this->t('Appointment'),
- '#markup' => $slot_display ?: $this->t('No slot selected.'),
- ];
-
- if ($uid && $provider = User::load($uid)) {
- $form['provider_summary'] = [
- '#type' => 'item',
- '#title' => $this->t('Provider'),
- '#markup' => $provider->getDisplayName(),
- ];
- }
-
- $form['first_name'] = [
- '#type' => 'textfield',
- '#title' => $this->t('First name'),
- '#required' => TRUE,
- ];
-
- $form['last_name'] = [
- '#type' => 'textfield',
- '#title' => $this->t('Last name'),
- '#required' => TRUE,
- '#default_value' => $slot['last_name'] ?? '',
- ];
-
- $form['phone'] = [
- '#type' => 'tel',
- '#title' => $this->t('Phone number'),
- '#required' => TRUE,
- '#default_value' => $slot['phone'] ?? '',
- '#attributes' => ['class' => ['rpt-phone']],
- ];
-
- $form['comments'] = [
- '#type' => 'textarea',
- '#title' => $this->t('Comments'),
- '#rows' => 4,
- '#default_value' => $slot['comments'] ?? '',
- ];
-
- $form['actions'] = ['#type' => 'actions'];
- $form['actions']['submit'] = [
- '#type' => 'submit',
- '#value' => $this->t('Request appointment'),
- ];
-
- return $form;
- }
-
- public function validateForm(array &$form, FormStateInterface $form_state): void {
- $slot = $this->tempStore->get('booking_slot') ?? [];
- $start = $slot['start'] ?? '';
-
- if (!$start) {
- $form_state->setError($form['slot_summary'], $this->t('No slot selected. Please go back and choose a time.'));
- return;
- }
-
- if (new \DateTime($start) < new \DateTime()) {
- $form_state->setError($form['slot_summary'], $this->t('That slot is in the past. Please go back and choose another time.'));
- return;
- }
-
- $provider_id = $slot['provider_id'] ?? '';
- $conflict = \Drupal::entityQuery('node')
- ->condition('type', 'appointment')
- ->condition('field_appointment_date', $start)
- ->condition('field_provider', $provider_id ?: 0)
- ->accessCheck(FALSE)
- ->count()
- ->execute();
-
- if ($conflict > 0) {
- $form_state->setError($form['slot_summary'], $this->t('That slot was just booked. Please go back and choose another time.'));
- }
- }
-
- public function submitForm(array &$form, FormStateInterface $form_state): void {
- $slot = $this->tempStore->get('booking_slot') ?? [];
- $this->tempStore->delete('booking_slot');
-
- $to = $this->configFactory->get('riverside_pt.settings')->get('notification_email');
- $lang = $this->languageManager()->getDefaultLanguage()->getId();
-
- $sent = $this->mailManager->mail('riverside_pt', 'booking_request', $to, $lang, [
- 'first_name' => $form_state->getValue('first_name'),
- 'last_name' => $form_state->getValue('last_name'),
- 'phone' => $form_state->getValue('phone'),
- 'comments' => $form_state->getValue('comments'),
- 'start' => $slot['start'] ?? '',
- 'end' => $slot['end'] ?? '',
- ]);
-
- if ($sent['result']) {
- $this->messenger()->addStatus($this->t('Your request has been submitted. We will contact you to confirm.'));
- }
- else {
- $this->messenger()->addError($this->t('Something went wrong. Please call us to book directly.'));
- }
-
- $form_state->setRedirect('riverside_pt.schedule');
- }
-
-}
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 490ae27..e5e0716 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
@@ -30,7 +30,7 @@
Every new patient starts with a comprehensive diagnostic assessment. From there we build a personalized plan that may include sports rehabilitation, pre- or post-surgical recovery, or neurological physical therapy.