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>.
|
<a href="/privacy" class="text-primary hover:underline">Privacy Policy</a>.
|
||||||
</p>
|
</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
|
<button
|
||||||
id="wrnexus-subscribe"
|
id="wrnexus-subscribe"
|
||||||
type="button"
|
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" />
|
<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>
|
</svg>
|
||||||
<span>Enable notifications</span>
|
<span id="wrnexus-subscribe-label">Enable notifications</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -205,9 +210,104 @@ const currentYear = new Date().getFullYear();
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</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>
|
<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>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
Reference in New Issue
Block a user