mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 12:04:08 +00:00
Feat: Add default user account and consolidate account actions in menu (#1130)
* feat: Add default account for manual transaction entries (#1061) Allow users to designate a default account that auto-selects in the transaction creation form. Also consolidates account list actions (edit, link/unlink, enable/disable) into a meatball menu. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * - handle context menu width on mobile - restrict default account to depository types only - added FR, ES and DE i18n files * - Add credit card accounts can also be used as default - Moved logic into controller * Scope context menu max-width to accounts menu only - decouples the width constraint from the shared DS::Menu component by introducing an optional max_width param * fix ci test and address issues raised by coderabbit and codex * Address CodeRabbit review feedback - Use .present? for institution_name guards to avoid empty UI artifacts - Align "Set default" menu visibility with actual preselection eligibility (active + unlinked + supports_default?) to prevent drift between UI and model - Keep disabled star visible when account is already default but now ineligible Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add eligible_for_transaction_default? predicate to Account model Consolidates active + unlinked + supports_default? checks into a single shared predicate used by the controller, view, and user model guard, preventing a direct PATCH from bypassing UI eligibility rules. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Added "Unset default" option Added negative test for default account Removed duplicated logic for account.eligible_for_transaction_default --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<%= tag.div data: { controller: "DS--menu", DS__menu_placement_value: placement, DS__menu_offset_value: offset, testid: testid } do %>
|
||||
<%= tag.div data: { controller: "DS--menu", DS__menu_placement_value: placement, DS__menu_offset_value: offset, DS__menu_mobile_fullwidth_value: mobile_fullwidth, testid: testid } do %>
|
||||
<% if variant == :icon %>
|
||||
<%= render DS::Button.new(variant: "icon", icon: icon_vertical ? "more-vertical" : "more-horizontal", data: { DS__menu_target: "button" }) %>
|
||||
<% elsif variant == :button %>
|
||||
@@ -12,7 +12,7 @@
|
||||
<% end %>
|
||||
|
||||
<div data-DS--menu-target="content" class="px-2 lg:px-0 max-w-full hidden z-50">
|
||||
<div class="mx-auto min-w-[200px] shadow-border-xs bg-container rounded-lg">
|
||||
<%= tag.div class: "mx-auto min-w-[200px] shadow-border-xs bg-container rounded-lg", style: ("max-width: #{max_width}" if max_width) do %>
|
||||
<%= header %>
|
||||
|
||||
<%= tag.div class: class_names("py-1" => !no_padding) do %>
|
||||
@@ -22,6 +22,6 @@
|
||||
|
||||
<%= custom_content %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class DS::Menu < DesignSystemComponent
|
||||
attr_reader :variant, :avatar_url, :initials, :placement, :offset, :icon_vertical, :no_padding, :testid
|
||||
attr_reader :variant, :avatar_url, :initials, :placement, :offset, :icon_vertical, :no_padding, :testid, :mobile_fullwidth, :max_width
|
||||
|
||||
renders_one :button, ->(**button_options, &block) do
|
||||
options_with_target = button_options.merge(data: { DS__menu_target: "button" })
|
||||
@@ -23,7 +23,7 @@ class DS::Menu < DesignSystemComponent
|
||||
|
||||
VARIANTS = %i[icon button avatar].freeze
|
||||
|
||||
def initialize(variant: "icon", avatar_url: nil, initials: nil, placement: "bottom-end", offset: 12, icon_vertical: false, no_padding: false, testid: nil)
|
||||
def initialize(variant: "icon", avatar_url: nil, initials: nil, placement: "bottom-end", offset: 12, icon_vertical: false, no_padding: false, testid: nil, mobile_fullwidth: true, max_width: nil)
|
||||
@variant = variant.to_sym
|
||||
@avatar_url = avatar_url
|
||||
@initials = initials
|
||||
@@ -32,6 +32,8 @@ class DS::Menu < DesignSystemComponent
|
||||
@icon_vertical = icon_vertical
|
||||
@no_padding = no_padding
|
||||
@testid = testid
|
||||
@mobile_fullwidth = mobile_fullwidth
|
||||
@max_width = max_width
|
||||
|
||||
raise ArgumentError, "Invalid variant: #{@variant}" unless VARIANTS.include?(@variant)
|
||||
end
|
||||
|
||||
@@ -16,6 +16,7 @@ export default class extends Controller {
|
||||
show: Boolean,
|
||||
placement: { type: String, default: "bottom-end" },
|
||||
offset: { type: Number, default: 6 },
|
||||
mobileFullwidth: { type: Boolean, default: true },
|
||||
};
|
||||
|
||||
connect() {
|
||||
@@ -105,13 +106,14 @@ export default class extends Controller {
|
||||
if (!this.buttonTarget || !this.contentTarget) return;
|
||||
|
||||
const isSmallScreen = !window.matchMedia("(min-width: 768px)").matches;
|
||||
const useMobileFullwidth = isSmallScreen && this.mobileFullwidthValue;
|
||||
|
||||
computePosition(this.buttonTarget, this.contentTarget, {
|
||||
placement: isSmallScreen ? "bottom" : this.placementValue,
|
||||
placement: useMobileFullwidth ? "bottom" : this.placementValue,
|
||||
middleware: [offset(this.offsetValue), shift({ padding: 5 })],
|
||||
strategy: "fixed",
|
||||
}).then(({ x, y }) => {
|
||||
if (isSmallScreen) {
|
||||
if (useMobileFullwidth) {
|
||||
Object.assign(this.contentTarget.style, {
|
||||
position: "fixed",
|
||||
left: "0px",
|
||||
|
||||
Reference in New Issue
Block a user