Fix kiosk intake routing, view names, and SignalR diagnostics
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 <noreply@anthropic.com>
This commit is contained in:
@@ -261,7 +261,7 @@ public class KioskController : Controller
|
|||||||
|
|
||||||
await PopulateKioskViewBagFromSession(session);
|
await PopulateKioskViewBagFromSession(session);
|
||||||
ViewBag.KioskStep = 1;
|
ViewBag.KioskStep = 1;
|
||||||
return View(new SubmitKioskContactDto
|
return View("Intake/Contact", new SubmitKioskContactDto
|
||||||
{
|
{
|
||||||
FirstName = session.CustomerFirstName,
|
FirstName = session.CustomerFirstName,
|
||||||
LastName = session.CustomerLastName,
|
LastName = session.CustomerLastName,
|
||||||
@@ -283,7 +283,7 @@ public class KioskController : Controller
|
|||||||
{
|
{
|
||||||
await PopulateKioskViewBagFromSession(session);
|
await PopulateKioskViewBagFromSession(session);
|
||||||
ViewBag.KioskStep = 1;
|
ViewBag.KioskStep = 1;
|
||||||
return View(dto);
|
return View("Intake/Contact", dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.CustomerFirstName = dto.FirstName.Trim();
|
session.CustomerFirstName = dto.FirstName.Trim();
|
||||||
@@ -308,7 +308,7 @@ public class KioskController : Controller
|
|||||||
|
|
||||||
await PopulateKioskViewBagFromSession(session);
|
await PopulateKioskViewBagFromSession(session);
|
||||||
ViewBag.KioskStep = 2;
|
ViewBag.KioskStep = 2;
|
||||||
return View(new SubmitKioskJobDto
|
return View("Intake/Job", new SubmitKioskJobDto
|
||||||
{
|
{
|
||||||
JobDescription = session.JobDescription,
|
JobDescription = session.JobDescription,
|
||||||
HowDidYouHearAboutUs = session.HowDidYouHearAboutUs
|
HowDidYouHearAboutUs = session.HowDidYouHearAboutUs
|
||||||
@@ -327,7 +327,7 @@ public class KioskController : Controller
|
|||||||
{
|
{
|
||||||
await PopulateKioskViewBagFromSession(session);
|
await PopulateKioskViewBagFromSession(session);
|
||||||
ViewBag.KioskStep = 2;
|
ViewBag.KioskStep = 2;
|
||||||
return View(dto);
|
return View("Intake/Job", dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.JobDescription = dto.JobDescription.Trim();
|
session.JobDescription = dto.JobDescription.Trim();
|
||||||
@@ -350,7 +350,7 @@ public class KioskController : Controller
|
|||||||
await PopulateKioskViewBagFromSession(session);
|
await PopulateKioskViewBagFromSession(session);
|
||||||
ViewBag.KioskStep = 3;
|
ViewBag.KioskStep = 3;
|
||||||
ViewBag.IsInPerson = session.SessionType == KioskSessionType.InPerson;
|
ViewBag.IsInPerson = session.SessionType == KioskSessionType.InPerson;
|
||||||
return View(new SubmitKioskTermsDto());
|
return View("Intake/Terms", new SubmitKioskTermsDto());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -376,7 +376,7 @@ public class KioskController : Controller
|
|||||||
await PopulateKioskViewBagFromSession(session);
|
await PopulateKioskViewBagFromSession(session);
|
||||||
ViewBag.KioskStep = 3;
|
ViewBag.KioskStep = 3;
|
||||||
ViewBag.IsInPerson = session.SessionType == KioskSessionType.InPerson;
|
ViewBag.IsInPerson = session.SessionType == KioskSessionType.InPerson;
|
||||||
return View(dto);
|
return View("Intake/Terms", dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
session.AgreedToTerms = true;
|
session.AgreedToTerms = true;
|
||||||
@@ -413,7 +413,7 @@ public class KioskController : Controller
|
|||||||
ViewBag.ShowInactivityTimer = false; // Handled by the countdown JS in the view
|
ViewBag.ShowInactivityTimer = false; // Handled by the countdown JS in the view
|
||||||
ViewBag.IsInPerson = session.SessionType == KioskSessionType.InPerson;
|
ViewBag.IsInPerson = session.SessionType == KioskSessionType.InPerson;
|
||||||
ViewBag.FirstName = session.CustomerFirstName;
|
ViewBag.FirstName = session.CustomerFirstName;
|
||||||
return View();
|
return View("Intake/Confirmation");
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
@@ -727,6 +727,12 @@ app.UseMiddleware<PowderCoating.Web.Middleware.MustChangePasswordMiddleware>();
|
|||||||
// Track authenticated user presence (throttled, in-memory)
|
// Track authenticated user presence (throttled, in-memory)
|
||||||
app.UseMiddleware<PowderCoating.Web.Middleware.OnlineUserMiddleware>();
|
app.UseMiddleware<PowderCoating.Web.Middleware.OnlineUserMiddleware>();
|
||||||
|
|
||||||
|
// 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(
|
app.MapControllerRoute(
|
||||||
name: "default",
|
name: "default",
|
||||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
|
|
||||||
<div class="kiosk-idle-indicator">
|
<div class="kiosk-idle-indicator">
|
||||||
<span id="kiosk-conn-dot" style="display:inline-block;width:10px;height:10px;
|
<span id="kiosk-conn-dot" style="display:inline-block;width:10px;height:10px;
|
||||||
border-radius:50%;background:#16a34a;margin-right:6px;transition:background 0.3s;"></span>
|
border-radius:50%;background:#94a3b8;margin-right:6px;transition:background 0.3s;"></span>
|
||||||
Ready
|
<span id="kiosk-conn-label">Connecting…</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,44 +5,54 @@
|
|||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|
||||||
const companyId = el.dataset.companyId;
|
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()
|
const connection = new signalR.HubConnectionBuilder()
|
||||||
.withUrl(`/hubs/kiosk?companyId=${companyId}`)
|
.withUrl(`/hubs/kiosk?companyId=${companyId}`)
|
||||||
.withAutomaticReconnect([2000, 5000, 10000, 30000])
|
.withAutomaticReconnect([2000, 5000, 10000, 30000])
|
||||||
.configureLogging(signalR.LogLevel.Warning)
|
.configureLogging(signalR.LogLevel.Information)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
connection.on("StartIntake", function (sessionToken) {
|
connection.on("StartIntake", function (sessionToken) {
|
||||||
|
setStatus("#2563eb", "Starting…");
|
||||||
window.location.href = `/Kiosk/Intake/${sessionToken}/Contact`;
|
window.location.href = `/Kiosk/Intake/${sessionToken}/Contact`;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function startConnection() {
|
async function startConnection() {
|
||||||
try {
|
try {
|
||||||
await connection.start();
|
await connection.start();
|
||||||
|
setStatus("#16a34a", "Ready");
|
||||||
|
console.info("KioskHub connected, group kiosk-" + companyId);
|
||||||
} catch (err) {
|
} 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);
|
setTimeout(startConnection, 10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startConnection();
|
startConnection();
|
||||||
|
|
||||||
// Show connection status indicator
|
connection.onreconnecting(() => setStatus("#f59e0b", "Reconnecting…"));
|
||||||
connection.onreconnecting(() => {
|
|
||||||
const dot = document.getElementById("kiosk-conn-dot");
|
|
||||||
if (dot) dot.style.background = "#f59e0b";
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.onreconnected(() => {
|
connection.onreconnected(() => {
|
||||||
const dot = document.getElementById("kiosk-conn-dot");
|
setStatus("#16a34a", "Ready");
|
||||||
if (dot) dot.style.background = "#16a34a";
|
console.info("KioskHub reconnected");
|
||||||
});
|
});
|
||||||
|
|
||||||
connection.onclose(() => {
|
connection.onclose(() => {
|
||||||
const dot = document.getElementById("kiosk-conn-dot");
|
setStatus("#ef4444", "Disconnected — retrying…");
|
||||||
if (dot) dot.style.background = "#ef4444";
|
|
||||||
// Keep retrying
|
|
||||||
setTimeout(startConnection, 10000);
|
setTimeout(startConnection, 10000);
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user