From 75aac689f52c3e987cf1390fc422c28c2ca610f5 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 10 Oct 2024 13:11:53 +0200 Subject: [PATCH] Add new users admin order history page This migrates the `users/:id/orders` page from the legacy soldius_backend to the new solidus_admin. --- .../users/addresses/component.rb | 2 +- .../solidus_admin/users/edit/component.rb | 5 +- .../users/orders/component.html.erb | 52 +++++++ .../solidus_admin/users/orders/component.rb | 145 ++++++++++++++++++ .../solidus_admin/users/orders/component.yml | 16 ++ .../solidus_admin/users_controller.rb | 16 +- admin/config/routes.rb | 1 + admin/spec/features/users_spec.rb | 45 ++++++ .../spec/requests/solidus_admin/users_spec.rb | 10 ++ 9 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 admin/app/components/solidus_admin/users/orders/component.html.erb create mode 100644 admin/app/components/solidus_admin/users/orders/component.rb create mode 100644 admin/app/components/solidus_admin/users/orders/component.yml diff --git a/admin/app/components/solidus_admin/users/addresses/component.rb b/admin/app/components/solidus_admin/users/addresses/component.rb index bc7665b3cc6..6116312b866 100644 --- a/admin/app/components/solidus_admin/users/addresses/component.rb +++ b/admin/app/components/solidus_admin/users/addresses/component.rb @@ -27,7 +27,7 @@ def tabs }, { text: t('.order_history'), - href: spree.orders_admin_user_path(@user), + href: solidus_admin.orders_user_path(@user), current: false, }, { diff --git a/admin/app/components/solidus_admin/users/edit/component.rb b/admin/app/components/solidus_admin/users/edit/component.rb index 1263af87cfd..19ff9adbc8e 100644 --- a/admin/app/components/solidus_admin/users/edit/component.rb +++ b/admin/app/components/solidus_admin/users/edit/component.rb @@ -25,9 +25,8 @@ def tabs }, { text: t('.order_history'), - href: spree.orders_admin_user_path(@user), - # @todo: update this "current" logic once folded into new admin - current: action_name != "edit", + href: solidus_admin.orders_user_path(@user), + current: action_name == "orders", }, { text: t('.items'), diff --git a/admin/app/components/solidus_admin/users/orders/component.html.erb b/admin/app/components/solidus_admin/users/orders/component.html.erb new file mode 100644 index 00000000000..a386d089b84 --- /dev/null +++ b/admin/app/components/solidus_admin/users/orders/component.html.erb @@ -0,0 +1,52 @@ +<%= page do %> + <%= page_header do %> + <%= page_header_back(solidus_admin.users_path) %> + <%= page_header_title(t(".title", email: @user.email)) %> + + <%= page_header_actions do %> + <%= render component("ui/button").new(tag: :a, text: t(".create_order_for_user"), href: spree.new_admin_order_path(user_id: @user.id)) %> + <% end %> + <% end %> + + <%= page_header do %> + <% tabs.each do |tab| %> + <%= render(component("ui/button").new(tag: :a, scheme: :ghost, text: tab[:text], 'aria-current': tab[:current], href: tab[:href])) %> + <% end %> + <% end %> + + <%= page_with_sidebar do %> + <%= page_with_sidebar_main do %> + <%= render component('ui/panel').new(title: t(".order_history")) do %> + <% if @orders.present? %> + <%= render component('ui/table').new( + id: stimulus_id, + data: { + class: model_class, + rows: rows, + fade: -> { row_fade(_1) }, + columns: columns, + url: -> { row_url(_1) }, + }, + )%> + <% else %> + <%= t(".no_orders_found") %> + <%= render component("ui/button").new(tag: :a, text: t(".create_one"), href: spree.new_admin_order_path(user_id: @user.id)) %> + <% end %> + <% end %> + <% end %> + + <%= page_with_sidebar_aside do %> + <%= render component("ui/panel").new(title: t("spree.lifetime_stats")) do %> + <%= render component("ui/details_list").new( + items: [ + { label: t("spree.total_sales"), value: @user.display_lifetime_value.to_html }, + { label: t("spree.order_count"), value: @user.order_count.to_i }, + { label: t("spree.average_order_value"), value: @user.display_average_order_value.to_html }, + { label: t("spree.member_since"), value: @user.created_at.to_date }, + { label: t(".last_active"), value: last_login(@user) }, + ] + ) %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/admin/app/components/solidus_admin/users/orders/component.rb b/admin/app/components/solidus_admin/users/orders/component.rb new file mode 100644 index 00000000000..ee57928111b --- /dev/null +++ b/admin/app/components/solidus_admin/users/orders/component.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +class SolidusAdmin::Users::Orders::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + + def initialize(user:, orders:) + @user = user + @orders = orders + end + + def form_id + @form_id ||= "#{stimulus_id}--form-#{@user.id}" + end + + def tabs + [ + { + text: t('.account'), + href: solidus_admin.user_path(@user), + current: false, + }, + { + text: t('.addresses'), + href: solidus_admin.addresses_user_path(@user), + current: false, + }, + { + text: t('.order_history'), + href: solidus_admin.orders_user_path(@user), + current: true, + }, + { + text: t('.items'), + href: spree.items_admin_user_path(@user), + current: false, + }, + { + text: t('.store_credit'), + href: spree.admin_user_store_credits_path(@user), + current: false, + }, + ] + end + + def last_login(user) + return t('.last_login.never') if user.try(:last_sign_in_at).blank? + + t( + '.last_login.login_time_ago', + # @note The second `.try` is only here for the specs to work. + last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) + ).capitalize + end + + def model_class + Spree::Order + end + + def row_url(order) + spree.edit_admin_order_path(order) + end + + def rows + @orders + end + + def row_fade(_order) + false + end + + def columns + [ + number_column, + state_column, + date_column, + payment_column, + shipment_column, + total_column, + ] + end + + def number_column + { + header: :order, + data: ->(order) do + if !row_fade(order) + content_tag :div, order.number, class: 'font-semibold' + else + content_tag :div, order.number + end + end + } + end + + def state_column + { + header: :state, + data: ->(order) do + color = { + 'complete' => :green, + 'returned' => :red, + 'canceled' => :blue, + 'cart' => :graphite_light, + }[order.state] || :yellow + component('ui/badge').new(name: order.state.humanize, color: color) + end + } + end + + def date_column + { + header: :date, + data: ->(order) do + content_tag :div, l(order.created_at, format: :short) + end + } + end + + def total_column + { + header: :total, + data: ->(order) do + content_tag :div, number_to_currency(order.total) + end + } + end + + def payment_column + { + header: :payment, + data: ->(order) do + component('ui/badge').new(name: order.payment_state.humanize, color: order.paid? ? :green : :yellow) if order.payment_state? + end + } + end + + def shipment_column + { + header: :shipment, + data: ->(order) do + component('ui/badge').new(name: order.shipment_state.humanize, color: order.shipped? ? :green : :yellow) if order.shipment_state? + end + } + end +end diff --git a/admin/app/components/solidus_admin/users/orders/component.yml b/admin/app/components/solidus_admin/users/orders/component.yml new file mode 100644 index 00000000000..6c4b6f1368a --- /dev/null +++ b/admin/app/components/solidus_admin/users/orders/component.yml @@ -0,0 +1,16 @@ +en: + title: "Users / %{email} / Order History" + account: Account + addresses: Addresses + order_history: Order History + items: Items + store_credit: Store Credit + last_active: Last Active + last_login: + login_time_ago: "%{last_login_time} ago" + never: Never + invitation_sent: Invitation sent + create_order_for_user: Create order for this user + no_orders_found: No Orders found. + create_one: Create One + back: Back diff --git a/admin/app/controllers/solidus_admin/users_controller.rb b/admin/app/controllers/solidus_admin/users_controller.rb index 59ac12d44c2..8b1a7858598 100644 --- a/admin/app/controllers/solidus_admin/users_controller.rb +++ b/admin/app/controllers/solidus_admin/users_controller.rb @@ -5,7 +5,7 @@ class UsersController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search include Spree::Core::ControllerHelpers::StrongParameters - before_action :set_user, only: [:edit, :addresses, :update_addresses] + before_action :set_user, only: [:edit, :addresses, :update_addresses, :orders] search_scope(:all, default: true) search_scope(:customers) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) } @@ -50,6 +50,14 @@ def update_addresses end end + def orders + set_orders + + respond_to do |format| + format.html { render component('users/orders').new(user: @user, orders: @orders) } + end + end + def edit respond_to do |format| format.html { render component('users/edit').new(user: @user) } @@ -93,6 +101,12 @@ def set_address_from_params end end + def set_orders + params[:q] ||= {} + @search = Spree::Order.reverse_chronological.ransack(params[:q].merge(user_id_eq: @user.id)) + @orders = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page]) + end + def authorization_subject Spree.user_class end diff --git a/admin/config/routes.rb b/admin/config/routes.rb index ac4693d32d5..d33b23d60cc 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -49,6 +49,7 @@ member do get :addresses put :update_addresses + get :orders end end diff --git a/admin/spec/features/users_spec.rb b/admin/spec/features/users_spec.rb index 5884e3bcc25..0a4f450c984 100644 --- a/admin/spec/features/users_spec.rb +++ b/admin/spec/features/users_spec.rb @@ -161,4 +161,49 @@ expect(page).to have_field("user[ship_address_attributes][name]", with: "Elrond") end end + + context "when viewing a user's order history" do + context "when a user has no orders" do + before do + create(:user, email: "customer@example.com") + visit "/admin/users" + find_row("customer@example.com").click + click_on "Order History" + end + + it "shows the order history page" do + expect(page).to have_content("Users / customer@example.com / Order History") + expect(page).to have_content("Lifetime Stats") + expect(page).to have_content("Order History") + expect(page).to be_axe_clean + end + + it "shows the appropriate content" do + expect(page).to have_content("No Orders found.") + end + end + + context "when a user has ordered before" do + before do + create(:user, :with_orders, email: "loyal_customer@example.com") + visit "/admin/users" + find_row("loyal_customer@example.com").click + click_on "Order History" + end + + it "shows the order history page" do + expect(page).to have_content("Users / loyal_customer@example.com / Order History") + expect(page).to have_content("Lifetime Stats") + expect(page).to have_content("Order History") + expect(page).to be_axe_clean + end + + it "shows the order history" do + expect(page).to have_content(/R\d+/) # Matches on any order number. + expect(page).to have_content("Shipment") + expect(page).to have_content("Payment") + expect(page).not_to have_content("No Orders found.") + end + end + end end diff --git a/admin/spec/requests/solidus_admin/users_spec.rb b/admin/spec/requests/solidus_admin/users_spec.rb index 36847fd639f..882342189b7 100644 --- a/admin/spec/requests/solidus_admin/users_spec.rb +++ b/admin/spec/requests/solidus_admin/users_spec.rb @@ -59,6 +59,16 @@ end end + describe "GET /orders" do + let!(:order) { create(:order, user: user) } + + it "renders the orders template and displays the user's orders" do + get solidus_admin.orders_user_path(user) + expect(response).to have_http_status(:ok) + expect(response.body).to include(order.number) + end + end + describe "DELETE /destroy" do it "deletes the user and redirects to the index page with a 303 See Other status" do # Ensure the user exists prior to deletion