From 66b1dac3b975e56f898c9f4172e01f17768d4474 Mon Sep 17 00:00:00 2001 From: Karina Kwiatek Date: Sun, 23 Oct 2022 16:00:21 +0200 Subject: [PATCH] Improve UX for push subscription management --- app/controllers/ajax/web_push_controller.rb | 7 +- .../retrospring/features/webpush/enable.ts | 8 +++ .../retrospring/features/webpush/index.ts | 35 +++++++--- .../features/webpush/unsubscribe.ts | 69 ++++++++++++------- app/views/settings/_push_notifications.haml | 8 +-- config/locales/controllers.en.yml | 5 ++ config/locales/views.en.yml | 1 + 7 files changed, 91 insertions(+), 42 deletions(-) diff --git a/app/controllers/ajax/web_push_controller.rb b/app/controllers/ajax/web_push_controller.rb index b3a671eb..0d00b852 100644 --- a/app/controllers/ajax/web_push_controller.rb +++ b/app/controllers/ajax/web_push_controller.rb @@ -17,18 +17,23 @@ class Ajax::WebPushController < AjaxController @response[:status] = :okay @response[:success] = true + @response[:message] = t(".subscription_count", count: current_user.web_push_subscriptions.count) end def unsubscribe params.permit(:endpoint) if params.key?(:endpoint) - current_user.web_push_subscriptions.where("subscription ->> 'endpoint' = ?", params[:endpoint]).destroy + current_user.web_push_subscriptions.where("subscription ->> 'endpoint' = ?", params[:endpoint]).destroy_all else current_user.web_push_subscriptions.destroy_all end + count = current_user.web_push_subscriptions.count + @response[:status] = :okay @response[:success] = true + @response[:message] = t(".subscription_count", count:) + @response[:count] = count end end diff --git a/app/javascript/retrospring/features/webpush/enable.ts b/app/javascript/retrospring/features/webpush/enable.ts index c52ca51c..56088c35 100644 --- a/app/javascript/retrospring/features/webpush/enable.ts +++ b/app/javascript/retrospring/features/webpush/enable.ts @@ -4,6 +4,7 @@ import { showNotification } from "utilities/notifications"; export function enableHandler (event: Event): void { event.preventDefault(); + const sender = event.target as HTMLButtonElement; try { installServiceWorker() @@ -26,6 +27,13 @@ export function enableHandler (event: Event): void { new Notification(I18n.translate("frontend.push_notifications.subscribe.success.title"), { body: I18n.translate("frontend.push_notifications.subscribe.success.body") }); + + document.querySelectorAll('button[data-action="push-disable"], button[data-action="push-remove-all"]') + .forEach(button => button.classList.remove('d-none')); + + sender.classList.add('d-none'); + + document.getElementById('subscription-count').textContent = data.message; } else { new Notification(I18n.translate("frontend.push_notifications.fail.title"), { body: I18n.translate("frontend.push_notifications.fail.body") diff --git a/app/javascript/retrospring/features/webpush/index.ts b/app/javascript/retrospring/features/webpush/index.ts index e9149684..dcdbeef1 100644 --- a/app/javascript/retrospring/features/webpush/index.ts +++ b/app/javascript/retrospring/features/webpush/index.ts @@ -8,17 +8,30 @@ export default (): void => { const notificationCapable = document.body.classList.contains('cap-notification'); if (swCapable && notificationCapable) { - navigator.serviceWorker.getRegistration().then(registration => { - if (!registration && localStorage.getItem('dismiss-push-settings-prompt') == null) { - document.querySelector('.push-settings').classList.remove('d-none'); - } - }) - registerEvents([ - {type: 'click', target: '[data-action="push-enable"]', handler: enableHandler, global: true}, - {type: 'click', target: '[data-action="push-dismiss"]', handler: dismissHandler, global: true}, - {type: 'click', target: '[data-action="push-disable"]', handler: () => unsubscribeHandler(false), global: true}, - {type: 'click', target: '[data-action="push-remove-all"]', handler: () => unsubscribeHandler(true), global: true}, - ]); + navigator.serviceWorker.getRegistration().then(registration => { + return registration.pushManager.getSubscription().then(subscription => { + if (!subscription) { + document.querySelector('button[data-action="push-enable"]').classList.remove('d-none'); + } else { + document.querySelector('[data-action="push-disable"]').classList.remove('d-none'); + if (localStorage.getItem('dismiss-push-settings-prompt') == null) { + document.querySelector('.push-settings')?.classList.remove('d-none'); + } + } + }); + }); } + + registerEvents([ + {type: 'click', target: '[data-action="push-enable"]', handler: enableHandler, global: true}, + {type: 'click', target: '[data-action="push-dismiss"]', handler: dismissHandler, global: true}, + {type: 'click', target: '[data-action="push-disable"]', handler: unsubscribeHandler, global: true}, + { + type: 'click', + target: '[data-action="push-remove-all"]', + handler: unsubscribeHandler, + global: true + }, + ]); } diff --git a/app/javascript/retrospring/features/webpush/unsubscribe.ts b/app/javascript/retrospring/features/webpush/unsubscribe.ts index c89a8d7e..fb4bda8e 100644 --- a/app/javascript/retrospring/features/webpush/unsubscribe.ts +++ b/app/javascript/retrospring/features/webpush/unsubscribe.ts @@ -2,35 +2,52 @@ import { destroy } from '@rails/request.js'; import { showErrorNotification, showNotification } from "utilities/notifications"; import I18n from "retrospring/i18n"; -export function unsubscribeHandler(all = false): void { - getSubscription().then(subscription => { - const body = all ? { endpoint: subscription.endpoint } : undefined; - - destroy('/ajax/webpush', { - body, - contentType: 'application/json', - }).then(async response => { - const data = await response.json; - - if (data.success) { - subscription?.unsubscribe().then(() => { - showNotification(I18n.translate("frontend.push_notifications.unsubscribe.success")); - }).catch(error => { - console.error("Tried to unsubscribe this browser but was unable to.\n" + - "As we've been unsubscribed on the server-side, this should not be an issue.", - error); - }) - } else { - showErrorNotification(I18n.translate("frontend.push_notifications.unsubscribe.fail")); - } - }).catch(error => { +export function unsubscribeHandler(): void { + navigator.serviceWorker.getRegistration() + .then(registration => registration.pushManager.getSubscription()) + .then(subscription => unsubscribeClient(subscription)) + .then(subscription => unsubscribeServer(subscription)) + .then() + .catch(error => { showErrorNotification(I18n.translate("frontend.push_notifications.unsubscribe.error")); console.error(error); }); - }) } -async function getSubscription(): Promise { - const registration = await navigator.serviceWorker.getRegistration('/'); - return await registration.pushManager.getSubscription(); +async function unsubscribeClient(subscription?: PushSubscription): Promise { + subscription?.unsubscribe().then(success => { + if (!success) { + throw new Error("Failed to unsubscribe."); + } + }); + + return subscription; +} + +async function unsubscribeServer(subscription?: PushSubscription): Promise { + const body = subscription != null ? { endpoint: subscription.endpoint } : undefined; + + return destroy('/ajax/webpush', { + body, + contentType: 'application/json', + }).then(async response => { + const data = await response.json; + + if (data.success) { + showNotification(I18n.translate("frontend.push_notifications.unsubscribe.success")); + + document.getElementById('subscription-count').textContent = data.message; + + if (data.count == 0) { + document.querySelectorAll('button[data-action="push-disable"], button[data-action="push-remove-all"]') + .forEach(button => button.classList.add('d-none')); + } + + if (document.body.classList.contains('cap-service-worker') && document.body.classList.contains('cap-notification')) { + document.querySelector('button[data-action="push-enable"]')?.classList.remove('d-none'); + } + } else { + showErrorNotification(I18n.translate("frontend.push_notifications.unsubscribe.fail")); + } + }) } diff --git a/app/views/settings/_push_notifications.haml b/app/views/settings/_push_notifications.haml index 05753739..12438a32 100644 --- a/app/views/settings/_push_notifications.haml +++ b/app/views/settings/_push_notifications.haml @@ -1,7 +1,7 @@ .card.push-notifications-settings .card-body %p= t('.description') - %p= t('.subscription_count', count: subscriptions.count) + %p#subscription-count= t('.subscription_count', count: subscriptions.count) .push-notifications-unavailable.text-danger %i.fa.fa-warning @@ -12,6 +12,6 @@ = t('.current_target') .button-group{ role: 'group' } - %button.btn.btn-primary{ data: { action: 'push-enable' } }= t('.subscribe') - %button.btn.btn-primary{ data: { action: 'push-disable' } }= t('.unsubscribe_current') - %button.btn.btn-danger{ data: { action: 'push-remove-all' } }= t('.unsubscribe_all') + %button.btn.btn-primary{ data: { action: 'push-enable' }, class: 'd-none' }= t('.subscribe') + %button.btn.btn-primary{ data: { action: 'push-disable' }, class: 'd-none' }= t('.unsubscribe_current') + %button.btn.btn-danger{ data: { action: 'push-remove-all' }, class: subscriptions.empty? ? 'd-none' : '' }= t('.unsubscribe_all') diff --git a/config/locales/controllers.en.yml b/config/locales/controllers.en.yml index 32a548be..1373b154 100644 --- a/config/locales/controllers.en.yml +++ b/config/locales/controllers.en.yml @@ -139,6 +139,11 @@ en: destroy_comment: success: "Successfully unsmiled comment." error: "You have not smiled that comment." + web_push: + subscription_count: + zero: "You are not currently subscribed to push notifications on any devices." + one: "You are currently receiving push notifications on one device." + other: "You are currently receiving push notifications on %{count} devices." inbox: author: info: "No questions from @%{author} found, showing entries from all users instead!" diff --git a/config/locales/views.en.yml b/config/locales/views.en.yml index 0c3aca6c..d4deff76 100644 --- a/config/locales/views.en.yml +++ b/config/locales/views.en.yml @@ -543,6 +543,7 @@ en: subscribe: "Enable on this device" unsubscribe_current: "Disable on this device" unsubscribe_all: "Disable on all devices" + description: "Here you can set up or disable push notifications for new questions in your inbox." shared: links: about: "About"