1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-09 23:45:21 +02:00

Simplify custom confirmation configuration in views

This commit is contained in:
Zach Gollwitzer 2025-04-26 22:22:44 -04:00
parent 6659e0cdb5
commit 17d073e58d
17 changed files with 123 additions and 169 deletions

View file

@ -5,9 +5,7 @@ class ButtonComponent < ViewComponent::Base
attr_reader :text, :icon, :icon_position
def initialize(
text: nil, variant: "primary", size: "md", icon: nil, icon_position: "left", full_width: false, rounded: false, **opts
)
def initialize(text: nil, variant: "primary", size: "md", icon: nil, icon_position: "left", full_width: false, rounded: false, confirm: nil, **opts)
@text = text
@variant = variant.underscore.to_sym
@size = size.to_sym
@ -15,18 +13,11 @@ class ButtonComponent < ViewComponent::Base
@icon_position = icon_position
@full_width = full_width
@rounded = rounded
@confirm = confirm
@opts = opts
end
def container(&block)
merged_opts = opts.dup || {}
extra_classes = merged_opts.delete(:class)
href = merged_opts.delete(:href)
merged_opts = merged_opts.merge(
class: class_names(container_classes, extra_classes)
)
if href.present?
button_to(href, **merged_opts, &block)
else
@ -35,5 +26,25 @@ class ButtonComponent < ViewComponent::Base
end
private
attr_reader :variant, :size, :rounded, :full_width, :opts
attr_reader :variant, :size, :rounded, :full_width, :confirm, :opts
def href
opts[:href]
end
def merged_opts
merged_opts = opts.dup || {}
extra_classes = merged_opts.delete(:class)
href = merged_opts.delete(:href)
data = merged_opts.delete(:data) || {}
if confirm.present?
data = data.merge(turbo_confirm: confirm.to_data_attribute)
end
merged_opts.merge(
class: class_names(container_classes, extra_classes),
data: data
)
end
end

View file

@ -1,9 +1,9 @@
class MenuItemComponent < ViewComponent::Base
VARIANTS = %i[link button divider].freeze
attr_reader :variant, :text, :icon, :href, :method, :destructive, :opts
attr_reader :variant, :text, :icon, :href, :method, :destructive, :confirm, :opts
def initialize(variant:, text: nil, icon: nil, href: nil, method: :post, destructive: false, **opts)
def initialize(variant:, text: nil, icon: nil, href: nil, method: :post, destructive: false, confirm: nil, **opts)
@variant = variant.to_sym
@text = text
@icon = icon
@ -11,13 +11,13 @@ class MenuItemComponent < ViewComponent::Base
@method = method.to_sym
@destructive = destructive
@opts = opts
@confirm = confirm
raise ArgumentError, "Invalid variant: #{@variant}" unless VARIANTS.include?(@variant)
end
def wrapper(&block)
if variant == :button
button_to href, method: method, class: container_classes, **opts, &block
button_to href, method: method, class: container_classes, **merged_button_opts, &block
elsif variant == :link
link_to href, class: container_classes, **opts, &block
else
@ -43,4 +43,15 @@ class MenuItemComponent < ViewComponent::Base
destructive? ? "hover:bg-red-tint-5 theme-dark:hover:bg-red-tint-10" : "hover:bg-container-hover"
].join(" ")
end
def merged_button_opts
merged_opts = opts.dup || {}
data = merged_opts.delete(:data) || {}
if confirm.present?
data = data.merge(turbo_confirm: confirm.to_data_attribute)
end
merged_opts.merge(data: data)
end
end

View file

@ -1,15 +1,6 @@
module ApplicationHelper
include Pagy::Frontend
def custom_turbo_confirm(title: "Are you sure?", body: "This action cannot be undone.", btn_text: "Confirm", btn_variant: "primary")
{
title: title,
body: body,
confirmText: btn_text,
variant: btn_variant
}
end
def icon(key, size: "md", color: "current")
render partial: "shared/icon", locals: { key:, size:, color: }
end

View file

@ -0,0 +1,51 @@
# The shape of data expected by `confirm_dialog_controller.js` to override the
# default browser confirm API via Turbo.
class CustomConfirm
class << self
def for_resource_deletion(resource_name, high_severity: false)
new(
destructive: true,
high_severity: high_severity,
title: "Delete #{resource_name}?",
body: "Are you sure you want to delete #{resource_name}? This is not reversible.",
btn_text: "Delete #{resource_name}"
)
end
end
def initialize(title: default_title, body: default_body, btn_text: default_btn_text, destructive: false, high_severity: false)
@title = title
@body = body
@btn_text = btn_text
@btn_variant = derive_btn_variant(destructive, high_severity)
end
def to_data_attribute
{
title: title,
body: body,
confirmText: btn_text,
variant: btn_variant
}
end
private
attr_reader :title, :body, :btn_text, :btn_variant
def derive_btn_variant(destructive, high_severity)
return "primary" unless destructive
high_severity ? "destructive" : "outline-destructive"
end
def default_title
"Are you sure?"
end
def default_body
"This is not reversible."
end
def default_btn_text
"Confirm"
end
end

View file

@ -19,14 +19,7 @@
href: account_path(account),
method: :delete,
icon: "trash-2",
data: {
turbo_frame: :_top,
turbo_confirm: custom_turbo_confirm(
title: t(".confirm_title"),
body: t(".confirm_body_html"),
btn_text: t(".confirm_accept", name: account.name),
btn_variant: "destructive"
)
}
confirm: CustomConfirm.for_resource_deletion("Account", high_severity: true),
data: { turbo_frame: :_top }
) %>
<% end %>

View file

@ -4,20 +4,12 @@
<div class="flex items-center gap-2">
<%= render MenuComponent.new do |menu| %>
<% menu.with_item(
variant: "button",
text: "Delete all",
href: destroy_all_categories_path,
method: :delete,
icon: "trash-2",
data: {
turbo_confirm: custom_turbo_confirm(
title: "Delete all categories?",
body: "All of your transactions will become uncategorized and this cannot be undone.",
btn_text: "Delete all categories",
btn_variant: "destructive"
)
}
) %>
variant: "button",
text: "Delete all",
href: destroy_all_categories_path,
method: :delete,
icon: "trash-2",
confirm: CustomConfirm.for_resource_deletion("All categories", high_severity: true)) %>
<% end %>
<%= render LinkComponent.new(

View file

@ -17,13 +17,6 @@
href: chat_path(chat),
icon: "trash-2",
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: "Delete chat?",
body: "Are you sure you want to delete this chat? This action cannot be undone.",
btn_text: "Delete chat",
btn_variant: "outline-destructive"
)
}) %>
confirm: CustomConfirm.for_resource_deletion("Chat")) %>
<% end %>
<% end %>

View file

@ -29,13 +29,7 @@
href: chat_path(chat),
icon: "trash-2",
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: "Are you sure you want to delete this chat?",
btn_text: "Delete chat",
btn_variant: "outline-destructive"
)
}) %>
confirm: CustomConfirm.for_resource_deletion("Chat")) %>
<% end %>
<% end %>
</nav>

View file

@ -23,14 +23,7 @@
href: family_merchant_path(family_merchant),
icon: "trash-2",
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: "Delete #{family_merchant.name}?",
body: "This will remove this merchant from all transactions it has been assigned to.",
btn_text: "Delete #{family_merchant.name}",
btn_variant: "outline-destructive"
)
}) %>
confirm: CustomConfirm.for_resource_deletion(family_merchant.name)) %>
<% end %>
</div>
</div>

View file

@ -46,14 +46,11 @@
href: revert_import_path(import),
icon: "rotate-ccw",
method: :put,
data: {
turbo_confirm: custom_turbo_confirm(
title: "Revert import?",
body: "This will delete transactions that were imported, but you will still be able to review and re-import your data at any time.",
btn_text: "Revert",
btn_variant: "outline-destructive"
)
}) %>
confirm: CustomConfirm.new(
title: "Revert import?",
body: "This will delete transactions that were imported, but you will still be able to review and re-import your data at any time.",
btn_text: "Revert"
)) %>
<% else %>
<% menu.with_item(
@ -62,14 +59,7 @@
href: import_path(import),
icon: "trash-2",
method: :delete,
data: {
turbo_confirm: {
title: "Delete import?",
body: "This will delete the import and is not reversible.",
btn_text: "Delete import",
btn_variant: "outline-destructive"
}
}) %>
confirm: CustomConfirm.for_resource_deletion("Import")) %>
<% end %>
<% end %>
</div>

View file

@ -23,13 +23,7 @@
href: merchant_path(merchant),
icon: "trash-2",
method: :delete,
data: { turbo_confirm: custom_turbo_confirm(
title: t(".confirm_title"),
body: t(".confirm_body"),
btn_text: t(".confirm_accept"),
btn_variant: "outline-destructive"
)
}) %>
confirm: CustomConfirm.for_resource_deletion(merchant.name)) %>
<% end %>
</div>
</div>

View file

@ -80,14 +80,7 @@
variant: "destructive",
href: plaid_item_path(plaid_item),
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: t(".confirm_title"),
body: t(".confirm_body"),
btn_text: t(".confirm_accept"),
btn_variant: "destructive"
)
}
confirm: CustomConfirm.for_resource_deletion(plaid_item.name, high_severity: true)
) %>
<%= render LinkComponent.new(
@ -115,11 +108,7 @@
icon: "trash-2",
href: plaid_item_path(plaid_item),
method: :delete,
data: { turbo_confirm: custom_turbo_confirm(
title: t(".confirm_title"),
body: t(".confirm_body"),
btn_text: t(".confirm_accept"),
btn_variant: "destructive") }
confirm: CustomConfirm.for_resource_deletion(plaid_item.name, high_severity: true)
) %>
<% end %>
</div>

View file

@ -53,14 +53,7 @@
href: rule_path(rule),
icon: "trash-2",
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: "Delete rule?",
body: "Are you sure you want to delete this rule? Data affected by this rule will no longer be automatically updated. This action cannot be undone.",
btn_text: "Delete rule",
btn_variant: "outline-destructive"
)
}) %>
confirm: CustomConfirm.for_resource_deletion("Rule")) %>
<% end %>
</div>
</div>

View file

@ -10,14 +10,7 @@
href: destroy_all_rules_path,
icon: "trash-2",
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: "Delete all rules?",
body: "Are you sure you want to delete all rules? This action cannot be undone.",
btn_text: "Delete all rules",
btn_variant: "destructive"
)
}) %>
confirm: CustomConfirm.for_resource_deletion("All rules", high_severity: true)) %>
<% end %>
<% end %>

View file

@ -56,14 +56,7 @@
icon: "x",
href: settings_profile_path(user_id: user),
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: t(".confirm_remove_member.title"),
body: t(".confirm_remove_member.body", name: user.display_name),
btn_text: t(".remove_member"),
btn_variant: "destructive"
)
}
confirm: CustomConfirm.for_resource_deletion(user.display_name, high_severity: true)
) %>
</div>
<% end %>
@ -110,14 +103,7 @@
icon: "x",
href: invitation_path(invitation),
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: t(".confirm_remove_invitation.title"),
body: t(".confirm_remove_invitation.body", email: invitation.email),
btn_text: t(".remove_invitation"),
btn_variant: "outline-destructive"
)
}
confirm: CustomConfirm.for_resource_deletion(invitation.email, high_severity: true)
) %>
<% end %>
</div>
@ -150,14 +136,7 @@
variant: "destructive",
href: reset_user_path(@user),
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: t(".confirm_reset.title"),
body: t(".confirm_reset.body"),
btn_text: t(".reset_account"),
btn_variant: "destructive"
)
}
confirm: CustomConfirm.for_resource_deletion("Account", high_severity: true)
) %>
</div>
<% end %>
@ -172,14 +151,7 @@
variant: "destructive",
href: user_path(@user),
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: t(".confirm_delete.title"),
body: t(".confirm_delete.body"),
btn_text: t(".delete_account"),
btn_variant: "destructive"
)
}
confirm: CustomConfirm.for_resource_deletion("your account", high_severity: true)
) %>
</div>
</div>

View file

@ -26,14 +26,12 @@
variant: "secondary",
href: disable_mfa_path,
method: :delete,
data: {
turbo_confirm: custom_turbo_confirm(
title: t(".disable_mfa_confirm"),
body: t(".disable_mfa_confirm"),
btn_text: t(".disable_mfa"),
btn_variant: "outline-destructive"
)
}
confirm: CustomConfirm.new(
title: t(".disable_mfa_confirm"),
body: t(".disable_mfa_confirm"),
btn_text: t(".disable_mfa"),
destructive: true
)
) %>
<% else %>
<%= render LinkComponent.new(

View file

@ -17,12 +17,8 @@
href: tag_path(tag),
icon: "trash-2",
method: :delete,
data: { turbo_confirm: custom_turbo_confirm(
title: "Delete tag?",
body: "Are you sure you want to delete this tag and remove it from assigned transactions? This action cannot be undone.",
btn_text: "Delete tag",
btn_variant: "outline-destructive"
) }) %>
confirm: CustomConfirm.for_resource_deletion(tag.name)
) %>
<% end %>
</div>
</div>