mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 23:45:21 +02:00
More button fixes
This commit is contained in:
parent
63a976dcb6
commit
641f32d0d8
43 changed files with 407 additions and 331 deletions
|
@ -6,7 +6,7 @@
|
|||
<%= lucide_icon(@leading_icon, class: icon_classes) %>
|
||||
<% end %>
|
||||
|
||||
<span class="<%= text_classes %>"><%= @text %></span>
|
||||
<%= @text %>
|
||||
|
||||
<% if @trailing_icon %>
|
||||
<%= lucide_icon(@trailing_icon, class: icon_classes) %>
|
||||
|
|
|
@ -18,19 +18,19 @@ class ButtonComponent < ViewComponent::Base
|
|||
icon: "fg-white"
|
||||
},
|
||||
outline: {
|
||||
bg: "bg-transparent hover:bg-gray-100 theme-dark:hover:bg-gray-700",
|
||||
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-red-100 theme-dark:hover:bg-red-700",
|
||||
fg: "text-destructive",
|
||||
border: "border border-red-500"
|
||||
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-secondary",
|
||||
text: "text-primary",
|
||||
icon: "fg-gray"
|
||||
},
|
||||
link_color: {
|
||||
|
@ -97,20 +97,13 @@ class ButtonComponent < ViewComponent::Base
|
|||
elsif @href
|
||||
link_to @href, class: container_classes, **@options, &block
|
||||
else
|
||||
content_tag :button, class: container_classes, **@options, &block
|
||||
content_tag :button, type: "button", class: container_classes, **@options, &block
|
||||
end
|
||||
end
|
||||
|
||||
def text_classes
|
||||
[
|
||||
"font-medium",
|
||||
size_meta[:text],
|
||||
variant_meta[:text]
|
||||
].join(" ")
|
||||
end
|
||||
|
||||
def icon_classes
|
||||
[
|
||||
"shrink-0",
|
||||
size_meta[:icon],
|
||||
variant_meta[:icon]
|
||||
].join(" ")
|
||||
|
@ -122,17 +115,30 @@ class ButtonComponent < ViewComponent::Base
|
|||
|
||||
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"
|
||||
|
||||
[
|
||||
"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
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
<div data-controller="menu">
|
||||
<div data-controller="menu" data-menu-placement-value="<%= @placement %>" data-menu-offset-value="<%= @offset %>">
|
||||
<% if @variant == :icon %>
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: "more-horizontal", data: { menu_target: "button" }) %>
|
||||
<% elsif @variant == :button %>
|
||||
<%= button %>
|
||||
<% elsif @variant == :avatar %>
|
||||
<button data-menu-target="button">
|
||||
<div class="w-9 h-9 cursor-pointer">
|
||||
<%= render "settings/user_avatar", avatar_url: @avatar_url %>
|
||||
</div>
|
||||
</button>
|
||||
<% end %>
|
||||
|
||||
<div data-menu-target="content" class="hidden min-w-[200px] p-1 z-50 shadow-border-xs bg-container rounded-lg">
|
||||
<% items.each do |item| %>
|
||||
<%= item %>
|
||||
<% end %>
|
||||
<div data-menu-target="content" class="hidden min-w-[200px] z-50 shadow-border-xs bg-container rounded-lg">
|
||||
<%= header %>
|
||||
|
||||
<div class="py-1">
|
||||
<% items.each do |item| %>
|
||||
<%= item %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -6,14 +6,22 @@ class MenuComponent < ViewComponent::Base
|
|||
ButtonComponent.new(**options_with_target)
|
||||
end
|
||||
|
||||
renders_one :header, ->(&block) do
|
||||
content_tag(:div, class: "border-b border-tertiary", &block)
|
||||
end
|
||||
|
||||
renders_many :items, MenuItemComponent
|
||||
|
||||
VARIANTS = {
|
||||
icon: {},
|
||||
button: {}
|
||||
button: {},
|
||||
avatar: {}
|
||||
}
|
||||
|
||||
def initialize(variant: "icon")
|
||||
def initialize(variant: "icon", avatar_url: nil, placement: "bottom-end", offset: 12)
|
||||
@variant = variant.to_sym
|
||||
@avatar_url = avatar_url
|
||||
@placement = placement
|
||||
@offset = offset
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
class MenuItemComponent < ViewComponent::Base
|
||||
erb_template <<~ERB
|
||||
<%= wrapper do %>
|
||||
<% if @icon %>
|
||||
<%= render IconComponent.new(@icon, variant: destructive? ? "destructive" : "default") %>
|
||||
<% end %>
|
||||
<%= tag.span(@text, class: text_classes) %>
|
||||
<% 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
|
||||
|
||||
def initialize(text:, href:, method: :get, icon: nil, data: {})
|
||||
def initialize(variant: "default", text: nil, href: nil, method: :get, icon: nil, data: {})
|
||||
@variant = variant.to_sym
|
||||
@text = text
|
||||
@icon = icon
|
||||
@href = href
|
||||
|
|
|
@ -56,7 +56,7 @@ class StyledFormBuilder < ActionView::Helpers::FormBuilder
|
|||
opts = options.dup
|
||||
opts[:data] = { turbo_submits_with: "Submitting..." }.merge(opts[:data] || {})
|
||||
|
||||
@template.render(ButtonComponent.new(text: value, full_width: true, **opts))
|
||||
@template.render(ButtonComponent.new(text: value, type: "submit", full_width: true, **opts))
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -6,52 +6,14 @@ const application = Application.start();
|
|||
application.debug = false;
|
||||
window.Stimulus = application;
|
||||
|
||||
Turbo.config.forms.confirm = (message) => {
|
||||
const dialog = document.getElementById("turbo-confirm");
|
||||
|
||||
try {
|
||||
const { title, body, accept, acceptClass } = JSON.parse(message);
|
||||
|
||||
if (title) {
|
||||
document.getElementById("turbo-confirm-title").innerHTML = title;
|
||||
}
|
||||
|
||||
if (body) {
|
||||
document.getElementById("turbo-confirm-body").innerHTML = body;
|
||||
}
|
||||
|
||||
if (accept) {
|
||||
document.getElementById("turbo-confirm-accept").innerHTML = accept;
|
||||
}
|
||||
|
||||
if (acceptClass) {
|
||||
document.getElementById("turbo-confirm-accept").className = acceptClass;
|
||||
}
|
||||
} catch (e) {
|
||||
document.getElementById("turbo-confirm-title").innerText = message;
|
||||
}
|
||||
|
||||
dialog.showModal();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
dialog.addEventListener(
|
||||
"close",
|
||||
() => {
|
||||
const confirmed = dialog.returnValue === "confirm";
|
||||
|
||||
if (!confirmed) {
|
||||
document.getElementById("turbo-confirm-title").innerHTML =
|
||||
"Are you sure?";
|
||||
document.getElementById("turbo-confirm-body").innerHTML =
|
||||
"You will not be able to undo this decision";
|
||||
document.getElementById("turbo-confirm-accept").innerHTML = "Confirm";
|
||||
}
|
||||
|
||||
resolve(confirmed);
|
||||
},
|
||||
{ once: true },
|
||||
Turbo.config.forms.confirm = (data) => {
|
||||
const confirmDialogController =
|
||||
application.getControllerForElementAndIdentifier(
|
||||
document.getElementById("confirm-dialog"),
|
||||
"confirm-dialog",
|
||||
);
|
||||
});
|
||||
|
||||
return confirmDialogController.handleConfirm(data);
|
||||
};
|
||||
|
||||
export { application };
|
||||
|
|
53
app/javascript/controllers/confirm_dialog_controller.js
Normal file
53
app/javascript/controllers/confirm_dialog_controller.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { Controller } from "@hotwired/stimulus";
|
||||
|
||||
// Connects to data-controller="confirm-dialog"
|
||||
// See javascript/controllers/application.js for how this is wired up
|
||||
export default class extends Controller {
|
||||
static targets = ["title", "subtitle", "confirmButton"];
|
||||
|
||||
handleConfirm(rawData) {
|
||||
const data = this.#normalizeRawData(rawData);
|
||||
|
||||
this.#prepareDialog(data);
|
||||
|
||||
this.element.showModal();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.element.addEventListener(
|
||||
"close",
|
||||
() => {
|
||||
const isConfirmed = this.element.returnValue === "confirm";
|
||||
resolve(isConfirmed);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#prepareDialog(data) {
|
||||
const variant = data.variant || "primary";
|
||||
|
||||
this.confirmButtonTargets.forEach((button) => {
|
||||
if (button.dataset.variant === variant) {
|
||||
button.removeAttribute("hidden");
|
||||
} else {
|
||||
button.setAttribute("hidden", true);
|
||||
}
|
||||
|
||||
button.textContent = data.confirmText || "Confirm";
|
||||
});
|
||||
|
||||
this.titleTarget.textContent = data.title || "Are you sure?";
|
||||
this.subtitleTarget.innerHTML =
|
||||
data.body || "This action cannot be undone.";
|
||||
}
|
||||
|
||||
// If data is a string, it's the title. Otherwise, return the parsed object.
|
||||
#normalizeRawData(rawData) {
|
||||
try {
|
||||
return JSON.parse(rawData);
|
||||
} catch (e) {
|
||||
return { title: rawData };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ export default class extends Controller {
|
|||
remove(e) {
|
||||
if (e.params.destroy) {
|
||||
this.destroyFieldTarget.value = true;
|
||||
this.element.classList.add("hidden");
|
||||
} else {
|
||||
this.element.remove();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%# locals: (accountable:) %>
|
||||
|
||||
<%= link_to new_polymorphic_path(accountable, step: "method_select", return_to: params[:return_to]),
|
||||
class: "flex items-center gap-4 w-full text-center focus:outline-hidden hover:bg-surface-hover focus:bg-surface-hover fg-contrast hover:fg-primary focus:fg-primary border border-transparent block px-2 rounded-lg p-2" do %>
|
||||
class: "flex items-center gap-4 w-full text-center focus:outline-hidden hover:bg-surface-hover focus:bg-surface-hover fg-primary border border-transparent block px-2 rounded-lg p-2" do %>
|
||||
<span style="background-color: color-mix(in srgb, <%= accountable.color %> 10%, white);" class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg border border-alpha-black-25">
|
||||
<%= lucide_icon(accountable.icon, style: "color: #{accountable.color}", class: "w-5 h-5") %>
|
||||
</span>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<% unless params[:return_to].present? %>
|
||||
<%= button_to imports_path(import: { type: "AccountImport" }),
|
||||
data: { turbo_frame: :_top },
|
||||
class: "flex items-center gap-4 w-full text-center focus:outline-hidden hover:bg-surface-hover focus:bg-surface-hover fg-contrast hover:fg-primary focus:fg-primary border border-transparent block px-2 rounded-lg p-2" do %>
|
||||
class: "flex items-center gap-4 w-full text-center focus:outline-hidden hover:bg-surface-hover focus:bg-surface-hover fg-primary border border-transparent block px-2 rounded-lg p-2" do %>
|
||||
<span style="background-color: color-mix(in srgb, #F79009 10%, white);" class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg border border-alpha-black-25">
|
||||
<%= lucide_icon("download", style: "color: #F79009", class: "w-5 h-5") %>
|
||||
</span>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<%= render layout: "accounts/new/container", locals: { title: t(".title"), back_path: new_account_path } do %>
|
||||
<div class="text-sm">
|
||||
<%= link_to path, class: "flex items-center gap-4 w-full text-center focus:outline-hidden focus:bg-surface border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-surface rounded-lg p-2" do %>
|
||||
<%= link_to path, class: "flex items-center gap-4 w-full text-center text-primary focus:outline-hidden focus:bg-surface border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-surface rounded-lg p-2" do %>
|
||||
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
||||
<%= lucide_icon("keyboard", class: "text-secondary w-5 h-5") %>
|
||||
</span>
|
||||
|
@ -11,7 +11,7 @@
|
|||
|
||||
<% if us_link_token %>
|
||||
<%# Default US-only Link %>
|
||||
<button data-controller="plaid" data-action="plaid#open modal#close" data-plaid-region-value="us" data-plaid-link-token-value="<%= us_link_token %>" class="flex items-center gap-4 w-full text-center focus:outline-hidden focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2">
|
||||
<button data-controller="plaid" data-action="plaid#open modal#close" data-plaid-region-value="us" data-plaid-link-token-value="<%= us_link_token %>" class="text-primary flex items-center gap-4 w-full text-center focus:outline-hidden focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2">
|
||||
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
||||
<%= lucide_icon("link-2", class: "text-secondary w-5 h-5") %>
|
||||
</span>
|
||||
|
@ -21,7 +21,7 @@
|
|||
|
||||
<%# EU Link %>
|
||||
<% if eu_link_token %>
|
||||
<button data-controller="plaid" data-action="plaid#open modal#close" data-plaid-region-value="eu" data-plaid-link-token-value="<%= eu_link_token %>" class="flex items-center gap-4 w-full text-center focus:outline-hidden focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2">
|
||||
<button data-controller="plaid" data-action="plaid#open modal#close" data-plaid-region-value="eu" data-plaid-link-token-value="<%= eu_link_token %>" class="text-primary flex items-center gap-4 w-full text-center focus:outline-hidden focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2">
|
||||
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
||||
<%= lucide_icon("link-2", class: "text-secondary w-5 h-5") %>
|
||||
</span>
|
||||
|
|
|
@ -2,9 +2,7 @@
|
|||
|
||||
<nav class="items-center gap-2 mb-6 hidden md:flex">
|
||||
<% if sidebar_toggle_enabled %>
|
||||
<button data-action="sidebar#toggleLeftPanel" class="hidden p-2 lg:inline-flex rounded-lg items-center justify-center hover:bg-container-inset cursor-pointer">
|
||||
<%= icon("panel-left", color: "gray") %>
|
||||
</button>
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: "panel-left", data: { action: "sidebar#toggleLeftPanel" }) %>
|
||||
<% end %>
|
||||
|
||||
<div class="py-2 flex items-center gap-2">
|
||||
|
@ -25,9 +23,7 @@
|
|||
|
||||
<% if sidebar_toggle_enabled %>
|
||||
<div class="ml-auto">
|
||||
<button data-action="sidebar#toggleRightPanel" class="p-2 hidden lg:inline-flex rounded-lg items-center justify-center hover:bg-container-inset cursor-pointer" title="Toggle AI Assistant">
|
||||
<%= icon("panel-right", color: "gray") %>
|
||||
</button>
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: "panel-right", data: { action: "sidebar#toggleRightPanel" }, title: "Toggle AI Assistant") %>
|
||||
</div>
|
||||
<% end %>
|
||||
</nav>
|
||||
|
|
33
app/views/layouts/shared/_custom_confirm_dialog.html.erb
Normal file
33
app/views/layouts/shared/_custom_confirm_dialog.html.erb
Normal file
|
@ -0,0 +1,33 @@
|
|||
<dialog id="confirm-dialog" data-controller="confirm-dialog" class="backdrop:bg-overlay bg-transparent m-auto p-1">
|
||||
<form method="dialog" class="p-4 bg-container rounded-xl shadow-border-xs space-y-4 min-w-full lg:min-w-[300px] lg:max-w-[400px]">
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="font-medium text-primary" data-confirm-dialog-target="title">Are you sure?</h3>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
variant: "icon",
|
||||
icon: "x",
|
||||
value: "cancel",
|
||||
type: "submit"
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-secondary" data-confirm-dialog-target="subtitle">This action cannot be undone.</p>
|
||||
</div>
|
||||
|
||||
<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"
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</form>
|
||||
</dialog>
|
|
@ -1,6 +0,0 @@
|
|||
<%= turbo_frame_tag "modal" %>
|
||||
<%= turbo_frame_tag "drawer" %>
|
||||
<%= render "shared/confirm_modal" %>
|
||||
|
||||
<%= render "impersonation_sessions/super_admin_bar" if Current.true_user&.super_admin? && show_super_admin_bar? %>
|
||||
<%= render "impersonation_sessions/approval_bar" if Current.true_user&.impersonated_support_sessions&.initiated&.any? %>
|
|
@ -22,7 +22,9 @@
|
|||
|
||||
<%= turbo_frame_tag "modal" %>
|
||||
<%= turbo_frame_tag "drawer" %>
|
||||
<%= render "shared/confirm_modal" %>
|
||||
|
||||
<%# Custom overrides for browser's confirm API %>
|
||||
<%= render "layouts/shared/custom_confirm_dialog" %>
|
||||
|
||||
<%= render "impersonation_sessions/super_admin_bar" if Current.true_user&.super_admin? && show_super_admin_bar? %>
|
||||
<%= render "impersonation_sessions/approval_bar" if Current.true_user&.impersonated_support_sessions&.initiated&.any? %>
|
||||
|
|
|
@ -19,15 +19,11 @@
|
|||
<div class="items-center gap-1 hidden lg:flex">
|
||||
<%# These are disabled for now, but in the future, will all open specific menus with their own context and search %>
|
||||
<% ["plus", "command", "at-sign", "mouse-pointer-click"].each do |icon| %>
|
||||
<button type="button" title="Coming soon" class="cursor-not-allowed w-8 h-8 flex justify-center items-center hover:bg-surface-hover rounded-lg">
|
||||
<%= icon(icon, color: "gray") %>
|
||||
</button>
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: icon, disabled: true, class: "cursor-not-allowed", title: "Coming soon") %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="w-8 h-8 flex justify-center items-center text-secondary hover:bg-surface-hover cursor-pointer rounded-lg">
|
||||
<%= icon("arrow-up") %>
|
||||
</button>
|
||||
<%= render ButtonComponent.new(variant: "icon", icon: "arrow-up", type: "submit") %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
|
|
@ -29,5 +29,10 @@
|
|||
</div>
|
||||
|
||||
<div class="flex justify-center py-8">
|
||||
<%= link_to "Edit account details", edit_property_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Edit account details",
|
||||
href: edit_property_path(account),
|
||||
variant: "ghost",
|
||||
data: { turbo_frame: :modal }
|
||||
) %>
|
||||
</div>
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
<% end %>
|
||||
</div>
|
||||
|
||||
<button type="button"
|
||||
data-action="rule--actions#remove"
|
||||
data-rule--actions-destroy-param="<%= action.persisted? %>">
|
||||
<%= icon("trash-2", color: "gray", size: "sm") %>
|
||||
</button>
|
||||
<%= render ButtonComponent.new(
|
||||
icon: "trash-2",
|
||||
variant: "icon",
|
||||
size: "sm",
|
||||
data: { action: "rule--actions#remove", rule__actions_destroy_param: action.persisted? }
|
||||
) %>
|
||||
</li>
|
||||
|
|
|
@ -32,9 +32,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button"
|
||||
data-action="rule--conditions#remove"
|
||||
data-rule--conditions-destroy-param="<%= condition.persisted? %>">
|
||||
<%= icon("trash-2", color: "gray", size: "sm") %>
|
||||
</button>
|
||||
<%= render ButtonComponent.new(
|
||||
icon: "trash-2",
|
||||
variant: "icon",
|
||||
size: "sm",
|
||||
data: { action: "rule--conditions#remove", rule__conditions_destroy_param: condition.persisted? }
|
||||
) %>
|
||||
</li>
|
||||
|
|
|
@ -19,9 +19,12 @@
|
|||
<p class="text-sm text-secondary">of the following conditions</p>
|
||||
</div>
|
||||
|
||||
<button type="button" data-action="element-removal#remove">
|
||||
<%= icon("trash-2", color: "gray", size: "sm") %>
|
||||
</button>
|
||||
<%= render ButtonComponent.new(
|
||||
icon: "trash-2",
|
||||
variant: "icon",
|
||||
size: "sm",
|
||||
data: { action: "element-removal#remove" }
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<%# Sub-condition template, used by Stimulus controller to add new sub-conditions dynamically %>
|
||||
|
@ -37,8 +40,10 @@
|
|||
<% end %>
|
||||
</ul>
|
||||
|
||||
<button type="button" class="btn btn--ghost" data-action="rule--conditions#addSubCondition">
|
||||
<%= icon("plus", color: "gray", size: "sm") %>
|
||||
<span>Add condition</span>
|
||||
</button>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Add condition",
|
||||
leading_icon: "plus",
|
||||
variant: "ghost",
|
||||
data: { action: "rule--conditions#addSubCondition" }
|
||||
) %>
|
||||
</li>
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
<%= f.hidden_field :rule_prompt_dismissed_at, value: Time.current %>
|
||||
|
||||
<%= tag.div class:"flex gap-2 justify-end" do %>
|
||||
<%= f.submit "Dismiss", class: "btn btn--secondary" %>
|
||||
<%= tag.a "Create rule", href: new_rule_path(resource_type: "transaction", action_type: "set_transaction_category", action_value: cta[:category_id]), class: "btn btn--primary", data: { turbo_frame: "modal" } %>
|
||||
<%= render ButtonComponent.new(text: "Dismiss", variant: "secondary", type: "submit") %>
|
||||
<% 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 }) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -37,15 +37,8 @@
|
|||
</ul>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button type="button" data-action="rules#addCondition" class="btn btn--ghost">
|
||||
<%= icon("plus") %>
|
||||
<span>Add condition</span>
|
||||
</button>
|
||||
|
||||
<button type="button" data-action="rules#addConditionGroup" class="btn btn--ghost">
|
||||
<%= icon("boxes") %>
|
||||
<span>Add condition group</span>
|
||||
</button>
|
||||
<%= 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" }) %>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
@ -65,13 +58,7 @@
|
|||
<% end %>
|
||||
</ul>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-action="rules#addAction"
|
||||
class="btn btn--ghost">
|
||||
<%= icon("plus") %>
|
||||
<span>Add action</span>
|
||||
</button>
|
||||
<%= render ButtonComponent.new(text: "Add action", leading_icon: "plus", variant: "ghost", data: { action: "rules#addAction" }) %>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4">
|
||||
|
|
|
@ -15,6 +15,12 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<%= button_to "Confirm changes", apply_rule_path(@rule), class: "btn btn--primary w-full justify-center", data: { turbo_frame: "_top"} %>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Confirm changes",
|
||||
href: apply_rule_path(@rule),
|
||||
method: :post,
|
||||
full_width: true,
|
||||
data: { turbo_frame: "_top" }) %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -14,10 +14,7 @@
|
|||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to new_rule_path(resource_type: "transaction"), class: "btn btn--primary flex items-center gap-1 justify-center", data: { turbo_frame: :modal } do %>
|
||||
<%= lucide_icon "plus", class: "w-5 h-5" %>
|
||||
<p>New rule</p>
|
||||
<% end %>
|
||||
<%= render ButtonComponent.new(text: "New rule", href: new_rule_path(resource_type: "transaction"), leading_icon: "plus", data: { turbo_frame: :modal }) %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
@ -50,10 +47,7 @@
|
|||
<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">
|
||||
<%= link_to new_rule_path(resource_type: "transaction"), class: "btn btn--primary flex items-center gap-1", data: { turbo_frame: "modal" } do %>
|
||||
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||
<span>New rule</span>
|
||||
<% end %>
|
||||
<%= render ButtonComponent.new(text: "New rule", href: new_rule_path(resource_type: "transaction"), leading_icon: "plus", data: { turbo_frame: :modal }) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
</section>
|
||||
|
||||
<section>
|
||||
<%= button_to session_path(Current.session), method: :delete, class: "flex items-center gap-2 btn btn--ghost text-destructive w-full" do %>
|
||||
<%= button_to session_path(Current.session), method: :delete, class: "flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-destructive hover:bg-surface-hover w-full" do %>
|
||||
<%= lucide_icon("log-out", class: "w-5 h-5 shrink-0") %>
|
||||
<span><%= t(".logout") %></span>
|
||||
<% end %>
|
||||
|
@ -141,7 +141,7 @@
|
|||
<%= render "settings/settings_nav_item", name: t(".feedback_label"), path: feedback_path, icon: "megaphone" %>
|
||||
</li>
|
||||
|
||||
<%= button_to session_path(Current.session), method: :delete, class: "flex items-center gap-2 btn btn--ghost text-destructive w-full" do %>
|
||||
<%= button_to session_path(Current.session), method: :delete, class: "flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-destructive hover:bg-surface-hover w-full" do %>
|
||||
<%= lucide_icon("log-out", class: "w-5 h-5 shrink-0") %>
|
||||
<span><%= t(".logout") %></span>
|
||||
<% end %>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%# locals: (name:, path:, icon:) %>
|
||||
|
||||
<%= link_to path, class: class_names(
|
||||
"flex items-center gap-2 btn btn--ghost whitespace-nowrap",
|
||||
"flex items-center gap-2 whitespace-nowrap px-3 py-2 rounded-lg text-sm",
|
||||
page_active?(path) ? "text-primary bg-container shadow-border-xs" : "text-secondary hover:bg-surface-hover border-transparent"
|
||||
), aria: { current: ("page" if page_active?(path)) } do %>
|
||||
<%= lucide_icon(icon, class: "w-5 h-5") if icon %>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<%# locals: (user:, variant: :thumbnail, lazy: false) %>
|
||||
<%# locals: (avatar_url: nil, initials: "U", lazy: false) %>
|
||||
|
||||
<% if user.profile_image.attached? %>
|
||||
<%= image_tag user.profile_image.variant(variant), class: "rounded-full w-full h-full object-cover", loading: lazy ? "lazy" : "eager" %>
|
||||
<% if avatar_url.present? %>
|
||||
<%= image_tag avatar_url, class: "rounded-full w-full h-full object-cover", loading: lazy ? "lazy" : "eager" %>
|
||||
<% else %>
|
||||
<div class="text-white w-full h-full bg-gray-400 rounded-full flex items-center justify-center text-lg uppercase"><%= user.initial %></div>
|
||||
<div class="text-white w-full h-full bg-gray-400 rounded-full flex items-center justify-center text-lg uppercase"><%= initials %></div>
|
||||
<% end %>
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
class="h-full w-full flex justify-center items-center <%= user.profile_image.attached? ? "" : "hidden" %>">
|
||||
<% if user.profile_image.attached? %>
|
||||
<div class="h-full w-full">
|
||||
<%= render "settings/user_avatar", user: user %>
|
||||
<%= render "settings/user_avatar", avatar_url: user.profile_image.url %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
@ -37,9 +37,8 @@
|
|||
<div class="md:text-left text-center">
|
||||
<%= form.hidden_field :delete_profile_image, value: "0", data: { profile_image_preview_target: "deleteProfileImage" } %>
|
||||
|
||||
<%= form.label :profile_image, class: "btn btn--outline inline-block", data: { profile_image_preview_target: "uploadButton" } do %>
|
||||
|
||||
<%= lucide_icon "camera", class: "w-5 h-5 mr-2 inline-block", data: { profile_image_preview_target: "cameraIcon" } %>
|
||||
<%= form.label :profile_image, class: "px-3 py-2 rounded-lg text-sm hover:bg-surface-hover border border-secondary inline-flex items-center gap-2 cursor-pointer", data: { profile_image_preview_target: "uploadButton" } do %>
|
||||
<%= lucide_icon "camera", class: "w-5 h-5 inline-block", data: { profile_image_preview_target: "cameraIcon" } %>
|
||||
<span data-profile-image-preview-target="uploadText">
|
||||
<%= t(".choose") %> <span class="text-secondary"><%= t(".choose_label") %></span>
|
||||
</span>
|
||||
|
|
|
@ -19,15 +19,9 @@
|
|||
</div>
|
||||
|
||||
<% if @user.family.subscribed? || subscription_pending? %>
|
||||
<%= link_to subscription_path, class: "btn btn--secondary flex items-center gap-1", target: "_blank", rel: "noopener" do %>
|
||||
<span>Manage</span>
|
||||
<%= lucide_icon "external-link", class: "w-5 h-5 shrink-0 text-secondary" %>
|
||||
<% end %>
|
||||
<%= render ButtonComponent.new(text: "Manage", trailing_icon: "external-link", href: subscription_path, target: "_blank", rel: "noopener") %>
|
||||
<% else %>
|
||||
<%= link_to new_subscription_path, class: "btn btn--secondary flex items-center gap-1", target: "_blank", rel: "noopener" do %>
|
||||
<span>Subscribe</span>
|
||||
<%= lucide_icon "external-link", class: "w-5 h-5 shrink-0 text-secondary" %>
|
||||
<% end %>
|
||||
<%= render ButtonComponent.new(text: "Subscribe", trailing_icon: "external-link", href: new_subscription_path, target: "_blank", rel: "noopener") %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -6,17 +6,20 @@
|
|||
|
||||
<div>
|
||||
<%= form.email_field :email, placeholder: t(".email"), label: t(".email") %>
|
||||
|
||||
<% if @user.unconfirmed_email.present? %>
|
||||
<p class="mt-2 text-sm text-gray-600">
|
||||
You have requested to change your email to <%= @user.unconfirmed_email %>. Please go to your email and confirm for the change to take effect.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
<%= form.text_field :first_name, placeholder: t(".first_name"), label: t(".first_name") %>
|
||||
<%= form.text_field :last_name, placeholder: t(".last_name"), label: t(".last_name") %>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<%= form.submit t(".save"), class: "btn btn--primary md:w-auto w-full" %>
|
||||
<%= form.submit t(".save"), class: "md:w-auto w-full" %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
@ -40,7 +43,7 @@
|
|||
<% @users.each do |user| %>
|
||||
<div class="flex gap-2 items-center bg-container p-4 border border-alpha-black-25 rounded-lg">
|
||||
<div class="w-9 h-9 shrink-0">
|
||||
<%= render "settings/user_avatar", user: user %>
|
||||
<%= render "settings/user_avatar", avatar_url: user.profile_image.url %>
|
||||
</div>
|
||||
<p class="text-primary font-medium text-sm"><%= user.display_name %></p>
|
||||
<div class="rounded-md bg-surface px-1.5 py-0.5">
|
||||
|
@ -48,17 +51,20 @@
|
|||
</div>
|
||||
<% if Current.user.admin? && user != Current.user %>
|
||||
<div class="ml-auto">
|
||||
<%= button_to settings_profile_path(user_id: user),
|
||||
method: :delete,
|
||||
class: "text-red-500 hover:text-red-700",
|
||||
data: { turbo_confirm: {
|
||||
title: t(".confirm_remove_member.title"),
|
||||
body: t(".confirm_remove_member.body", name: user.display_name),
|
||||
accept: t(".remove_member"),
|
||||
acceptClass: "w-full btn btn--destructive text-white rounded-xl text-center p-[10px] mb-2"
|
||||
}} do %>
|
||||
<%= lucide_icon "x", class: "w-5 h-5" %>
|
||||
<% end %>
|
||||
<%= render ButtonComponent.new(
|
||||
variant: "icon",
|
||||
icon: "x",
|
||||
href: settings_profile_path(user_id: user),
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: t(".confirm_remove_member.title"),
|
||||
body: t(".confirm_remove_member.body", name: user.display_name),
|
||||
confirmText: t(".remove_member"),
|
||||
variant: "destructive"
|
||||
}
|
||||
}
|
||||
) %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
@ -97,18 +103,22 @@
|
|||
</button>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if Current.user.admin? %>
|
||||
<%= button_to invitation_path(invitation),
|
||||
method: :delete,
|
||||
class: "text-red-500 hover:text-red-700",
|
||||
data: { turbo_confirm: {
|
||||
title: t(".confirm_remove_invitation.title"),
|
||||
body: t(".confirm_remove_invitation.body", email: invitation.email),
|
||||
accept: t(".remove_invitation"),
|
||||
acceptClass: "w-full btn btn--destructive text-white rounded-xl text-center p-[10px] mb-2"
|
||||
}} do %>
|
||||
<%= lucide_icon "x", class: "w-5 h-5" %>
|
||||
<% end %>
|
||||
<%= render ButtonComponent.new(
|
||||
variant: "icon",
|
||||
icon: "x",
|
||||
href: invitation_path(invitation),
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: t(".confirm_remove_invitation.title"),
|
||||
body: t(".confirm_remove_invitation.body", email: invitation.email),
|
||||
confirmText: t(".remove_invitation"),
|
||||
variant: "outline-destructive"
|
||||
}
|
||||
}
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -134,16 +144,21 @@
|
|||
<h3 class="font-medium text-primary"><%= t(".reset_account") %></h3>
|
||||
<p class="text-secondary text-sm"><%= t(".reset_account_warning") %></p>
|
||||
</div>
|
||||
<%=
|
||||
button_to t(".reset_account"), reset_user_path(@user), method: :delete,
|
||||
class: "w-full md:w-auto btn btn--destructive",
|
||||
data: { turbo_confirm: {
|
||||
title: t(".confirm_reset.title"),
|
||||
body: t(".confirm_reset.body"),
|
||||
accept: t(".reset_account"),
|
||||
acceptClass: "w-full btn btn--destructive text-primary rounded-xl text-center p-[10px] mb-2"
|
||||
}}
|
||||
%>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".reset_account"),
|
||||
variant: "destructive",
|
||||
href: reset_user_path(@user),
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: t(".confirm_reset.title"),
|
||||
body: t(".confirm_reset.body"),
|
||||
confirmText: t(".reset_account"),
|
||||
variant: "destructive"
|
||||
}
|
||||
}
|
||||
) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
|
@ -151,16 +166,21 @@
|
|||
<h3 class="font-medium text-primary"><%= t(".delete_account") %></h3>
|
||||
<p class="text-secondary text-sm"><%= t(".delete_account_warning") %></p>
|
||||
</div>
|
||||
<%=
|
||||
button_to t(".delete_account"), user_path(@user), method: :delete,
|
||||
class: "w-full md:w-auto btn btn--destructive",
|
||||
data: { turbo_confirm: {
|
||||
title: t(".confirm_delete.title"),
|
||||
body: t(".confirm_delete.body"),
|
||||
accept: t(".delete_account"),
|
||||
acceptClass: "w-full btn btn--destructive text-white rounded-xl text-center p-[10px] mb-2"
|
||||
}}
|
||||
%>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".delete_account"),
|
||||
variant: "destructive",
|
||||
href: user_path(@user),
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: t(".confirm_delete.title"),
|
||||
body: t(".confirm_delete.body"),
|
||||
confirmText: t(".delete_account"),
|
||||
variant: "destructive"
|
||||
}
|
||||
}
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -21,18 +21,25 @@
|
|||
|
||||
<div class="mt-4 md:mt-0">
|
||||
<% if Current.user.otp_required? %>
|
||||
<%= button_to t(".disable_mfa"), disable_mfa_path,
|
||||
method: :delete,
|
||||
class: "w-full md:w-auto btn btn--secondary flex items-center gap-1 justify-center",
|
||||
data: { turbo_confirm: {
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".disable_mfa"),
|
||||
variant: "secondary",
|
||||
href: disable_mfa_path,
|
||||
method: :delete,
|
||||
data: {
|
||||
turbo_confirm: {
|
||||
title: t(".disable_mfa_confirm"),
|
||||
body: t(".disable_mfa_confirm"),
|
||||
accept: t(".disable_mfa"),
|
||||
acceptClass: "w-full bg-red-500 text-white rounded-xl text-center p-[10px] border mb-2"
|
||||
} } %>
|
||||
confirmText: t(".disable_mfa"),
|
||||
variant: "outline-destructive"
|
||||
}
|
||||
}
|
||||
) %>
|
||||
<% else %>
|
||||
<%= link_to t(".enable_mfa"), new_mfa_path,
|
||||
class: "w-full md:w-auto btn btn--primary flex items-center gap-1 justify-center" %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".enable_mfa"),
|
||||
href: new_mfa_path
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<dialog id="turbo-confirm" class="max-w-[420px] w-full rounded-xl m-auto">
|
||||
<form method="dialog" class="p-4 bg-container">
|
||||
<div class="flex flex-col mb-4">
|
||||
<div class="flex justify-between mb-2 gap-4">
|
||||
<h3 id="turbo-confirm-title" class="font-medium text-primary text-md"><%= t(".title") %></h3>
|
||||
<button value="cancel">
|
||||
<%= lucide_icon("x", class: "w-5 h-5 shrink-0 text-secondary") %>
|
||||
</button>
|
||||
</div>
|
||||
<div id="turbo-confirm-body" class="text-secondary text-sm">
|
||||
<%= t(".body_html") %>
|
||||
</div>
|
||||
</div>
|
||||
<button id="turbo-confirm-accept" class="btn btn--outline-destructive justify-center w-full mb-2" value="confirm"><%= t(".accept") %></button>
|
||||
</form>
|
||||
</dialog>
|
|
@ -16,7 +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>
|
||||
|
||||
<%= link_to "Upgrade to Maybe+", new_subscription_path, class: "btn btn--primary text-center w-full block" %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Upgrade to Maybe+",
|
||||
href: new_subscription_path,
|
||||
class: "w-full"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<%# locals: (message:, description: nil, cta: nil) %>
|
||||
<%# locals: (message:, description: nil) %>
|
||||
|
||||
<%= tag.div class: "relative flex gap-3 rounded-lg bg-container-inset p-4 group w-full md:max-w-80 shadow-border-xs",
|
||||
data: {
|
||||
controller: "element-removal",
|
||||
action: "animationend->element-removal#remove"
|
||||
} do %>
|
||||
|
||||
<div class="h-5 w-5 shrink-0 p-px text-primary">
|
||||
<div class="flex h-full items-center justify-center rounded-full bg-success">
|
||||
<%= lucide_icon "check", class: "w-3 h-3" %>
|
||||
|
@ -20,29 +19,18 @@
|
|||
<%= tag.p description, class: "text-secondary text-sm" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if cta %>
|
||||
<%= tag.div class:"flex gap-2 justify-end" do %>
|
||||
<%= tag.button cta[:decline][:label], class: "btn btn--secondary", data: { action: "click->element-removal#remove" } %>
|
||||
<%= tag.a cta[:accept][:label], href: cta[:accept][:href], class: "btn btn--primary" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto">
|
||||
<div class="h-5 shrink-0">
|
||||
<% unless cta %>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="shrink-0">
|
||||
<path d="M18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2C14.4183 2 18 5.58172 18 10ZM3.6 10C3.6 13.5346 6.46538 16.4 10 16.4C13.5346 16.4 16.4 13.5346 16.4 10C16.4 6.46538 13.5346 3.6 10 3.6C6.46538 3.6 3.6 6.46538 3.6 10Z" fill="#E5E5E5" />
|
||||
<circle class="origin-center -rotate-90 animate-stroke-fill" stroke="#141414" stroke-opacity="0.4" r="7.2" cx="10" cy="10" stroke-dasharray="43.9822971503" stroke-dashoffset="43.9822971503" />
|
||||
</svg>
|
||||
<% end %>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="shrink-0">
|
||||
<path d="M18 10C18 14.4183 14.4183 18 10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2C14.4183 2 18 5.58172 18 10ZM3.6 10C3.6 13.5346 6.46538 16.4 10 16.4C13.5346 16.4 16.4 13.5346 16.4 10C16.4 6.46538 13.5346 3.6 10 3.6C6.46538 3.6 3.6 6.46538 3.6 10Z" fill="#E5E5E5" />
|
||||
<circle class="origin-center -rotate-90 animate-stroke-fill" stroke="#141414" stroke-opacity="0.4" r="7.2" cx="10" cy="10" stroke-dasharray="43.9822971503" stroke-dashoffset="43.9822971503" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% unless cta %>
|
||||
<div class="absolute -top-2 -right-2">
|
||||
<%= lucide_icon "x", class: "w-5 h-5 p-0.5 hidden group-hover:inline-block border border-alpha-black-50 border-solid rounded-lg bg-white text-subdued cursor-pointer", data: { action: "click->element-removal#remove" } %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="absolute -top-2 -right-2">
|
||||
<%= lucide_icon "x", class: "w-5 h-5 p-0.5 hidden group-hover:inline-block border border-alpha-black-50 border-solid rounded-lg bg-white text-subdued cursor-pointer", data: { action: "click->element-removal#remove" } %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
<header class="flex items-center justify-between">
|
||||
<h1 class="text-primary text-xl font-medium"><%= t(".tags") %></h1>
|
||||
|
||||
<%= link_to new_tag_path, class: "btn btn--primary flex items-center gap-1 justify-center", data: { turbo_frame: :modal } do %>
|
||||
<%= lucide_icon "plus", class: "w-5 h-5" %>
|
||||
<p><%= t(".new") %></p>
|
||||
<% end %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".new"),
|
||||
variant: "primary",
|
||||
href: new_tag_path,
|
||||
leading_icon: "plus",
|
||||
data: { turbo_frame: :modal }
|
||||
) %>
|
||||
</header>
|
||||
|
||||
<div class="bg-container shadow-border-xs rounded-xl p-4">
|
||||
|
|
|
@ -50,12 +50,16 @@
|
|||
</div>
|
||||
|
||||
<div class="flex justify-end items-center gap-2">
|
||||
<%= link_to "Cancel", transactions_path, class: "btn btn--ghost" %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Cancel",
|
||||
variant: "ghost",
|
||||
href: transactions_path
|
||||
) %>
|
||||
|
||||
<%= tag.button "Save",
|
||||
type: "button",
|
||||
data: { "bulk-select-scope-param": "bulk_update", action: "bulk-select#submitBulkRequest" },
|
||||
class: "btn btn--primary" %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Save",
|
||||
data: { "bulk-select-scope-param": "bulk_update", action: "bulk-select#submitBulkRequest" }
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -5,8 +5,15 @@
|
|||
<div class="flex items-center gap-2">
|
||||
<%= contextual_menu do %>
|
||||
<% if Rails.env.development? %>
|
||||
<%= button_to "Dev only: Sync all", sync_all_accounts_path, class: "btn btn--ghost w-full" %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Dev only: Sync all",
|
||||
variant: "ghost",
|
||||
href: sync_all_accounts_path,
|
||||
method: :post,
|
||||
full_width: true
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<%= contextual_menu_item "New rule", url: new_rule_path(resource_type: "transaction"), icon: "plus", turbo_frame: :modal %>
|
||||
<%= contextual_menu_item "Edit rules", url: rules_path, icon: "git-branch", turbo_frame: :_top %>
|
||||
<%= contextual_menu_modal_action_item t(".edit_categories"), categories_path, icon: "shapes", turbo_frame: :_top %>
|
||||
|
@ -16,17 +23,30 @@
|
|||
<%= contextual_menu_modal_action_item t(".import"), new_import_path, icon: "download", turbo_frame: "modal", class_name: "md:!hidden" %>
|
||||
<% end %>
|
||||
|
||||
<%= link_to new_import_path, class: "btn btn--outline flex items-center gap-2 hidden md:flex", data: { turbo_frame: "modal" } do %>
|
||||
<%= lucide_icon("download", class: "text-secondary w-4 h-4") %>
|
||||
<p class="text-sm font-medium text-primary"><%= t(".import") %></p>
|
||||
<% end %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".import"),
|
||||
leading_icon: "download",
|
||||
variant: "outline",
|
||||
href: new_import_path,
|
||||
data: { turbo_frame: :modal },
|
||||
class: "hidden md:flex"
|
||||
) %>
|
||||
|
||||
<%= link_to new_transaction_path, class: "btn btn--primary flex items-center justify-center gap-2 rounded-full md:rounded-lg w-9 h-9 md:w-auto md:h-auto", data: { turbo_frame: :modal } do %>
|
||||
<span class="flex items-center justify-center">
|
||||
<%= lucide_icon("plus", class: "w-5 h-5") %>
|
||||
</span>
|
||||
<p class="text-sm font-medium hidden md:block">New transaction</p>
|
||||
<% end %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "New transaction",
|
||||
leading_icon: "plus",
|
||||
href: new_transaction_path,
|
||||
data: { turbo_frame: :modal },
|
||||
class: "hidden md:flex"
|
||||
) %>
|
||||
|
||||
<%= render ButtonComponent.new(
|
||||
icon: "plus",
|
||||
variant: "icon-inverse",
|
||||
href: new_transaction_path,
|
||||
data: { turbo_frame: :modal },
|
||||
class: "md:hidden !rounded-full"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
@ -17,10 +17,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div data-controller="menu" class="relative">
|
||||
<button id="transaction-filters-button" data-menu-target="button" type="button" class="btn btn--outline flex items-center gap-2">
|
||||
<%= lucide_icon("list-filter", class: "w-5 h-5 text-secondary") %>
|
||||
<p class="text-sm font-medium text-primary md:block hidden">Filter</p>
|
||||
</button>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Filter",
|
||||
leading_icon: "list-filter",
|
||||
variant: "outline",
|
||||
id: "transaction-filters-button",
|
||||
data: { menu_target: "button" }
|
||||
) %>
|
||||
|
||||
<%= render "transactions/searches/menu", form: form %>
|
||||
</div>
|
||||
|
|
|
@ -32,15 +32,17 @@
|
|||
<div class="flex justify-between items-center gap-2 bg-container p-3">
|
||||
<div>
|
||||
<% if @q.present? %>
|
||||
<%= link_to t(".clear_filters"), transactions_path(clear_filters: true), class: "btn btn--ghost" %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: t(".clear_filters"),
|
||||
variant: "ghost",
|
||||
href: transactions_path(clear_filters: true),
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= button_tag type: "reset", data: { action: "menu#close" }, class: "py-2 px-3 bg-container-inset rounded-lg text-sm text-primary font-medium" do %>
|
||||
<%= t(".cancel") %>
|
||||
<% end %>
|
||||
<%= form.submit t(".apply"), name: nil, class: "py-2 px-3 bg-primary hover:bg-primary-dark rounded-lg text-sm text-primary font-medium cursor-pointer" %>
|
||||
<%= render ButtonComponent.new(text: t(".cancel"), variant: "ghost", data: { action: "menu#close" }) %>
|
||||
<%= render ButtonComponent.new(text: t(".apply"), type: "submit") %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -125,10 +125,13 @@
|
|||
<p class="text-secondary">Transfers and payments are special types of transactions that indicate money movement between 2 accounts.</p>
|
||||
</div>
|
||||
|
||||
<%= link_to new_transaction_transfer_match_path(@entry), class: "btn btn--outline flex items-center gap-2", data: { turbo_frame: :modal } do %>
|
||||
<%= lucide_icon "arrow-left-right", class: "w-4 h-4 shrink-0" %>
|
||||
<span class="whitespace-nowrap">Open matcher</span>
|
||||
<% end %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Open matcher",
|
||||
leading_icon: "arrow-left-right",
|
||||
variant: "outline",
|
||||
href: new_transaction_transfer_match_path(@entry),
|
||||
data: { turbo_frame: :modal }
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<!-- Delete Transaction Form -->
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
<%# locals: (user:, placement: "right-start", offset: 16) %>
|
||||
|
||||
<div id="user-menu" data-controller="menu" data-menu-placement-value="<%= placement %>" data-menu-offset-value="<%= offset %>">
|
||||
<button data-menu-target="button">
|
||||
<div class="w-9 h-9 cursor-pointer">
|
||||
<%= render "settings/user_avatar", user: user, variant: :small %>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div data-menu-target="content" class="hidden absolute w-[276px] z-100 divide-y divide-alpha-black-100 bg-container rounded-xl shadow-border-sm">
|
||||
<%= 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", user: user, variant: :small, lazy: true %>
|
||||
<%= render "settings/user_avatar", avatar_url: user.profile_image&.variant(:small)&.url, lazy: true %>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden text-ellipsis text-sm">
|
||||
|
@ -22,7 +16,7 @@
|
|||
</div>
|
||||
|
||||
<% if self_hosted? %>
|
||||
<div class="p-3">
|
||||
<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" %>
|
||||
|
@ -33,41 +27,19 @@
|
|||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<div class="p-1">
|
||||
<%= link_to settings_profile_path(return_to: request.fullpath), class: "btn btn--ghost flex gap-2 items-center" do %>
|
||||
<%= lucide_icon("settings", class: "w-5 h-5 text-secondary shrink-0") %>
|
||||
<span class="text-sm">Settings</span>
|
||||
<% 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) %>
|
||||
|
||||
<%= link_to changelog_path, class: "btn btn--ghost flex gap-2 items-center" do %>
|
||||
<%= lucide_icon("box", class: "w-5 h-5 text-secondary shrink-0") %>
|
||||
<span class="text-sm">Changelog</span>
|
||||
<% end %>
|
||||
<% 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 %>
|
||||
|
||||
<%= link_to feedback_path, class: "btn btn--ghost flex gap-2 items-center" do %>
|
||||
<%= lucide_icon("megaphone", class: "w-5 h-5 text-secondary shrink-0") %>
|
||||
<span class="text-sm">Feedback</span>
|
||||
<% end %>
|
||||
<% menu.with_item(variant: "divider") %>
|
||||
|
||||
<% if self_hosted? %>
|
||||
<%= link_to "https://link.maybe.co/discord", class: "btn btn--ghost flex gap-2 items-center" do %>
|
||||
<%= lucide_icon("message-square-more", class: "w-5 h-5 text-secondary shrink-0") %>
|
||||
<span class="text-sm">Contact</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to "mailto:hello@maybefinance.com", class: "btn btn--ghost flex gap-2 items-center", onclick: "Intercom('showNewMessage'); return false;" do %>
|
||||
<%= lucide_icon("message-square-more", class: "w-5 h-5 text-secondary shrink-0") %>
|
||||
<span class="text-sm">Contact</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="p-1">
|
||||
<%= button_to session_path(Current.session), method: :delete, class: "btn btn--ghost text-destructive w-full flex gap-2 items-center" do %>
|
||||
<%= lucide_icon("log-out", class: "w-5 h-5 shrink-0") %>
|
||||
<span class="text-sm">Logout</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% menu.with_item(text: "Log out", icon: "log-out", href: session_path(Current.session), method: :delete) %>
|
||||
<% end %>
|
|
@ -33,5 +33,10 @@
|
|||
</div>
|
||||
|
||||
<div class="flex justify-center py-8">
|
||||
<%= link_to "Edit account details", edit_vehicle_path(account), class: "btn btn--ghost", data: { turbo_frame: :modal } %>
|
||||
<%= render ButtonComponent.new(
|
||||
text: "Edit account details",
|
||||
variant: "ghost",
|
||||
href: edit_vehicle_path(account),
|
||||
data: { turbo_frame: :modal }
|
||||
) %>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue