// pgv3-flash.js — render a toast for ?flash=<type>:<message> query param.
//
// Closes UX gap U34: after a successful POST, the server redirects with
// ?flash=success:Created%20org%20"demo". This script reads that param
// on DOMContentLoaded, renders a dismissible toast, auto-fades after
// ~3.5s, and strips the param from the URL via history.replaceState
// (so refresh doesn't re-show it).
//
// URL format: ?flash=<type>:<message>
// type: success | info | error (styles the toast)
// message: any URL-encoded text
//
// Example redirect (server):
// /orgs/foo?flash=success:Org%20"foo"%20created
//
// The script is idempotent — multiple loads are safe, it only acts if
// the flash param is present.
(function () {
'use strict'
function readFlash () {
try {
var params = new URLSearchParams(window.location.search)
var raw = params.get('flash')
if (!raw) return null
var colon = raw.indexOf(':')
var type = colon > 0 ? raw.slice(0, colon) : 'info'
var message = colon > 0 ? raw.slice(colon + 1) : raw
if (!['success', 'info', 'error'].includes(type)) type = 'info'
return { type: type, message: message }
} catch {
return null
}
}
function stripFlashFromUrl () {
try {
var params = new URLSearchParams(window.location.search)
params.delete('flash')
var search = params.toString()
var newUrl = window.location.pathname +
(search ? '?' + search : '') +
window.location.hash
history.replaceState(null, '', newUrl)
} catch {}
}
function renderToast (type, message) {
var toast = document.createElement('div')
toast.className = 'pgv3-flash pgv3-flash-' + type
toast.setAttribute('role', 'status')
toast.setAttribute('aria-live', 'polite')
toast.innerHTML =
'<span class="pgv3-flash-message"></span>' +
'<button class="pgv3-flash-close" aria-label="Dismiss">×</button>'
toast.querySelector('.pgv3-flash-message').textContent = message
document.body.appendChild(toast)
var dismiss = function () {
toast.classList.add('pgv3-flash-leaving')
setTimeout(function () {
if (toast.parentNode) toast.parentNode.removeChild(toast)
}, 250)
}
toast.querySelector('.pgv3-flash-close').addEventListener('click', dismiss)
// Auto-dismiss after 3.5s (errors stay a bit longer — 6s).
setTimeout(dismiss, type === 'error' ? 6000 : 3500)
}
// A flash can arrive via two channels:
// 1. ?flash=<type>:<msg> on the URL — set by the server's
// redirectWithFlash() helper after a successful POST.
// 2. sessionStorage['pgv3-flash-deferred'] — set by client-side
// code that needs to survive a window.location.reload() and
// doesn't have a URL to pass through (U6+U7: session-expiry
// re-auth + explicit sign-in). Consumed and cleared on read.
function readDeferredFlash () {
try {
var raw = sessionStorage.getItem('pgv3-flash-deferred')
if (!raw) return null
sessionStorage.removeItem('pgv3-flash-deferred')
var colon = raw.indexOf(':')
var type = colon > 0 ? raw.slice(0, colon) : 'info'
var message = colon > 0 ? raw.slice(colon + 1) : raw
if (!['success', 'info', 'error'].includes(type)) type = 'info'
return { type: type, message: message }
} catch {
return null
}
}
function init () {
// URL-borne flash wins if both are present (fresh server action
// trumps a deferred client-side signal that's stuck in
// sessionStorage from an earlier reload).
var flash = readFlash()
if (flash) {
renderToast(flash.type, flash.message)
stripFlashFromUrl()
// Still clear the deferred bucket if any, so stale messages
// don't pop up later.
try { sessionStorage.removeItem('pgv3-flash-deferred') } catch {}
return
}
var deferred = readDeferredFlash()
if (deferred) renderToast(deferred.type, deferred.message)
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init)
} else {
init()
}
})()