diff --git a/app/furniture/marketplace/breadcrumbs.rb b/app/furniture/marketplace/breadcrumbs.rb index d5472a309..74c21e21f 100644 --- a/app/furniture/marketplace/breadcrumbs.rb +++ b/app/furniture/marketplace/breadcrumbs.rb @@ -27,6 +27,23 @@ link t("marketplace.orders.index.link_to"), marketplace.location(child: :orders) end +crumb :marketplace_order_notification_methods do |marketplace| + parent :edit_marketplace, marketplace + link t("marketplace.order.notification_methods.index.link_to"), marketplace.location(child: :order_notification_methods) +end + +crumb :new_marketplace_order_notification_method do |order_notification_method| + parent :marketplace_order_notification_methods, order_notification_method.marketplace + link t("marketplace.order.notification_methods.index.link_to"), + order_notification_method.marketplace.location(child: :order_notification_methods) +end + +crumb :edit_marketplace_order_notification_method do |order_notification_method| + parent :marketplace_order_notification_methods, order_notification_method.marketplace + link t("marketplace.order.notification_methods.edit.link_to", contact_location: order_notification_method.contact_location) + order_notification_method.marketplace.location(child: :order_notification_methods) +end + crumb :marketplace_products do |marketplace| parent :edit_marketplace, marketplace link t("marketplace.products.index.link_to"), marketplace.location(child: :products) diff --git a/app/furniture/marketplace/locales/en.yml b/app/furniture/marketplace/locales/en.yml index 31b2631f1..0137ba428 100644 --- a/app/furniture/marketplace/locales/en.yml +++ b/app/furniture/marketplace/locales/en.yml @@ -1,6 +1,9 @@ en: activerecord: + attributes: + marketplace/order/notification_method: + contact_location: "Email Address" errors: models: marketplace/delivery: @@ -57,6 +60,17 @@ en: notification: subject: "Order Received for %{marketplace_name}: %{order_id}" placed_at: "Received At %{placed_at}" + notification_methods: + new: + link_to: "Add Order Notification" + index: + link_to: "Order Notifications" + edit: + link_to: "Edit Order Notification '%{contact_location}'" + update: + success: "Order Notification '%{contact_location}' Saved!" + destroy: + success: "Order Notification to '%{contact_location}' Removed!" orders: index: link_to: "Order History" diff --git a/app/furniture/marketplace/management_component.html.erb b/app/furniture/marketplace/management_component.html.erb index fac21c4a6..5e1732aa9 100644 --- a/app/furniture/marketplace/management_component.html.erb +++ b/app/furniture/marketplace/management_component.html.erb @@ -45,6 +45,14 @@ href: marketplace.location(child: :orders), turbo_stream: false, method: :get, scheme: :secondary ) if policy(marketplace.orders).index? %> + + + <%= render ButtonComponent.new( + label: t("marketplace.order.notification_methods.index.link_to"), + icon: :bell, + href: marketplace.location(child: :order_notification_methods), + method: :get, turbo_stream: false, scheme: :secondary + ) if policy(marketplace.order_notification_methods).index? %> <% end %> <% end %> diff --git a/app/furniture/marketplace/marketplace.rb b/app/furniture/marketplace/marketplace.rb index 9891838ff..850ac73fd 100644 --- a/app/furniture/marketplace/marketplace.rb +++ b/app/furniture/marketplace/marketplace.rb @@ -15,7 +15,7 @@ class Marketplace < Furniture has_many :delivery_areas, inverse_of: :marketplace, dependent: :destroy - has_many :order_notification_methods, inverse_of: :marketplace, dependent: :destroy + has_many :order_notification_methods, inverse_of: :marketplace, dependent: :destroy, class_name: "Order::NotificationMethod" setting :notify_emails setting :stripe_account diff --git a/app/furniture/marketplace/marketplaces/_form.html.erb b/app/furniture/marketplace/marketplaces/_form.html.erb deleted file mode 100644 index 0ae9d9546..000000000 --- a/app/furniture/marketplace/marketplaces/_form.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -
- <%= form_with model: marketplace.location do |f| %> - <%= render "text_field", { attribute: :notify_emails, form: f } %> - - <%= f.submit %> - <% end %> -
diff --git a/app/furniture/marketplace/marketplaces/edit.html.erb b/app/furniture/marketplace/marketplaces/edit.html.erb index c5a4fe856..3fb84e232 100644 --- a/app/furniture/marketplace/marketplaces/edit.html.erb +++ b/app/furniture/marketplace/marketplaces/edit.html.erb @@ -1,7 +1,6 @@ <%- breadcrumb :edit_marketplace, marketplace %> <%= render Marketplace::ManagementComponent.new(marketplace: marketplace) do %> - <%= render "form", marketplace: marketplace %>

<%- if marketplace.stripe_api_key? %> <%- if marketplace.stripe_account_connected? %> diff --git a/app/furniture/marketplace/order_notification_method.rb b/app/furniture/marketplace/order/notification_method.rb similarity index 87% rename from app/furniture/marketplace/order_notification_method.rb rename to app/furniture/marketplace/order/notification_method.rb index dd5fe3fa3..4f75f8e64 100644 --- a/app/furniture/marketplace/order_notification_method.rb +++ b/app/furniture/marketplace/order/notification_method.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Marketplace - class OrderNotificationMethod < Record + class Order::NotificationMethod < Record self.table_name = :marketplace_order_notification_methods location(parent: :marketplace) belongs_to :marketplace, inverse_of: :order_notification_methods diff --git a/app/furniture/marketplace/order/notification_method_component.html.erb b/app/furniture/marketplace/order/notification_method_component.html.erb new file mode 100644 index 000000000..87d94587d --- /dev/null +++ b/app/furniture/marketplace/order/notification_method_component.html.erb @@ -0,0 +1,11 @@ +<%= render CardComponent.new(dom_id: dom_id(notification_method), classes: "flex flex-col justify-between gap-y-2 w-full") do %> + +

+ <%= contact_location %> +
+ + +<%- end %> diff --git a/app/furniture/marketplace/order/notification_method_component.rb b/app/furniture/marketplace/order/notification_method_component.rb new file mode 100644 index 000000000..919d9df56 --- /dev/null +++ b/app/furniture/marketplace/order/notification_method_component.rb @@ -0,0 +1,38 @@ +class Marketplace + class Order + class NotificationMethodComponent < ApplicationComponent + attr_accessor :notification_method + delegate :contact_location, to: :notification_method + + def initialize(notification_method:, **kwargs) + super(**kwargs) + + self.notification_method = notification_method + end + + def edit_button + super(title: t("marketplace.order.notification_methods.edit.link_to", contact_location: contact_location), + href: notification_method.location(:edit)) + end + + def edit_button? + notification_method.persisted? && policy(notification_method).edit? + end + + def destroy_button + return unless destroy_button? + + ButtonComponent.new(label: "#{t("icons.destroy")} #{t("destroy.link_to")}", + title: t("marketplace.order.notification_methods.destroy.link_to", contact_location: contact_location), + href: notification_method.location, turbo_stream: true, + method: :delete, + confirm: t("destroy.confirm"), + scheme: :secondary) + end + + def destroy_button? + notification_method.persisted? && policy(notification_method).destroy? + end + end + end +end diff --git a/app/furniture/marketplace/order/notification_method_policy.rb b/app/furniture/marketplace/order/notification_method_policy.rb new file mode 100644 index 000000000..c43bf1e02 --- /dev/null +++ b/app/furniture/marketplace/order/notification_method_policy.rb @@ -0,0 +1,17 @@ +class Marketplace + class Order + class NotificationMethodPolicy < Policy + def permitted_attributes(_) + [:contact_location] + end + + class Scope < ApplicationScope + def resolve + return scope.all if person.operator? + + scope.joins(marketplace: [:room]).where(rooms: {space_id: person.spaces}) + end + end + end + end +end diff --git a/app/furniture/marketplace/order/notification_methods/_form.html.erb b/app/furniture/marketplace/order/notification_methods/_form.html.erb new file mode 100644 index 000000000..9bed31552 --- /dev/null +++ b/app/furniture/marketplace/order/notification_methods/_form.html.erb @@ -0,0 +1,6 @@ +<%= form_with(model: order_notification_method.location) do |form| %> + +<%= render "email_field", attribute: :contact_location, form: form %> +<%= form.submit %> + +<%- end %> diff --git a/app/furniture/marketplace/order/notification_methods/edit.html.erb b/app/furniture/marketplace/order/notification_methods/edit.html.erb new file mode 100644 index 000000000..d2e84a7e5 --- /dev/null +++ b/app/furniture/marketplace/order/notification_methods/edit.html.erb @@ -0,0 +1,2 @@ +<%- breadcrumb :edit_marketplace_order_notification_method, order_notification_method %> +<%= render "form", order_notification_method: order_notification_method %> diff --git a/app/furniture/marketplace/order/notification_methods/index.html.erb b/app/furniture/marketplace/order/notification_methods/index.html.erb new file mode 100644 index 000000000..c687545ef --- /dev/null +++ b/app/furniture/marketplace/order/notification_methods/index.html.erb @@ -0,0 +1,23 @@ +<%- breadcrumb(:marketplace_order_notification_methods, marketplace) %> + +<%= render Marketplace::ManagementComponent.new(marketplace: marketplace) do %> +
+
+ + <%= render Marketplace::Order::NotificationMethodComponent.with_collection(order_notification_methods) %> + +
+ +
+ <%- order_notification_methods = marketplace.order_notification_methods.new %> + <%- if policy(order_notification_methods).create? %> + <%= render ButtonComponent.new( + label: "#{t('marketplace.order.notification_methods.new.link_to')} #{t('icons.new')}", + title: t('marketplace.order.notification_methods.new.link_to'), + href: marketplace.location(:new, child: :order_notification_method), + method: :get, + scheme: :secondary) %> + <%- end %> +
+
+<% end %> diff --git a/app/furniture/marketplace/order/notification_methods/new.html.erb b/app/furniture/marketplace/order/notification_methods/new.html.erb new file mode 100644 index 000000000..b54ec183a --- /dev/null +++ b/app/furniture/marketplace/order/notification_methods/new.html.erb @@ -0,0 +1,2 @@ +<%- breadcrumb :new_marketplace_order_notification_method, order_notification_method %> +<%= render "form", order_notification_method: order_notification_method %> diff --git a/app/furniture/marketplace/order/notification_methods_controller.rb b/app/furniture/marketplace/order/notification_methods_controller.rb new file mode 100644 index 000000000..38683bbd9 --- /dev/null +++ b/app/furniture/marketplace/order/notification_methods_controller.rb @@ -0,0 +1,55 @@ +class Marketplace + class Order + class NotificationMethodsController < Controller + def new + order_notification_method + end + + def edit + order_notification_method + end + + def update + if order_notification_method.update(order_notification_method_params) + redirect_to marketplace.location(child: :order_notification_methods), notice: t(".success", contact_location: order_notification_method.contact_location) + else + render :edit, status: :unprocessable_entity + end + end + + def create + if order_notification_method.save + redirect_to marketplace.location(child: :order_notification_methods) + else + render :new, status: :unprocessable_entity + end + end + + def destroy + order_notification_method.destroy + + redirect_to marketplace.location(child: :order_notification_methods), notice: t(".success", contact_location: order_notification_method.contact_location) + end + + helper_method def order_notification_methods + @order_notification_methods ||= marketplace.order_notification_methods + end + + helper_method def order_notification_method + @order_notification_method ||= if params[:id] + order_notification_methods.find(params[:id]) + elsif params[:order_notification_method] + order_notification_methods.new(order_notification_method_params) + else + order_notification_methods.new + end.tap do |order_notification_method| + authorize(order_notification_method) + end + end + + def order_notification_method_params + policy(NotificationMethod).permit(params.require(:order_notification_method)) + end + end + end +end diff --git a/app/furniture/marketplace/routes.rb b/app/furniture/marketplace/routes.rb index ee3a0d55f..32b10f393 100644 --- a/app/furniture/marketplace/routes.rb +++ b/app/furniture/marketplace/routes.rb @@ -4,6 +4,8 @@ def self.append_routes(router) router.resources :marketplaces, only: [:show, :edit, :update], module: "marketplace" do router.resources :stripe_events + router.resources :order_notification_methods, controller: "order/notification_methods" + router.resources :carts, only: [] do router.resources :cart_products router.resource :checkout, only: [:show, :create] diff --git a/app/views/application/_email_field.html.erb b/app/views/application/_email_field.html.erb index 94e5f836f..8d8b5b80a 100644 --- a/app/views/application/_email_field.html.erb +++ b/app/views/application/_email_field.html.erb @@ -2,4 +2,4 @@ <%= form.label attribute %> <%= form.email_field attribute %> <%= render partial: "error", locals: { model: form.object, attribute: attribute } %> - \ No newline at end of file + diff --git a/spec/factories/furniture/marketplace.rb b/spec/factories/furniture/marketplace.rb index 93bba0ed2..5f3d7d31d 100644 --- a/spec/factories/furniture/marketplace.rb +++ b/spec/factories/furniture/marketplace.rb @@ -111,6 +111,11 @@ cart { association(:marketplace_cart, marketplace: marketplace) } end + factory :marketplace_order_notification_method, class: "Marketplace::Order::NotificationMethod" do + marketplace + contact_location { Faker::Internet.email } + end + factory :marketplace_order, class: "Marketplace::Order" do marketplace shopper { association(:marketplace_shopper) } diff --git a/spec/furniture/marketplace/order_notification_method_spec.rb b/spec/furniture/marketplace/order/notification_method_spec.rb similarity index 62% rename from spec/furniture/marketplace/order_notification_method_spec.rb rename to spec/furniture/marketplace/order/notification_method_spec.rb index bc7a720cd..757706ab2 100644 --- a/spec/furniture/marketplace/order_notification_method_spec.rb +++ b/spec/furniture/marketplace/order/notification_method_spec.rb @@ -1,5 +1,5 @@ require "rails_helper" -RSpec.describe Marketplace::OrderNotificationMethod, type: :model do +RSpec.describe Marketplace::Order::NotificationMethod, type: :model do it { is_expected.to belong_to(:marketplace).inverse_of(:order_notification_methods) } end diff --git a/spec/furniture/marketplace/order/notification_methods_controller_request_spec.rb b/spec/furniture/marketplace/order/notification_methods_controller_request_spec.rb new file mode 100644 index 000000000..a96d8bdd8 --- /dev/null +++ b/spec/furniture/marketplace/order/notification_methods_controller_request_spec.rb @@ -0,0 +1,87 @@ +require "rails_helper" + +RSpec.describe Marketplace::Order::NotificationMethodsController, type: :request do + let(:marketplace) { create(:marketplace) } + let(:space) { marketplace.space } + let(:room) { marketplace.room } + let(:member) { create(:membership, space: space).member } + let(:order_notification_method) { create(:marketplace_order_notification_method, marketplace: marketplace) } + + before do + sign_in(space, member) + end + + describe "#new" do + subject(:perform_request) do + get polymorphic_path(marketplace.location(:new, child: :order_notification_method)) + response + end + + it { is_expected.to have_rendered(:new) } + end + + describe "#create" do + subject(:perform_request) do + post polymorphic_path(marketplace.location(child: :order_notification_methods)), + params: {order_notification_method: order_notification_method_attributes} + response + end + + let(:order_notification_method_attributes) { attributes_for(:marketplace_order_notification_method) } + + specify { expect { perform_request }.to change(marketplace.order_notification_methods, :count).by(1) } + + describe "the created order notification" do + let(:created_order_notification_method) { marketplace.order_notification_methods.last } + + before { perform_request } + + specify { expect(created_order_notification_method.contact_method).to eql("email") } + specify { expect(created_order_notification_method.contact_location).to eql(order_notification_method_attributes[:contact_location]) } + end + + describe "when request is invalid" do + let(:order_notification_method_attributes) { {} } + + it { is_expected.to have_rendered(:new) } + end + end + + describe "#edit" do + subject(:perform_request) do + get polymorphic_path(order_notification_method.location(:edit)) + response + end + + it { is_expected.to have_rendered(:edit) } + end + + describe "#update" do + subject(:perform_request) do + put polymorphic_path(order_notification_method.location), + params: {order_notification_method: order_notification_method_attributes} + order_notification_method.reload + response + end + + let(:order_notification_method_attributes) { attributes_for(:marketplace_order_notification_method) } + + specify { expect { perform_request }.to change(order_notification_method, :contact_location).to order_notification_method_attributes[:contact_location] } + + it { is_expected.to redirect_to(polymorphic_path(marketplace.location(child: :order_notification_methods))) } + end + + describe "#destroy" do + subject(:perform_request) do + delete polymorphic_path(order_notification_method.location) + + response + end + + it "destroys the NotificationMethod" do + expect { perform_request && order_notification_method.reload }.to(raise_error(ActiveRecord::RecordNotFound)) + end + + it { is_expected.to redirect_to(polymorphic_path(marketplace.location(child: :order_notification_methods))) } + end +end