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