diff --git a/app/furniture/marketplace/bazaar.rb b/app/furniture/marketplace/bazaar.rb index 2f4362604..eee8c4763 100644 --- a/app/furniture/marketplace/bazaar.rb +++ b/app/furniture/marketplace/bazaar.rb @@ -4,6 +4,7 @@ class Marketplace class Bazaar < ::Space has_many :marketplaces, through: :rooms, source: :gizmos, inverse_of: :bazaar, class_name: "Marketplace" has_many :tax_rates, inverse_of: :bazaar, dependent: :destroy + has_many :tags, inverse_of: :bazaar, dependent: :destroy def space becomes(Space) diff --git a/app/furniture/marketplace/breadcrumbs.rb b/app/furniture/marketplace/breadcrumbs.rb index c26cb14ab..b09a1e3ff 100644 --- a/app/furniture/marketplace/breadcrumbs.rb +++ b/app/furniture/marketplace/breadcrumbs.rb @@ -104,6 +104,16 @@ link t("marketplace.stripe_accounts.show.link_to"), marketplace.location(child: :stripe_account) end +crumb :marketplace_tags do |marketplace| + parent :edit_marketplace, marketplace + link(t("marketplace.tags.index.link_to"), marketplace.location(child: :tags)) +end + +crumb :new_marketplace_tag do |tag| + parent :marketplace_tags, tag.marketplace + link t("marketplace.tags.new.link_to"), marketplace.location(:new, child: :tag) +end + crumb :marketplace_tax_rates do |marketplace| parent :edit_marketplace, marketplace link t("marketplace.tax_rates.index.link_to"), marketplace.location(child: :tax_rates) diff --git a/app/furniture/marketplace/locales/en.yml b/app/furniture/marketplace/locales/en.yml index a8c0aa18a..4da9047a0 100644 --- a/app/furniture/marketplace/locales/en.yml +++ b/app/furniture/marketplace/locales/en.yml @@ -136,3 +136,8 @@ en: payment_settings: index: link_to: "Payment Settings" + tags: + index: + link_to: "Tags" + new: + link_to: "Add Tag" diff --git a/app/furniture/marketplace/management_component.html.erb b/app/furniture/marketplace/management_component.html.erb index 5c7b32a85..76d802a9a 100644 --- a/app/furniture/marketplace/management_component.html.erb +++ b/app/furniture/marketplace/management_component.html.erb @@ -21,6 +21,7 @@ <%= link_to_child(:notification_methods, icon: :bell_alert) if policy(marketplace.notification_methods).index? %> <%= link_to_child(:flyer, icon: :qr_code) if policy(marketplace.flyer).show? %> <%= link_to_child(:vendor_representatives, icon: :building_storefront) if policy(marketplace.vendor_representatives).index? %> + <%= link_to_child(:tags, icon: :tag) if policy(marketplace.tags).index? %> <% end %> <% end %> diff --git a/app/furniture/marketplace/marketplace.rb b/app/furniture/marketplace/marketplace.rb index 0cd4a250a..1031296b2 100644 --- a/app/furniture/marketplace/marketplace.rb +++ b/app/furniture/marketplace/marketplace.rb @@ -9,6 +9,8 @@ class Marketplace < Furniture # @todo replace with through :bazaar has_many :tax_rates, inverse_of: :marketplace + has_many :tags, through: :bazaar + has_many :products, inverse_of: :marketplace, dependent: :destroy has_many :carts, inverse_of: :marketplace, dependent: :destroy has_many :orders, inverse_of: :marketplace diff --git a/app/furniture/marketplace/policy.rb b/app/furniture/marketplace/policy.rb index bbad7025c..f0912237f 100644 --- a/app/furniture/marketplace/policy.rb +++ b/app/furniture/marketplace/policy.rb @@ -2,7 +2,7 @@ class Marketplace class Policy < ApplicationPolicy def create? return true if current_person.operator? - return true if current_person.member_of?(marketplace.space) + return true if current_person.member_of?(space) return true if shopper&.person.blank? && !current_person.authenticated? @@ -23,6 +23,7 @@ def marketplace object.marketplace if object.respond_to?(:marketplace) end + delegate :space, to: :marketplace module SpecFactories def self.included(spec) diff --git a/app/furniture/marketplace/product.rb b/app/furniture/marketplace/product.rb index 93e2dd6d2..be06f3411 100644 --- a/app/furniture/marketplace/product.rb +++ b/app/furniture/marketplace/product.rb @@ -26,6 +26,9 @@ class Product < Record has_many :ordered_products, inverse_of: :product, dependent: :destroy has_many :orders, -> { checked_out }, through: :ordered_products, inverse_of: :products + has_many :product_tags, inverse_of: :product, dependent: :destroy + has_many :tags, through: :product_tags, inverse_of: :products + has_many :product_tax_rates, inverse_of: :product, dependent: :destroy has_many :tax_rates, through: :product_tax_rates, inverse_of: :products diff --git a/app/furniture/marketplace/product/title_component.html.erb b/app/furniture/marketplace/product/title_component.html.erb index 9f21e96af..b5626e5b9 100644 --- a/app/furniture/marketplace/product/title_component.html.erb +++ b/app/furniture/marketplace/product/title_component.html.erb @@ -2,3 +2,7 @@ <%- if servings.present? %>

Serves <%= servings %>

<%- end %> + +<%- if product.tags.present? %> +

(<%= product.tags.pluck(:label).join(", ") %>)

+<%- end %> diff --git a/app/furniture/marketplace/product_policy.rb b/app/furniture/marketplace/product_policy.rb index ef63c0979..3b8d09636 100644 --- a/app/furniture/marketplace/product_policy.rb +++ b/app/furniture/marketplace/product_policy.rb @@ -4,7 +4,7 @@ class Marketplace class ProductPolicy < Policy alias_method :product, :object def permitted_attributes(_params = nil) - %i[name description price_cents price_currency price photo restore servings] + [tax_rate_ids: []] + %i[name description price_cents price_currency price photo restore servings] + [tag_ids: [], tax_rate_ids: []] end def update? diff --git a/app/furniture/marketplace/product_tag.rb b/app/furniture/marketplace/product_tag.rb new file mode 100644 index 000000000..368ddacd3 --- /dev/null +++ b/app/furniture/marketplace/product_tag.rb @@ -0,0 +1,8 @@ +class Marketplace + class ProductTag < Record + self.table_name = :marketplace_product_tags + + belongs_to :product, inverse_of: :product_tags + belongs_to :tag, inverse_of: :product_tags + end +end diff --git a/app/furniture/marketplace/products/_form.html.erb b/app/furniture/marketplace/products/_form.html.erb index c4e3f705b..4a38ab9db 100644 --- a/app/furniture/marketplace/products/_form.html.erb +++ b/app/furniture/marketplace/products/_form.html.erb @@ -5,6 +5,8 @@ <%= render "number_field", { attribute: :servings, form: f, min: 0, step: 1} %> <%= render "money_field", { attribute: :price, form: f, min: 0, step: 0.01} %> + <%= render "collection_check_boxes", { attribute: :tag_ids, collection: bazaar.tags, value_method: :id, + text_method: :label, form: f} %> <%= render "collection_check_boxes", { attribute: :tax_rate_ids, collection: bazaar.tax_rates, value_method: :id, text_method: :label, form: f} %> <%- if product.photo.present? %>
diff --git a/app/furniture/marketplace/routes.rb b/app/furniture/marketplace/routes.rb index d8bb9061d..52ae8e6e0 100644 --- a/app/furniture/marketplace/routes.rb +++ b/app/furniture/marketplace/routes.rb @@ -16,6 +16,7 @@ def self.append_routes(router) router.resources :products router.resource :stripe_account, only: [:show, :new, :create] router.resources :stripe_events + router.resources :tags router.resources :tax_rates router.resources :vendor_representatives router.resources :payment_settings, only: [:index] diff --git a/app/furniture/marketplace/tag.rb b/app/furniture/marketplace/tag.rb new file mode 100644 index 000000000..709a893fd --- /dev/null +++ b/app/furniture/marketplace/tag.rb @@ -0,0 +1,12 @@ +class Marketplace + class Tag < Record + self.table_name = "marketplace_tags" + + belongs_to :bazaar, inverse_of: :tags + has_many :product_tags, inverse_of: :tag, dependent: :destroy + has_many :products, through: :product_tags, inverse_of: :tags + + attr_accessor :marketplace + location(parent: :marketplace) + end +end diff --git a/app/furniture/marketplace/tag_policy.rb b/app/furniture/marketplace/tag_policy.rb new file mode 100644 index 000000000..cd43c1be4 --- /dev/null +++ b/app/furniture/marketplace/tag_policy.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class Marketplace + class TagPolicy < Policy + alias_method :tag, :object + def space + tag.bazaar + end + + def permitted_attributes(_params = nil) + %i[label] + end + + def update? + return false unless current_person.authenticated? + + super + end + + alias_method :create?, :update? + + def show? + true + end + + class Scope < ApplicationScope + def resolve + scope.all + end + end + end +end diff --git a/app/furniture/marketplace/tags/_form.html.erb b/app/furniture/marketplace/tags/_form.html.erb new file mode 100644 index 000000000..acd3ab69e --- /dev/null +++ b/app/furniture/marketplace/tags/_form.html.erb @@ -0,0 +1,6 @@ +<%= form_with(model: tag.location) do |form| %> + +<%= render "text_field", attribute: :label, form: form %> + +<%= form.submit %> +<%- end %> diff --git a/app/furniture/marketplace/tags/index.html.erb b/app/furniture/marketplace/tags/index.html.erb new file mode 100644 index 000000000..61832ff4c --- /dev/null +++ b/app/furniture/marketplace/tags/index.html.erb @@ -0,0 +1,20 @@ +<%- breadcrumb :marketplace_tags, marketplace %> + +<%= render CardComponent.new do |card| %> + + <%- marketplace.tags.each do |tag| %> + <%- tag.marketplace = marketplace %> +
+ + <%= tag.label %> + +
+ <%- end %> + + <%- card.with_footer(variant: :action_bar) do %> + <%- new_tag = bazaar.tags.new %> + <%- if policy(new_tag).create? %> + <%= link_to t("marketplace.tags.new.link_to"), marketplace.location(:new, child: :tag), class: "button w-full" %> + <%- end %> + <%- end %> +<%- end %> diff --git a/app/furniture/marketplace/tags/new.html.erb b/app/furniture/marketplace/tags/new.html.erb new file mode 100644 index 000000000..d5e9383ef --- /dev/null +++ b/app/furniture/marketplace/tags/new.html.erb @@ -0,0 +1,2 @@ +<%- breadcrumb :new_marketplace_tag, mtag %> +<%= render "form", tag: mtag %> diff --git a/app/furniture/marketplace/tags_controller.rb b/app/furniture/marketplace/tags_controller.rb new file mode 100644 index 000000000..8e25ffb8a --- /dev/null +++ b/app/furniture/marketplace/tags_controller.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Marketplace + class TagsController < Controller + # Apparently, `tag` is used inside of `turbo_frame_tag`, so if we define a + # helper_method named `tag` our method gets called when it really shouldn't + # be... so `mtag` it is. For now. + expose :mtag, scope: -> { tags }, model: Tag + expose :tags, -> { policy_scope(bazaar.tags.create_with(marketplace: marketplace)) } + + def new + authorize(mtag) + end + + def create + if authorize(mtag).save + redirect_to marketplace.location(child: :tags) + else + render :new + end + end + + def index + skip_authorization + end + + def mtag_params + policy(Tag).permit(params.require(:tag)) + end + end +end diff --git a/db/migrate/20240208025354_create_marketplace_product_tags.rb b/db/migrate/20240208025354_create_marketplace_product_tags.rb new file mode 100644 index 000000000..4e1fcfaad --- /dev/null +++ b/db/migrate/20240208025354_create_marketplace_product_tags.rb @@ -0,0 +1,17 @@ +class CreateMarketplaceProductTags < ActiveRecord::Migration[7.1] + def change + create_table :marketplace_tags, id: :uuid do |t| + t.references :bazaar, type: :uuid + t.string :label + + t.timestamps + end + + create_table :marketplace_product_tags, id: :uuid do |t| + t.references :product, type: :uuid, foreign_key: {to_table: :marketplace_products} + t.references :tag, type: :uuid, foreign_key: {to_table: :marketplace_tags} + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 378aa23c1..c1fdcaeb2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -202,6 +202,15 @@ t.index ["shopper_id"], name: "index_marketplace_orders_on_shopper_id" end + create_table "marketplace_product_tags", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "product_id" + t.uuid "tag_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["product_id"], name: "index_marketplace_product_tags_on_product_id" + t.index ["tag_id"], name: "index_marketplace_product_tags_on_tag_id" + end + create_table "marketplace_product_tax_rates", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "tax_rate_id" t.uuid "product_id" @@ -231,6 +240,14 @@ t.index ["person_id"], name: "index_marketplace_shoppers_on_person_id", unique: true end + create_table "marketplace_tags", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.uuid "bazaar_id" + t.string "label" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["bazaar_id"], name: "index_marketplace_tags_on_bazaar_id" + end + create_table "marketplace_tax_rates", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.float "tax_rate" t.string "label" @@ -337,6 +354,8 @@ add_foreign_key "marketplace_notification_methods", "furnitures", column: "marketplace_id" add_foreign_key "marketplace_orders", "marketplace_delivery_areas", column: "delivery_area_id" add_foreign_key "marketplace_orders", "marketplace_shoppers", column: "shopper_id" + add_foreign_key "marketplace_product_tags", "marketplace_products", column: "product_id" + add_foreign_key "marketplace_product_tags", "marketplace_tags", column: "tag_id" add_foreign_key "marketplace_product_tax_rates", "marketplace_products", column: "product_id" add_foreign_key "marketplace_product_tax_rates", "marketplace_tax_rates", column: "tax_rate_id" add_foreign_key "marketplace_products", "furnitures", column: "marketplace_id" diff --git a/spec/furniture/marketplace/product_tags_system_spec.rb b/spec/furniture/marketplace/product_tags_system_spec.rb index 1954ce1ab..a4b642844 100644 --- a/spec/furniture/marketplace/product_tags_system_spec.rb +++ b/spec/furniture/marketplace/product_tags_system_spec.rb @@ -25,6 +25,9 @@ click_link("⚙️ Edit") end + check("🚫🌾 Gluten Free") + click_button("Save") + visit(marketplace) within(muffins) do