feat(footer): make push notification button reflect subscription state
E2E Test Suite / Smoke Tests (P0) (push) Failing after 9m36s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 10m42s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m35s
E2E Test Suite / Security Header Tests (push) Failing after 11m20s
E2E Test Suite / Mobile Device Tests (push) Failing after 12m13s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 13m5s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 13m57s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 14m50s
E2E Test Suite / Test Report Summary (push) Failing after 12m50s
Deploy to Production / Build & Verify (push) Failing after 19m19s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m59s
Deploy to Production / Deploy to Fly.io (push) Failing after 12m23s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 12m26s
Deploy to Production / Deploy to Render (push) Failing after 13m16s
Deploy to Production / Deploy to Railway (push) Failing after 14m8s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m57s
Deploy to Production / Notify on Failure (push) Failing after 14m58s
E2E Test Suite / Smoke Tests (P0) (push) Failing after 9m36s
E2E Test Suite / Critical User Journeys (push) Has been skipped
E2E Test Suite / API Integration Tests (push) Has been skipped
E2E Test Suite / Destructive & Chaos Tests (push) Failing after 10m42s
E2E Test Suite / Form Interaction Tests (push) Failing after 11m35s
E2E Test Suite / Security Header Tests (push) Failing after 11m20s
E2E Test Suite / Mobile Device Tests (push) Failing after 12m13s
E2E Test Suite / Cross-Browser Regression (webkit) (push) Failing after 13m5s
E2E Test Suite / Cross-Browser Regression (firefox) (push) Failing after 13m57s
E2E Test Suite / Cross-Browser Regression (chromium) (push) Failing after 14m50s
E2E Test Suite / Test Report Summary (push) Failing after 12m50s
Deploy to Production / Build & Verify (push) Failing after 19m19s
Deploy to Production / Pre-Deploy Tests (push) Failing after 14m59s
Deploy to Production / Deploy to Fly.io (push) Failing after 12m23s
Deploy to Production / Deploy to VPS (PM2) (push) Failing after 12m26s
Deploy to Production / Deploy to Render (push) Failing after 13m16s
Deploy to Production / Deploy to Railway (push) Failing after 14m8s
Deploy to Production / Post-Deploy Verification (push) Failing after 14m57s
Deploy to Production / Notify on Failure (push) Failing after 14m58s
The WRNexus web-push button always showed "Enable notifications". It now reads WRNexusPush.isSubscribed() on load and renders the correct state: - hides entirely when SDK/browser push support is missing - "Enable notifications" -> calls prompt() (user gesture) - "Unsubscribe" when already subscribed -> calls unsubscribe() - disabled "Notifications blocked" when permission is denied - in-progress disabled state to prevent double-clicks; re-reads state after each action and re-renders the label Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a8d7db562d
commit
8e7070cbaf
+106
-6
@@ -167,16 +167,21 @@ const currentYear = new Date().getFullYear();
|
||||
<a href="/privacy" class="text-primary hover:underline">Privacy Policy</a>.
|
||||
</p>
|
||||
|
||||
<!-- Web push opt-in (WRNexus). User-gesture subscribe control. -->
|
||||
<!-- Web push opt-in (WRNexus). User-gesture subscribe control.
|
||||
Hidden by default; the client script below reveals it only when the
|
||||
WRNexus SDK + browser push support are available, and sets the
|
||||
correct label based on the visitor's current subscription state. -->
|
||||
<button
|
||||
id="wrnexus-subscribe"
|
||||
type="button"
|
||||
class="mt-6 w-full px-4 py-3 bg-secondary-800 border border-secondary-700 text-white font-medium rounded-lg hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-secondary-900 transition-all duration-200 text-sm flex items-center justify-center gap-2"
|
||||
hidden
|
||||
aria-label="Enable notifications"
|
||||
class="mt-6 w-full px-4 py-3 bg-secondary-800 border border-secondary-700 text-white font-medium rounded-lg hover:bg-secondary-700 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-secondary-900 transition-all duration-200 text-sm flex items-center justify-center gap-2 disabled:opacity-60 disabled:cursor-not-allowed"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<svg class="w-4 h-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
||||
</svg>
|
||||
<span>Enable notifications</span>
|
||||
<span id="wrnexus-subscribe-label">Enable notifications</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -205,9 +210,104 @@ const currentYear = new Date().getFullYear();
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- WRNexus push subscribe: wired via addEventListener (Astro strips inline on* handlers). -->
|
||||
<!-- WRNexus push subscribe: state-aware control wired via addEventListener
|
||||
(Astro strips inline on* handlers). The button reflects whether the
|
||||
visitor is already subscribed and lets them toggle the subscription. -->
|
||||
<script is:inline>
|
||||
document.getElementById('wrnexus-subscribe')?.addEventListener('click', () => window.WRNexusPush?.subscribe());
|
||||
(function () {
|
||||
function init() {
|
||||
var btn = document.getElementById('wrnexus-subscribe');
|
||||
var label = document.getElementById('wrnexus-subscribe-label');
|
||||
if (!btn || !label) return;
|
||||
|
||||
var push = window.WRNexusPush;
|
||||
var supported =
|
||||
push &&
|
||||
'Notification' in window &&
|
||||
'serviceWorker' in navigator &&
|
||||
'PushManager' in window;
|
||||
|
||||
// No SDK or no browser push support: keep the button hidden entirely.
|
||||
if (!supported) {
|
||||
btn.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Permission permanently blocked by the user: the SDK can't re-prompt,
|
||||
// so surface a clear, non-actionable disabled state instead of a button
|
||||
// that silently does nothing.
|
||||
if (window.Notification && Notification.permission === 'denied') {
|
||||
label.textContent = 'Notifications blocked';
|
||||
btn.setAttribute('aria-label', 'Notifications are blocked in your browser settings');
|
||||
btn.disabled = true;
|
||||
btn.hidden = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Render the button for a given subscription state.
|
||||
// Default UX: subscribed users get an actionable "Unsubscribe" toggle
|
||||
// rather than a dead "Already subscribed" label.
|
||||
// (Alternative: render a disabled "Already subscribed" indicator instead
|
||||
// of the Unsubscribe toggle if product prefers a non-actionable state.)
|
||||
function render(subscribed) {
|
||||
if (subscribed) {
|
||||
label.textContent = 'Unsubscribe';
|
||||
btn.setAttribute('aria-label', 'Unsubscribe from notifications');
|
||||
} else {
|
||||
label.textContent = 'Enable notifications';
|
||||
btn.setAttribute('aria-label', 'Enable notifications');
|
||||
}
|
||||
btn.dataset.subscribed = subscribed ? 'true' : 'false';
|
||||
btn.disabled = false;
|
||||
btn.hidden = false;
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
var subscribed = await push.isSubscribed();
|
||||
render(!!subscribed);
|
||||
} catch (err) {
|
||||
// On error, fall back to the unsubscribed label so the control stays usable.
|
||||
console.error('[wrnexus-push] isSubscribed() failed:', err);
|
||||
render(false);
|
||||
}
|
||||
}
|
||||
|
||||
btn.addEventListener('click', async function () {
|
||||
if (btn.disabled) return;
|
||||
|
||||
var wasSubscribed = btn.dataset.subscribed === 'true';
|
||||
var busyLabel = wasSubscribed ? 'Unsubscribing…' : 'Enabling…';
|
||||
|
||||
// In-progress state to prevent double-clicks while the promise is pending.
|
||||
btn.disabled = true;
|
||||
label.textContent = busyLabel;
|
||||
|
||||
try {
|
||||
if (wasSubscribed) {
|
||||
await push.unsubscribe();
|
||||
} else {
|
||||
// prompt() must run from this real user gesture.
|
||||
await push.prompt();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[wrnexus-push] toggle failed:', err);
|
||||
} finally {
|
||||
// Re-read the authoritative state and re-render the label.
|
||||
await refresh();
|
||||
}
|
||||
});
|
||||
|
||||
// Determine and render the initial state.
|
||||
refresh();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
Reference in New Issue
Block a user