From f343e78cbe9de58013752ca381437204e3d149a1 Mon Sep 17 00:00:00 2001 From: Hampton Lintorn-Catlin Date: Wed, 13 May 2026 12:48:03 -0400 Subject: [PATCH] Auto-refresh device list on Settings after enable/disable Wraps the per-device list in a turbo-frame and reloads it from the Stimulus controller after subscribe/unsubscribe succeeds, so the new browser appears (or disappears) without a full page refresh. Changes - Extracts the device list into _devices.html.erb wrapped in a . - Adds GET /web_push/devices that renders just that partial back into the same frame. - web_push_settings_controller.js: new devicesUrl Stimulus value; after enable/disable success, sets the frame's src to refetch. Generated with Amp --- .../web_push/subscriptions_controller.rb | 9 ++++++ .../coplan/web_push_settings_controller.js | 13 ++++++++ .../settings/settings/_devices.html.erb | 28 +++++++++++++++++ .../settings/settings/_notifications.html.erb | 30 ++++--------------- engine/config/routes.rb | 4 +++ 5 files changed, 59 insertions(+), 25 deletions(-) create mode 100644 engine/app/views/coplan/settings/settings/_devices.html.erb diff --git a/engine/app/controllers/coplan/web_push/subscriptions_controller.rb b/engine/app/controllers/coplan/web_push/subscriptions_controller.rb index 5552856..d24b799 100644 --- a/engine/app/controllers/coplan/web_push/subscriptions_controller.rb +++ b/engine/app/controllers/coplan/web_push/subscriptions_controller.rb @@ -17,6 +17,15 @@ def create render json: { id: record.id, created_at: record.created_at }, status: :created end + # GET /web_push/devices + # Renders the device list inside its turbo-frame so the Settings page + # can refresh just that section after enabling/disabling on this browser. + def devices + @web_push_subscriptions = current_user.web_push_subscriptions.order(created_at: :desc) + render partial: "coplan/settings/settings/devices", + locals: { web_push_subscriptions: @web_push_subscriptions } + end + # DELETE /web_push/subscription # Body: { subscription: { endpoint } } def destroy diff --git a/engine/app/javascript/controllers/coplan/web_push_settings_controller.js b/engine/app/javascript/controllers/coplan/web_push_settings_controller.js index b15aa62..60ec3a7 100644 --- a/engine/app/javascript/controllers/coplan/web_push_settings_controller.js +++ b/engine/app/javascript/controllers/coplan/web_push_settings_controller.js @@ -5,6 +5,7 @@ import * as WebPush from "coplan/web_push" // browser's subscription state (which the server can't know — it's per-device). export default class extends Controller { static targets = ["enableButton", "disableButton", "status"] + static values = { devicesUrl: { type: String, default: "" } } async connect() { await this._refresh() @@ -15,6 +16,7 @@ export default class extends Controller { try { await WebPush.subscribe() this._setStatus("Notifications enabled on this device.") + this._reloadDevices() } catch (err) { this._setStatus(this._friendlyError(err)) } @@ -26,12 +28,23 @@ export default class extends Controller { try { await WebPush.unsubscribe() this._setStatus("Notifications disabled on this device.") + this._reloadDevices() } catch (err) { this._setStatus(this._friendlyError(err)) } await this._refresh() } + // Tell the device-list turbo-frame to refetch itself so the row for this + // browser appears (or disappears) without a full page reload. + _reloadDevices() { + if (!this.devicesUrlValue) return + const frame = document.getElementById("web-push-devices") + if (!frame) return + // Setting src triggers a fetch even when the URL hasn't changed. + frame.src = this.devicesUrlValue + } + // ---- internals ---- async _refresh() { diff --git a/engine/app/views/coplan/settings/settings/_devices.html.erb b/engine/app/views/coplan/settings/settings/_devices.html.erb new file mode 100644 index 0000000..ae1fa80 --- /dev/null +++ b/engine/app/views/coplan/settings/settings/_devices.html.erb @@ -0,0 +1,28 @@ +<%# Per-device list rendered both in-page (initial load) and as a turbo-frame %> +<%# response after the Stimulus controller refreshes it post-enable/disable. %> +<%= turbo_frame_tag "web-push-devices" do %> + <% if web_push_subscriptions.any? %> +
+
Devices receiving notifications
+
    + <% web_push_subscriptions.each do |sub| %> +
  • +
    <%= sub.device_label %>
    +
    + Added <%= time_ago_in_words(sub.created_at) %> ago + <% if sub.last_delivered_at %> + · last notified <%= time_ago_in_words(sub.last_delivered_at) %> ago + · <%= pluralize(sub.notifications_delivered_count, "notification") %> + <% else %> + · no notifications yet + <% end %> +
    +
  • + <% end %> +
+

+ To remove a device, sign in to CoPlan from that browser and click "Disable on this device". +

+
+ <% end %> +<% end %> diff --git a/engine/app/views/coplan/settings/settings/_notifications.html.erb b/engine/app/views/coplan/settings/settings/_notifications.html.erb index 03ed2e4..c2fa0b4 100644 --- a/engine/app/views/coplan/settings/settings/_notifications.html.erb +++ b/engine/app/views/coplan/settings/settings/_notifications.html.erb @@ -1,6 +1,8 @@ <% return unless CoPlan.configuration.web_push_configured? %> -
+
Browser notifications
@@ -28,28 +30,6 @@
- <% if @web_push_subscriptions.any? %> -
-
Devices receiving notifications
-
    - <% @web_push_subscriptions.each do |sub| %> -
  • -
    <%= sub.device_label %>
    -
    - Added <%= time_ago_in_words(sub.created_at) %> ago - <% if sub.last_delivered_at %> - · last notified <%= time_ago_in_words(sub.last_delivered_at) %> ago - · <%= pluralize(sub.notifications_delivered_count, "notification") %> - <% else %> - · no notifications yet - <% end %> -
    -
  • - <% end %> -
-

- To remove a device, sign in to CoPlan from that browser and click "Disable on this device". -

-
- <% end %> + <%= render partial: "coplan/settings/settings/devices", + locals: { web_push_subscriptions: @web_push_subscriptions } %>
diff --git a/engine/config/routes.rb b/engine/config/routes.rb index e7e801e..beabb6e 100644 --- a/engine/config/routes.rb +++ b/engine/config/routes.rb @@ -76,6 +76,10 @@ # PushManager and uniquely identify a (browser, device, app) tuple per user. scope :web_push, module: "web_push", as: :web_push do resource :subscription, only: [:create, :destroy], controller: "subscriptions" + # Turbo-frame target for the per-device list on the Settings page. + # Reloaded by the settings Stimulus controller after enable/disable so + # the list reflects the new browser without a full page refresh. + get "devices", to: "subscriptions#devices", as: :devices end root "plans#index"