From 14f220347bf8e1e56499cfa3b8d104ace37c572b Mon Sep 17 00:00:00 2001 From: Scott Pouliot Date: Fri, 22 May 2026 17:38:09 -0400 Subject: [PATCH] Add scheme failsafe to all inventory URL link buttons If a stored URL is missing http:// or https:// the browser treats it as relative and appends it to the app URL. Guard in three places: - inventory-catalog-lookup.js syncLinkButton: ensureAbsoluteUrl() prepends https:// - inventory-label-scan.js syncLink: same guard for scan-filled URL fields - Details.cshtml SafeUrl() Razor helper on SpecPageUrl, SdsUrl, TdsUrl links Co-Authored-By: Claude Sonnet 4.6 --- src/PowderCoating.Web/Views/Inventory/Details.cshtml | 11 ++++++++--- .../wwwroot/js/inventory-catalog-lookup.js | 7 ++++++- .../wwwroot/js/inventory-label-scan.js | 7 ++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/PowderCoating.Web/Views/Inventory/Details.cshtml b/src/PowderCoating.Web/Views/Inventory/Details.cshtml index f5926ce..23ec7a1 100644 --- a/src/PowderCoating.Web/Views/Inventory/Details.cshtml +++ b/src/PowderCoating.Web/Views/Inventory/Details.cshtml @@ -5,6 +5,11 @@ ViewData["PageIcon"] = "bi-box-seam"; ViewData["PageHelpTitle"] = "Inventory Item"; ViewData["PageHelpContent"] = "Full detail for this inventory item. Stock Information shows current quantity and reorder thresholds — a Low Stock banner appears when quantity is at or below the Reorder Point. Pricing shows Unit Cost (what you paid), Average Cost (weighted average across purchases), and Total Stock Value. Use the Actions panel to edit, view jobs using this powder, or delete the item."; + + string SafeUrl(string? url) => + string.IsNullOrEmpty(url) ? "#" + : (url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + ? url : "https://" + url; } @section Styles { @@ -184,7 +189,7 @@

- + View on Manufacturer's Web Site

@@ -197,13 +202,13 @@
@if (!string.IsNullOrEmpty(Model.SdsUrl)) { - + Safety Data Sheet } @if (!string.IsNullOrEmpty(Model.TdsUrl)) { - + Technical Data Sheet } diff --git a/src/PowderCoating.Web/wwwroot/js/inventory-catalog-lookup.js b/src/PowderCoating.Web/wwwroot/js/inventory-catalog-lookup.js index 7748a31..b2a38e9 100644 --- a/src/PowderCoating.Web/wwwroot/js/inventory-catalog-lookup.js +++ b/src/PowderCoating.Web/wwwroot/js/inventory-catalog-lookup.js @@ -413,10 +413,15 @@ // ── Helpers ─────────────────────────────────────────────────────────────── + function ensureAbsoluteUrl(url) { + if (!url) return url; + return /^https?:\/\//i.test(url) ? url : 'https://' + url; + } + function syncLinkButton(inputId, linkId, url) { const link = document.getElementById(linkId); if (!link) return; - if (url) { link.href = url; link.classList.remove('d-none'); } + if (url) { link.href = ensureAbsoluteUrl(url); link.classList.remove('d-none'); } else { link.classList.add('d-none'); } } diff --git a/src/PowderCoating.Web/wwwroot/js/inventory-label-scan.js b/src/PowderCoating.Web/wwwroot/js/inventory-label-scan.js index 91d9ce2..6406a2e 100644 --- a/src/PowderCoating.Web/wwwroot/js/inventory-label-scan.js +++ b/src/PowderCoating.Web/wwwroot/js/inventory-label-scan.js @@ -550,10 +550,15 @@ }); } + function ensureAbsoluteUrl(url) { + if (!url) return url; + return /^https?:\/\//i.test(url) ? url : 'https://' + url; + } + function syncLink(inputId, linkId, url) { const link = document.getElementById(linkId); if (!link) return; - if (url) { link.href = url; link.classList.remove('d-none'); } + if (url) { link.href = ensureAbsoluteUrl(url); link.classList.remove('d-none'); } else { link.classList.add('d-none'); } }