mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-10 07:55:21 +02:00
Split into Link and Button components for clarity
This commit is contained in:
parent
cbd05ae3da
commit
1c9dd6ea5f
77 changed files with 738 additions and 579 deletions
|
@ -6,6 +6,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
@utility text-inverse {
|
||||
@apply text-white;
|
||||
|
||||
@variant theme-dark {
|
||||
@apply text-gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
@utility text-secondary {
|
||||
@apply text-gray-500;
|
||||
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
<%= wrapper_tag do %>
|
||||
<% if icon_only? %>
|
||||
<%= lucide_icon(@icon, class: icon_classes) %>
|
||||
<% else %>
|
||||
<% if @leading_icon %>
|
||||
<%= lucide_icon(@leading_icon, class: icon_classes) %>
|
||||
<% end %>
|
||||
<%= container do %>
|
||||
<% if icon && (icon_position != "right") %>
|
||||
<%= lucide_icon(icon, class: icon_classes) %>
|
||||
<% end %>
|
||||
|
||||
<%= @text %>
|
||||
<% unless icon_only? %>
|
||||
<%= text %>
|
||||
<% end %>
|
||||
|
||||
<% if @trailing_icon %>
|
||||
<%= lucide_icon(@trailing_icon, class: icon_classes) %>
|
||||
<% end %>
|
||||
<% if icon && icon_position == "right" %>
|
||||
<%= lucide_icon(icon, class: icon_classes) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -1,149 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ButtonComponent < ViewComponent::Base
|
||||
VARIANTS = {
|
||||
primary: {
|
||||
bg: "bg-inverse hover:bg-inverse-hover disabled:bg-gray-500 theme-dark:disabled:bg-gray-400",
|
||||
text: "text-white theme-dark:text-gray-900",
|
||||
icon: "fg-inverse"
|
||||
},
|
||||
secondary: {
|
||||
bg: "bg-gray-50 theme-dark:bg-gray-700 hover:bg-gray-100 theme-dark:hover:bg-gray-600 disabled:bg-gray-200 theme-dark:disabled:bg-gray-600",
|
||||
text: "text-gray-900 theme-dark:text-white",
|
||||
icon: "fg-primary"
|
||||
},
|
||||
destructive: {
|
||||
bg: "bg-red-500 theme-dark:bg-red-400 hover:bg-red-600 theme-dark:hover:bg-red-500 disabled:bg-red-200 theme-dark:disabled:bg-red-600",
|
||||
text: "text-white theme-dark:text-white",
|
||||
icon: "fg-white"
|
||||
},
|
||||
outline: {
|
||||
bg: "bg-transparent hover:bg-surface-hover",
|
||||
text: "text-gray-900 theme-dark:text-white",
|
||||
border: "border border-secondary",
|
||||
icon: "fg-gray"
|
||||
},
|
||||
outline_destructive: {
|
||||
bg: "bg-transparent hover:bg-gray-100 theme-dark:hover:bg-gray-700",
|
||||
text: "text-destructive",
|
||||
border: "border border-secondary"
|
||||
},
|
||||
ghost: {
|
||||
bg: "bg-transparent hover:bg-gray-100 theme-dark:hover:bg-gray-700",
|
||||
text: "text-primary",
|
||||
icon: "fg-gray"
|
||||
},
|
||||
link_color: {
|
||||
bg: "bg-transparent hover:bg-gray-100 theme-dark:hover:bg-gray-700",
|
||||
text: "text-primary",
|
||||
icon: "fg-inverse"
|
||||
},
|
||||
link_gray: {
|
||||
bg: "bg-transparent hover:bg-gray-100 theme-dark:hover:bg-gray-700",
|
||||
text: "text-secondary",
|
||||
icon: "fg-gray"
|
||||
},
|
||||
icon: {
|
||||
bg: "bg-transparent hover:bg-gray-100 theme-dark:hover:bg-gray-700 rounded-lg",
|
||||
text: "text-secondary",
|
||||
icon: "fg-gray"
|
||||
},
|
||||
icon_inverse: {
|
||||
bg: "bg-inverse hover:bg-inverse-hover rounded-lg",
|
||||
text: "fg-inverse",
|
||||
icon: "fg-inverse"
|
||||
}
|
||||
}.freeze
|
||||
include ButtonStylable
|
||||
|
||||
SIZES = {
|
||||
sm: {
|
||||
icon_container: "w-8 h-8",
|
||||
container: "px-2 py-1 rounded-md",
|
||||
text: "text-sm",
|
||||
icon: "w-4 h-4"
|
||||
},
|
||||
md: {
|
||||
icon_container: "w-9 h-9",
|
||||
container: "px-3 py-2 rounded-lg",
|
||||
text: "text-sm",
|
||||
icon: "w-5 h-5"
|
||||
},
|
||||
lg: {
|
||||
icon_container: "w-10 h-10",
|
||||
container: "px-4 py-3 rounded-xl",
|
||||
text: "text-base",
|
||||
icon: "w-6 h-6"
|
||||
}
|
||||
}
|
||||
attr_reader :text, :icon, :icon_position
|
||||
|
||||
def initialize(options = {})
|
||||
@text = options.delete(:text)
|
||||
@variant = (options.delete(:variant) || "primary").underscore.to_sym
|
||||
@size = (options.delete(:size) || :md).to_sym
|
||||
@href = options.delete(:href)
|
||||
@method = options.delete(:method) || :get
|
||||
@leading_icon = options.delete(:leading_icon)
|
||||
@trailing_icon = options.delete(:trailing_icon)
|
||||
@icon = options.delete(:icon)
|
||||
@full_width = options.delete(:full_width)
|
||||
@left_align = options.delete(:left_align)
|
||||
@extra_classes = options.delete(:class)
|
||||
@options = options
|
||||
def initialize(
|
||||
text: nil, variant: "primary", size: "md", icon: nil, icon_position: "left", full_width: false, rounded: false, **opts
|
||||
)
|
||||
@text = text
|
||||
@variant = variant.underscore.to_sym
|
||||
@size = size.to_sym
|
||||
@icon = icon
|
||||
@icon_position = icon_position
|
||||
@full_width = full_width
|
||||
@rounded = rounded
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def wrapper_tag(&block)
|
||||
if @href && @method != :get
|
||||
button_to @href, class: container_classes, method: @method, **@options, &block
|
||||
elsif @href
|
||||
link_to @href, class: container_classes, **@options, &block
|
||||
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
|
||||
content_tag :button, type: "button", class: container_classes, **@options, &block
|
||||
content_tag(:button, **merged_opts, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def icon_classes
|
||||
[
|
||||
"shrink-0",
|
||||
size_meta[:icon],
|
||||
variant_meta[:icon]
|
||||
].join(" ")
|
||||
end
|
||||
|
||||
def icon_only?
|
||||
@variant == :icon || @variant == :icon_inverse
|
||||
end
|
||||
|
||||
private
|
||||
def container_classes
|
||||
hidden_override = (@extra_classes || "").split(" ").include?("hidden")
|
||||
default_classes = hidden_override ? "items-center gap-1" : "inline-flex items-center gap-1"
|
||||
|
||||
[
|
||||
"whitespace-nowrap",
|
||||
default_classes,
|
||||
@full_width ? "w-full" : nil,
|
||||
@left_align ? "justify-start" : "justify-center",
|
||||
icon_only? ? size_meta[:icon_container] : size_meta[:container],
|
||||
variant_meta[:bg],
|
||||
variant_meta.dig(:border),
|
||||
text_classes,
|
||||
@extra_classes
|
||||
].compact.join(" ")
|
||||
end
|
||||
|
||||
def text_classes
|
||||
[
|
||||
"font-medium",
|
||||
size_meta[:text],
|
||||
variant_meta[:text]
|
||||
].join(" ")
|
||||
end
|
||||
|
||||
def size_meta
|
||||
SIZES[@size]
|
||||
end
|
||||
|
||||
def variant_meta
|
||||
VARIANTS[@variant]
|
||||
end
|
||||
attr_reader :variant, :size, :rounded, :full_width, :opts
|
||||
end
|
||||
|
|
99
app/components/button_stylable.rb
Normal file
99
app/components/button_stylable.rb
Normal file
|
@ -0,0 +1,99 @@
|
|||
module ButtonStylable
|
||||
VARIANTS = {
|
||||
primary: {
|
||||
container_classes: "text-inverse bg-inverse hover:bg-inverse-hover disabled:bg-gray-500 theme-dark:disabled:bg-gray-400",
|
||||
icon_classes: "fg-inverse"
|
||||
},
|
||||
secondary: {
|
||||
container_classes: "text-secondary bg-gray-50 theme-dark:bg-gray-700 hover:bg-gray-100 theme-dark:hover:bg-gray-600 disabled:bg-gray-200 theme-dark:disabled:bg-gray-600",
|
||||
icon_classes: "fg-primary"
|
||||
},
|
||||
destructive: {
|
||||
container_classes: "text-inverse bg-red-500 theme-dark:bg-red-400 hover:bg-red-600 theme-dark:hover:bg-red-500 disabled:bg-red-200 theme-dark:disabled:bg-red-600",
|
||||
icon_classes: "fg-white"
|
||||
},
|
||||
outline: {
|
||||
container_classes: "text-primary border border-secondary bg-transparent hover:bg-surface-hover",
|
||||
icon_classes: "fg-gray"
|
||||
},
|
||||
outline_destructive: {
|
||||
container_classes: "text-destructive border border-secondary bg-transparent hover:bg-gray-100 theme-dark:hover:bg-gray-700",
|
||||
icon_classes: "fg-gray"
|
||||
},
|
||||
ghost: {
|
||||
container_classes: "text-primary bg-transparent hover:bg-gray-100 theme-dark:hover:bg-gray-700",
|
||||
icon_classes: "fg-gray"
|
||||
},
|
||||
icon: {
|
||||
container_classes: "hover:bg-gray-100 theme-dark:hover:bg-gray-700",
|
||||
icon_classes: "fg-gray"
|
||||
},
|
||||
icon_inverse: {
|
||||
container_classes: "bg-inverse hover:bg-inverse-hover",
|
||||
icon_classes: "fg-inverse"
|
||||
}
|
||||
}.freeze
|
||||
|
||||
SIZES = {
|
||||
sm: {
|
||||
padding_classes: "px-2 py-1",
|
||||
icon_padding_classes: "p-2",
|
||||
radius_classes: "rounded-md",
|
||||
text_classes: "text-sm",
|
||||
icon_classes: "w-4 h-4"
|
||||
},
|
||||
md: {
|
||||
padding_classes: "px-3 py-2",
|
||||
icon_padding_classes: "p-2",
|
||||
radius_classes: "rounded-lg",
|
||||
text_classes: "text-sm",
|
||||
icon_classes: "w-5 h-5"
|
||||
},
|
||||
lg: {
|
||||
padding_classes: "px-4 py-3",
|
||||
icon_padding_classes: "p-2",
|
||||
radius_classes: "rounded-xl",
|
||||
text_classes: "text-base",
|
||||
icon_classes: "w-6 h-6"
|
||||
}
|
||||
}.freeze
|
||||
|
||||
def container_classes
|
||||
class_names(
|
||||
"inline-flex items-center gap-1 font-medium whitespace-nowrap",
|
||||
full_width ? "w-full justify-center" : "",
|
||||
icon_only? ? SIZES.dig(size, :icon_padding_classes) : SIZES.dig(size, :padding_classes),
|
||||
rounded ? "rounded-full" : SIZES.dig(size, :radius_classes),
|
||||
SIZES.dig(size, :text_classes),
|
||||
VARIANTS.dig(variant, :container_classes)
|
||||
)
|
||||
end
|
||||
|
||||
def icon_classes
|
||||
class_names(
|
||||
SIZES.dig(size, :icon_classes),
|
||||
VARIANTS.dig(variant, :icon_classes)
|
||||
)
|
||||
end
|
||||
|
||||
def icon_only?
|
||||
variant.in?([ :icon, :icon_inverse ])
|
||||
end
|
||||
|
||||
private
|
||||
def full_width
|
||||
@full_width ||= false
|
||||
end
|
||||
|
||||
def rounded
|
||||
@rounded ||= false
|
||||
end
|
||||
|
||||
def variant
|
||||
@variant ||= :primary
|
||||
end
|
||||
|
||||
def size
|
||||
@size ||= :md
|
||||
end
|
||||
end
|
|
@ -1,69 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IconComponent < ViewComponent::Base
|
||||
erb_template <<~ERB
|
||||
<%= tag.div class: container_classes do %>
|
||||
<%= lucide_icon(@icon, class: icon_classes) %>
|
||||
<% end %>
|
||||
ERB
|
||||
|
||||
VARIANTS = {
|
||||
default: {
|
||||
icon: "fg-gray",
|
||||
container: "bg-transparent"
|
||||
},
|
||||
destructive: {
|
||||
icon: "text-destructive",
|
||||
container: "bg-transparent"
|
||||
}
|
||||
}
|
||||
|
||||
SIZES = {
|
||||
sm: {
|
||||
icon: "w-4 h-4",
|
||||
container: "w-8 h-8"
|
||||
},
|
||||
md: {
|
||||
icon: "w-5 h-5",
|
||||
container: "w-10 h-10"
|
||||
},
|
||||
lg: {
|
||||
icon: "w-6 h-6",
|
||||
container: "w-12 h-12"
|
||||
}
|
||||
}
|
||||
|
||||
def initialize(icon, variant: "default", size: "md")
|
||||
@icon = icon
|
||||
@variant = variant.to_sym
|
||||
@size = size.to_sym
|
||||
end
|
||||
|
||||
def icon_classes
|
||||
[
|
||||
size_meta[:icon],
|
||||
variant_meta[:icon]
|
||||
].join(" ")
|
||||
end
|
||||
|
||||
def container_classes
|
||||
[
|
||||
"flex justify-center items-center",
|
||||
show_padding? ? size_meta[:container] : "",
|
||||
variant_meta[:container]
|
||||
].join(" ")
|
||||
end
|
||||
|
||||
private
|
||||
def show_padding?
|
||||
@variant != :default && @variant != :destructive
|
||||
end
|
||||
|
||||
def variant_meta
|
||||
VARIANTS[@variant]
|
||||
end
|
||||
|
||||
def size_meta
|
||||
SIZES[@size]
|
||||
end
|
||||
end
|
13
app/components/link_component.html.erb
Normal file
13
app/components/link_component.html.erb
Normal file
|
@ -0,0 +1,13 @@
|
|||
<%= link_to href, **merged_opts do %>
|
||||
<% if icon && (icon_position != "right") %>
|
||||
<%= lucide_icon(icon, class: icon_classes) %>
|
||||
<% end %>
|
||||
|
||||
<% unless icon_only? %>
|
||||
<%= text %>
|
||||
<% end %>
|
||||
|
||||
<% if icon && icon_position == "right" %>
|
||||
<%= lucide_icon(icon, class: icon_classes) %>
|
||||
<% end %>
|
||||
<% end %>
|
46
app/components/link_component.rb
Normal file
46
app/components/link_component.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
class LinkComponent < ViewComponent::Base
|
||||
include ButtonStylable
|
||||
|
||||
attr_reader :href, :variant, :size, :text, :icon, :icon_position, :open_in, :opts
|
||||
|
||||
VARIANTS = VARIANTS.merge(
|
||||
default: {
|
||||
container_classes: "",
|
||||
text_classes: "text-primary",
|
||||
icon_classes: "fg-gray"
|
||||
},
|
||||
link_destructive: {
|
||||
container_classes: "",
|
||||
text_classes: "text-destructive",
|
||||
icon_classes: "fg-destructive"
|
||||
}
|
||||
).freeze
|
||||
|
||||
def initialize(href:, variant: "default", size: "md", text: nil, icon: nil, icon_position: "left", rounded: false, full_width: false, open_in: nil, **opts)
|
||||
@href = href
|
||||
@variant = variant.underscore.to_sym
|
||||
@size = size.underscore.to_sym
|
||||
@text = text
|
||||
@icon = icon
|
||||
@icon_position = icon_position
|
||||
@rounded = rounded
|
||||
@full_width = full_width
|
||||
@open_in = open_in
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def merged_opts
|
||||
merged_opts = opts.dup || {}
|
||||
extra_classes = merged_opts.delete(:class)
|
||||
data = merged_opts.delete(:data) || {}
|
||||
|
||||
if open_in
|
||||
data = data.merge(turbo_frame: open_in)
|
||||
end
|
||||
|
||||
merged_opts.merge(
|
||||
class: class_names(container_classes, extra_classes),
|
||||
data: data
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,12 +1,12 @@
|
|||
<%= tag.div data: merged_data do %>
|
||||
<% if @variant == :icon %>
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: @icon_vertical ? "more-vertical" : "more-horizontal", data: { menu_target: "button" }) %>
|
||||
<% elsif @variant == :button %>
|
||||
<%= tag.div data: { controller: "menu", menu_placement_value: placement, menu_offset_value: offset } do %>
|
||||
<% if variant == :icon %>
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: icon_vertical ? "more-vertical" : "more-horizontal", data: { menu_target: "button" }) %>
|
||||
<% elsif variant == :button %>
|
||||
<%= button %>
|
||||
<% elsif @variant == :avatar %>
|
||||
<% elsif variant == :avatar %>
|
||||
<button data-menu-target="button">
|
||||
<div class="w-9 h-9 cursor-pointer">
|
||||
<%= render "settings/user_avatar", avatar_url: @avatar_url %>
|
||||
<%= render "settings/user_avatar", avatar_url: avatar_url %>
|
||||
</div>
|
||||
</button>
|
||||
<% end %>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MenuComponent < ViewComponent::Base
|
||||
renders_one :button, ->(**options, &block) do
|
||||
options_with_target = options.merge(data: { menu_target: "button" })
|
||||
attr_reader :variant, :avatar_url, :placement, :offset, :icon_vertical
|
||||
|
||||
renders_one :button, ->(**button_options, &block) do
|
||||
options_with_target = button_options.merge(data: { menu_target: "button" })
|
||||
|
||||
if block
|
||||
content_tag(:button, **options_with_target, &block)
|
||||
|
@ -19,26 +21,15 @@ class MenuComponent < ViewComponent::Base
|
|||
|
||||
renders_many :items, MenuItemComponent
|
||||
|
||||
VARIANTS = {
|
||||
icon: {},
|
||||
button: {},
|
||||
avatar: {}
|
||||
}
|
||||
VARIANTS = %i[icon button avatar].freeze
|
||||
|
||||
def initialize(variant: "icon", avatar_url: nil, placement: "bottom-end", offset: 12, icon_vertical: false, data: {})
|
||||
def initialize(variant: "icon", avatar_url: nil, placement: "bottom-end", offset: 12, icon_vertical: false)
|
||||
@variant = variant.to_sym
|
||||
@avatar_url = avatar_url
|
||||
@placement = placement
|
||||
@offset = offset
|
||||
@icon_vertical = icon_vertical
|
||||
@data = data
|
||||
end
|
||||
|
||||
def merged_data
|
||||
{
|
||||
controller: "menu",
|
||||
menu_placement_value: @placement,
|
||||
menu_offset_value: @offset
|
||||
}.merge(@data)
|
||||
raise ArgumentError, "Invalid variant: #{@variant}" unless VARIANTS.include?(@variant)
|
||||
end
|
||||
end
|
||||
|
|
12
app/components/menu_item_component.html.erb
Normal file
12
app/components/menu_item_component.html.erb
Normal file
|
@ -0,0 +1,12 @@
|
|||
<% if variant == :divider %>
|
||||
<hr class="border-tertiary my-1">
|
||||
<% else %>
|
||||
<div class="px-1">
|
||||
<%= wrapper do %>
|
||||
<% if icon %>
|
||||
<%= lucide_icon(icon, class: destructive? ? "text-destructive" : "fg-gray") %>
|
||||
<% end %>
|
||||
<%= tag.span(text, class: text_classes) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
|
@ -1,34 +1,27 @@
|
|||
class MenuItemComponent < ViewComponent::Base
|
||||
erb_template <<~ERB
|
||||
<% if @variant == :divider %>
|
||||
<hr class="border-tertiary my-1">
|
||||
<% else %>
|
||||
<div class="px-1">
|
||||
<%= wrapper do %>
|
||||
<% if @icon %>
|
||||
<%= render IconComponent.new(@icon, variant: destructive? ? "destructive" : "default") %>
|
||||
<% end %>
|
||||
<%= tag.span(@text, class: text_classes) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
ERB
|
||||
VARIANTS = %i[link button divider].freeze
|
||||
|
||||
def initialize(variant: "default", text: nil, href: nil, method: :get, icon: nil, destructive: false, data: {})
|
||||
attr_reader :variant, :text, :icon, :href, :method, :destructive, :opts
|
||||
|
||||
def initialize(variant:, text: nil, icon: nil, href: nil, method: :post, destructive: false, **opts)
|
||||
@variant = variant.to_sym
|
||||
@text = text
|
||||
@icon = icon
|
||||
@href = href
|
||||
@method = method.to_sym
|
||||
@data = data
|
||||
@destructive = destructive
|
||||
@opts = opts
|
||||
|
||||
raise ArgumentError, "Invalid variant: #{@variant}" unless VARIANTS.include?(@variant)
|
||||
end
|
||||
|
||||
def wrapper(&block)
|
||||
if @method.in?([ :post, :patch, :delete ])
|
||||
button_to @href, method: @method, data: @data, class: container_classes, &block
|
||||
if variant == :button
|
||||
button_to href, method: method, class: container_classes, **opts, &block
|
||||
elsif variant == :link
|
||||
link_to href, class: container_classes, **opts, &block
|
||||
else
|
||||
link_to @href, data: @data, class: container_classes, &block
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -40,7 +33,7 @@ class MenuItemComponent < ViewComponent::Base
|
|||
end
|
||||
|
||||
def destructive?
|
||||
@method == :delete || @destructive
|
||||
method == :delete || destructive
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
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
|
||||
|
|
|
@ -53,10 +53,13 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder
|
|||
value, options = nil, value if value.is_a?(Hash)
|
||||
value ||= submit_default_value
|
||||
|
||||
opts = options.dup
|
||||
opts[:data] = { turbo_submits_with: "Submitting..." }.merge(opts[:data] || {})
|
||||
|
||||
@template.render(ButtonComponent.new(text: value, type: "submit", full_width: true, **opts))
|
||||
@template.render(
|
||||
ButtonComponent.new(
|
||||
text: value,
|
||||
full_width: true,
|
||||
data: { turbo_submits_with: "Submitting..." }
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -42,14 +42,14 @@
|
|||
</div>
|
||||
|
||||
<div data-tabs-target="tab" id="assets-tab">
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "New asset",
|
||||
variant: "ghost",
|
||||
full_width: true,
|
||||
left_align: true,
|
||||
href: new_account_path(step: "method_select", classification: "asset"),
|
||||
leading_icon: "plus",
|
||||
data: { turbo_frame: "modal" }
|
||||
icon: "plus",
|
||||
open_in: "modal",
|
||||
full_width: true,
|
||||
class: "justify-start"
|
||||
) %>
|
||||
|
||||
<div class="space-y-2">
|
||||
|
@ -60,14 +60,14 @@
|
|||
</div>
|
||||
|
||||
<div data-tabs-target="tab" id="debts-tab" class="hidden">
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "New debt",
|
||||
variant: "ghost",
|
||||
full_width: true,
|
||||
left_align: true,
|
||||
href: new_account_path(step: "method_select", classification: "liability"),
|
||||
leading_icon: "plus",
|
||||
data: { turbo_frame: "modal" }
|
||||
icon: "plus",
|
||||
open_in: "modal",
|
||||
full_width: true,
|
||||
class: "justify-start"
|
||||
) %>
|
||||
|
||||
<div class="space-y-2">
|
||||
|
@ -78,14 +78,14 @@
|
|||
</div>
|
||||
|
||||
<div data-tabs-target="tab" id="all-tab" class="hidden">
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "New account",
|
||||
variant: "ghost",
|
||||
full_width: true,
|
||||
left_align: true,
|
||||
href: new_account_path(step: "method_select"),
|
||||
leading_icon: "plus",
|
||||
data: { turbo_frame: "modal" }
|
||||
icon: "plus",
|
||||
open_in: "modal",
|
||||
class: "justify-start"
|
||||
) %>
|
||||
|
||||
<div class="space-y-2">
|
||||
|
|
|
@ -40,13 +40,13 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
href: new_polymorphic_path(account_group.key, step: "method_select"),
|
||||
text: "New #{account_group.name.downcase.singularize}",
|
||||
leading_icon: "plus",
|
||||
left_align: true,
|
||||
icon: "plus",
|
||||
full_width: true,
|
||||
variant: "ghost",
|
||||
data: { turbo_frame: "modal" }
|
||||
open_in: "modal",
|
||||
class: "justify-start"
|
||||
) %>
|
||||
</details>
|
||||
|
|
|
@ -8,15 +8,15 @@
|
|||
method: :post,
|
||||
variant: "outline",
|
||||
disabled: Current.family.syncing?,
|
||||
leading_icon: "refresh-cw"
|
||||
icon: "refresh-cw"
|
||||
) %>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "New account",
|
||||
href: new_account_path(return_to: accounts_path),
|
||||
variant: "primary",
|
||||
leading_icon: "plus",
|
||||
data: { turbo_frame: "modal" }
|
||||
icon: "plus",
|
||||
open_in: "modal"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,20 +2,23 @@
|
|||
|
||||
<%= turbo_frame_tag dom_id(account, "entries") do %>
|
||||
<div class="bg-container p-5 shadow-border-xs rounded-xl" data-controller="focus-record" data-focus-record-id-value="<%= @focused_record ? dom_id(@focused_record) : nil %>">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="flex items-center justify-between mb-4" data-testid="activity-menu">
|
||||
<%= tag.h2 t(".title"), class: "font-medium text-lg" %>
|
||||
<% unless @account.plaid_account_id.present? %>
|
||||
<%= render MenuComponent.new(variant: "button", data: { testid: "activity-menu" }) do |menu| %>
|
||||
<% menu.with_button(text: "New", variant: "secondary", leading_icon: "plus") %>
|
||||
<%= render MenuComponent.new(variant: "button") do |menu| %>
|
||||
<% menu.with_button(text: "New", variant: "secondary", icon: "plus") %>
|
||||
|
||||
<% menu.with_item(
|
||||
variant: "link",
|
||||
text: "New balance",
|
||||
icon: "circle-dollar-sign",
|
||||
href: new_valuation_path(account_id: @account.id), data: { turbo_frame: :modal }) %>
|
||||
href: new_valuation_path(account_id: @account.id),
|
||||
data: { turbo_frame: :modal }) %>
|
||||
|
||||
<% unless @account.crypto? %>
|
||||
<% href = @account.investment? ? new_trade_path(account_id: @account.id) : new_transaction_path(account_id: @account.id) %>
|
||||
<% menu.with_item(
|
||||
variant: "link",
|
||||
text: "New transaction",
|
||||
icon: "credit-card",
|
||||
href: href,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<%# locals: (account:) %>
|
||||
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: "Edit", href: edit_account_path(account), icon: "pencil-line", data: { turbo_frame: :modal }) %>
|
||||
<% menu.with_item(variant: "link", text: "Edit", href: edit_account_path(account), icon: "pencil-line", data: { turbo_frame: :modal }) %>
|
||||
|
||||
<% unless account.crypto? %>
|
||||
<% menu.with_item(
|
||||
variant: "link",
|
||||
text: "Import transactions",
|
||||
href: imports_path({ import: { type: account.investment? ? "TradeImport" : "TransactionImport", account_id: account.id } }),
|
||||
icon: "download",
|
||||
|
@ -13,14 +14,19 @@
|
|||
<% end %>
|
||||
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: "Delete account",
|
||||
href: account_path(account),
|
||||
method: :delete,
|
||||
icon: "trash-2",
|
||||
data: { turbo_frame: :_top, turbo_confirm: {
|
||||
title: t(".confirm_title"),
|
||||
body: t(".confirm_body_html"),
|
||||
accept: t(".confirm_accept", name: account.name)
|
||||
}}
|
||||
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"
|
||||
)
|
||||
}
|
||||
) %>
|
||||
<% end %>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
variant: "primary",
|
||||
full_width: true,
|
||||
href: budget_path(budget),
|
||||
method: :get,
|
||||
disabled: !budget.allocations_valid?
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -9,15 +9,14 @@
|
|||
<%= render ButtonComponent.new(
|
||||
text: "Use defaults (recommended)",
|
||||
href: bootstrap_categories_path,
|
||||
method: :post
|
||||
) %>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "New category",
|
||||
variant: "outline",
|
||||
leading_icon: "plus",
|
||||
icon: "plus",
|
||||
href: new_category_path,
|
||||
data: { turbo_frame: "modal" }
|
||||
open_in: "modal",
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -133,7 +133,7 @@
|
|||
<% end %>
|
||||
</ul>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "View all category transactions",
|
||||
variant: "outline",
|
||||
full_width: true,
|
||||
|
@ -142,7 +142,7 @@
|
|||
start_date: @budget.start_date,
|
||||
end_date: @budget.end_date
|
||||
}),
|
||||
data: { turbo_frame: :_top }
|
||||
open_in: :_top
|
||||
) %>
|
||||
<% else %>
|
||||
<p class="text-secondary text-sm mb-4">
|
||||
|
|
|
@ -12,10 +12,11 @@
|
|||
<%= format_money(budget.actual_spending_money) %>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "of #{budget.budgeted_spending_money.format}",
|
||||
variant: "secondary",
|
||||
trailing_icon: "pencil",
|
||||
icon: "pencil",
|
||||
icon_position: "right",
|
||||
size: "sm",
|
||||
href: edit_budget_path(budget)
|
||||
) %>
|
||||
|
@ -24,10 +25,10 @@
|
|||
<span><%= format_money Money.new(0, budget.currency || budget.family.currency) %></span>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "New budget",
|
||||
size: "sm",
|
||||
leading_icon: "plus",
|
||||
icon: "plus",
|
||||
href: edit_budget_path(budget)
|
||||
) %>
|
||||
<% end %>
|
||||
|
@ -45,10 +46,11 @@
|
|||
<%= format_money(bc.actual_spending_money) %>
|
||||
</p>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "of #{bc.budgeted_spending_money.format(precision: 0)}",
|
||||
variant: "secondary",
|
||||
trailing_icon: "pencil",
|
||||
icon: "pencil",
|
||||
icon_position: "right",
|
||||
size: "sm",
|
||||
href: budget_budget_categories_path(budget)
|
||||
) %>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
</div>
|
||||
|
||||
<div class="ml-auto">
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Today",
|
||||
variant: "outline",
|
||||
href: budget_path(Budget.date_to_param(Date.current)),
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
<%= lucide_icon "alert-triangle", class: "w-6 h-6 text-red-500" %>
|
||||
<p class="text-secondary text-sm text-center">You have over-allocated your budget. Please fix your allocations.</p>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Fix allocations",
|
||||
variant: "secondary",
|
||||
size: "sm",
|
||||
trailing_icon: "pencil",
|
||||
icon: "pencil",
|
||||
icon_position: "right",
|
||||
href: budget_budget_categories_path(budget)
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -38,7 +38,13 @@
|
|||
<% param_key = Budget.date_to_param(date) %>
|
||||
|
||||
<% if Budget.budget_date_valid?(date, family: family) %>
|
||||
<%= render ButtonComponent.new(variant: "ghost", text: month_name, href: budget_path(param_key), data: { turbo_frame: :_top }) %>
|
||||
<%= render LinkComponent.new(
|
||||
variant: "ghost",
|
||||
text: month_name,
|
||||
href: budget_path(param_key),
|
||||
full_width: true,
|
||||
open_in: :_top
|
||||
) %>
|
||||
<% else %>
|
||||
<span class="px-3 py-2 text-subdued rounded-md"><%= month_name %></span>
|
||||
<% end %>
|
||||
|
|
|
@ -54,10 +54,10 @@
|
|||
<h2 class="text-lg font-medium">Categories</h2>
|
||||
|
||||
<% if @budget.initialized? %>
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Edit",
|
||||
variant: "secondary",
|
||||
leading_icon: "settings-2",
|
||||
icon: "settings-2",
|
||||
href: budget_budget_categories_path(@budget)
|
||||
) %>
|
||||
<% end %>
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
|
||||
<div class="justify-self-end">
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: t(".edit"), icon: "pencil", href: edit_category_path(category), data: { turbo_frame: :modal }) %>
|
||||
<% menu.with_item(variant: "link", text: t(".edit"), icon: "pencil", href: edit_category_path(category), data: { turbo_frame: :modal }) %>
|
||||
|
||||
<% if category.transactions.any? %>
|
||||
<% menu.with_item(text: t(".delete"), icon: "trash-2", href: new_category_deletion_path(category), data: { turbo_frame: :modal }, method: :delete) %>
|
||||
<% menu.with_item(variant: "link", text: t(".delete"), icon: "trash-2", href: new_category_deletion_path(category), data: { turbo_frame: :modal }) %>
|
||||
<% else %>
|
||||
<% menu.with_item(text: t(".delete"), icon: "trash-2", href: category_path(category), method: :delete) %>
|
||||
<% menu.with_item(variant: "link", text: t(".delete"), icon: "trash-2", href: category_path(category), method: :delete) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -4,26 +4,28 @@
|
|||
<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: {
|
||||
turbo_confirm: custom_turbo_confirm(
|
||||
title: "Delete all categories?",
|
||||
body: "All of your transactions will become uncategorized and this cannot be undone.",
|
||||
accept: "Delete all categories",
|
||||
}
|
||||
btn_text: "Delete all categories",
|
||||
btn_variant: "destructive"
|
||||
)
|
||||
}
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".new"),
|
||||
variant: "primary",
|
||||
leading_icon: "plus",
|
||||
icon: "plus",
|
||||
href: new_category_path,
|
||||
data: { turbo_frame: :modal }
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -47,15 +49,14 @@
|
|||
<%= render ButtonComponent.new(
|
||||
text: t(".bootstrap"),
|
||||
href: bootstrap_categories_path,
|
||||
method: :post
|
||||
) %>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".new"),
|
||||
variant: "outline",
|
||||
leading_icon: "plus",
|
||||
icon: "plus",
|
||||
href: new_category_path,
|
||||
data: { turbo_frame: :modal }
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<% end %>
|
||||
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: t(".edit"), icon: "pencil-line", href: edit_category_path(category), data: { turbo_frame: :modal }) %>
|
||||
<% menu.with_item(text: t(".delete"), icon: "trash-2", href: new_category_deletion_path(category), data: { turbo_frame: :modal }, destructive: true) %>
|
||||
<% menu.with_item(variant: "link", text: t(".edit"), icon: "pencil-line", href: edit_category_path(category), data: { turbo_frame: :modal }) %>
|
||||
<% menu.with_item(variant: "link", text: t(".delete"), icon: "trash-2", href: new_category_deletion_path(category), data: { turbo_frame: :modal }, destructive: true) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -10,17 +10,20 @@
|
|||
</div>
|
||||
|
||||
<%= render MenuComponent.new(icon_vertical: true) do |menu| %>
|
||||
<% menu.with_item(text: "Edit chat", href: edit_chat_path(chat), icon: "pencil", data: { turbo_frame: dom_id(chat, "title") }) %>
|
||||
<% menu.with_item(variant: "link", text: "Edit chat", href: edit_chat_path(chat), icon: "pencil", open_in: dom_id(chat, "title")) %>
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: "Delete chat",
|
||||
href: chat_path(chat),
|
||||
icon: "trash-2",
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: "Are you sure you want to delete this chat?",
|
||||
variant: "outline-destructive"
|
||||
}
|
||||
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"
|
||||
)
|
||||
}) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -14,20 +14,27 @@
|
|||
</div>
|
||||
|
||||
<%= render MenuComponent.new(icon_vertical: true) do |menu| %>
|
||||
<% menu.with_item(text: "Start new chat", href: new_chat_path, icon: "plus") %>
|
||||
<% menu.with_item(variant: "link", text: "Start new chat", href: new_chat_path, icon: "plus") %>
|
||||
|
||||
<% unless chat.new_record? %>
|
||||
<% menu.with_item(text: "Edit chat title", href: edit_chat_path(chat, ctx: "chat"), icon: "pencil", data: { turbo_frame: dom_id(chat, "title") }) %>
|
||||
<% menu.with_item(
|
||||
variant: "link",
|
||||
text: "Edit chat title",
|
||||
href: edit_chat_path(chat, ctx: "chat"),
|
||||
icon: "pencil", data: { turbo_frame: dom_id(chat, "title") }) %>
|
||||
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: "Delete chat",
|
||||
href: chat_path(chat),
|
||||
icon: "trash-2",
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
turbo_confirm: custom_turbo_confirm(
|
||||
title: "Are you sure you want to delete this chat?",
|
||||
variant: "outline-destructive"
|
||||
}
|
||||
btn_text: "Delete chat",
|
||||
btn_variant: "outline-destructive"
|
||||
)
|
||||
}) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -12,9 +12,7 @@
|
|||
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Retry",
|
||||
variant: "primary",
|
||||
href: retry_chat_path(chat),
|
||||
method: :post
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -27,10 +27,10 @@
|
|||
</div>
|
||||
|
||||
<div class="flex justify-center py-8">
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Edit account details",
|
||||
variant: "ghost",
|
||||
href: edit_credit_card_path(account),
|
||||
data: { turbo_frame: :modal }
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -16,12 +16,21 @@
|
|||
</div>
|
||||
<div class="justify-self-end">
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: "Edit", href: edit_family_merchant_path(family_merchant), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(text: "Delete", href: family_merchant_path(family_merchant), icon: "trash-2", method: :delete, data: { turbo_confirm: {
|
||||
title: "Delete #{family_merchant.name}?",
|
||||
body: "This will remove this merchant from all transactions it has been assigned to.",
|
||||
variant: "outline-destructive"
|
||||
} }) %>
|
||||
<% menu.with_item(variant: "link", text: "Edit", href: edit_family_merchant_path(family_merchant), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: "Delete",
|
||||
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"
|
||||
)
|
||||
}) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<header class="flex items-center justify-between">
|
||||
<h1 class="text-primary text-xl font-medium">Merchants</h1>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "New merchant",
|
||||
variant: "primary",
|
||||
href: new_family_merchant_path,
|
||||
data: { turbo_frame: "modal" }
|
||||
open_in: :modal
|
||||
) %>
|
||||
</header>
|
||||
|
||||
|
|
|
@ -17,10 +17,11 @@
|
|||
<p class="text-green-500 text-sm md:text-base">Your data has been cleaned</p>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Next step",
|
||||
variant: "primary",
|
||||
href: import_confirm_path(@import),
|
||||
data: { turbo_frame: :_top },
|
||||
open_in: :_top,
|
||||
class: "w-full md:w-auto"
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<p class="text-sm text-secondary">We found a configuration from a previous import for this account. Would you like to apply it to this import?</p>
|
||||
|
||||
<div class="mt-4 flex gap-2 items-center">
|
||||
<%= render ButtonComponent.new(text: "Manually configure", href: import_configuration_path(@import), variant: "outline") %>
|
||||
<%= render LinkComponent.new(text: "Manually configure", href: import_configuration_path(@import), variant: "outline") %>
|
||||
<%= render ButtonComponent.new(text: "Apply template", href: apply_template_import_path(@import), method: :put, data: { turbo_frame: :_top }) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,12 @@
|
|||
<div class="flex items-center justify-between p-4 gap-4 text-secondary bg-red-100 border border-red-200 rounded-lg w-[650px] min-w-0 mx-auto">
|
||||
<%= tag.p t(".no_accounts"), class: "text-sm" %>
|
||||
|
||||
<%= render ButtonComponent.new(text: "Create account", href: new_account_path(return_to: import_confirm_path(import)), data: { turbo_frame: :modal }) %>
|
||||
<%= render LinkComponent.new(
|
||||
text: "Create account",
|
||||
variant: "primary",
|
||||
href: new_account_path(return_to: import_confirm_path(import)),
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -20,7 +25,12 @@
|
|||
<div class="overflow-x-auto">
|
||||
<div class="flex items-center justify-between p-4 gap-4 text-secondary bg-yellow-100 border border-yellow-200 rounded-lg w-[650px] min-w-0 mx-auto">
|
||||
<%= tag.p t(".unassigned_account"), class: "text-sm" %>
|
||||
<%= render ButtonComponent.new(text: t(".create_account"), href: new_account_path(return_to: import_confirm_path(import)), data: { turbo_frame: :modal }) %>
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".create_account"),
|
||||
variant: "primary",
|
||||
href: new_account_path(return_to: import_confirm_path(import)),
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -49,7 +59,14 @@
|
|||
</div>
|
||||
|
||||
<div class="flex justify-center w-full">
|
||||
<%= render ButtonComponent.new(text: "Next", href: is_last_step ? import_path(import) : url_for(step: step_idx + 2), trailing_icon: "arrow-right", class: "w-full md:w-auto") %>
|
||||
<%= render LinkComponent.new(
|
||||
text: "Next",
|
||||
variant: "primary",
|
||||
href: is_last_step ? import_path(import) : url_for(step: step_idx + 2),
|
||||
icon: "arrow-right",
|
||||
icon_position: "right",
|
||||
class: "w-full md:w-auto"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
<div class="text-center flex flex-col items-center max-w-[300px] gap-4">
|
||||
<p class="text-primary mb-1 font-medium text-sm"><%= t(".message") %></p>
|
||||
|
||||
<%= render ButtonComponent.new(text: t(".new"), href: new_import_path, leading_icon: "plus", data: { turbo_frame: "modal" }) %>
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".new"),
|
||||
variant: "primary",
|
||||
href: new_import_path,
|
||||
icon: "plus",
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
<p class="text-sm text-secondary">Please check that your file format, for any errors and that all required fields are filled, then come back and try again.</p>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(text: "Try again", href: publish_import_path(import), method: :post) %>
|
||||
<%= render ButtonComponent.new(text: "Try again", href: publish_import_path(import), full_width: true) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -37,24 +37,27 @@
|
|||
</div>
|
||||
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: t(".view"), href: import_path(import), icon: "eye") %>
|
||||
<% menu.with_item(variant: "link", text: t(".view"), href: import_path(import), icon: "eye") %>
|
||||
|
||||
<% if import.complete? || import.revert_failed? %>
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: t(".revert"),
|
||||
href: revert_import_path(import),
|
||||
icon: "rotate-ccw",
|
||||
method: :put,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
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.",
|
||||
accept: "Revert"
|
||||
}
|
||||
btn_text: "Revert",
|
||||
btn_variant: "outline-destructive"
|
||||
)
|
||||
}) %>
|
||||
|
||||
<% else %>
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: t(".delete"),
|
||||
href: import_path(import),
|
||||
icon: "trash-2",
|
||||
|
@ -63,7 +66,8 @@
|
|||
turbo_confirm: {
|
||||
title: "Delete import?",
|
||||
body: "This will delete the import and is not reversible.",
|
||||
variant: "outline-destructive"
|
||||
btn_text: "Delete import",
|
||||
btn_variant: "outline-destructive"
|
||||
}
|
||||
}) %>
|
||||
<% end %>
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<%= render ButtonComponent.new(text: "Check status", href: import_path(import)) %>
|
||||
<%= render ButtonComponent.new(text: "Back to dashboard", href: root_path, variant: "secondary") %>
|
||||
<%= render LinkComponent.new(text: "Check status", href: import_path(import), variant: "primary") %>
|
||||
<%= render LinkComponent.new(text: "Back to dashboard", href: root_path, variant: "secondary") %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -35,5 +35,5 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(text: "Publish import", href: publish_import_path(import), method: :post) %>
|
||||
<%= render ButtonComponent.new(text: "Publish import", href: publish_import_path(import)) %>
|
||||
</div>
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
<%= render ButtonComponent.new(
|
||||
text: "Try again",
|
||||
full_width: true,
|
||||
href: revert_import_path(import),
|
||||
method: :post
|
||||
href: revert_import_path(import)
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -11,8 +11,9 @@
|
|||
<p class="text-sm text-secondary">Your imported data has been successfully added to the app and is now ready for use.</p>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Back to dashboard",
|
||||
variant: "primary",
|
||||
full_width: true,
|
||||
href: root_path
|
||||
) %>
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-medium text-primary"><%= t(".title") %></h1>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "New import",
|
||||
href: new_import_path,
|
||||
leading_icon: "plus",
|
||||
data: { turbo_frame: :modal }
|
||||
icon: "plus",
|
||||
variant: "primary",
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
|
||||
<%= javascript_importmap_tags %>
|
||||
</head>
|
||||
<body class="p-4 bg-surface">
|
||||
<body class="p-4 bg-container">
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
variant: "icon",
|
||||
icon: "x",
|
||||
value: "cancel",
|
||||
type: "submit"
|
||||
) %>
|
||||
</div>
|
||||
|
||||
|
@ -18,15 +17,14 @@
|
|||
<div>
|
||||
<% ["primary", "outline-destructive", "destructive"].each do |variant| %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Confirm",
|
||||
variant: variant,
|
||||
autofocus: true,
|
||||
full_width: true,
|
||||
value: "confirm",
|
||||
data: { variant: variant, confirm_dialog_target: "confirmButton" },
|
||||
hidden: true,
|
||||
type: "submit"
|
||||
) %>
|
||||
text: "Confirm",
|
||||
variant: variant,
|
||||
autofocus: true,
|
||||
full_width: true,
|
||||
value: "confirm",
|
||||
data: { variant: variant, confirm_dialog_target: "confirmButton" },
|
||||
hidden: true,
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -45,10 +45,10 @@
|
|||
</div>
|
||||
|
||||
<div class="flex justify-center py-8">
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Edit loan details",
|
||||
variant: "ghost",
|
||||
href: edit_loan_path(account),
|
||||
data: { turbo_frame: :modal }
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -16,18 +16,20 @@
|
|||
</div>
|
||||
<div class="justify-self-end">
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: t(".edit"), href: edit_merchant_path(merchant), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(variant: "link", text: t(".edit"), href: edit_merchant_path(merchant), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: t(".delete"),
|
||||
href: merchant_path(merchant),
|
||||
icon: "trash-2",
|
||||
method: :delete,
|
||||
data: { turbo_confirm: {
|
||||
data: { turbo_confirm: custom_turbo_confirm(
|
||||
title: t(".confirm_title"),
|
||||
body: t(".confirm_body"),
|
||||
confirmText: t(".confirm_accept"),
|
||||
variant: "outline-destructive"
|
||||
} }) %>
|
||||
btn_text: t(".confirm_accept"),
|
||||
btn_variant: "outline-destructive"
|
||||
)
|
||||
}) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".continue"),
|
||||
href: settings_security_path,
|
||||
variant: "primary",
|
||||
full_width: true
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
<%= tag.h1 t(".title"), class: "text-3xl font-medium mb-2" %>
|
||||
<%= tag.p t(".message"), class: "text-sm text-secondary mb-6" %>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".setup"),
|
||||
href: profile_onboarding_path,
|
||||
variant: "primary",
|
||||
full_width: true
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -5,12 +5,11 @@
|
|||
<p class="text-gray-500">Here's what's happening with your finances</p>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
variant: "icon-inverse",
|
||||
icon: "plus",
|
||||
href: new_account_path(step: "method_select", classification: "asset"),
|
||||
data: { turbo_frame: "modal" },
|
||||
class: "rounded-full! md:hidden"
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
<p class="text-secondary"><%= t(".no_account_subtitle") %></p>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".new_account"),
|
||||
href: new_account_path,
|
||||
leading_icon: "plus",
|
||||
data: { turbo_frame: "modal" }
|
||||
icon: "plus",
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".update"),
|
||||
leading_icon: "refresh-cw",
|
||||
icon: "refresh-cw",
|
||||
variant: "secondary",
|
||||
data: {
|
||||
controller: "plaid",
|
||||
|
@ -76,21 +76,23 @@
|
|||
<div class="flex items-center gap-2">
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".delete"),
|
||||
leading_icon: "trash-2",
|
||||
icon: "trash-2",
|
||||
variant: "destructive",
|
||||
href: plaid_item_path(plaid_item),
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
turbo_confirm: custom_turbo_confirm(
|
||||
title: t(".confirm_title"),
|
||||
body: t(".confirm_body"),
|
||||
accept: t(".confirm_accept")
|
||||
}
|
||||
btn_text: t(".confirm_accept"),
|
||||
btn_variant: "destructive"
|
||||
)
|
||||
}
|
||||
) %>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".add_new"),
|
||||
leading_icon: "plus",
|
||||
icon: "plus",
|
||||
variant: "secondary",
|
||||
href: new_account_path
|
||||
) %>
|
||||
|
@ -102,18 +104,23 @@
|
|||
variant: "icon",
|
||||
icon: "refresh-cw",
|
||||
href: sync_plaid_item_path(plaid_item),
|
||||
method: :post,
|
||||
disabled: plaid_item.syncing? || plaid_item.scheduled_for_deletion?
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item text: t(".delete"), icon: "trash-2", href: plaid_item_path(plaid_item), method: :delete, data: { turbo_confirm: {
|
||||
title: t(".confirm_title"),
|
||||
body: t(".confirm_body"),
|
||||
accept: t(".confirm_accept"),
|
||||
variant: "destructive"
|
||||
} } %>
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: t(".delete"),
|
||||
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") }
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</summary>
|
||||
|
|
|
@ -29,10 +29,10 @@
|
|||
</div>
|
||||
|
||||
<div class="flex justify-center py-8">
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Edit account details",
|
||||
href: edit_property_path(account),
|
||||
variant: "ghost",
|
||||
data: { turbo_frame: :modal }
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
<%= f.hidden_field :rule_prompt_dismissed_at, value: Time.current %>
|
||||
|
||||
<%= tag.div class:"flex gap-2 justify-end" do %>
|
||||
<%= render ButtonComponent.new(text: "Dismiss", variant: "secondary", type: "submit") %>
|
||||
<%= render ButtonComponent.new(text: "Dismiss", variant: "secondary") %>
|
||||
<% rule_href = new_rule_path(resource_type: "transaction", action_type: "set_transaction_category", action_value: cta[:category_id]) %>
|
||||
<%= render ButtonComponent.new(text: "Create rule", href: rule_href, data: { turbo_frame: :modal }) %>
|
||||
<%= render LinkComponent.new(text: "Create rule", variant: "primary", href: rule_href, open_in: :modal) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -37,8 +37,8 @@
|
|||
</ul>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<%= render ButtonComponent.new(text: "Add condition", leading_icon: "plus", variant: "ghost", data: { action: "rules#addCondition" }) %>
|
||||
<%= render ButtonComponent.new(text: "Add condition group", leading_icon: "boxes", variant: "ghost", data: { action: "rules#addConditionGroup" }) %>
|
||||
<%= render ButtonComponent.new(text: "Add condition", icon: "plus", variant: "ghost", data: { action: "rules#addCondition" }) %>
|
||||
<%= render ButtonComponent.new(text: "Add condition group", icon: "boxes", variant: "ghost", data: { action: "rules#addConditionGroup" }) %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
@ -58,7 +58,7 @@
|
|||
<% end %>
|
||||
</ul>
|
||||
|
||||
<%= render ButtonComponent.new(text: "Add action", leading_icon: "plus", variant: "ghost", data: { action: "rules#addAction" }) %>
|
||||
<%= render ButtonComponent.new(text: "Add action", icon: "plus", variant: "ghost", data: { action: "rules#addAction" }) %>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4">
|
||||
|
|
|
@ -45,14 +45,22 @@
|
|||
<%= render "shared/toggle_form", model: rule, attribute: :active %>
|
||||
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: "Edit", href: edit_rule_path(rule), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(text: "Re-apply rule", href: confirm_rule_path(rule), icon: "refresh-cw", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(text: "Delete", href: rule_path(rule), icon: "trash-2", method: :delete, data: { 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.",
|
||||
confirmText: "Delete rule",
|
||||
variant: "outline-destructive"
|
||||
} }) %>
|
||||
<% menu.with_item(variant: "link", text: "Edit", href: edit_rule_path(rule), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(variant: "link", text: "Re-apply rule", href: confirm_rule_path(rule), icon: "refresh-cw", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: "Delete",
|
||||
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"
|
||||
)
|
||||
}) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,22 +5,29 @@
|
|||
<% if @rules.any? %>
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: "Delete all rules",
|
||||
href: destroy_all_rules_path,
|
||||
icon: "trash-2",
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: "Delete all rules",
|
||||
turbo_confirm: custom_turbo_confirm(
|
||||
title: "Delete all rules?",
|
||||
body: "Are you sure you want to delete all rules? This action cannot be undone.",
|
||||
confirmText: "Delete all rules",
|
||||
variant: "destructive"
|
||||
}
|
||||
btn_text: "Delete all rules",
|
||||
btn_variant: "destructive"
|
||||
)
|
||||
}) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= render ButtonComponent.new(text: "New rule", href: new_rule_path(resource_type: "transaction"), leading_icon: "plus", data: { turbo_frame: :modal }) %>
|
||||
<%= render LinkComponent.new(
|
||||
text: "New rule",
|
||||
variant: "primary",
|
||||
href: new_rule_path(resource_type: "transaction"),
|
||||
icon: "plus",
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
@ -53,7 +60,13 @@
|
|||
<p class="text-sm text-primary font-medium mb-1">No rules yet</p>
|
||||
<p class="text-sm text-secondary mb-4">Set up rules to perform actions to your transactions and other data on every account sync.</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<%= render ButtonComponent.new(text: "New rule", href: new_rule_path(resource_type: "transaction"), leading_icon: "plus", data: { turbo_frame: :modal }) %>
|
||||
<%= render LinkComponent.new(
|
||||
text: "New rule",
|
||||
variant: "primary",
|
||||
href: new_rule_path(resource_type: "transaction"),
|
||||
icon: "plus",
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,9 +19,24 @@
|
|||
</div>
|
||||
|
||||
<% if @user.family.subscribed? || subscription_pending? %>
|
||||
<%= render ButtonComponent.new(text: "Manage", trailing_icon: "external-link", href: subscription_path, target: "_blank", rel: "noopener") %>
|
||||
<%= render LinkComponent.new(
|
||||
text: "Manage",
|
||||
icon: "external-link",
|
||||
variant: "primary",
|
||||
icon_position: "right",
|
||||
href: subscription_path,
|
||||
target: "_blank",
|
||||
rel: "noopener"
|
||||
) %>
|
||||
<% else %>
|
||||
<%= render ButtonComponent.new(text: "Subscribe", trailing_icon: "external-link", href: new_subscription_path, target: "_blank", rel: "noopener") %>
|
||||
<%= render LinkComponent.new(
|
||||
text: "Subscribe",
|
||||
variant: "primary",
|
||||
icon: "external-link",
|
||||
icon_position: "right",
|
||||
href: new_subscription_path,
|
||||
target: "_blank",
|
||||
rel: "noopener") %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -57,12 +57,12 @@
|
|||
href: settings_profile_path(user_id: user),
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
turbo_confirm: custom_turbo_confirm(
|
||||
title: t(".confirm_remove_member.title"),
|
||||
body: t(".confirm_remove_member.body", name: user.display_name),
|
||||
confirmText: t(".remove_member"),
|
||||
variant: "destructive"
|
||||
}
|
||||
btn_text: t(".remove_member"),
|
||||
btn_variant: "destructive"
|
||||
)
|
||||
}
|
||||
) %>
|
||||
</div>
|
||||
|
@ -111,12 +111,12 @@
|
|||
href: invitation_path(invitation),
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
turbo_confirm: custom_turbo_confirm(
|
||||
title: t(".confirm_remove_invitation.title"),
|
||||
body: t(".confirm_remove_invitation.body", email: invitation.email),
|
||||
confirmText: t(".remove_invitation"),
|
||||
variant: "outline-destructive"
|
||||
}
|
||||
btn_text: t(".remove_invitation"),
|
||||
btn_variant: "outline-destructive"
|
||||
)
|
||||
}
|
||||
) %>
|
||||
<% end %>
|
||||
|
@ -151,12 +151,12 @@
|
|||
href: reset_user_path(@user),
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
turbo_confirm: custom_turbo_confirm(
|
||||
title: t(".confirm_reset.title"),
|
||||
body: t(".confirm_reset.body"),
|
||||
confirmText: t(".reset_account"),
|
||||
variant: "destructive"
|
||||
}
|
||||
btn_text: t(".reset_account"),
|
||||
btn_variant: "destructive"
|
||||
)
|
||||
}
|
||||
) %>
|
||||
</div>
|
||||
|
@ -173,12 +173,12 @@
|
|||
href: user_path(@user),
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
turbo_confirm: custom_turbo_confirm(
|
||||
title: t(".confirm_delete.title"),
|
||||
body: t(".confirm_delete.body"),
|
||||
confirmText: t(".delete_account"),
|
||||
variant: "destructive"
|
||||
}
|
||||
btn_text: t(".delete_account"),
|
||||
btn_variant: "destructive"
|
||||
)
|
||||
}
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -27,18 +27,19 @@
|
|||
href: disable_mfa_path,
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
turbo_confirm: custom_turbo_confirm(
|
||||
title: t(".disable_mfa_confirm"),
|
||||
body: t(".disable_mfa_confirm"),
|
||||
confirmText: t(".disable_mfa"),
|
||||
variant: "outline-destructive"
|
||||
}
|
||||
btn_text: t(".disable_mfa"),
|
||||
btn_variant: "outline-destructive"
|
||||
)
|
||||
}
|
||||
) %>
|
||||
<% else %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".enable_mfa"),
|
||||
href: new_mfa_path
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".enable_mfa"),
|
||||
variant: "primary",
|
||||
href: new_mfa_path
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
<p>To continue using the app, please subscribe. In this early beta testing phase, we require that you upgrade within one hour to claim your spot.</p>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Upgrade to Maybe+",
|
||||
href: new_subscription_path,
|
||||
class: "w-full"
|
||||
variant: "primary",
|
||||
full_width: true
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,14 +9,20 @@
|
|||
</div>
|
||||
<div class="justify-self-end">
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: t(".edit"), href: edit_tag_path(tag), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
<% menu.with_item(variant: "link", text: t(".edit"), href: edit_tag_path(tag), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||
|
||||
<% menu.with_item(text: t(".delete"), href: tag_path(tag), icon: "trash-2", method: :delete, data: { turbo_confirm: {
|
||||
title: "Delete tag",
|
||||
<% menu.with_item(
|
||||
variant: "button",
|
||||
text: t(".delete"),
|
||||
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.",
|
||||
confirmText: "Delete tag",
|
||||
variant: "outline-destructive"
|
||||
} }) %>
|
||||
btn_text: "Delete tag",
|
||||
btn_variant: "outline-destructive"
|
||||
) }) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<header class="flex items-center justify-between">
|
||||
<h1 class="text-primary text-xl font-medium"><%= t(".tags") %></h1>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".new"),
|
||||
variant: "primary",
|
||||
href: new_tag_path,
|
||||
leading_icon: "plus",
|
||||
data: { turbo_frame: :modal }
|
||||
icon: "plus",
|
||||
open_in: :modal
|
||||
) %>
|
||||
</header>
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
</div>
|
||||
|
||||
<div class="flex justify-end items-center gap-2">
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Cancel",
|
||||
variant: "ghost",
|
||||
href: transactions_path
|
||||
|
|
|
@ -4,40 +4,45 @@
|
|||
<div class="flex items-center gap-5">
|
||||
<div class="flex items-center gap-2">
|
||||
<%= render MenuComponent.new do |menu| %>
|
||||
<% menu.with_item(text: "Dev only: Sync all", href: sync_all_accounts_path, method: :post, icon: "refresh-cw") %>
|
||||
<% menu.with_item(text: "New rule", href: new_rule_path(resource_type: "transaction"), icon: "plus", data: { turbo_frame: :modal }) %>
|
||||
<% menu.with_item(text: "Edit rules", href: rules_path, icon: "git-branch", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(text: "Edit categories", href: categories_path, icon: "shapes", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(text: "Edit tags", href: tags_path, icon: "tags", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(text: "Edit merchants", href: family_merchants_path, icon: "store", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(text: "Edit imports", href: imports_path, icon: "hard-drive-upload", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(text: "Import", href: new_import_path, icon: "download", data: { turbo_frame: "modal", class_name: "md:!hidden" }) %>
|
||||
<% menu.with_item(variant: "button", text: "Dev only: Sync all", href: sync_all_accounts_path, method: :post, icon: "refresh-cw") %>
|
||||
<% menu.with_item(variant: "link", text: "New rule", href: new_rule_path(resource_type: "transaction"), icon: "plus", data: { turbo_frame: :modal }) %>
|
||||
<% menu.with_item(variant: "link", text: "Edit rules", href: rules_path, icon: "git-branch", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(variant: "link", text: "Edit categories", href: categories_path, icon: "shapes", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(variant: "link", text: "Edit tags", href: tags_path, icon: "tags", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(variant: "link", text: "Edit merchants", href: family_merchants_path, icon: "store", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(variant: "link", text: "Edit imports", href: imports_path, icon: "hard-drive-upload", data: { turbo_frame: :_top }) %>
|
||||
<% menu.with_item(variant: "link", text: "Import", href: new_import_path, icon: "download", data: { turbo_frame: "modal", class_name: "md:!hidden" }) %>
|
||||
<% end %>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".import"),
|
||||
leading_icon: "download",
|
||||
variant: "outline",
|
||||
href: new_import_path,
|
||||
data: { turbo_frame: :modal },
|
||||
class: "hidden md:flex"
|
||||
) %>
|
||||
<div class="hidden md:flex">
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".import"),
|
||||
icon: "download",
|
||||
variant: "outline",
|
||||
href: new_import_path,
|
||||
open_in: :modal,
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
text: "New transaction",
|
||||
leading_icon: "plus",
|
||||
href: new_transaction_path,
|
||||
data: { turbo_frame: :modal },
|
||||
class: "hidden md:flex"
|
||||
) %>
|
||||
<div class="hidden md:flex">
|
||||
<%= render LinkComponent.new(
|
||||
text: "New transaction",
|
||||
icon: "plus",
|
||||
variant: "primary",
|
||||
href: new_transaction_path,
|
||||
open_in: :modal,
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
icon: "plus",
|
||||
variant: "icon-inverse",
|
||||
href: new_transaction_path,
|
||||
data: { turbo_frame: :modal },
|
||||
class: "md:hidden !rounded-full"
|
||||
) %>
|
||||
<div class="md:hidden">
|
||||
<%= render LinkComponent.new(
|
||||
icon: "plus",
|
||||
variant: "icon-inverse",
|
||||
href: new_transaction_path,
|
||||
open_in: :modal,
|
||||
rounded: true
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
<div data-controller="menu" class="relative">
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Filter",
|
||||
leading_icon: "list-filter",
|
||||
icon: "list-filter",
|
||||
variant: "outline",
|
||||
id: "transaction-filters-button",
|
||||
type: "button",
|
||||
data: { menu_target: "button" }
|
||||
) %>
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<div class="flex justify-between items-center gap-2 bg-container p-3">
|
||||
<div>
|
||||
<% if @q.present? %>
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: t(".clear_filters"),
|
||||
variant: "ghost",
|
||||
href: transactions_path(clear_filters: true),
|
||||
|
@ -41,8 +41,8 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<%= render ButtonComponent.new(text: t(".cancel"), variant: "ghost", data: { action: "menu#close" }) %>
|
||||
<%= render ButtonComponent.new(text: t(".apply"), type: "submit") %>
|
||||
<%= render ButtonComponent.new(text: t(".cancel"), type: "button", variant: "ghost", data: { action: "menu#close" }) %>
|
||||
<%= render ButtonComponent.new(text: t(".apply")) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -125,12 +125,12 @@
|
|||
<p class="text-secondary">Transfers and payments are special types of transactions that indicate money movement between 2 accounts.</p>
|
||||
</div>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Open matcher",
|
||||
leading_icon: "arrow-left-right",
|
||||
icon: "arrow-left-right",
|
||||
variant: "outline",
|
||||
href: new_transaction_transfer_match_path(@entry),
|
||||
data: { turbo_frame: :modal }
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,45 +1,47 @@
|
|||
<%# locals: (user:, placement: "right-start", offset: 16) %>
|
||||
|
||||
<%= render MenuComponent.new(variant: "avatar", avatar_url: user.profile_image&.variant(:small)&.url, placement: placement, offset: offset, data: { testid: "user-menu" }) do |menu| %>
|
||||
<%= menu.with_header do %>
|
||||
<div class="px-4 py-3 flex items-center gap-3">
|
||||
<div class="w-9 h-9 shrink-0">
|
||||
<%= render "settings/user_avatar", avatar_url: user.profile_image&.variant(:small)&.url, lazy: true %>
|
||||
<div data-testid="user-menu">
|
||||
<%= render MenuComponent.new(variant: "avatar", avatar_url: user.profile_image&.variant(:small)&.url, placement: placement, offset: offset) do |menu| %>
|
||||
<%= menu.with_header do %>
|
||||
<div class="px-4 py-3 flex items-center gap-3">
|
||||
<div class="w-9 h-9 shrink-0">
|
||||
<%= render "settings/user_avatar", avatar_url: user.profile_image&.variant(:small)&.url, lazy: true %>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden text-ellipsis text-sm">
|
||||
<p class="font-medium"><%= user.display_name %></p>
|
||||
<% if user.display_name != user.email %>
|
||||
<p class="text-secondary"><%= user.email %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden text-ellipsis text-sm">
|
||||
<p class="font-medium"><%= user.display_name %></p>
|
||||
<% if user.display_name != user.email %>
|
||||
<p class="text-secondary"><%= user.email %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% if self_hosted? %>
|
||||
<div class="px-4 py-3 border-t border-tertiary">
|
||||
<p class="text-sm">
|
||||
<span class="font-medium text-primary">Version:</span>
|
||||
<%= link_to Maybe.version.to_release_tag, "https://github.com/maybe-finance/maybe/releases/tag/#{Maybe.version.to_release_tag}", target: "_blank", class: "hover:underline" %>
|
||||
|
||||
<% if Maybe.commit_sha.present? %>
|
||||
(<%= link_to Maybe.commit_sha.first(7), "https://github.com/maybe-finance/maybe/commit/#{Maybe.commit_sha}", target: "_blank", class: "hover:underline" %>)
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% menu.with_item(variant: "link", text: "Settings", icon: "settings", href: settings_profile_path(return_to: request.fullpath)) %>
|
||||
<% menu.with_item(variant: "link", text: "Changelog", icon: "box", href: changelog_path) %>
|
||||
<% menu.with_item(variant: "link", text: "Feedback", icon: "megaphone", href: feedback_path) %>
|
||||
|
||||
<% if self_hosted? %>
|
||||
<div class="px-4 py-3 border-t border-tertiary">
|
||||
<p class="text-sm">
|
||||
<span class="font-medium text-primary">Version:</span>
|
||||
<%= link_to Maybe.version.to_release_tag, "https://github.com/maybe-finance/maybe/releases/tag/#{Maybe.version.to_release_tag}", target: "_blank", class: "hover:underline" %>
|
||||
|
||||
<% if Maybe.commit_sha.present? %>
|
||||
(<%= link_to Maybe.commit_sha.first(7), "https://github.com/maybe-finance/maybe/commit/#{Maybe.commit_sha}", target: "_blank", class: "hover:underline" %>)
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
<% menu.with_item(variant: "link", text: "Contact", icon: "message-square-more", href: "https://link.maybe.co/discord") %>
|
||||
<% else %>
|
||||
<% menu.with_item(variant: "link", text: "Contact", icon: "message-square-more", href: "mailto:hello@maybefinance.com") %>
|
||||
<% end %>
|
||||
|
||||
<% menu.with_item(variant: "divider") %>
|
||||
|
||||
<% menu.with_item(variant: "button", text: "Log out", icon: "log-out", href: session_path(Current.session), method: :delete) %>
|
||||
<% end %>
|
||||
|
||||
<% menu.with_item(text: "Settings", icon: "settings", href: settings_profile_path(return_to: request.fullpath)) %>
|
||||
<% menu.with_item(text: "Changelog", icon: "box", href: changelog_path) %>
|
||||
<% menu.with_item(text: "Feedback", icon: "megaphone", href: feedback_path) %>
|
||||
|
||||
<% if self_hosted? %>
|
||||
<% menu.with_item(text: "Contact", icon: "message-square-more", href: "https://link.maybe.co/discord") %>
|
||||
<% else %>
|
||||
<% menu.with_item(text: "Contact", icon: "message-square-more", href: "mailto:hello@maybefinance.com") %>
|
||||
<% end %>
|
||||
|
||||
<% menu.with_item(variant: "divider") %>
|
||||
|
||||
<% menu.with_item(text: "Log out", icon: "log-out", href: session_path(Current.session), method: :delete) %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -33,10 +33,10 @@
|
|||
</div>
|
||||
|
||||
<div class="flex justify-center py-8">
|
||||
<%= render ButtonComponent.new(
|
||||
<%= render LinkComponent.new(
|
||||
text: "Edit account details",
|
||||
variant: "ghost",
|
||||
href: edit_vehicle_path(account),
|
||||
data: { turbo_frame: :modal }
|
||||
open_in: :modal
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -2,18 +2,17 @@ class ButtonComponentPreview < ViewComponent::Preview
|
|||
# @param variant select {{ ButtonComponent::VARIANTS.keys }}
|
||||
# @param size select {{ ButtonComponent::SIZES.keys }}
|
||||
# @param disabled toggle
|
||||
# @param leading_icon text
|
||||
# @param trailing_icon text
|
||||
# @param icon text "This is only used for icon-only buttons"
|
||||
def default(variant: "primary", size: "md", disabled: false, leading_icon: "plus", trailing_icon: nil, icon: "circle")
|
||||
# @param icon select ["plus", "circle"]
|
||||
# @param rounded toggle
|
||||
def default(variant: "primary", size: "md", disabled: false, icon: "plus", rounded: false)
|
||||
render ButtonComponent.new(
|
||||
text: "Sample button",
|
||||
variant: variant,
|
||||
size: size,
|
||||
disabled: disabled,
|
||||
leading_icon: leading_icon,
|
||||
trailing_icon: trailing_icon,
|
||||
icon: icon
|
||||
icon: icon,
|
||||
rounded: rounded,
|
||||
data: { menu_target: "button" }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
class IconComponentPreview < ViewComponent::Preview
|
||||
# @param variant select {{ IconComponent::VARIANTS.keys }}
|
||||
# @param size select {{ IconComponent::SIZES.keys }}
|
||||
def default(variant: "default", size: "md")
|
||||
render IconComponent.new(
|
||||
"circle-user",
|
||||
variant: variant,
|
||||
size: size
|
||||
)
|
||||
end
|
||||
end
|
27
test/components/previews/link_component_preview.rb
Normal file
27
test/components/previews/link_component_preview.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
class LinkComponentPreview < ViewComponent::Preview
|
||||
# Usage
|
||||
# -------------
|
||||
#
|
||||
# LinkComponent is a small abstraction on top of the `link_to` helper.
|
||||
#
|
||||
# It can be used as a regular link or styled as a "Link button" using any of the available ButtonComponent variants.
|
||||
#
|
||||
# @param variant select {{ LinkComponent::VARIANTS.keys }}
|
||||
# @param size select {{ LinkComponent::SIZES.keys }}
|
||||
# @param icon select ["", "plus", "arrow-right"]
|
||||
# @param icon_position select ["left", "right"]
|
||||
# @param full_width toggle
|
||||
# @param rounded toggle
|
||||
def default(variant: "default", size: "md", icon: "plus", icon_position: "left", full_width: false, rounded: false)
|
||||
render LinkComponent.new(
|
||||
href: "#",
|
||||
text: "Preview link",
|
||||
variant: variant,
|
||||
size: size,
|
||||
icon: icon,
|
||||
icon_position: icon_position,
|
||||
full_width: full_width,
|
||||
rounded: rounded
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,19 +1,44 @@
|
|||
class MenuComponentPreview < ViewComponent::Preview
|
||||
# @param variant select {{ MenuComponent::VARIANTS.keys }}
|
||||
def default(variant: "icon")
|
||||
if variant == "icon"
|
||||
render MenuComponent.new(variant: variant) do |menu|
|
||||
menu.with_item(text: "Menu item 1", href: "#", icon: "plus")
|
||||
menu.with_item(text: "Menu item 2", href: "#", icon: "circle")
|
||||
menu.with_item(text: "Destructive", href: "#", method: :delete, icon: "circle")
|
||||
end
|
||||
else
|
||||
render MenuComponent.new(variant: variant) do |menu|
|
||||
menu.with_button(text: "New", icon: "plus")
|
||||
menu.with_item(text: "Menu item 1", href: "#", icon: "plus")
|
||||
menu.with_item(text: "Menu item 2", href: "#", icon: "circle")
|
||||
menu.with_item(text: "Destructive", href: "#", method: :delete, icon: "circle")
|
||||
end
|
||||
def icon
|
||||
render MenuComponent.new(variant: "icon") do |menu|
|
||||
menu_contents(menu)
|
||||
end
|
||||
end
|
||||
|
||||
def button
|
||||
render MenuComponent.new(variant: "button") do |menu|
|
||||
menu.with_button(text: "Open menu", variant: "secondary")
|
||||
menu_contents(menu)
|
||||
end
|
||||
end
|
||||
|
||||
def avatar
|
||||
render MenuComponent.new(variant: "avatar") do |menu|
|
||||
menu_contents(menu)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def menu_contents(menu)
|
||||
menu.with_header do
|
||||
content_tag(:div, class: "p-3") do
|
||||
content_tag(:h3, "Menu header", class: "font-medium text-gray-900")
|
||||
end
|
||||
end
|
||||
|
||||
menu.with_item(variant: "link", text: "Link", href: "#", icon: "plus")
|
||||
menu.with_item(variant: "button", text: "Action", href: "#", method: :post, icon: "circle")
|
||||
menu.with_item(variant: "button", text: "Action destructive", href: "#", method: :delete, icon: "circle")
|
||||
|
||||
menu.with_item(variant: "divider")
|
||||
|
||||
menu.with_custom_content do
|
||||
content_tag(:div, class: "p-4") do
|
||||
safe_join([
|
||||
content_tag(:h3, "Custom content header", class: "font-medium text-gray-900"),
|
||||
content_tag(:p, "Some custom content", class: "text-sm text-gray-500")
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue