diff --git a/.vscode/ltex.dictionary.en-US.txt b/.vscode/ltex.dictionary.en-US.txt new file mode 100644 index 000000000..52030509d --- /dev/null +++ b/.vscode/ltex.dictionary.en-US.txt @@ -0,0 +1 @@ +Piikup diff --git a/app/furniture/furniture.rb b/app/furniture/furniture.rb index 6ffab535f..203aaf951 100644 --- a/app/furniture/furniture.rb +++ b/app/furniture/furniture.rb @@ -9,6 +9,7 @@ module Furniture markdown_text_block: MarkdownTextBlock, video_bridge: VideoBridge, livestream: Livestream, + marketplace: Marketplace, embedded_form: EmbeddedForm, spotlight: Spotlight, }.freeze diff --git a/app/furniture/marketplace.rb b/app/furniture/marketplace.rb new file mode 100644 index 000000000..86681b8d5 --- /dev/null +++ b/app/furniture/marketplace.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +# Provids Vendors a location to sell their products +class Marketplace + include Placeable + + def self.append_routes(router) + router.namespace :marketplace do + router.resources :orders do + router.resources :items + router.resource :checkout + end + end + end + + def delivery_fee=(delivery_fee) + settings['delivery_fee'] = delivery_fee + end + + def delivery_fee + settings['delivery_fee'] + end + + def order_notification_email=(order_notification_email) + settings['order_notification_email'] = order_notification_email + end + + def order_notification_email + settings['order_notification_email'] + end + + def stripe_account=(stripe_account) + settings['stripe_account'] = stripe_account + end + + def stripe_account + settings['stripe_account'] + end + + def products + [{ name: "1lb of Bananas", price_cents: 1_39 }, { name: "32oz of Greek Yogurt", price_cents: 7_99 }].map do |product_attributes| + Marketplace::Product.new(product_attributes) + end + end + + def order + orders.last || Marketplace::Order.create(location:placement) + end + + def orders + Marketplace::Order.where(location: placement) + end + + def attribute_names + super + %w[delivery_fee order_notification_email stripe_account] + end +end diff --git a/app/furniture/marketplace/_form.html.erb b/app/furniture/marketplace/_form.html.erb new file mode 100644 index 000000000..10fb8a5ba --- /dev/null +++ b/app/furniture/marketplace/_form.html.erb @@ -0,0 +1,5 @@ +<%= form.fields_for(:furniture) do |furniture_fields| %> + <%= render "text_field", attribute: :stripe_account, form: furniture_fields %> + <%= render "text_field", attribute: :delivery_fee, form: furniture_fields %> + <%= render "email_field", attribute: :order_notification_email, form: furniture_fields %> +<%- end %> \ No newline at end of file diff --git a/app/furniture/marketplace/_in_room.html.erb b/app/furniture/marketplace/_in_room.html.erb new file mode 100644 index 000000000..f837a1614 --- /dev/null +++ b/app/furniture/marketplace/_in_room.html.erb @@ -0,0 +1,19 @@ +<%- marketplace = furniture %> +
+ <%- marketplace.products.each do |product| %> + +

<%= product.name %> (<%= product.price.format %>)

+ + <%= button_to "Add to Order", [space, room, :furniture, marketplace.order, :items], params: { marketplace_item: { product: product.name } }, method: :post %> + <%- end %> + +
+
+
+ +
+
+ + <%= link_to "Checkout", [space, room, :furniture, marketplace.order, :checkout], class: "button" %> +
+
\ No newline at end of file diff --git a/app/furniture/marketplace/checkout.rb b/app/furniture/marketplace/checkout.rb new file mode 100644 index 000000000..46cfdb6a6 --- /dev/null +++ b/app/furniture/marketplace/checkout.rb @@ -0,0 +1,4 @@ +class Marketplace::Checkout + include ActiveModel::Model + attr_accessor :order +end \ No newline at end of file diff --git a/app/furniture/marketplace/checkouts/_checkout.html.erb b/app/furniture/marketplace/checkouts/_checkout.html.erb new file mode 100644 index 000000000..89a3fb359 --- /dev/null +++ b/app/furniture/marketplace/checkouts/_checkout.html.erb @@ -0,0 +1,4 @@ +
+ +

BOOGA BOGOA

+
\ No newline at end of file diff --git a/app/furniture/marketplace/checkouts_controller.rb b/app/furniture/marketplace/checkouts_controller.rb new file mode 100644 index 000000000..1aef5dc2e --- /dev/null +++ b/app/furniture/marketplace/checkouts_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class Marketplace + class CheckoutsController < MarketplaceController + def show + render turbo_stream: turbo_stream.replace(dom_id(marketplace), checkout) + end + + helper_method def checkout + order.checkout + end + + helper_method def order + marketplace.orders.find(params[:order_id]) + end + end +end diff --git a/app/furniture/marketplace/item.rb b/app/furniture/marketplace/item.rb new file mode 100644 index 000000000..df930cdb0 --- /dev/null +++ b/app/furniture/marketplace/item.rb @@ -0,0 +1,23 @@ +class Marketplace::Item + include ActiveModel::Model + + def id + @id ||= SecureRandom.uuid + end + + def persisted? + true + end + + attr_accessor :product, :order + + delegate :price, to: :product + + def save + order.save + end + + def quantity + 1 + end +end \ No newline at end of file diff --git a/app/furniture/marketplace/item_policy.rb b/app/furniture/marketplace/item_policy.rb new file mode 100644 index 000000000..7cf9290a7 --- /dev/null +++ b/app/furniture/marketplace/item_policy.rb @@ -0,0 +1,7 @@ +class Marketplace::ItemPolicy < ApplicationPolicy + + + def permitted_attributes(_params) + %i[product] + end +end \ No newline at end of file diff --git a/app/furniture/marketplace/items/_item.html.erb b/app/furniture/marketplace/items/_item.html.erb new file mode 100644 index 000000000..f9022a95f --- /dev/null +++ b/app/furniture/marketplace/items/_item.html.erb @@ -0,0 +1,3 @@ +
+ <%= item.product.name %> x <%= item.quantity %> (<%= item.price.format %>) +
\ No newline at end of file diff --git a/app/furniture/marketplace/items_controller.rb b/app/furniture/marketplace/items_controller.rb new file mode 100644 index 000000000..8e2df8505 --- /dev/null +++ b/app/furniture/marketplace/items_controller.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class Marketplace + class ItemsController < MarketplaceController + def create + item.save + + render turbo_stream: turbo_stream + .append("#{dom_id(order)}-items", item) + end + + private def item_params + policy(items.new).permit(params.require(:marketplace_item)).merge(order: order, product: product) + end + + helper_method def item + @item ||= items.new(item_params) + end + + helper_method def items + @items ||= order.items + end + + helper_method def order + marketplace.orders.find(params[:order_id]) + end + + helper_method def product + marketplace.products.find { |p| p.name == params[:marketplace_item][:product] } + end + end +end diff --git a/app/furniture/marketplace/marketplace_controller.rb b/app/furniture/marketplace/marketplace_controller.rb new file mode 100644 index 000000000..d4030cf6a --- /dev/null +++ b/app/furniture/marketplace/marketplace_controller.rb @@ -0,0 +1,8 @@ +class Marketplace + class MarketplaceController < FurnitureController + # @returns [Marketplace] + helper_method def marketplace + room.furniture_placements.find_by(furniture_kind: 'marketplace').furniture + end + end +end \ No newline at end of file diff --git a/app/furniture/marketplace/order.rb b/app/furniture/marketplace/order.rb new file mode 100644 index 000000000..9b64f6536 --- /dev/null +++ b/app/furniture/marketplace/order.rb @@ -0,0 +1,13 @@ +class Marketplace::Order < Item + def items + Marketplace::Item + end + + def marketplace + furniture + end + + def checkout + Marketplace::Checkout.new(order: self) + end +end \ No newline at end of file diff --git a/app/furniture/marketplace/product.rb b/app/furniture/marketplace/product.rb new file mode 100644 index 000000000..2533b4132 --- /dev/null +++ b/app/furniture/marketplace/product.rb @@ -0,0 +1,8 @@ +class Marketplace::Product + include ActiveModel::Model + attr_accessor :name, :price_cents + + def price + Money.from_cents(price_cents, 'USD') + end +end diff --git a/features/furniture/delivery-order-form.feature.md b/features/furniture/delivery-order-form.feature.md deleted file mode 100644 index 796ed5776..000000000 --- a/features/furniture/delivery-order-form.feature.md +++ /dev/null @@ -1,45 +0,0 @@ -# Feature: Furniture: Delivery Order Form - -Local Caterers and Delivery Companies partner to bring affordable, high-quality, local food right to your doorstep! - -This piece of furniture is for the Delivery Company to provide folks who are near-by that a local caterer is setting up; -and if they want they can add their order and ?save some cash? (Need to check w/April re: most compelling hook). - -`@unimplemented-steps` `@unstarted` - -## Scenario: Offering a Delivery Order Form - -- Given a "Delivery Order Form" Furniture in the Entrance Hall to "Dev's Deliveries" Space -- And a Space Member "dev@example.com" for "Dev's Deliveries" Space -- And a Delivery Order Form Menu "Zee's Munchies Merchants" is in "Dev's Deliveries" Space with the following Menu Items: - | name | price | description | photo | - | A God Damn Pizza | $8.95 | It's a 12" margherita: Tomato's, Moz, Basil. WARNING: Does NOT travel well. No refunds. | | - | Some Mother Effin' Granola| $2.99 | Maple syrup, dried cranberries, cinnamon, cardamom, almonds, and oats. What more can you want?! | | -- When the following Delivery Order Form is opened in Dev's Deliveries" Space by the Space Member "dev@example.com": - | date | 2022-03-05 | - | delivery_window | 11AM to 1PM | - | menu | Zee's Munchies Merchants | -- Then a Delivery Order Form Order may be placed by Guests to the Entrance Hall to "Dev's Deliveries" Space - -`@unimplemented-steps` `@unstarted` - -## Scenario: Placing a Delivery Order Form Order - -- Given a "Delivery Order Form" Furniture in the Entrance Hall to "Dev's Deliveries" Space with the following configuration: - | order_email | orders@devs-deliveries.example.com | -- And a Delivery Order Form Menu "Zee's Munchies Merchants" is in "Dev's Deliveries" Space with the following Menu Items: - | name | price | description | photo | - | A God Damn Pizza | $8.95 | It's a 12" margherita: Tomato's, Moz, Basil. WARNING: Does NOT travel well. No refunds. | | - | Some Mother Effin' Granola| $2.99 | Maple syrup, dried cranberries, cinnamon, cardamom, almonds, and oats. What more can you want?! | | -- And the following Delivery Order Form is open in Dev's Deliveries" Space: - | name | Downtown Oakland! Get Your Munchies! | - | date | 2022-03-05 | - | delivery_window | 11AM to 1PM | - | menu | Zee's Munchies Merchants | -- When the following Delivery Order Form Order is placed by the Guest "a-rando@example.com" in "Dev's Deliveries" Space: - | popup | Downtown Oakland! Get Your Munchies! | - | item[] | { "name": "A God Damn Pizza", price: "$8.95" } | - | delivery_fee | $2.95 | - | deliver_to | "123 N West St, Oakland, CA 94612 | -- Then a Delivery Order Form Payment of $11.90 is collected from the Guest "A-rando@example.com" in "Dev's Deliveries" Space -- And the following Delivery Order Form Order Confirmation Email is sent to "orders@devs-deliveries.example.com" diff --git a/features/furniture/marketplace.feature.md b/features/furniture/marketplace.feature.md new file mode 100644 index 000000000..17f7da272 --- /dev/null +++ b/features/furniture/marketplace.feature.md @@ -0,0 +1,36 @@ +# Feature: Furniture: Marketplace! + +Whether you're a farmers market or other local distributor, transactions between +local vendors and the community is the cornerstone of a strong regional economy. + +A Marketplace connects producers, distributors and consumers in a single Space. +## Scenario: Place a Delivery Order +For now, our primary client is local owned and operated delivery +organizations, like [Piikup](https://piikup.com/) or +[Candlestick](https://www.candlestickcourier.com/). + +These organizations serve as distributors for regional vendors, closing the +last-mile and keeping money flowing within the community. + +- Given a "Piikup Marketplace" Space +- And a "Marketplace" Furniture in the Entrance Hall to "Piikup Marketplace" Space is configured with: + | delivery_fee | $6.99 | + | order_notification_email | orders@piikup.org | + | stripe_account | piikup-stripe-key | +- And a Marketplace Vendor "Mandela Grocery" in the "Piikup Marketplace" Space has: + | stripe_account | mandela-stripe-key | + | order_notification_email | orders@mandelagrocery.coop | +- And the Marketplace Vendor "Mandela Grocery" offers the following Products in the "Piikup Marketplace" Space + | name | price | + | 1lb of Bananas | $1.39 | + | 32oz of Greek Yogurt | $7.99 | +- When a Guest places a Delivery Marketplace Order in the "Piikup Marketplace" Space for: + | item | quantity | price | + | 1lb of Bananas | 4 | $5.56 | + | 32oz of Greek Yogurt | 1 | $7.99 | + | Delivery by Piikup | 1 | $6.99 | +- Then the Guest is charged $20.54 for their Marketplace Order +- And the Marketplace Order placed by the Guest in the "Piikup Marketplace" Space is delivered to "orders@piikup.org" +- And the Stripe Account "piikup-stripe-key" receives a Payment of $6.99 +- And the Marketplace Order placed by the Guest in the "Piikup Marketplace" Space is delivered to "orders@mandelagrocery.coop" +- And the Stripe Account "mandela-stripe-key" receives a Payment of $13.55 diff --git a/features/steps/furniture/marketplace_steps.js b/features/steps/furniture/marketplace_steps.js new file mode 100644 index 000000000..fb807c7b1 --- /dev/null +++ b/features/steps/furniture/marketplace_steps.js @@ -0,0 +1,31 @@ +import { Given, When, Then } from "@cucumber/cucumber"; +Given('{a} Marketplace Vendor {string} in {a} {space} has:', function (a, string, a2, space, dataTable) { + // Write code here that turns the phrase above into concrete actions + return 'pending'; +}); + + +Given('{a} Marketplace Vendor {string} offers {a} following Products in {a} {space}', function (a, string, a2, a3, space, dataTable) { + // Write code here that turns the phrase above into concrete actions + return 'pending'; +}); + +When('{a} {actor} places {a} Delivery Marketplace Order in {a} {space} for:', function (a, actor, a2, a3, space, dataTable) { + // Write code here that turns the phrase above into concrete actions + return 'pending'; +}); + +Then('{a} Marketplace Order placed by {a} {actor} in {a} {space} is delivered to {string}', function (a, a2, actor, a3, space, string) { + // Write code here that turns the phrase above into concrete actions + return 'pending'; +}); + +Then('{a} {actor} is charged ${float} for their Marketplace Order', function (a, actor, float) { + // Write code here that turns the phrase above into concrete actions + return 'pending'; +}); + +Then('{a} Stripe Account {string} receives {a} Payment of ${float}', function (a, string, a2, float) { + // Write code here that turns the phrase above into concrete actions + return 'pending'; +}); \ No newline at end of file