mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
feat: Allow account linking for Enable Banking accounts (#428)
* feat: Allow account linking for Enable Banking accounts * fix: Typo in function name * fix: naming issue * fix: Add missing Enable Banking route * feat: Add ability to link Enable Banking when adding a new account * Mispelling * fix: typo in method call * fix: typo in column name * Review suggestions * Linter noise * Small copy changes to avoid mobile UI blowout * Provider generator (#364) * Move provider config to family * Update schema.rb * Add provier generator * Add table creation also * FIX generator namespace * Add support for global providers also * Remove over-engineered stuff * FIX parser * FIX linter * Some generator fixes * Update generator with fixes * Update item_model.rb.tt * Add missing linkable concern * Add missing routes * Update adapter.rb.tt * Update connectable_concern.rb.tt * Update unlinking_concern.rb.tt * Update family_generator.rb * Update family_generator.rb * Delete .claude/settings.local.json Signed-off-by: soky srm <sokysrm@gmail.com> * Move docs under API related folder * Rename Rails generator doc * Light edits to LLM generated doc * Small Lunch Flow config panel regressions. --------- Signed-off-by: soky srm <sokysrm@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> * Skip generators autoloading (#430) * Include Enable Banking items in Syncer (#434) * feat: Include Enable Banking items in Syncer * feat: include only active Enable Banking accounts * Fix budgets page UI (#427) * fix: Budget UI improvements * feat: Reduce padding for sub-categories * fix: Adjust padding for sub-category arrow * Revert "feat: Reduce padding for sub-categories" This reverts commit7516c5a8e0. * Revert "fix: Adjust padding for sub-category arrow" This reverts commitebc82542cf. * fix: adjust padding for sub-categories * fix: Add padding to uncategorized budget * fix: Remove unnecessary HTML tag * feat: Add translation keys for budgeted/actual * feat(lang): add all brazilian portuguese translations (#416) * feat(lang): add all brazilian portuguese translations * feat: update pt-BR errors on translation * fix: atualizar fix base * feat: add reports translations * feat: finish translation to brazilian portuguese * fix: add to supported locales * fix: number of translations * fix: errors on translations * fix: error on rubocop lint --------- Co-authored-by: Leonardo Ralph <theleoralph@gmail.com> * Add exclude transaction rule action (#437) * Initial plan * Add ExcludeTransaction rule action executor with tests Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com> * Copy clarification --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> * Preparing for v0.6.6-alpha.3 Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> * fix: remove account_id clearing for Enable Banking accounts * fix: Remove unexisting available_balance attribute and rename variable for consistency --------- Signed-off-by: soky srm <sokysrm@gmail.com> Signed-off-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com> Co-authored-by: soky srm <sokysrm@gmail.com> Co-authored-by: Marcon Neves <marconwillian@icloud.com> Co-authored-by: Leonardo Ralph <theleoralph@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>
This commit is contained in:
94
app/controllers/concerns/enable_banking_items/maps_helper.rb
Normal file
94
app/controllers/concerns/enable_banking_items/maps_helper.rb
Normal file
@@ -0,0 +1,94 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module EnableBankingItems
|
||||
module MapsHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Build per-item maps consumed by the enable_banking_item partial.
|
||||
# Accepts a single EnableBankingItem or a collection.
|
||||
def build_enable_banking_maps_for(items)
|
||||
items = Array(items).compact
|
||||
return if items.empty?
|
||||
|
||||
@enable_banking_sync_stats_map ||= {}
|
||||
@enable_banking_has_unlinked_map ||= {}
|
||||
@enable_banking_unlinked_count_map ||= {}
|
||||
@enable_banking_duplicate_only_map ||= {}
|
||||
@enable_banking_show_relink_map ||= {}
|
||||
|
||||
# Batch-check if ANY family has manual accounts (same result for all items from same family)
|
||||
family_ids = items.map { |i| i.family_id }.uniq
|
||||
families_with_manuals = Account
|
||||
.visible_manual
|
||||
.where(family_id: family_ids)
|
||||
.distinct
|
||||
.pluck(:family_id)
|
||||
.to_set
|
||||
|
||||
# Batch-fetch unlinked counts for all items in one query
|
||||
unlinked_counts = EnableBankingAccount
|
||||
.where(enable_banking_item_id: items.map(&:id))
|
||||
.left_joins(:account, :account_provider)
|
||||
.where(accounts: { id: nil }, account_providers: { id: nil })
|
||||
.group(:enable_banking_item_id)
|
||||
.count
|
||||
|
||||
items.each do |item|
|
||||
# Latest sync stats (avoid N+1; rely on includes(:syncs) where appropriate)
|
||||
latest_sync = if item.syncs.loaded?
|
||||
item.syncs.max_by(&:created_at)
|
||||
else
|
||||
item.syncs.ordered.first
|
||||
end
|
||||
stats = (latest_sync&.sync_stats || {})
|
||||
@enable_banking_sync_stats_map[item.id] = stats
|
||||
|
||||
# Whether the family has any manual accounts available to link (from batch query)
|
||||
@enable_banking_has_unlinked_map[item.id] = families_with_manuals.include?(item.family_id)
|
||||
|
||||
# Count from batch query (defaults to 0 if not found)
|
||||
@enable_banking_unlinked_count_map[item.id] = unlinked_counts[item.id] || 0
|
||||
|
||||
# Whether all reported errors for this item are duplicate-account warnings
|
||||
@enable_banking_duplicate_only_map[item.id] = compute_duplicate_only_flag(stats)
|
||||
|
||||
# Compute CTA visibility: show relink only when there are zero unlinked SFAs,
|
||||
# there exist manual accounts to link, and the item has at least one SFA
|
||||
begin
|
||||
unlinked_count = @enable_banking_unlinked_count_map[item.id] || 0
|
||||
manuals_exist = @enable_banking_has_unlinked_map[item.id]
|
||||
sfa_any = if item.enable_banking_accounts.loaded?
|
||||
item.enable_banking_accounts.any?
|
||||
else
|
||||
item.enable_banking_accounts.exists?
|
||||
end
|
||||
@enable_banking_show_relink_map[item.id] = (unlinked_count.to_i == 0 && manuals_exist && sfa_any)
|
||||
rescue StandardError => e
|
||||
Rails.logger.warn("Enable Banking card: CTA computation failed for item #{item.id}: #{e.class} - #{e.message}")
|
||||
@enable_banking_show_relink_map[item.id] = false
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure maps are hashes even when items empty
|
||||
@enable_banking_sync_stats_map ||= {}
|
||||
@enable_banking_has_unlinked_map ||= {}
|
||||
@enable_banking_unlinked_count_map ||= {}
|
||||
@enable_banking_duplicate_only_map ||= {}
|
||||
@enable_banking_show_relink_map ||= {}
|
||||
end
|
||||
|
||||
private
|
||||
def compute_duplicate_only_flag(stats)
|
||||
errs = Array(stats && stats["errors"]).map do |e|
|
||||
if e.is_a?(Hash)
|
||||
e["message"] || e[:message]
|
||||
else
|
||||
e.to_s
|
||||
end
|
||||
end
|
||||
errs.present? && errs.all? { |m| m.to_s.downcase.include?("duplicate upstream account detected") }
|
||||
rescue
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,12 @@
|
||||
class EnableBankingItemsController < ApplicationController
|
||||
include EnableBankingItems::MapsHelper
|
||||
before_action :set_enable_banking_item, only: [ :update, :destroy, :sync, :select_bank, :authorize, :reauthorize, :setup_accounts, :complete_account_setup, :new_connection ]
|
||||
skip_before_action :verify_authenticity_token, only: [ :callback ]
|
||||
|
||||
def new
|
||||
@enable_banking_item = Current.family.enable_banking_items.build
|
||||
end
|
||||
|
||||
def create
|
||||
@enable_banking_item = Current.family.enable_banking_items.build(enable_banking_item_params)
|
||||
@enable_banking_item.name ||= "Enable Banking Connection"
|
||||
@@ -150,7 +155,7 @@ class EnableBankingItemsController < ApplicationController
|
||||
rescue Provider::EnableBanking::EnableBankingError => e
|
||||
if e.message.include?("REDIRECT_URI_NOT_ALLOWED")
|
||||
Rails.logger.error "Enable Banking redirect URI not allowed: #{e.message}"
|
||||
redirect_to settings_providers_path, alert: t(".redirect_uri_not_allowed", default: "Redirect not allowew. Configure `%{callback_url}` in your Enable Banking application settings.", callback_url: enable_banking_callback_url)
|
||||
redirect_to settings_providers_path, alert: t(".redirect_uri_not_allowed", default: "Redirect not allowed. Configure `%{callback_url}` in your Enable Banking application settings.", callback_url: enable_banking_callback_url)
|
||||
else
|
||||
Rails.logger.error "Enable Banking authorization error: #{e.message}"
|
||||
redirect_to settings_providers_path, alert: t(".authorization_failed", default: "Failed to start authorization: %{message}", message: e.message)
|
||||
@@ -403,6 +408,115 @@ class EnableBankingItemsController < ApplicationController
|
||||
redirect_to accounts_path, status: :see_other
|
||||
end
|
||||
|
||||
def select_existing_account
|
||||
@account = Current.family.accounts.find(params[:account_id])
|
||||
|
||||
# Filter out Enable Banking accounts that are already linked to any account
|
||||
# (either via account_provider or legacy account association)
|
||||
@available_enable_banking_accounts = Current.family.enable_banking_items
|
||||
.includes(:enable_banking_accounts)
|
||||
.flat_map(&:enable_banking_accounts)
|
||||
.reject { |sfa| sfa.account_provider.present? || sfa.account.present? }
|
||||
.sort_by { |sfa| sfa.updated_at || sfa.created_at }
|
||||
.reverse
|
||||
|
||||
# Always render a modal: either choices or a helpful empty-state
|
||||
render :select_existing_account, layout: false
|
||||
end
|
||||
|
||||
def link_existing_account
|
||||
@account = Current.family.accounts.find(params[:account_id])
|
||||
enable_banking_account = EnableBankingAccount.find(params[:enable_banking_account_id])
|
||||
|
||||
# Guard: only manual accounts can be linked (no existing provider links or legacy IDs)
|
||||
if @account.account_providers.any? || @account.plaid_account_id.present? || @account.simplefin_account_id.present?
|
||||
flash[:alert] = "Only manual accounts can be linked"
|
||||
if turbo_frame_request?
|
||||
return render turbo_stream: Array(flash_notification_stream_items)
|
||||
else
|
||||
return redirect_to account_path(@account), alert: flash[:alert]
|
||||
end
|
||||
end
|
||||
|
||||
# Verify the Enable Banking account belongs to this family's Enable Banking items
|
||||
unless enable_banking_account.enable_banking_item.present? &&
|
||||
Current.family.enable_banking_items.include?(enable_banking_account.enable_banking_item)
|
||||
flash[:alert] = "Invalid Enable Banking account selected"
|
||||
if turbo_frame_request?
|
||||
render turbo_stream: Array(flash_notification_stream_items)
|
||||
else
|
||||
redirect_to account_path(@account), alert: flash[:alert]
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Relink behavior: detach any legacy link and point provider link at the chosen account
|
||||
Account.transaction do
|
||||
enable_banking_account.lock!
|
||||
|
||||
# Upsert the AccountProvider mapping deterministically
|
||||
ap = AccountProvider.find_or_initialize_by(provider: enable_banking_account)
|
||||
previous_account = ap.account
|
||||
ap.account_id = @account.id
|
||||
ap.save!
|
||||
|
||||
# If the provider was previously linked to a different account in this family,
|
||||
# and that account is now orphaned, quietly disable it so it disappears from the
|
||||
# visible manual list. This mirrors the unified flow expectation that the provider
|
||||
# follows the chosen account.
|
||||
if previous_account && previous_account.id != @account.id && previous_account.family_id == @account.family_id
|
||||
begin
|
||||
previous_account.disable!
|
||||
rescue => e
|
||||
Rails.logger.warn("Failed to disable orphaned account #{previous_account.id}: #{e.class} - #{e.message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if turbo_frame_request?
|
||||
# Reload the item to ensure associations are fresh
|
||||
enable_banking_account.reload
|
||||
item = enable_banking_account.enable_banking_item
|
||||
item.reload
|
||||
|
||||
# Recompute data needed by Accounts#index partials
|
||||
@manual_accounts = Account.uncached {
|
||||
Current.family.accounts
|
||||
.visible_manual
|
||||
.order(:name)
|
||||
.to_a
|
||||
}
|
||||
@enable_banking_items = Current.family.enable_banking_items.ordered.includes(:syncs)
|
||||
build_enable_banking_maps_for(@enable_banking_items)
|
||||
|
||||
flash[:notice] = "Account successfully linked to Enable Banking"
|
||||
@account.reload
|
||||
manual_accounts_stream = if @manual_accounts.any?
|
||||
turbo_stream.update(
|
||||
"manual-accounts",
|
||||
partial: "accounts/index/manual_accounts",
|
||||
locals: { accounts: @manual_accounts }
|
||||
)
|
||||
else
|
||||
turbo_stream.replace("manual-accounts", view_context.tag.div(id: "manual-accounts"))
|
||||
end
|
||||
|
||||
render turbo_stream: [
|
||||
# Optimistic removal of the specific account row if it exists in the DOM
|
||||
turbo_stream.remove(ActionView::RecordIdentifier.dom_id(@account)),
|
||||
manual_accounts_stream,
|
||||
turbo_stream.replace(
|
||||
ActionView::RecordIdentifier.dom_id(item),
|
||||
partial: "enable_banking_items/enable_banking_item",
|
||||
locals: { enable_banking_item: item }
|
||||
),
|
||||
turbo_stream.replace("modal", view_context.turbo_frame_tag("modal"))
|
||||
] + Array(flash_notification_stream_items)
|
||||
else
|
||||
redirect_to accounts_path(cache_bust: SecureRandom.hex(6)), notice: "Account successfully linked to Enable Banking", status: :see_other
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_enable_banking_item
|
||||
|
||||
@@ -5,6 +5,33 @@ class Provider::EnableBankingAdapter < Provider::Base
|
||||
# Register this adapter with the factory
|
||||
Provider::Factory.register("EnableBankingAccount", self)
|
||||
|
||||
# Define which account types this provider supports
|
||||
def self.supported_account_types
|
||||
%w[Depository CreditCard]
|
||||
end
|
||||
|
||||
# Returns connection configurations for this provider
|
||||
def self.connection_configs(family:)
|
||||
return [] unless family.can_connect_enable_banking?
|
||||
|
||||
[ {
|
||||
key: "enable_banking",
|
||||
name: "Enable Banking",
|
||||
description: "Connect to your bank via Enable Banking",
|
||||
can_connect: true,
|
||||
new_account_path: ->(accountable_type, return_to) {
|
||||
Rails.application.routes.url_helpers.new_enable_banking_item_path(
|
||||
accountable_type: accountable_type
|
||||
)
|
||||
},
|
||||
existing_account_path: ->(account_id) {
|
||||
Rails.application.routes.url_helpers.select_existing_account_enable_banking_items_path(
|
||||
account_id: account_id
|
||||
)
|
||||
}
|
||||
} ]
|
||||
end
|
||||
|
||||
def provider_name
|
||||
"enable_banking"
|
||||
end
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<% elsif enable_banking_item.requires_update? %>
|
||||
<div class="text-warning flex items-center gap-1">
|
||||
<%= icon "alert-triangle", size: "sm", color: "warning" %>
|
||||
<%= tag.span "Re-authorization required" %>
|
||||
<%= tag.span "Reconnect" %>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="text-secondary">
|
||||
@@ -56,7 +56,7 @@
|
||||
class: "inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-lg text-white bg-warning hover:opacity-90 transition-colors",
|
||||
data: { turbo: false } do %>
|
||||
<%= icon "refresh-cw", size: "sm" %>
|
||||
Re-authorize
|
||||
Update
|
||||
<% end %>
|
||||
<% elsif Rails.env.development? %>
|
||||
<%= icon(
|
||||
|
||||
117
app/views/enable_banking_items/new.html.erb
Normal file
117
app/views/enable_banking_items/new.html.erb
Normal file
@@ -0,0 +1,117 @@
|
||||
<%= turbo_frame_tag "modal" do %>
|
||||
<%= render DS::Dialog.new do |dialog| %>
|
||||
<% dialog.with_header(title: t(".link_enable_banking_title")) %>
|
||||
<% dialog.with_body do %>
|
||||
<% items = local_assigns[:enable_banking_items] || @enable_banking_items || Current.family.enable_banking_items.where.not(client_certificate: nil) %>
|
||||
<% if items&.any? %>
|
||||
<%
|
||||
# Find the first item with valid session to use for "Add Connection" button
|
||||
item_for_new_connection = items.find(&:session_valid?)
|
||||
# Check if any item needs initial connection (configured but no session yet)
|
||||
item_needing_connection = items.find { |i| !i.session_valid? && !i.session_expired? }
|
||||
%>
|
||||
<div class="border-t border-primary pt-4 space-y-3">
|
||||
<% items.each do |item| %>
|
||||
<div class="flex items-center justify-between p-3 rounded-lg bg-container border border-primary">
|
||||
<div class="flex items-center gap-3">
|
||||
<% if item.session_valid? %>
|
||||
<div class="w-2 h-2 bg-success rounded-full"></div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-primary"><%= item.aspsp_name || "Connected Bank" %></p>
|
||||
<p class="text-xs text-secondary">
|
||||
Session expires: <%= item.session_expires_at&.strftime("%b %d, %Y") || "Unknown" %>
|
||||
</p>
|
||||
</div>
|
||||
<% elsif item.session_expired? %>
|
||||
<div class="w-2 h-2 bg-warning rounded-full"></div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-primary"><%= item.aspsp_name || "Connection" %></p>
|
||||
<p class="text-xs text-destructive">Session expired - re-authorization required</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="w-2 h-2 bg-secondary rounded-full"></div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-primary">Configured</p>
|
||||
<p class="text-xs text-secondary">Ready to connect a bank</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<% if item.session_valid? %>
|
||||
<%= button_to sync_enable_banking_item_path(item),
|
||||
method: :post,
|
||||
class: "inline-flex items-center justify-center rounded-lg px-3 py-1.5 text-xs font-medium text-primary bg-container border border-primary hover:bg-gray-50 transition-colors",
|
||||
data: { turbo: false } do %>
|
||||
Sync
|
||||
<% end %>
|
||||
<% elsif item.session_expired? %>
|
||||
<%= button_to reauthorize_enable_banking_item_path(item),
|
||||
method: :post,
|
||||
class: "inline-flex items-center justify-center rounded-lg px-3 py-1.5 text-xs font-medium text-white bg-warning hover:opacity-90 transition-colors",
|
||||
data: { turbo: false } do %>
|
||||
Reconnect
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to select_bank_enable_banking_item_path(item),
|
||||
class: "inline-flex items-center justify-center rounded-lg px-3 py-1.5 text-xs font-medium text-white bg-gray-900 hover:bg-gray-800 transition-colors",
|
||||
data: { turbo_frame: "modal" } do %>
|
||||
Connect Bank
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%= button_to enable_banking_item_path(item),
|
||||
method: :delete,
|
||||
class: "inline-flex items-center justify-center rounded-lg px-3 py-1.5 text-xs font-medium text-destructive hover:bg-destructive/10 transition-colors",
|
||||
data: { turbo_confirm: "Are you sure you want to remove this connection?" } do %>
|
||||
Remove
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%# Add Connection button below the list - only show if we have a valid session to copy credentials from %>
|
||||
<% if item_for_new_connection %>
|
||||
<div class="flex justify-center pt-2">
|
||||
<%= button_to new_connection_enable_banking_item_path(item_for_new_connection),
|
||||
method: :post,
|
||||
class: "inline-flex items-center gap-2 justify-center rounded-lg px-4 py-2 text-sm font-medium text-white bg-gray-900 hover:bg-gray-800 transition-colors",
|
||||
data: { turbo_frame: "modal" } do %>
|
||||
<%= icon "plus", size: "sm" %>
|
||||
Add Connection
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<%= icon("alert-circle", class: "text-warning w-5 h-5 shrink-0 mt-0.5") %>
|
||||
<div class="text-sm text-secondary">
|
||||
<p class="font-medium text-primary mb-2">Enable Banking connection not configured</p>
|
||||
<p>Before you can link Enable Banking accounts, you need to configure your Enable Banking connection.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-surface rounded-lg p-4 space-y-2 text-sm">
|
||||
<p class="font-medium text-primary">Setup Steps:</p>
|
||||
<ol class="list-decimal list-inside space-y-1 text-secondary">
|
||||
<li>Go to <strong>Settings → Bank Sync Providers</strong></li>
|
||||
<li>Find the <strong>Enable Banking</strong> section</li>
|
||||
<li>Enter your Enable Banking credentials</li>
|
||||
<li>Return here to link your accounts</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<%= link_to settings_providers_path,
|
||||
class: "w-full inline-flex items-center justify-center rounded-lg font-medium whitespace-nowrap rounded-lg hidden md:inline-flex px-3 py-2 text-sm text-inverse bg-inverse hover:bg-inverse-hover disabled:bg-gray-500 theme-dark:disabled:bg-gray-400",
|
||||
data: { turbo: false } do %>
|
||||
Go to Provider Settings
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,40 @@
|
||||
<%# Modal: Link an existing manual account to a Enable Banking account %>
|
||||
<%= turbo_frame_tag "modal" do %>
|
||||
<%= render DS::Dialog.new do |dialog| %>
|
||||
<% dialog.with_header(title: "Link Enable Banking account") %>
|
||||
|
||||
<% dialog.with_body do %>
|
||||
<% if @available_enable_banking_accounts.blank? %>
|
||||
<div class="p-4 text-sm text-secondary">
|
||||
<p class="mb-2">All Enable Banking accounts appear to be linked already.</p>
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
<li>If you just connected or synced, try again after the sync completes.</li>
|
||||
<li>To link a different account, first unlink it from the account’s actions menu.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= form_with url: link_existing_account_enable_banking_items_path, method: :post, class: "space-y-4" do %>
|
||||
<%= hidden_field_tag :account_id, @account.id %>
|
||||
<div class="space-y-2 max-h-64 overflow-auto">
|
||||
<% @available_enable_banking_accounts.each do |eba| %>
|
||||
<label class="flex items-center gap-3 p-2 rounded border border-surface-inset/50 hover:border-primary cursor-pointer">
|
||||
<%= radio_button_tag :enable_banking_account_id, eba.id, false %>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-sm text-primary font-medium"><%= eba.name.presence || eba.account_id %></span>
|
||||
<span class="text-xs text-secondary">
|
||||
<%= eba.currency %> • Balance: <%= number_to_currency((eba.current_balance || 0), unit: eba.currency) %>
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<%= render DS::Button.new(text: "Link", variant: :primary, icon: "link-2", type: :submit) %>
|
||||
<%= render DS::Link.new(text: "Cancel", variant: :secondary, href: accounts_path, data: { turbo_frame: "_top" }) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -127,13 +127,13 @@
|
||||
<div class="w-2 h-2 bg-warning rounded-full"></div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-primary"><%= item.aspsp_name || "Connection" %></p>
|
||||
<p class="text-xs text-destructive">Session expired - re-authorization required</p>
|
||||
<p class="text-xs text-destructive">Session expired - reconnect</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="w-2 h-2 bg-secondary rounded-full"></div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-primary">Configured</p>
|
||||
<p class="text-xs text-secondary">Ready to connect a bank</p>
|
||||
<p class="text-xs text-secondary">Ready to link accounts</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -151,7 +151,7 @@
|
||||
method: :post,
|
||||
class: "inline-flex items-center justify-center rounded-lg px-3 py-1.5 text-xs font-medium text-white bg-warning hover:opacity-90 transition-colors",
|
||||
data: { turbo: false } do %>
|
||||
Re-authorize
|
||||
Reconnect
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= link_to select_bank_enable_banking_item_path(item),
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
<%= icon "info", size: "sm", class: "text-primary mt-0.5 flex-shrink-0" %>
|
||||
<div>
|
||||
<p class="text-sm text-primary mb-2">
|
||||
<strong>Your SimpleFin connection needs to be updated:</strong>
|
||||
<strong>Your SimpleFIN connection needs to be updated:</strong>
|
||||
</p>
|
||||
<ol class="text-xs text-secondary space-y-1 list-decimal list-inside">
|
||||
<li>Visit <a href="https://bridge.simplefin.org/simplefin/create" target="_blank" rel="noopener noreferrer" class="text-link hover:text-link underline">SimpleFin Bridge</a> to create a new setup token</li>
|
||||
<li>Visit <a href="https://bridge.simplefin.org/simplefin/create" target="_blank" rel="noopener noreferrer" class="text-link hover:text-link underline">SimpleFIN Bridge</a> to create a new setup token</li>
|
||||
<li>Copy the token and paste it below</li>
|
||||
<li>Click "Update Connection" to restore access</li>
|
||||
<li>Click "Update" to restore access</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
<div class="flex gap-3">
|
||||
<%= render DS::Button.new(
|
||||
text: "Update Connection",
|
||||
text: "Update",
|
||||
variant: "primary",
|
||||
icon: "refresh-cw",
|
||||
type: "submit",
|
||||
|
||||
@@ -19,11 +19,11 @@ en:
|
||||
no_accounts_description: We could not load any accounts from this financial
|
||||
institution.
|
||||
no_accounts_title: No accounts found
|
||||
requires_update: Requires re-authentication
|
||||
requires_update: Reconnect
|
||||
status: Last synced %{timestamp} ago
|
||||
status_never: Requires data sync
|
||||
syncing: Syncing...
|
||||
update: Update connection
|
||||
update: Update
|
||||
select_existing_account:
|
||||
title: "Link %{account_name} to Plaid"
|
||||
description: Select a Plaid account to link to your existing account
|
||||
|
||||
@@ -44,7 +44,7 @@ en:
|
||||
error: Error occurred while syncing data
|
||||
no_accounts_description: This connection doesn't have any synchronized accounts yet.
|
||||
no_accounts_title: No accounts found
|
||||
requires_update: Requires re-authentication
|
||||
requires_update: Reconnect
|
||||
setup_needed: New accounts ready to set up
|
||||
setup_description: Choose account types for your newly imported SimpleFin accounts.
|
||||
setup_action: Set Up New Accounts
|
||||
@@ -52,7 +52,7 @@ en:
|
||||
status_never: Never synced
|
||||
status_with_summary: "Last synced %{timestamp} ago • %{summary}"
|
||||
syncing: Syncing...
|
||||
update: Update connection
|
||||
update: Update
|
||||
select_existing_account:
|
||||
title: "Link %{account_name} to SimpleFIN"
|
||||
description: Select a SimpleFIN account to link to your existing account
|
||||
|
||||
@@ -2,10 +2,12 @@ require "sidekiq/web"
|
||||
require "sidekiq/cron/web"
|
||||
|
||||
Rails.application.routes.draw do
|
||||
resources :enable_banking_items, only: [ :create, :update, :destroy ] do
|
||||
resources :enable_banking_items, only: [ :new, :create, :update, :destroy ] do
|
||||
collection do
|
||||
get :callback
|
||||
post :link_accounts
|
||||
get :select_existing_account
|
||||
post :link_existing_account
|
||||
end
|
||||
member do
|
||||
post :sync
|
||||
|
||||
Reference in New Issue
Block a user