From c58a65a52b7a51ffd487f99ebd1d1214c9e5391a Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 21 Jan 2026 16:27:20 +1100 Subject: [PATCH] Add a tab to list customer payment on the account page --- .../_customer_account_transactions.html.haml | 45 ++++++++++++ app/views/spree/users/show.html.haml | 11 +-- .../frontend_toggle_control_controller.js | 23 ++++++ config/locales/en.yml | 9 +++ ...frontend_toggle_control_controller_test.js | 71 +++++++++++++++++++ spec/models/customer_spec.rb | 2 +- 6 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 app/views/spree/users/_customer_account_transactions.html.haml create mode 100644 app/webpacker/controllers/frontend_toggle_control_controller.js create mode 100644 spec/javascripts/stimulus/frontend_toggle_control_controller_test.js diff --git a/app/views/spree/users/_customer_account_transactions.html.haml b/app/views/spree/users/_customer_account_transactions.html.haml new file mode 100644 index 0000000000..bcce135486 --- /dev/null +++ b/app/views/spree/users/_customer_account_transactions.html.haml @@ -0,0 +1,45 @@ +%script{ type: "text/ng-template", id: "account/customer_account_transactions.html" } + .active_table.orders + %h3= t('.title') + - @shops.each do |shop| + - data_loader = CustomerAccountTransactions::DataLoaderService.new(user: @user, enterprise: shop) + %distributor.active_table_node.row.animate-repeat.closed.inactive{ "data-controller": "frontend-toggle-control", "data-frontend-toggle-control-selector-value": "#transaction-list-#{shop.id}", "data-frontend-toggle-control-target": "classUpdate" } + .small-12.columns + .row.active_table_row.skinny-head.margin-top.closed{ "data-action": "click->frontend-toggle-control#toggleDisplay", "data-frontend-toggle-control-target": "classUpdate" } + .columns.small-2 + - if shop.logo_url(:medium) + = image_tag shop.logo_url(:medium), class: "margin-top account-logo" + - else + %i.ofn-i_059-producer + .columns.small-5 + %h3.margin-top + = link_to shop.name, enterprise_url_selector(shop) + .columns.small-4.text-right + %h3.margin-top.distributor-balance + = t(".credit_available", credit: Spree::Money.new(data_loader.available_credit)) + .columns.small-1.text-right + %h3.margin-top + %i.ofn-i_005-caret-down{ "data-frontend-toggle-control-target": "chevron" } + .row{ style: "display: none", id: "transaction-list-#{shop.id}" } + .columns.small-12.fat + %table + %tr + %th.order1= t(".transaction_date") + %th.order2= t(".description") + %th.order3= t(".payment_method") + %th.order4= t(".amount") + %th.order5= t(".running_balance") + %tbody.transaction-group + - data_loader.customer_account_transactions.each do |transaction| + %tr.transaction-row.even + %td.order1 + - # TODO move to helper + = transaction.updated_at.strftime("%Y-%m-%d") + %td.order2 + = transaction.description + %td.order3 + = t(transaction.payment_method.name.to_s) + %td.order4 + = Spree::Money.new(transaction.amount) + %td.order5 + = Spree::Money.new(transaction.balance) diff --git a/app/views/spree/users/show.html.haml b/app/views/spree/users/show.html.haml index 34c7260977..8150da81c1 100644 --- a/app/views/spree/users/show.html.haml +++ b/app/views/spree/users/show.html.haml @@ -19,18 +19,21 @@ = render 'orders' = render 'cards' = render 'transactions' + = render 'customer_account_transactions' = render 'settings' = render 'developer_settings' if @user.show_api_key_view .row.tabset-ctrl#account-tabs{ style: 'margin-bottom: 100px', navigate: 'true', selected: 'orders', prefix: 'account' } - .small.12.medium-2.columns.tab{ name: "orders" } + .small.12.medium-3.columns.tab{ name: "orders" } %a=t('.tabs.orders') - if Spree::Config.stripe_connect_enabled && Stripe.publishable_key - .small.12.medium-2.columns.tab{ name: "cards" } + .small.12.medium-3.columns.tab{ name: "cards" } %a=t('.tabs.cards') - .small.12.medium-2.columns.tab{ name: "transactions" } + .small.12.medium-3.columns.tab{ name: "transactions" } %a=t('.tabs.transactions') - .small.12.medium-2.columns.tab{ name: "settings" } + .small.12.medium-3.columns.tab{ name: "customer_account_transactions" } + %a=t('.tabs.customer_account_transactions') + .small.12.medium-3.columns.tab{ name: "settings" } %a=t('.tabs.settings') // the api_keys partial is the only content for now, so we have to hide the whole tab for now // if there is new content, we will need to handle this inside the developer_settings partial diff --git a/app/webpacker/controllers/frontend_toggle_control_controller.js b/app/webpacker/controllers/frontend_toggle_control_controller.js new file mode 100644 index 0000000000..68b246284c --- /dev/null +++ b/app/webpacker/controllers/frontend_toggle_control_controller.js @@ -0,0 +1,23 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ["chevron", "classUpdate"]; + static values = { selector: String }; + + toggleDisplay(_event) { + if (this.hasChevronTarget) { + this.chevronTarget.classList.toggle("ofn-i_005-caret-down"); + this.chevronTarget.classList.toggle("ofn-i_006-caret-up"); + } + + if (this.hasClassUpdateTarget) { + this.classUpdateTargets.forEach((t) => { + t.classList.toggle("closed"); + t.classList.toggle("open"); + }); + } + + const element = document.querySelector(this.selectorValue); + element.style.display = element.style.display === "none" ? "block" : "none"; + } +} diff --git a/config/locales/en.yml b/config/locales/en.yml index d53cc17866..e1cba89aff 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -5020,6 +5020,7 @@ en: orders: Orders cards: Credit Cards transactions: Transactions + customer_account_transactions: Customer Transactions settings: Account Settings unconfirmed_email: "Pending email confirmation for: %{unconfirmed_email}. Your email address will be updated once the new email is confirmed." orders: @@ -5029,6 +5030,14 @@ en: transaction_history: Transaction History authorisation_required: Authorisation Required authorise: Authorize + customer_account_transactions: + title: Customer Transactions + credit_available: "Credit available: %{credit}" + transaction_date: Transaction Date + payment_method: Payment method + description: Description + amount: Amount + running_balance: Running balance open_orders: order: Order shop: Shop diff --git a/spec/javascripts/stimulus/frontend_toggle_control_controller_test.js b/spec/javascripts/stimulus/frontend_toggle_control_controller_test.js new file mode 100644 index 0000000000..f1565694aa --- /dev/null +++ b/spec/javascripts/stimulus/frontend_toggle_control_controller_test.js @@ -0,0 +1,71 @@ +/** + * @jest-environment jsdom + */ + +import { Application } from "stimulus"; +import frontendToggleController from "controllers/frontend_toggle_control_controller"; + +describe("FrontendToggleControlController", () => { + beforeAll(() => { + const application = Application.start(); + application.register("frontend-toggle-control", frontendToggleController); + }); + + describe("#toggleDispay", () => { + beforeEach(() => { + document.body.innerHTML = ` +
+
+ + +
+
+
...
+ `; + }); + + it("switches the visibility of the element macthing the selector value", () => { + const button = document.getElementById("remote-toggle"); + const content = document.getElementById("content"); + expect(content.style.display).toBe(""); + + button.click(); + + expect(content.style.display).toBe("none"); + + button.click(); + + expect(content.style.display).toBe("block"); + }); + + it("switches the direction of the chevron icon", () => { + const button = document.getElementById("remote-toggle"); + const chevron = document.querySelector("i"); + + expect(chevron.className).toBe("ofn-i_005-caret-down"); + + button.click(); + + expect(chevron.className).toBe("ofn-i_006-caret-up"); + + button.click(); + + expect(chevron.className).toBe("ofn-i_005-caret-down"); + }); + + it("toggles the open/closed class on the matching targets", () => { + const button = document.getElementById("remote-toggle"); + const div = document.getElementById("class-update"); + + expect(div.className).toBe("closed"); + + button.click(); + + expect(div.className).toBe("open"); + + button.click(); + + expect(div.className).toBe("closed"); + }); + }); +}); diff --git a/spec/models/customer_spec.rb b/spec/models/customer_spec.rb index 1812717210..a43ddd650b 100644 --- a/spec/models/customer_spec.rb +++ b/spec/models/customer_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Customer do it { is_expected.to belong_to(:user).optional } it { is_expected.to belong_to(:bill_address).optional } it { is_expected.to belong_to(:ship_address).optional } - it { is_expected.to have_many(:customer_account_transactions) } + it { is_expected.to have_many(:customer_account_transactions).dependent(:restrict_with_error) } describe 'an existing customer' do let(:customer) { create(:customer) }