diff --git a/src/PowderCoating.Web/Controllers/KioskController.cs b/src/PowderCoating.Web/Controllers/KioskController.cs
index 5ceb1cb..0f18df7 100644
--- a/src/PowderCoating.Web/Controllers/KioskController.cs
+++ b/src/PowderCoating.Web/Controllers/KioskController.cs
@@ -158,17 +158,34 @@ public class KioskController : Controller
return Json(new { success = true });
}
+ ///
+ /// Cancels a pending kiosk SMS consent request, freeing the kiosk to return to the Welcome
+ /// screen. Called by staff if they pushed consent accidentally or the customer isn't coming.
+ ///
+ [HttpPost, ValidateAntiForgeryToken]
+ public IActionResult CancelSmsConsent()
+ {
+ var companyId = HttpContext.User.FindFirst("CompanyId")?.Value;
+ if (int.TryParse(companyId, out var cid))
+ _cache.Remove(SmsConsentCacheKey(cid));
+ return Json(new { success = true });
+ }
+
///
/// Displays the full-screen SMS consent form on the kiosk tablet (anonymous, kiosk layout).
/// Loads the customer by ID with ignoreQueryFilters because the kiosk has no tenant context.
///
[AllowAnonymous]
- public async Task SmsConsent(int customerId)
+ public async Task SmsConsent(int id)
{
var cookie = ReadKioskCookie();
if (cookie == null) return Forbid();
- var customer = await _unitOfWork.Customers.GetByIdAsync(customerId, ignoreQueryFilters: true);
+ // Clear the pending entry immediately — the kiosk is now showing the form,
+ // so Welcome must not redirect again if the customer cancels or navigates back.
+ _cache.Remove(SmsConsentCacheKey(cookie.Value.companyId));
+
+ var customer = await _unitOfWork.Customers.GetByIdAsync(id, ignoreQueryFilters: true);
if (customer == null) return NotFound();
var company = await _unitOfWork.Companies.GetByIdAsync(cookie.Value.companyId, ignoreQueryFilters: true);
@@ -179,25 +196,23 @@ public class KioskController : Controller
? $"{customer.ContactFirstName} {customer.ContactLastName}".Trim()
: customer.CompanyName ?? "Customer";
- return View(customerId);
+ return View(id);
}
///
- /// Records the customer's SMS consent from the kiosk tablet and clears the pending cache entry.
+ /// Records the customer's SMS consent from the kiosk tablet.
/// Sets NotifyBySms, SmsConsentedAt, SmsConsentMethod = "KioskInPerson" on the customer record.
+ /// Cache is already cleared by the GET; this handles the agree/decline outcome.
///
[AllowAnonymous, HttpPost]
- public async Task SmsConsent(int customerId, bool agreed)
+ public async Task SmsConsent(int id, bool agreed)
{
var cookie = ReadKioskCookie();
if (cookie == null) return Forbid();
- // Always clear the pending consent so the kiosk stops showing the form
- _cache.Remove(SmsConsentCacheKey(cookie.Value.companyId));
-
if (agreed)
{
- var customer = await _unitOfWork.Customers.GetByIdAsync(customerId, ignoreQueryFilters: true);
+ var customer = await _unitOfWork.Customers.GetByIdAsync(id, ignoreQueryFilters: true);
if (customer != null)
{
customer.NotifyBySms = true;
@@ -206,15 +221,15 @@ public class KioskController : Controller
customer.SmsOptedOutAt = null;
await _unitOfWork.Customers.UpdateAsync(customer);
await _unitOfWork.CompleteAsync();
- _logger.LogInformation("SMS consent recorded via kiosk for customer {CustomerId}", customerId);
+ _logger.LogInformation("SMS consent recorded via kiosk for customer {CustomerId}", id);
await _inApp.CreateAsync(
customer.CompanyId,
"SMS Consent Recorded",
$"{customer.ContactFirstName} {customer.ContactLastName} agreed to SMS notifications on the kiosk.",
"KioskConsent",
- link: $"/Customers/Details/{customerId}",
- customerId: customerId);
+ link: $"/Customers/Details/{id}",
+ customerId: id);
}
}
diff --git a/src/PowderCoating.Web/Views/Customers/Details.cshtml b/src/PowderCoating.Web/Views/Customers/Details.cshtml
index cf2bfec..53fc64e 100644
--- a/src/PowderCoating.Web/Views/Customers/Details.cshtml
+++ b/src/PowderCoating.Web/Views/Customers/Details.cshtml
@@ -185,13 +185,20 @@
SMS off
-
+
}
diff --git a/src/PowderCoating.Web/wwwroot/js/customer-details.js b/src/PowderCoating.Web/wwwroot/js/customer-details.js
index 53f71f9..fb081b3 100644
--- a/src/PowderCoating.Web/wwwroot/js/customer-details.js
+++ b/src/PowderCoating.Web/wwwroot/js/customer-details.js
@@ -10,6 +10,8 @@ async function pushSmsConsent(customerId) {
const data = await res.json();
if (data.success) {
toastr.success('Consent form sent to the kiosk tablet — hand it to the customer.', 'Sent to Kiosk');
+ document.getElementById('btnGetSmsConsent')?.classList.add('d-none');
+ document.getElementById('btnCancelSmsConsent')?.classList.remove('d-none');
} else {
toastr.warning(data.message || 'Could not send consent to kiosk.');
}
@@ -17,3 +19,21 @@ async function pushSmsConsent(customerId) {
toastr.error('An error occurred. Please try again.');
}
}
+
+async function cancelSmsConsent() {
+ const tok = document.querySelector('input[name="__RequestVerificationToken"]')?.value ?? '';
+ try {
+ const res = await fetch('/Kiosk/CancelSmsConsent', {
+ method: 'POST',
+ headers: { 'RequestVerificationToken': tok }
+ });
+ const data = await res.json();
+ if (data.success) {
+ toastr.info('Consent request cancelled — kiosk is free.');
+ document.getElementById('btnCancelSmsConsent')?.classList.add('d-none');
+ document.getElementById('btnGetSmsConsent')?.classList.remove('d-none');
+ }
+ } catch {
+ toastr.error('An error occurred. Please try again.');
+ }
+}