Avoid submitting duplicate connected apps

Simple Rails forms prevent double-clicking on submit already. Converting
the StimulusReflex interaction to a simple form submit to a controller
solves the race condition.

The UX is slightly worse because the whole page is reloaded instead
rendering only the connected app panel. But we can solve that when we
add more apps and want to activate them independently. By then, we may
have good patterns for working with Turbo.

Technically, the new buttons are a form within a form which is invalid
HTML, but it works.
This commit is contained in:
Maikel Linke
2024-05-15 16:47:37 +10:00
parent ae09124946
commit 45c164d5ae
4 changed files with 47 additions and 57 deletions

View File

@@ -0,0 +1,43 @@
# frozen_string_literal: true
module Admin
class ConnectedAppsController < ApplicationController
def create
authorize! :admin, enterprise
app = ConnectedApp.create!(enterprise_id: enterprise.id)
ConnectAppJob.perform_later(
app, spree_current_user.spree_api_key,
channel: SessionChannel.for_request(request),
)
render_panel
end
def destroy
authorize! :admin, enterprise
app = enterprise.connected_apps.first
app.destroy
WebhookDeliveryJob.perform_later(
app.data["destroy"],
"disconnect-app",
nil
)
render_panel
end
private
def enterprise
@enterprise ||= Enterprise.find(params.require(:enterprise_id))
end
def render_panel
redirect_to "#{edit_admin_enterprise_path(enterprise)}#/connected_apps_panel"
end
end
end

View File

@@ -1,53 +0,0 @@
# frozen_string_literal: true
module Admin
class ConnectedAppReflex < ApplicationReflex
def create
authorize! :admin, enterprise
app = ConnectedApp.create!(enterprise_id: enterprise.id)
# Avoid race condition by sending before enqueuing job:
broadcast_partial
ConnectAppJob.perform_later(
app, current_user.spree_api_key,
channel: SessionChannel.for_request(request),
)
morph :nothing
end
def destroy
authorize! :admin, enterprise
app = enterprise.connected_apps.first
app.destroy
broadcast_partial
WebhookDeliveryJob.perform_later(
app.data["destroy"],
"disconnect-app",
nil
)
morph :nothing
end
private
def enterprise
@enterprise ||= Enterprise.find(element.dataset.enterprise_id)
end
def broadcast_partial
selector = "#edit_enterprise_#{enterprise.id} #connected-app-discover-regen"
html = ApplicationController.render(
partial: "admin/enterprises/form/connected_apps",
locals: { enterprise: },
)
# Avoid race condition by sending before enqueuing job:
cable_ready.morph(selector:, html:).broadcast
end
end
end

View File

@@ -6,16 +6,14 @@
%p= t ".tagline"
%div
- if enterprise.connected_apps.empty?
%button{ data: {reflex: "click->Admin::ConnectedApp#create", enterprise_id: enterprise.id} }
= t ".enable"
= button_to t(".enable"), admin_enterprise_connected_apps_path(enterprise.id), method: :post
- elsif enterprise.connected_apps.connecting.present?
%button{ disabled: true }
%i.spinner.fa.fa-spin.fa-circle-o-notch
&nbsp;
= t ".loading"
- else
%button{ data: {reflex: "click->Admin::ConnectedApp#destroy", enterprise_id: enterprise.id} }
= t ".disable"
= button_to t(".disable"), admin_enterprise_connected_app_path(0, enterprise_id: enterprise.id), method: :delete
.connected-app__connection
- if enterprise.connected_apps.ready.present?

View File

@@ -35,6 +35,8 @@ Openfoodnetwork::Application.routes.draw do
patch :register
end
resources :connected_apps, only: [:create, :destroy]
resources :producer_properties do
post :update_positions, on: :collection
end