mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-10 07:55:21 +02:00
CTA message for rule when user changes transaction category
This commit is contained in:
parent
fe8008e5ed
commit
c5ef622849
24 changed files with 265 additions and 158 deletions
|
@ -3,6 +3,47 @@ class Account::TransactionsController < ApplicationController
|
|||
|
||||
permitted_entryable_attributes :id, :category_id, :merchant_id, { tag_ids: [] }
|
||||
|
||||
def update
|
||||
if @entry.update(update_entry_params)
|
||||
@entry.sync_account_later
|
||||
|
||||
if @entry.account_transaction.saved_change_to_category_id? && @entry.account_transaction.eligible_for_category_rule?
|
||||
flash[:cta] = {
|
||||
message: "Updated to #{@entry.account_transaction.category.name}",
|
||||
description: "You can create a rule to automatically categorize transactions like this one",
|
||||
accept_label: "Create rule",
|
||||
accept_href: new_rule_path(resource_type: "transaction"),
|
||||
accept_turbo_frame: "modal",
|
||||
decline_label: "Dismiss"
|
||||
}
|
||||
else
|
||||
flash[:notice] = "Transaction updated"
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_to account_path(@entry.account), notice: t("account.entries.update.success") }
|
||||
format.turbo_stream do
|
||||
items = [
|
||||
turbo_stream.replace(
|
||||
"header_account_entry_#{@entry.id}",
|
||||
partial: "account/transactions/header",
|
||||
locals: { entry: @entry }
|
||||
),
|
||||
turbo_stream.replace("account_entry_#{@entry.id}", partial: "account/entries/entry", locals: { entry: @entry })
|
||||
]
|
||||
|
||||
if flash[:cta].present?
|
||||
items << turbo_stream.replace("cta", partial: "shared/notifications/cta", locals: { cta: flash[:cta] })
|
||||
end
|
||||
|
||||
render turbo_stream: items
|
||||
end
|
||||
end
|
||||
else
|
||||
render :show, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def bulk_delete
|
||||
destroyed = Current.family.entries.destroy_by(id: bulk_delete_params[:entry_ids])
|
||||
destroyed.map(&:account).uniq.each(&:sync_later)
|
||||
|
|
|
@ -8,6 +8,11 @@ class ApplicationController < ActionController::Base
|
|||
before_action :set_default_chat
|
||||
|
||||
private
|
||||
def stream_redirect_back_or_to(path)
|
||||
redirect_target_url = request.referer || path
|
||||
render turbo_stream: turbo_stream.action(:redirect, redirect_target_url)
|
||||
end
|
||||
|
||||
def require_upgrade?
|
||||
return false if self_hosted?
|
||||
return false unless Current.session
|
||||
|
|
|
@ -2,6 +2,8 @@ module EntryableResource
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include StreamExtensions
|
||||
|
||||
before_action :set_entry, only: %i[show update destroy]
|
||||
end
|
||||
|
||||
|
@ -35,9 +37,7 @@ module EntryableResource
|
|||
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_to account_path(@entry.account) }
|
||||
|
||||
redirect_target_url = request.referer || account_path(@entry.account)
|
||||
format.turbo_stream { render turbo_stream: turbo_stream.action(:redirect, redirect_target_url) }
|
||||
format.turbo_stream { stream_redirect_back_or_to account_path(@entry.account) }
|
||||
end
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
|
|
11
app/controllers/concerns/stream_extensions.rb
Normal file
11
app/controllers/concerns/stream_extensions.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module StreamExtensions
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def stream_redirect_back_or_to(path, options = {})
|
||||
flash[:notice] = options[:notice] if options[:notice].present?
|
||||
flash[:alert] = options[:alert] if options[:alert].present?
|
||||
|
||||
redirect_target_url = request.referer || path
|
||||
render turbo_stream: turbo_stream.action(:redirect, redirect_target_url)
|
||||
end
|
||||
end
|
|
@ -1,41 +1,42 @@
|
|||
class RulesController < ApplicationController
|
||||
before_action :set_rule, only: [ :show, :edit, :update, :destroy ]
|
||||
include StreamExtensions
|
||||
|
||||
before_action :set_rule, only: [ :edit, :update, :destroy ]
|
||||
|
||||
def index
|
||||
@rules = Current.family.rules.order(created_at: :desc)
|
||||
render layout: "settings"
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def new
|
||||
@rule = Current.family.rules.build(
|
||||
resource_type: params[:resource_type] || "transaction",
|
||||
conditions: [
|
||||
Rule::Condition.new(condition_type: "transaction_name", operator: "like", value: "test")
|
||||
],
|
||||
actions: [
|
||||
Rule::Action.new(action_type: "set_transaction_category", value: Current.family.categories.first.id)
|
||||
]
|
||||
)
|
||||
|
||||
@template_condition = Rule::Condition.new(rule: @rule, condition_type: "transaction_name")
|
||||
@template_action = Rule::Action.new(rule: @rule, action_type: "set_transaction_category")
|
||||
@rule = Current.family.rules.build(resource_type: params[:resource_type] || "transaction")
|
||||
end
|
||||
|
||||
def create
|
||||
Current.family.rules.create!(rule_params)
|
||||
redirect_to rules_path
|
||||
@rule = Current.family.rules.build(rule_params)
|
||||
|
||||
if @rule.save
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_to rules_path, notice: "Rule created" }
|
||||
format.turbo_stream { stream_redirect_back_or_to rules_path, notice: "Rule created" }
|
||||
end
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@rule = Current.family.rules.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@rule.update!(rule_params)
|
||||
redirect_to rules_path
|
||||
if @rule.update(rule_params)
|
||||
respond_to do |format|
|
||||
format.html { redirect_back_or_to rules_path, notice: "Rule updated" }
|
||||
format.turbo_stream { stream_redirect_back_or_to rules_path, notice: "Rule updated" }
|
||||
end
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
@ -44,7 +45,6 @@ class RulesController < ApplicationController
|
|||
end
|
||||
|
||||
private
|
||||
|
||||
def set_rule
|
||||
@rule = Current.family.rules.find(params[:id])
|
||||
end
|
||||
|
@ -53,11 +53,11 @@ class RulesController < ApplicationController
|
|||
params.require(:rule).permit(
|
||||
:resource_type, :effective_date, :active,
|
||||
conditions_attributes: [
|
||||
:id, :condition_type, :operator, :value,
|
||||
sub_conditions_attributes: [ :id, :condition_type, :operator, :value ]
|
||||
:id, :condition_type, :operator, :value, :_destroy,
|
||||
sub_conditions_attributes: [ :id, :condition_type, :operator, :value, :_destroy ]
|
||||
],
|
||||
actions_attributes: [
|
||||
:id, :action_type, :value
|
||||
:id, :action_type, :value, :_destroy
|
||||
]
|
||||
)
|
||||
end
|
||||
|
|
|
@ -140,7 +140,7 @@ export default class extends Controller {
|
|||
}
|
||||
|
||||
#uniqueKey() {
|
||||
return `${Date.now()}_${Math.floor(Math.random() * 100000)}`;
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
#getConditionFilterDefinition(key) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Account::Transaction < ApplicationRecord
|
||||
include Account::Entryable, Transferable
|
||||
include Account::Entryable, Transferable, Ruleable
|
||||
|
||||
belongs_to :category, optional: true
|
||||
belongs_to :merchant, optional: true
|
||||
|
|
17
app/models/account/transaction/ruleable.rb
Normal file
17
app/models/account/transaction/ruleable.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
module Account::Transaction::Ruleable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def eligible_for_category_rule?
|
||||
rules.joins(:actions).where(
|
||||
actions: {
|
||||
action_type: "set_transaction_category",
|
||||
value: category_id
|
||||
}
|
||||
).empty?
|
||||
end
|
||||
|
||||
private
|
||||
def rules
|
||||
entry.account.family.rules
|
||||
end
|
||||
end
|
|
@ -11,85 +11,20 @@ class Rule < ApplicationRecord
|
|||
validates :resource_type, presence: true
|
||||
validate :no_nested_compound_conditions
|
||||
|
||||
class << self
|
||||
# def transaction_template
|
||||
# new(
|
||||
# resource_type: "transaction",
|
||||
# conditions: [
|
||||
# Condition.new(
|
||||
# condition_type: "transaction_name",
|
||||
# operator: "=",
|
||||
# value: nil
|
||||
# )
|
||||
# ]
|
||||
# )
|
||||
# end
|
||||
# Every rule must have at least 1 condition + action
|
||||
validate :min_conditions_and_actions
|
||||
validate :no_duplicate_actions
|
||||
|
||||
def transaction_template
|
||||
new(
|
||||
resource_type: "transaction",
|
||||
conditions: [
|
||||
Condition.new(
|
||||
condition_type: "transaction_name",
|
||||
operator: "=",
|
||||
value: nil
|
||||
),
|
||||
Condition.new(
|
||||
condition_type: "compound",
|
||||
operator: "or",
|
||||
value: nil,
|
||||
sub_conditions: [
|
||||
Condition.new(
|
||||
condition_type: "transaction_name",
|
||||
operator: "like",
|
||||
value: nil
|
||||
),
|
||||
Condition.new(
|
||||
condition_type: "transaction_name",
|
||||
operator: "like",
|
||||
value: nil
|
||||
),
|
||||
Condition.new(
|
||||
condition_type: "compound",
|
||||
operator: "and",
|
||||
value: nil,
|
||||
sub_conditions: [
|
||||
Condition.new(
|
||||
condition_type: "transaction_amount",
|
||||
operator: ">",
|
||||
value: nil
|
||||
),
|
||||
Condition.new(
|
||||
condition_type: "transaction_amount",
|
||||
operator: "<",
|
||||
value: nil
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
Condition.new(
|
||||
condition_type: "transaction_name",
|
||||
operator: "=",
|
||||
value: nil
|
||||
)
|
||||
],
|
||||
actions: [
|
||||
Action.new(
|
||||
action_type: "set_category",
|
||||
value: nil
|
||||
)
|
||||
]
|
||||
)
|
||||
end
|
||||
def action_executors
|
||||
registry.action_executors
|
||||
end
|
||||
|
||||
def action_types
|
||||
registry.action_executors.map { |option| [ option.label, option.key ] }
|
||||
def condition_filters
|
||||
registry.condition_filters
|
||||
end
|
||||
|
||||
def condition_types
|
||||
registry.condition_filters.map { |option| [ option.label, option.key ] }
|
||||
def title
|
||||
"Test title"
|
||||
end
|
||||
|
||||
def registry
|
||||
|
@ -127,6 +62,22 @@ class Rule < ApplicationRecord
|
|||
end
|
||||
|
||||
private
|
||||
def min_conditions_and_actions
|
||||
if conditions.reject(&:marked_for_destruction?).empty?
|
||||
errors.add(:conditions, "must have at least one condition")
|
||||
end
|
||||
|
||||
if actions.reject(&:marked_for_destruction?).empty?
|
||||
errors.add(:actions, "must have at least one action")
|
||||
end
|
||||
end
|
||||
|
||||
def no_duplicate_actions
|
||||
action_types = actions.map(&:action_type)
|
||||
|
||||
errors.add(:base, "Rule cannot have duplicate actions #{action_types.inspect}") if action_types.uniq.count != action_types.count
|
||||
end
|
||||
|
||||
# Validation: To keep rules simple and easy to understand, we don't allow nested compound conditions.
|
||||
def no_nested_compound_conditions
|
||||
return true if conditions.none? { |condition| condition.compound? }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<%= drawer(reload_on_close: true) do %>
|
||||
<%= drawer do %>
|
||||
<%= render "account/transactions/header", entry: @entry %>
|
||||
|
||||
<div class="space-y-2">
|
||||
|
|
|
@ -32,14 +32,7 @@
|
|||
</p>
|
||||
|
||||
<% unless account.scheduled_for_deletion? %>
|
||||
<%= form_with model: account,
|
||||
namespace: account.id,
|
||||
data: { controller: "auto-submit-form", turbo_frame: "_top" } do |form| %>
|
||||
<div class="relative inline-block select-none">
|
||||
<%= form.check_box :is_active, { class: "sr-only peer", data: { "auto-submit-form-target": "auto" } } %>
|
||||
<%= form.label :is_active, " ".html_safe, class: "switch" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render "shared/toggle_form", model: account, attribute: :is_active, turbo_frame: "_top" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
<div id="notification-tray" class="space-y-1 w-full">
|
||||
<%= render_flash_notifications %>
|
||||
|
||||
<div id="cta"></div>
|
||||
|
||||
<% if Current.family&.syncing? %>
|
||||
<% render "shared/notifications/loading", id: "syncing-notice", message: "Syncing accounts data..." %>
|
||||
<% end %>
|
||||
|
|
|
@ -5,9 +5,15 @@
|
|||
|
||||
<li data-rule-target="action">
|
||||
<%= form.hidden_field :_destroy, value: false, data: { rule_target: "destroyField" } %>
|
||||
<%= form.select :action_type, rule.action_types, {}, data: { action: "rule#handleActionTypeChange" } %>
|
||||
<span>to</span>
|
||||
<%= form.select :value, action.options, {}, data: { rule_target: "valueField" } %>
|
||||
<%= form.select :action_type, rule.action_executors.map { |executor| [ executor.label, executor.key ] }, {}, data: { action: "rule#handleActionTypeChange" } %>
|
||||
|
||||
<% if action.executor.type == "select" %>
|
||||
<span>to</span>
|
||||
<%= form.select :value, action.options, {}, data: { rule_target: "valueField" } %>
|
||||
<% else %>
|
||||
<%= form.hidden_field :value, data: { rule_target: "valueField" } %>
|
||||
<% end %>
|
||||
|
||||
<button type="button"
|
||||
data-action="rule#removeAction"
|
||||
data-rule-destroy-param="<%= action.persisted? %>">
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<li data-rule-target="condition">
|
||||
<%= form.hidden_field :_destroy, value: false, data: { rule_target: "destroyField" } %>
|
||||
<%= form.select :condition_type, rule.condition_types, {}, data: { action: "rule#handleConditionTypeChange" } %>
|
||||
<%= form.select :condition_type, rule.condition_filters.map { |filter| [ filter.label, filter.key ] }, {}, data: { action: "rule#handleConditionTypeChange" } %>
|
||||
<%= form.select :operator, condition.operators, {}, data: { rule_target: "operatorField" } %>
|
||||
|
||||
<% if condition.filter.type == "select" %>
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
<%# locals: (rule:) %>
|
||||
|
||||
<%= form_with model: rule,
|
||||
class: "p-4 space-y-8 min-w-[600px]",
|
||||
data: {
|
||||
controller: "rule",
|
||||
rule_registry_value: rule.registry.to_json
|
||||
} do |f| %>
|
||||
class: "p-4 space-y-8 min-w-[600px]",
|
||||
data: {
|
||||
controller: "rule",
|
||||
rule_registry_value: rule.registry.to_json
|
||||
} do |f| %>
|
||||
|
||||
<%= f.hidden_field :resource_type, value: rule.resource_type %>
|
||||
|
||||
<% if @rule.errors.any? %>
|
||||
<%= render "shared/form_errors", model: @rule %>
|
||||
<% end %>
|
||||
|
||||
<section class="space-y-4">
|
||||
<h3 class="mb-4 font-bold">Conditions</h3>
|
||||
<hr>
|
||||
|
||||
<template data-rule-target="newConditionTemplate">
|
||||
<%= f.fields_for :conditions, @template_condition, child_index: "IDX_PLACEHOLDER" do |cf| %>
|
||||
<%= f.fields_for :conditions, Rule::Condition.new(rule: rule, condition_type: rule.condition_filters.first.key), child_index: "IDX_PLACEHOLDER" do |cf| %>
|
||||
<%= render "rule/conditions/condition_group", form: cf %>
|
||||
<% end %>
|
||||
</template>
|
||||
|
@ -35,7 +39,7 @@
|
|||
<hr>
|
||||
|
||||
<template data-rule-target="newActionTemplate">
|
||||
<%= f.fields_for :actions, @template_action, child_index: "IDX_PLACEHOLDER" do |af| %>
|
||||
<%= f.fields_for :actions, Rule::Action.new(rule: rule, action_type: rule.action_executors.first.key), child_index: "IDX_PLACEHOLDER" do |af| %>
|
||||
<%= render "rule/actions/action", form: af %>
|
||||
<% end %>
|
||||
</template>
|
||||
|
|
33
app/views/rules/_rule.html.erb
Normal file
33
app/views/rules/_rule.html.erb
Normal file
|
@ -0,0 +1,33 @@
|
|||
<%# locals: (rule:) %>
|
||||
|
||||
<div class="flex justify-between items-center gap-4 bg-white shadow-border-xs rounded-md p-4">
|
||||
<div class="text-sm space-y-1.5">
|
||||
|
||||
<p class="flex items-center flex-wrap gap-1.5">
|
||||
<span class="px-2 py-1 border border-alpha-black-200 rounded-full">
|
||||
<%= rule.actions.first.executor.label %>
|
||||
</span>
|
||||
|
||||
<% if rule.actions.count > 1 %>
|
||||
and <%= rule.actions.count - 1 %> more <%= rule.actions.count - 1 == 1 ? "action" : "actions" %>
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
<p class="flex items-center flex-wrap gap-1.5">
|
||||
<% if rule.effective_date.nil? %>
|
||||
To all past and future <%= rule.resource_type.pluralize %>
|
||||
<% else %>
|
||||
To all <%= rule.resource_type.pluralize %> on or after <%= rule.effective_date %>
|
||||
<% end %>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-4">
|
||||
<%= render "shared/toggle_form", model: rule, attribute: :active %>
|
||||
|
||||
<%= contextual_menu icon: "more-vertical", id: "chat-menu" do %>
|
||||
<%= contextual_menu_item "Edit", url: edit_rule_path(rule), icon: "pencil", turbo_frame: "modal" %>
|
||||
<%= contextual_menu_destructive_item "Delete", rule_path(rule), turbo_confirm: "Are you sure you want to delete this rule?" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
|
@ -1,4 +1,7 @@
|
|||
<%= link_to "Back to rules", rules_path %>
|
||||
<h2>Edit <%= @rule.resource_type %> rule</h2>
|
||||
|
||||
<%= render "rules/form", rule: @rule %>
|
||||
<%= modal do %>
|
||||
<h2>Edit <%= @rule.resource_type %> rule</h2>
|
||||
|
||||
<%= render "rules/form", rule: @rule %>
|
||||
<% end %>
|
||||
|
|
|
@ -1,12 +1,37 @@
|
|||
<% content_for :page_title, "Rules" %>
|
||||
<header class="flex items-center justify-between">
|
||||
<h1 class="text-primary text-xl font-medium">Rules</h1>
|
||||
|
||||
<div>
|
||||
<%= link_to "New rule", new_rule_path %>
|
||||
<ul>
|
||||
<% @rules.each do |rule| %>
|
||||
<li>
|
||||
<%= link_to rule.id, edit_rule_path(rule) %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<%= 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 %>
|
||||
</header>
|
||||
|
||||
<div class="bg-white shadow-border-xs rounded-xl p-4">
|
||||
<% if @rules.any? %>
|
||||
<div class="rounded-xl bg-gray-25 space-y-1">
|
||||
<div class="flex items-center gap-1.5 px-4 py-2 text-xs font-medium text-secondary uppercase">
|
||||
<p>Rules</p>
|
||||
<span class="text-subdued">·</span>
|
||||
<p><%= @rules.count %></p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1 p-1">
|
||||
<%= render @rules %>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="flex justify-center items-center py-20">
|
||||
<div class="text-center flex flex-col items-center max-w-[500px]">
|
||||
<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 %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
<%= link_to "Back to rules", rules_path %>
|
||||
<h2>New <%= @rule.resource_type %> rule</h2>
|
||||
|
||||
<%= render "rules/form", rule: @rule %>
|
||||
<%= modal do %>
|
||||
<h2>New <%= @rule.resource_type %> rule</h2>
|
||||
|
||||
<%= render "rules/form", rule: @rule %>
|
||||
<% end %>
|
11
app/views/shared/_toggle_form.html.erb
Normal file
11
app/views/shared/_toggle_form.html.erb
Normal file
|
@ -0,0 +1,11 @@
|
|||
<%# locals: (model:, attribute:, turbo_frame: nil) %>
|
||||
|
||||
<%= form_with model: model,
|
||||
namespace: model.id,
|
||||
class: "flex items-center",
|
||||
data: { controller: "auto-submit-form", turbo_frame: turbo_frame } do |form| %>
|
||||
<div class="relative inline-block select-none">
|
||||
<%= form.check_box attribute, { class: "sr-only peer", data: { "auto-submit-form-target": "auto" } } %>
|
||||
<%= form.label attribute, " ".html_safe, class: "switch" %>
|
||||
</div>
|
||||
<% end %>
|
|
@ -1,21 +1,23 @@
|
|||
<%# locals: (cta:) %>
|
||||
|
||||
<%= tag.div class: "relative flex gap-3 rounded-lg bg-white p-4 group max-w-80 shadow-border-xs", data: { controller: "element-removal" } do %>
|
||||
<div class="h-5 w-5 shrink-0 p-px text-white">
|
||||
<div class="flex h-full items-center justify-center rounded-full bg-success">
|
||||
<%= lucide_icon "check", class: "w-3 h-3" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-1">
|
||||
<%= tag.p cta[:message], class: "text-primary text-sm font-medium" %>
|
||||
<%= tag.p cta[:description], class: "text-secondary text-sm" %>
|
||||
<div id="cta">
|
||||
<%= tag.div class: "relative flex gap-3 rounded-lg bg-white p-4 group max-w-80 shadow-border-xs", data: { controller: "element-removal" } do %>
|
||||
<div class="h-5 w-5 shrink-0 p-px text-white">
|
||||
<div class="flex h-full items-center justify-center rounded-full bg-success">
|
||||
<%= lucide_icon "check", class: "w-3 h-3" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= 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 %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-1">
|
||||
<%= tag.p cta[:message], class: "text-primary text-sm font-medium" %>
|
||||
<%= tag.p cta[:description], class: "text-secondary text-sm" %>
|
||||
</div>
|
||||
|
||||
<%= 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", data: { turbo_frame: cta[:accept_turbo_frame] || "_top" } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
|
@ -143,7 +143,7 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resources :rules do
|
||||
resources :rules, except: :show do
|
||||
resources :triggers, only: :new
|
||||
resources :actions, only: :new
|
||||
end
|
||||
|
|
|
@ -23,7 +23,7 @@ class CreateRules < ActiveRecord::Migration[7.2]
|
|||
t.references :rule, null: false, foreign_key: true, type: :uuid
|
||||
|
||||
t.string :action_type, null: false
|
||||
t.string :value, null: false
|
||||
t.string :value
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
|
2
db/schema.rb
generated
2
db/schema.rb
generated
|
@ -477,7 +477,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_04_03_110915) do
|
|||
create_table "rule_actions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||
t.uuid "rule_id", null: false
|
||||
t.string "action_type", null: false
|
||||
t.string "value", null: false
|
||||
t.string "value"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["rule_id"], name: "index_rule_actions_on_rule_id"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue