From 0b24c320cdbe9a0b7b56da39089509cf05077981 Mon Sep 17 00:00:00 2001 From: Scott Pouliot Date: Wed, 13 May 2026 18:28:16 -0400 Subject: [PATCH] Fix kiosk intake routing, view names, and SignalR diagnostics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three bugs identified: 1. Routing: /Kiosk/Intake/{token}/{action} had no matching route — 4-segment URL fell through the default 3-segment {controller}/{action}/{id?} route. Added explicit kiosk_intake route in Program.cs. 2. View names: Contact/Job/Terms/Confirmation actions returned View(model) which resolved to Views/Kiosk/{Action}.cshtml — those files don't exist. Views live in Views/Kiosk/Intake/. Fixed all six return statements. 3. Diagnostics: conn dot now starts gray ("Connecting...") and turns green only when SignalR actually connects. Red + message if no company ID or connection fails. Makes it easy to confirm the hub connection is live. Co-Authored-By: Claude Sonnet 4.6 --- .../Controllers/KioskController.cs | 14 +++---- src/PowderCoating.Web/Program.cs | 6 +++ .../Views/Kiosk/Welcome.cshtml | 4 +- .../wwwroot/js/kiosk-welcome.js | 40 ++++++++++++------- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/PowderCoating.Web/Controllers/KioskController.cs b/src/PowderCoating.Web/Controllers/KioskController.cs index 5b61967..fa6686d 100644 --- a/src/PowderCoating.Web/Controllers/KioskController.cs +++ b/src/PowderCoating.Web/Controllers/KioskController.cs @@ -261,7 +261,7 @@ public class KioskController : Controller await PopulateKioskViewBagFromSession(session); ViewBag.KioskStep = 1; - return View(new SubmitKioskContactDto + return View("Intake/Contact", new SubmitKioskContactDto { FirstName = session.CustomerFirstName, LastName = session.CustomerLastName, @@ -283,7 +283,7 @@ public class KioskController : Controller { await PopulateKioskViewBagFromSession(session); ViewBag.KioskStep = 1; - return View(dto); + return View("Intake/Contact", dto); } session.CustomerFirstName = dto.FirstName.Trim(); @@ -308,7 +308,7 @@ public class KioskController : Controller await PopulateKioskViewBagFromSession(session); ViewBag.KioskStep = 2; - return View(new SubmitKioskJobDto + return View("Intake/Job", new SubmitKioskJobDto { JobDescription = session.JobDescription, HowDidYouHearAboutUs = session.HowDidYouHearAboutUs @@ -327,7 +327,7 @@ public class KioskController : Controller { await PopulateKioskViewBagFromSession(session); ViewBag.KioskStep = 2; - return View(dto); + return View("Intake/Job", dto); } session.JobDescription = dto.JobDescription.Trim(); @@ -350,7 +350,7 @@ public class KioskController : Controller await PopulateKioskViewBagFromSession(session); ViewBag.KioskStep = 3; ViewBag.IsInPerson = session.SessionType == KioskSessionType.InPerson; - return View(new SubmitKioskTermsDto()); + return View("Intake/Terms", new SubmitKioskTermsDto()); } /// @@ -376,7 +376,7 @@ public class KioskController : Controller await PopulateKioskViewBagFromSession(session); ViewBag.KioskStep = 3; ViewBag.IsInPerson = session.SessionType == KioskSessionType.InPerson; - return View(dto); + return View("Intake/Terms", dto); } session.AgreedToTerms = true; @@ -413,7 +413,7 @@ public class KioskController : Controller ViewBag.ShowInactivityTimer = false; // Handled by the countdown JS in the view ViewBag.IsInPerson = session.SessionType == KioskSessionType.InPerson; ViewBag.FirstName = session.CustomerFirstName; - return View(); + return View("Intake/Confirmation"); } // ========================================================================= diff --git a/src/PowderCoating.Web/Program.cs b/src/PowderCoating.Web/Program.cs index 044fc35..23342f0 100644 --- a/src/PowderCoating.Web/Program.cs +++ b/src/PowderCoating.Web/Program.cs @@ -727,6 +727,12 @@ app.UseMiddleware(); // Track authenticated user presence (throttled, in-memory) app.UseMiddleware(); +// Kiosk intake steps use /Kiosk/Intake/{token}/{action} so the token is a path segment +app.MapControllerRoute( + name: "kiosk_intake", + pattern: "Kiosk/Intake/{token}/{action}", + defaults: new { controller = "Kiosk" }); + app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); diff --git a/src/PowderCoating.Web/Views/Kiosk/Welcome.cshtml b/src/PowderCoating.Web/Views/Kiosk/Welcome.cshtml index 8fbbe4d..e06550d 100644 --- a/src/PowderCoating.Web/Views/Kiosk/Welcome.cshtml +++ b/src/PowderCoating.Web/Views/Kiosk/Welcome.cshtml @@ -23,8 +23,8 @@
- Ready + border-radius:50%;background:#94a3b8;margin-right:6px;transition:background 0.3s;"> + Connecting…
diff --git a/src/PowderCoating.Web/wwwroot/js/kiosk-welcome.js b/src/PowderCoating.Web/wwwroot/js/kiosk-welcome.js index c656f09..e249e91 100644 --- a/src/PowderCoating.Web/wwwroot/js/kiosk-welcome.js +++ b/src/PowderCoating.Web/wwwroot/js/kiosk-welcome.js @@ -5,44 +5,54 @@ if (!el) return; const companyId = el.dataset.companyId; - if (!companyId) return; + const dot = document.getElementById("kiosk-conn-dot"); + const label = document.getElementById("kiosk-conn-label"); + + function setStatus(color, text) { + if (dot) dot.style.background = color; + if (label) label.textContent = text; + } + + if (!companyId) { + setStatus("#ef4444", "Not configured (no company ID)"); + console.error("KioskHub: data-company-id is empty — kiosk activation may be invalid."); + return; + } + + setStatus("#94a3b8", "Connecting…"); const connection = new signalR.HubConnectionBuilder() .withUrl(`/hubs/kiosk?companyId=${companyId}`) .withAutomaticReconnect([2000, 5000, 10000, 30000]) - .configureLogging(signalR.LogLevel.Warning) + .configureLogging(signalR.LogLevel.Information) .build(); connection.on("StartIntake", function (sessionToken) { + setStatus("#2563eb", "Starting…"); window.location.href = `/Kiosk/Intake/${sessionToken}/Contact`; }); async function startConnection() { try { await connection.start(); + setStatus("#16a34a", "Ready"); + console.info("KioskHub connected, group kiosk-" + companyId); } catch (err) { - console.warn("Kiosk SignalR connect failed, retrying in 10s...", err); + setStatus("#ef4444", "Connection failed — retrying…"); + console.warn("KioskHub connect failed, retrying in 10s…", err); setTimeout(startConnection, 10000); } } startConnection(); - // Show connection status indicator - connection.onreconnecting(() => { - const dot = document.getElementById("kiosk-conn-dot"); - if (dot) dot.style.background = "#f59e0b"; - }); - + connection.onreconnecting(() => setStatus("#f59e0b", "Reconnecting…")); connection.onreconnected(() => { - const dot = document.getElementById("kiosk-conn-dot"); - if (dot) dot.style.background = "#16a34a"; + setStatus("#16a34a", "Ready"); + console.info("KioskHub reconnected"); }); - connection.onclose(() => { - const dot = document.getElementById("kiosk-conn-dot"); - if (dot) dot.style.background = "#ef4444"; - // Keep retrying + setStatus("#ef4444", "Disconnected — retrying…"); setTimeout(startConnection, 10000); }); })();