Skip to content

Commit

Permalink
Initial implementation of bulk promo codes admin UI
Browse files Browse the repository at this point in the history
  • Loading branch information
tomdonarski authored and rafalcymerys committed Feb 14, 2024
1 parent 808c4ac commit 3d957d3
Show file tree
Hide file tree
Showing 17 changed files with 363 additions and 1 deletion.
63 changes: 63 additions & 0 deletions app/controllers/spree/admin/promotion_batches_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module Spree
module Admin
class PromotionBatchesController < ResourceController
def update
if @object.template_promotion_id
flash[:error] = Spree.t(:template_promotion_already_assigned)
respond_with(@object) do |format|
format.html { render action: :edit, status: :unprocessable_entity }
format.js { render layout: false, status: :unprocessable_entity }
end
return
end
super
end

def destroy
result = Spree::PromotionBatches::Destroy.call(promotion_batch: @promotion_batch)

if result.success?
flash[:success] = flash_message_for(@promotion_batch, :successfully_removed)
else
flash[:error] = @promotion_batch.errors.full_messages.join(', ')
end

respond_with(@promotion_batch) do |format|
format.html { redirect_to location_after_destroy }
format.js { render_js_for_destroy }
end
end

def csv_export
send_data Spree::PromotionBatches::PromotionCodesExporter.new(params).call,
filename: "promo_codes_from_batch_id_#{params[:id]}.csv",
disposition: :attachment,
type: 'text/csv'
end

def csv_import
file = params[:file]
Spree::PromotionBatches::PromotionCodesImporter.new(file: file, promotion_batch_id: params[:id]).call
redirect_back fallback_location: admin_promotions_path, notice: Spree.t('code_upload')
rescue Spree::PromotionBatches::PromotionCodesImporter::Error => e
redirect_back fallback_location: admin_promotions_path, alert: e.message
end

def populate
batch_id = params[:id]
options = {
batch_size: params[:batch_size].to_i,
affix: params.dig(:code, :affix)&.to_sym,
content: params[:affix_content],
deny_list: params[:forbidden_phrases].split,
random_part_bytes: params[:random_part_bytes].to_i
}

Spree::Promotions::PopulatePromotionBatch.new(batch_id, options).call

flash[:success] = Spree.t('promotion_batch_populated')
redirect_to spree.edit_admin_promotion_batch_url(@promotion_batch)
end
end
end
end
1 change: 1 addition & 0 deletions app/controllers/spree/admin/promotions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def collection
params[:q][:s] ||= 'id desc'

@collection = super
@collection = @collection.non_batched
@search = @collection.ransack(params[:q])
@collection = @search.result(distinct: true).
includes(promotion_includes).
Expand Down
8 changes: 8 additions & 0 deletions app/helpers/spree/admin/navigation_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,14 @@ def variants_actions
def product_properties_actions
Rails.application.config.spree_backend.actions[:product_properties]
end

def promotion_batch_actions
Rails.application.config.spree_backend.actions[:promotion_batch_actions]
end

def promotion_batches_actions
Rails.application.config.spree_backend.actions[:promotion_batches_actions]
end
# rubocop:enable Metrics/ModuleLength
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Spree
module Admin
module Actions
class PromotionBatchDefaultActionsBuilder
include Spree::Core::Engine.routes.url_helpers

def build
root = Root.new
export_codes_to_csv_action(root)
root
end

private

def export_codes_to_csv_action(root)
action =
ActionBuilder.new('csv_export', ->(resource) { csv_export_admin_promotion_batch_path(resource) }).
with_icon_key('download.svg').
with_style(::Spree::Admin::Actions::ActionStyle::PRIMARY).
build

root.add(action)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Spree
module Admin
module Actions
class PromotionBatchesDefaultActionsBuilder
include Spree::Core::Engine.routes.url_helpers

def build
root = Root.new
add_new_promotion_batch_action(root)
root
end

private

def add_new_promotion_batch_action(root)
action =
ActionBuilder.new('new_promotion_batch', new_admin_promotion_batch_path).
with_icon_key('add.svg').
with_style(::Spree::Admin::Actions::ActionStyle::PRIMARY).
with_create_ability_check(::Spree::PromotionBatch).
build

root.add(action)
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def build
add_integrations_section(root)
add_oauth_section(root)
add_settings_section(root)
add_promotion_batches_section(root)
root
end

Expand Down Expand Up @@ -255,6 +256,13 @@ def add_settings_section(root)
build
root.add(section)
end

def add_promotion_batches_section(root)
root.add(ItemBuilder.new('promotion_batches', admin_promotion_batches_path).
with_icon_key('stack.svg').
with_admin_ability_check(Spree::PromotionBatch).
build)
end
# rubocop:enable Metrics/AbcSize
end
# rubocop:enable Metrics/ClassLength
Expand Down
6 changes: 6 additions & 0 deletions app/views/spree/admin/promotion_batches/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div data-hook="new_promotion_batch_template_promotion" class="col-12 col-md-4">
<%= f.field_container :template_promotion do %>
<%= f.label :template_promotion_id, Spree.t(:template_promotion) %>
<%= f.collection_select :template_promotion_id, Spree::Promotion.non_batched, :id, ->(promotion) { "#{promotion.name} # #{promotion.id}" }, { include_blank: true }, { class: 'select2-clear w-100' } %>
<% end %>
</div>
6 changes: 6 additions & 0 deletions app/views/spree/admin/promotion_batches/_size.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class="col-12 col-lg-4">
<%= form_tag(duplicate_admin_promotion_batch_path(@promotion_batch), method: :post) do %>
<%= number_field_tag(:batch_size) %>
<%= submit_tag("Duplicate") %>
<% end %>
</div>
68 changes: 68 additions & 0 deletions app/views/spree/admin/promotion_batches/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<% content_for :page_title do %>
<%= link_to Spree.t(:promotion_batches), admin_promotion_batches_path %> /
<%= @promotion_batch.id %>
<% end %>

<% if @promotion_batch.template_promotion_id%>
<div class="card">
<div class="card-body">
<%= form_tag csv_import_admin_promotion_batch_path, method: :post, multipart: true do %>
<%= file_field_tag :file, accept: ".csv" %>
<%= submit_tag Spree.t('promotion_batch_form.import_codes') %>
<% end %>
</div>
</div>
<% end %>

<% unless @promotion_batch.template_promotion_id%>
<%= form_for @promotion_batch, url: object_url, method: :put do |f| %>
<div class="card mb-4">
<div class="card-body">
<%= render partial: 'form', locals: { f: f } %>
<%= render partial: 'spree/admin/shared/edit_resource_links' %>
</div>
</div>
<% end %>
<% end %>

<% if @promotion_batch.template_promotion_id %>
<%= form_with url: populate_admin_promotion_batch_path(@promotion_batch), method: :post do %>
<div>
<%= label_tag :batch_size do %>
<%= number_field_tag(:batch_size) %>
<%= Spree.t('promotion_batch_form.batch_size') %>
<% end %>
</div>
<div>
<%= label_tag :prefix do %>
<%= radio_button :code, :affix, 'prefix' %>
<%= Spree.t('promotion_batch_form.prefix') %>
<% end %>
</div>
<div>
<%= label_tag :suffix do %>
<%= radio_button :code, :affix, 'suffix' %>
<%= Spree.t('promotion_batch_form.suffix') %>
<% end %>
</div>
<div>
<%= label_tag :affix_content do %>
<%= text_field_tag(:affix_content) %>
<%= Spree.t('promotion_batch_form.affix_content') %>
<% end %>
</div>
<div>
<%= label_tag :forbidden_phrases do %>
<%= text_area_tag(:forbidden_phrases) %>
<%= Spree.t('promotion_batch_form.forbidden_phrases') %>
<% end %>
</div>
<div>
<%= label_tag :random_part_bytes do %>
<%= number_field_tag(:random_part_bytes, value = 4) %>
<%= Spree.t('promotion_batch_form.random_part_bytes') %>
<% end %>
</div>
<%= submit_tag(Spree.t('promotion_batch_form.populate')) %>
<% end %>
<% end %>
45 changes: 45 additions & 0 deletions app/views/spree/admin/promotion_batches/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<% content_for :page_title do %>
<%= plural_resource_name(Spree::PromotionBatch) %>
<% end %>

<% content_for :page_actions do %>
<% promotion_batches_actions.items.each do |action| %>
<% next unless action.available?(current_ability) %>
<%= button_link_to(
Spree.t(action.label_translation_key),
action.url,
class: action.classes,
icon: action.icon_key
) %>
<% end %>
<% end %>

<% if @promotion_batches.any? %>
<div class="table-responsive border rounded bg-white">
<table class="table">
<thead class="text-muted">
<tr>
<th><%= Spree.t(:id) %></th>
<th><%= Spree.t(:size) %></th>
<th><%= Spree.t(:template_promotion) %></th>
<th data-hook="admin_promotion_batches_index_header_actions" class="actions"></th>
</tr>
</thead>
<tbody>
<% @promotion_batches.each do |promotion_batch| %>
<tr id="<%= spree_dom_id promotion_batch %>">
<td><%= link_to Spree::PromotionBatchPresenter.new(promotion_batch).call[:model_name_id], spree.admin_promotion_batch_path(promotion_batch) %></td>
<td><%= promotion_batch.promotions.count %></td>
<td><%= link_to Spree::PromotionBatchPresenter.new(promotion_batch).call[:template_promotion_name_id], spree.edit_admin_promotion_path(promotion_batch.template_promotion) if promotion_batch.template_promotion %></td>
<td class="actions" data-hook="admin_promotion_batches_index_row_actions">
<span class="d-flex justify-content-end">
<%= link_to_edit promotion_batch, no_text: true if can?(:edit, promotion_batch) %>
<%= link_to_delete promotion_batch, no_text: true if can?(:delete, promotion_batch) %>
</span>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
13 changes: 13 additions & 0 deletions app/views/spree/admin/promotion_batches/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<% content_for :page_title do %>
<%= link_to Spree.t(:promotion_batches), admin_promotion_batches_path %> /
<%= Spree.t(:new_promotion_batch) %>
<% end %>

<div class="card">
<div class="card-body">
<%= form_for :promotion_batch, url: collection_url do |f| %>
<%= render partial: 'form', locals: { f: f } %>
<%= render partial: 'spree/admin/shared/new_resource_links' %>
<% end %>
</div>
</div>
37 changes: 37 additions & 0 deletions app/views/spree/admin/promotion_batches/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<% content_for :page_title do %>
<%= link_to Spree.t(:promotion_batches), admin_promotion_batches_path %> /
<%= @promotion_batch.id %>
<% end %>

<% content_for :page_actions do %>
<% promotion_batch_actions.items.each do |action| %>
<% next unless action.available?(current_ability) %>
<%= button_link_to(
Spree.t(action.label_translation_key),
action.url(@promotion_batch),
class: action.classes,
icon: action.icon_key
) %>
<% end %>
<% end %>

<table class="table">
<thead class="text-muted">
<tr>
<th><%= Spree.t(:code) %></th>
<th><%= Spree.t(:description) %></th>
<th><%= Spree.t(:redeemed) %></th>
<th><%= Spree.t(:expiration) %></th>
</tr>
</thead>
<tbody>
<% @promotion_batch.promotions.each do |promotion| %>
<tr id="<%= spree_dom_id promotion %>">
<td><%= promotion.code %></td>
<td><%= promotion.description %></td>
<td><%= promotion.credits_count == promotion.usage_limit ? Spree.t(:say_yes) : Spree.t(:say_no) %></td>
<td><%= promotion.expires_at.to_date if promotion.expires_at %></td>
</tr>
<% end %>
</tbody>
</table>
8 changes: 8 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@

resources :promotion_categories, except: [:show]

resources :promotion_batches do
member do
get :csv_export, to: 'promotion_batches#csv_export'
post :csv_import, to: 'promotion_batches#csv_import'
post :populate
end
end

resources :zones

resources :stores, except: %i[index show] do
Expand Down
4 changes: 4 additions & 0 deletions lib/spree/backend/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ class Engine < ::Rails::Engine
Rails.application.config.spree_backend.actions[:payments] = Spree::Admin::Actions::PaymentsDefaultActionsBuilder.new.build
Rails.application.config.spree_backend.actions[:variants] = Spree::Admin::Actions::VariantsDefaultActionsBuilder.new.build
Rails.application.config.spree_backend.actions[:product_properties] = Spree::Admin::Actions::ProductPropertiesDefaultActionsBuilder.new.build
Rails.application.config.spree_backend.actions[:promotion_batch_actions] =
Spree::Admin::Actions::PromotionBatchDefaultActionsBuilder.new.build
Rails.application.config.spree_backend.actions[:promotion_batches_actions] =
Spree::Admin::Actions::PromotionBatchesDefaultActionsBuilder.new.build
end
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require 'spec_helper'

module Spree
module Admin
describe Actions::PromotionBatchDefaultActionsBuilder, type: :model do
let(:builder) { described_class.new }
let(:default_actions) do
%w(csv_export)
end

describe '#build' do
subject { builder.build }

it 'builds default tabs' do
expect(subject.items.map(&:key)).to match(default_actions)
end
end
end
end
end
Loading

0 comments on commit 3d957d3

Please sign in to comment.