diff --git a/back-office/Gemfile b/back-office/Gemfile index f0d486c..7d29afe 100644 --- a/back-office/Gemfile +++ b/back-office/Gemfile @@ -1,6 +1,5 @@ source 'https://rubygems.org' - gem 'rails_admin' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '4.2.0' @@ -25,8 +24,11 @@ gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc', '~> 0.4.0', group: :doc +gem 'tzinfo-data' + +gem "nokogiri", ">= 1.6.6" # great server -gem 'puma' +gem 'puma', '~> 2.15.3' # for uploading images gem 'cloudinary' diff --git a/back-office/Gemfile.lock b/back-office/Gemfile.lock index 5ab1f66..f438da0 100644 --- a/back-office/Gemfile.lock +++ b/back-office/Gemfile.lock @@ -79,6 +79,7 @@ GEM execjs (2.2.2) faraday (0.9.1) multipart-post (>= 1.2, < 3) + ffi (1.9.10-x64-mingw32) font-awesome-rails (4.3.0.0) railties (>= 3.2, < 5.0) globalid (0.3.0) @@ -111,11 +112,13 @@ GEM multipart-post (2.0.0) nested_form (0.3.2) netrc (0.10.2) - nokogiri (1.6.5) + nokogiri (1.6.6.2) + mini_portile (~> 0.6.0) + nokogiri (1.6.6.2-x64-mingw32) mini_portile (~> 0.6.0) pg (0.18.1) - puma (2.10.2) - rack (>= 1.1, < 2.0) + pg (0.18.1-x64-mingw32) + puma (2.15.3) rack (1.6.0) rack-pjax (0.8.0) nokogiri (~> 1.5) @@ -167,6 +170,10 @@ GEM rest-client (1.7.2) mime-types (>= 1.16, < 3.0) netrc (~> 0.7) + rest-client (1.7.2-x64-mingw32) + ffi (~> 1.9) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) safe_yaml (1.0.4) sass (3.4.9) sass-rails (5.0.1) @@ -199,6 +206,8 @@ GEM coffee-rails tzinfo (1.2.2) thread_safe (~> 0.1) + tzinfo-data (1.2015.7) + tzinfo (>= 1.0.0) uglifier (2.7.0) execjs (>= 0.3.0) json (>= 1.8.0) @@ -210,6 +219,7 @@ GEM PLATFORMS ruby + x64-mingw32 DEPENDENCIES active_scaffold! @@ -220,8 +230,9 @@ DEPENDENCIES jbuilder (~> 2.0) jquery-rails jquery-ui-rails + nokogiri (>= 1.6.6) pg - puma + puma (~> 2.15.3) rails (= 4.2.0) rails_admin sass-rails (~> 5.0) @@ -229,8 +240,9 @@ DEPENDENCIES spring tabulous turbolinks + tzinfo-data uglifier (>= 1.3.0) web-console (~> 2.0) BUNDLED WITH - 1.10.3 + 1.10.6 diff --git a/back-office/app/constraints/check_items_constraint.rb b/back-office/app/constraints/check_items_constraint.rb new file mode 100644 index 0000000..023d041 --- /dev/null +++ b/back-office/app/constraints/check_items_constraint.rb @@ -0,0 +1,5 @@ +class CheckItemsConstraint + def self.matches?(request) + request.params[:commit] == 'Check' + end +end diff --git a/back-office/app/constraints/delete_items_constraint.rb b/back-office/app/constraints/delete_items_constraint.rb new file mode 100644 index 0000000..208fcaa --- /dev/null +++ b/back-office/app/constraints/delete_items_constraint.rb @@ -0,0 +1,5 @@ +class DeleteItemsConstraint + def self.matches?(request) + request.params[:commit] == 'Delete Items' + end +end diff --git a/back-office/app/controllers/items_controller.rb b/back-office/app/controllers/items_controller.rb index a3a9b02..6098a32 100644 --- a/back-office/app/controllers/items_controller.rb +++ b/back-office/app/controllers/items_controller.rb @@ -15,4 +15,80 @@ class ItemsController < ApplicationController @missing_from_database = (@codes_to_check_array - items_to_check) || [] @missing_from_codes = (items_to_check - @codes_to_check_array) || [] end + + def delete_items + @suppliers = Supplier.order(:name).all + @selected_supplier = Supplier.find_by_id(params[:supplier_id]) + @items = @selected_supplier.try(:items) || [] + @codes_to_check = params[:codes] || "" + @codes_to_check_array = @codes_to_check.split("\n").reject { |code| code.strip.blank? }.map(&:strip) + items_to_check = @items.map { |i| i.code.strip } + + @items_for_delete = items_to_check & @codes_to_check_array + Item.where(:code => @items_for_delete).destroy_all + + @not_deleted_items = (@codes_to_check_array - @items_for_delete) || [] + + render :template => "items/check_availability" + end + + def export_import + @tasks = [ ["Validate items", "validate_items"], + ["Import items", "import_items"], + ["Update prices", "update_prices"] ] + + @task = "validate_items" + end + + def export_import_post + @tasks = [ ["Validate items", "validate_items"], + ["Import items", "import_items"], + ["Update prices", "update_prices"] ] + + @task = params[:task] + + @csv_content = params[:csv_content] + + @error_message = "" + @output = [] + + if params[:csv_content] == "" + @error_message = "Format of CSV is wrong (CSV content is empty)" + else + begin + csv_parsed = CSV.parse(params[:csv_content]) + rescue CSV::MalformedCSVError => er + @error_message = "Format of CSV is wrong (#{er.message})" + end + + unless csv_parsed.nil? + csv_file = ItemsHelper::create_csv(csv_parsed) + + begin + ENV["INPUT"] = csv_file.path + + case @task + when 'validate_items' + @output = ItemsHelper::execute_command("rake ribica:validate_items -f #{Rails.root}/Rakefile") + @output.collect{|x| x.sub! "\n", "" } + when 'import_items' + @output = ItemsHelper::execute_command("rake ribica:import_items -f #{Rails.root}/Rakefile") + @output.collect{|x| x.sub! "\n", "" } + %x(rake ribica:reindex -f #{Rails.root}/Rakefile) unless RakeTasksHelper::is_error_occurred @output + when 'update_prices' + @output = ItemsHelper::execute_command("rake ribica:update_prices -f #{Rails.root}/Rakefile") + %x(rake ribica:reindex -f #{Rails.root}/Rakefile) unless RakeTasksHelper::is_error_occurred @output + else + @error_message = "There is no such task" + end + ensure + csv_file.unlink + end + end + + @output = @output.join("") + end + + render :template => "items/export_import" + end end diff --git a/back-office/app/helpers/items_helper.rb b/back-office/app/helpers/items_helper.rb index f2092c6..247dedb 100644 --- a/back-office/app/helpers/items_helper.rb +++ b/back-office/app/helpers/items_helper.rb @@ -1,5 +1,27 @@ module ItemsHelper + def self.create_csv(data) + file = Tempfile.new([Rails.root.join('tmp/').to_s, ".csv"], "") + csv = CSV.new(file) + data.each do |row| + csv << row + end + file.rewind + file.close + file + end + def self.execute_command(command) + buffer = [] + Open3.popen3(command) do |stdin, stdout, stderr| + begin + while line = stdout.readline + buffer << line + end + rescue + end + end -end \ No newline at end of file + buffer + end +end diff --git a/back-office/app/helpers/rake_tasks_helper.rb b/back-office/app/helpers/rake_tasks_helper.rb new file mode 100644 index 0000000..e8b9815 --- /dev/null +++ b/back-office/app/helpers/rake_tasks_helper.rb @@ -0,0 +1,9 @@ +module RakeTasksHelper + def self.task_error_message + "Rake task execution error" + end + + def self.is_error_occurred(buffer) + buffer.include? self.task_error_message + end +end diff --git a/back-office/app/models/cart.rb b/back-office/app/models/cart.rb index 299a08a..82b6b77 100644 --- a/back-office/app/models/cart.rb +++ b/back-office/app/models/cart.rb @@ -5,7 +5,9 @@ class Cart < ActiveRecord::Base belongs_to :delivery_destination def delivery_cost - place = Place.by_code_or_default(delivery_destination.place) + place = delivery_destination.gift ? Place.by_code_or_default(delivery_destination.recipient_place) + : Place.by_code_or_default(delivery_destination.place) + if delivery_destination.instant_delivery place.instant_delivery_price else @@ -21,5 +23,4 @@ class Cart < ActiveRecord::Base def confirmed_at delivery_destination.updated_at.in_time_zone('Europe/Sarajevo') end - end diff --git a/back-office/app/models/delivery_destination.rb b/back-office/app/models/delivery_destination.rb index ecb39d0..8fa2e38 100644 --- a/back-office/app/models/delivery_destination.rb +++ b/back-office/app/models/delivery_destination.rb @@ -1,2 +1,14 @@ -class DeliveryDestination < ActiveRecord::Base +class DeliveryDestination < ActiveRecord::Base + def get_payment_string + case self.payment_method + when 'cash_on_delivery' + 'Prilikom preuzimanja' + when 'paypal' + 'Paypal' + when 'pikpay' + 'Pikpay' + else + 'Nepoznato' + end + end end diff --git a/back-office/app/models/item.rb b/back-office/app/models/item.rb index c991c44..a8300c2 100644 --- a/back-office/app/models/item.rb +++ b/back-office/app/models/item.rb @@ -124,12 +124,13 @@ class Item < ActiveRecord::Base end end puts "Nonexistent codes: " - puts nonexistent_codes.join("\n") + puts "#{nonexistent_codes.join("\n")}" end def we_must_earn_money if list_price.to_f <= current_input_price.to_f errors[:list_price] << "#{code} Ulazna cijena veca od izlazne" + puts "#{code} Ulazna cijena veca od izlazne" end end diff --git a/back-office/app/views/carts/_show.html.erb b/back-office/app/views/carts/_show.html.erb index eb9c5fc..f3f66fd 100644 --- a/back-office/app/views/carts/_show.html.erb +++ b/back-office/app/views/carts/_show.html.erb @@ -3,14 +3,14 @@ .tg td{ } .tg th{ } -<% +<% dd = @record.delivery_destination c = @record %>
Ime: <%= dd.name %>
Adresa: +
Adresa: <%= dd.address %> <%= dd.place.to_s.strip %> <%= Place.name_from_code(dd.place.to_s) %> Bosna i Hercegovina @@ -22,15 +22,27 @@ Bosna i Hercegovina
Telefon: +387 <%= dd.phone %>
Plaćanje: <%= dd.get_payment_string %>
Napomena: <%= dd.note %>
Ovo je poklon +
Ime primaoca: <%= dd.recipient_name %> +
Adresa primaoca: <%= dd.recipient_address %> +
Postanski broj primaoca: <%= dd.recipient_postal_code %> +
Grad primaoca: <%= dd.recipient_place %> +
Email primaoca: <%= dd.recipient_email %> +
Telefon primaoca: <%= dd.recipient_phone %> +<% end %> +
Naručeno: <%= c.confirmed_at.in_time_zone("Europe/Sarajevo").strftime("%A %d.%m.%Y. %H:%M") %> - - + +
@@ -47,7 +59,7 @@ OVO JE NARUDŽBA ZA INSTANT DOSTAVU
Supplier: <%= select_tag "supplier_id", options_from_collection_for_select(@suppliers, "id", "name", @selected_supplier.try(:id)) %>
+ Codes to check: <%= text_area_tag "codes", @codes_to_check, rows: 20, columns: 50 %> <%= submit_tag "Check" %> + <%= submit_tag "Delete Items" %> <% end %> - - In database: <%= @items.length %>, in file: <%= @codes_to_check_array.length %> - +<% if controller.action_name == 'delete_items' %> + + Delete successful + + <% if @not_deleted_items.length > 0 %> + Not deleted items + <% @not_deleted_items.each do |code| %> + <%= code %> + <% end %> + <% end %> + +<% else %> + + + In database: <%= @items.length %>, in file: <%= @codes_to_check_array.length %> + + + + Not in database (<%= @missing_from_database.length %>): + + <% @missing_from_database.each do |code| %> + <%= code %> + <% end %> + + + + + Not in file (<%= @missing_from_codes.length %>): + + <% @missing_from_codes.each do |code| %> + <%= code %> + <% end %> + + - -Not in database (<%= @missing_from_database.length %>): - -<% @missing_from_database.each do |code| %> - <%= code %> <% end %> - - - - -Not in file (<%= @missing_from_codes.length %>): - -<% @missing_from_codes.each do |code| %> - <%= code %> -<% end %> - - - diff --git a/back-office/app/views/items/export_import.html.erb b/back-office/app/views/items/export_import.html.erb new file mode 100644 index 0000000..28e8a0d --- /dev/null +++ b/back-office/app/views/items/export_import.html.erb @@ -0,0 +1,21 @@ +<%= form_tag('./export_import') do %> + + CSV content goes here: + + <%= @csv_content %> + + Task: + <%= select_tag "task", options_for_select(@tasks, @task) %> + + <%= submit_tag "Run task" %> + + <% if !@error_message.nil? && @error_message != "" %> + Error message: <%= @error_message %> + <% end %> + + + Output area: + <%= unless @output.nil? + @output.html_safe end %> + +<% end %> diff --git a/back-office/config/application.rb b/back-office/config/application.rb index c5c415e..b489e5c 100644 --- a/back-office/config/application.rb +++ b/back-office/config/application.rb @@ -1,6 +1,8 @@ require File.expand_path('../boot', __FILE__) require 'rails/all' +require 'rake' +require 'open3' # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. @@ -25,5 +27,3 @@ module Ribicabackoffice config.assets.precompile += %w(*.svg *.eot *.woff *.ttf *.gif *.png *.ico) end end - - diff --git a/back-office/config/initializers/rails_admin.rb b/back-office/config/initializers/rails_admin.rb index 011e0a1..ba817d5 100644 --- a/back-office/config/initializers/rails_admin.rb +++ b/back-office/config/initializers/rails_admin.rb @@ -35,6 +35,7 @@ RailsAdmin.config do |config| config.navigation_static_links = { 'Orders to Confirm' => './reports/orders_to_confirm', 'Items to Order' => './backreports/items_to_order', - 'Check availability' => './items/check_availability' + 'Check availability' => './items/check_availability', + 'Export/import' => './items/export_import' } end diff --git a/back-office/config/routes.rb b/back-office/config/routes.rb index 9c38196..30adfc0 100644 --- a/back-office/config/routes.rb +++ b/back-office/config/routes.rb @@ -15,12 +15,15 @@ Rails.application.routes.draw do resources :multi_media_descriptions do as_routes end resources :sections do as_routes end resources :media_types do as_routes end - resources :items do - collection do + resources :items do + collection do get 'check_availability' - post 'check_availability' + post 'check_availability' => 'items#check_availability', constraints: CheckItemsConstraint + post 'check_availability' => 'items#delete_items', constraints: DeleteItemsConstraint + get 'export_import' + post 'export_import' => 'items#export_import_post' end - as_routes + as_routes end resources :units do as_routes end resources :sub_categories do as_routes end diff --git a/back-office/lib/tasks/ribica.rake b/back-office/lib/tasks/ribica.rake index 1c7ed47..0827dc8 100644 --- a/back-office/lib/tasks/ribica.rake +++ b/back-office/lib/tasks/ribica.rake @@ -223,6 +223,7 @@ def import_single_item(row, index, logger) success = true rescue Exception => e logger.error "Could not import item on row number #{index} (#{row[lookup[:name_on_ribica]]}), reason: #{e}" + puts "Could not import item on row number #{index} (#{row[lookup[:name_on_ribica]]}), reason: #{e}" success = false end @@ -242,14 +243,15 @@ def do_import(validate_only) input_file = ENV['INPUT'] if input_file.to_s == "" puts "Input file is missing! Please provide input file in form INPUT=somefile.csv" + puts RakeTasksHelper.task_error_message return end lookup = get_column_lookup path = Rails.root.join(input_file) - log_filename = "import.log" - log_filename = "import_validate.log" if validate_only + log_filename = Rails.root.join("import.log") + log_filename = Rails.root.join("import_validate.log") if validate_only logger = Logger.new(log_filename) @@ -274,6 +276,7 @@ def do_import(validate_only) if should_rollback puts "Import failed, please check the import log file for error details." logger.info "Rolling back because of errors" + puts RakeTasksHelper.task_error_message end raise ActiveRecord::Rollback @@ -281,8 +284,11 @@ def do_import(validate_only) end rescue Exception => e puts "Import failed, please check the import log file for error details." + puts "Error while importing: #{e}" logger.error "Error while importing: #{e}" + puts RakeTasksHelper.task_error_message end + puts "Import done" logger.info "Import done" end @@ -359,7 +365,7 @@ namespace :ribica do begin es_client.indices.delete index: 'ribica' rescue - logger.warn "Ribica index could not be deleted. Continuing with indexing operation..." + Rails.logger.warn "Ribica index could not be deleted. Continuing with indexing operation..." end # now index items diff --git a/front-api/Gemfile.lock b/front-api/Gemfile.lock index df763fb..db7f6ac 100644 --- a/front-api/Gemfile.lock +++ b/front-api/Gemfile.lock @@ -24,6 +24,7 @@ GEM backports (3.6.6) bcrypt (3.1.10) bcrypt (3.1.10-java) + bcrypt (3.1.10-x64-mingw32) builder (3.2.2) celluloid (0.16.0) timers (~> 4.0.0) @@ -39,12 +40,14 @@ GEM multipart-post (>= 1.2, < 3) ffi (1.9.10) ffi (1.9.10-java) + ffi (1.9.10-x64-mingw32) hitimes (1.2.3) hitimes (1.2.3-java) i18n (0.7.0) jdbc-postgres (9.4.1200) jruby-openssl (0.9.11-java) json (1.8.3) + json (1.8.3-java) listen (2.10.1) celluloid (~> 0.16.0) rb-fsevent (>= 0.9.3) @@ -71,6 +74,10 @@ GEM rest-client (1.7.3) mime-types (>= 1.16, < 3.0) netrc (~> 0.7) + rest-client (1.7.3-x64-mingw32) + ffi (~> 1.9) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) ruby-trello (1.2.1) activemodel (>= 3.2.0) addressable (~> 2.3) @@ -96,6 +103,7 @@ GEM tilt (>= 1.3, < 3) smtpapi (0.1.0) thread_safe (0.3.5) + thread_safe (0.3.5-java) tilt (2.0.1) timers (4.0.4) hitimes @@ -106,6 +114,7 @@ GEM PLATFORMS java ruby + x64-mingw32 DEPENDENCIES activerecord @@ -125,4 +134,4 @@ DEPENDENCIES xxhash (~> 0.3.0) BUNDLED WITH - 1.10.3 + 1.10.6 diff --git a/front-api/app.rb b/front-api/app.rb index 85bcc47..fc2b7c2 100644 --- a/front-api/app.rb +++ b/front-api/app.rb @@ -14,11 +14,11 @@ require 'sendgrid-ruby' Trello.configure do |config| # API key generated by visiting https://trello.com/1/appKey/generate - config.developer_public_key = "f13dd6c2dcc65f48b9a56c6d420e32e7" + config.developer_public_key = RibicaConfig::TRELLO_DEVELOPER_PUBLIC_KEY # Member token # larry-price.com/blog/2014/03/18/connecting-to-the-trello-api/ - config.member_token = "e8e1885d794dbc4d9d8d1ae586a84e580be224b2737254de6064d7d7219f3064" + config.member_token = RibicaConfig::TRELLO_MEMBER_TOKEN end Dir[File.dirname(__FILE__) + '/models/*.rb'].each {|file| require file } @@ -37,9 +37,11 @@ before do 'Access-Control-Expose-Headers' => 'X-Total-Count', 'Access-Control-Allow-Credentials' => 'true' - request.body.rewind - json_string = request.body.read - @json_params = JSON.parse json_string if json_string.length > 1 + unless Helper::do_not_parse_as_json.include? env['PATH_INFO'] + request.body.rewind + json_string = request.body.read + @json_params = JSON.parse json_string if json_string.length > 1 + end if request.request_method == 'OPTIONS' halt 200 diff --git a/front-api/config.rb.example b/front-api/config.rb.example index b1b8bdf..1f96275 100644 --- a/front-api/config.rb.example +++ b/front-api/config.rb.example @@ -10,3 +10,23 @@ ActiveRecord::Base.establish_connection( :database => db.path[1..-1], :encoding => 'utf8' ) + + +module RibicaConfig + ROOT_ADDRESS = "https://ribica.ba" + SENDGRID_API_USER = "ribica" + SENDGRID_API_KEY = "(enter key here)" + + BACKOFFICE_ORDER_EMAIL_TO = "narudzbe@ribica.ba" + BACKOFFICE_ORDER_EMAIL_FROM = "draga@ribica.ba" + BACKOFFICE_ORDER_EMAIL_FROM_NAME = "Prodavnica Ribica" + + # API key generated by visiting https://trello.com/1/appKey/generate + TRELLO_DEVELOPER_PUBLIC_KEY = "f13dd6c2dcc65f48b9a56c6d420e32e7" + # Member token + # larry-price.com/blog/2014/03/18/connecting-to-the-trello-api/ + TRELLO_MEMBER_TOKEN = "(enter token here)" + TRELLO_BOARD_NAME = "FqDO1eFL" + + BAM_TO_EURO_CONVERSION_RATE = 0.51 +end diff --git a/front-api/controllers/cart.rb b/front-api/controllers/cart.rb index 7d554fc..6774025 100644 --- a/front-api/controllers/cart.rb +++ b/front-api/controllers/cart.rb @@ -44,7 +44,9 @@ end update_delivery_destination = ->() { cart = Cart.just_find(anonymous_id, logged_in_user_id) - allowed_keys = ["name", "address", "place", "postal_code", "phone", "email", "note"] + allowed_keys = ["name", "address", "place", "postal_code", "phone", "email", "note", "payment_method", "gift", + "recipient_name", "recipient_address", "recipient_place", "recipient_postal_code", "recipient_phone", "recipient_email"] + params = @json_params.reject { |key,_| !allowed_keys.include?(key) } cart.delivery_destination.update_attributes(params) cart.delivery_destination.save! @@ -53,11 +55,10 @@ update_delivery_destination = ->() { put '/cart/delivery_destination', &update_delivery_destination post '/cart/delivery_destination', &update_delivery_destination - def report_to_trello(cart) - Thread.new do + Thread.new do @cart = cart - board = Trello::Board.find('FqDO1eFL') + board = Trello::Board.find(RibicaConfig.TRELLO_BOARD_NAME) list = board.lists.first card = Trello::Card.new card.list_id = list.id @@ -69,18 +70,18 @@ def report_to_trello(cart) end def send_order_email(cart) - Thread.new do + Thread.new do client = SendGrid::Client.new( - api_user: "ribica", - api_key: "plava*kutija*svjetlo*torba07" + api_user: RibicaConfig.SENDGRID_API_USER, + api_key: RibicaConfig.SENDGRID_API_KEY ) email = SendGrid::Mail.new do |m| - m.to = "narudzbe@ribica.ba" - m.from = "draga@ribica.ba" + m.to = RibicaConfig.BACKOFFICE_ORDER_EMAIL_TO + m.from = RibicaConfig.BACKOFFICE_ORDER_EMAIL_FROM m.from_name = "Prodavnica Ribica" m.subject = "Nova Narudžba: #{cart.id}" - m.html = "Mušterija naručila nešto. Pogledati https://www.ribica.ba/backoffice/carts/#{cart.id}" + m.html = "Mušterija naručila nešto. Pogledati #{RibicaConfig.ROOT_ADDRESS}/backoffice/carts/#{cart.id}" end client.send(email) end @@ -101,3 +102,44 @@ post '/cart/confirmation' do send_order_email(cart) "OK".to_json end + +post '/payment/confirmation' do + data = JSON.parse params[:custom] + + puts "Data #{data.inspect}" + + anonymous = data["anonymous_id_string"] + user = data["user_id"] + user ||= -1 + user = user.to_i + + cart = Cart.just_find(anonymous, user) + if cart.item_in_carts.length > 0 + cart.ordered = true + cart.save! + end + + Cart.find_or_create(anonymous, user) + report_to_trello(cart) + send_order_email(cart) + "OK".to_json +end + +get '/pikpay/confirmation' do + anonymous = params["anonymous_id_string"] + user = params["user_id"] + user ||= -1 + user = user.to_i + + cart = Cart.just_find(anonymous, user) + if cart.item_in_carts.length > 0 + cart.ordered = true + cart.save! + end + + Cart.find_or_create(anonymous, user) + report_to_trello(cart) + send_order_email(cart) + + redirect "#{RibicaConfig::ROOT_ADDRESS}/hvala" +end diff --git a/front-api/controllers/search.rb b/front-api/controllers/search.rb index 784cb14..4fe52d4 100644 --- a/front-api/controllers/search.rb +++ b/front-api/controllers/search.rb @@ -1,35 +1,45 @@ get '/search' do - es_client = Elasticsearch::Client.new log: true - q = params[:q] - # for now we do the basic query - results = es_client.search index: 'ribica', type: 'items', body: { query: { match: { _all: q } } } - ids = results["hits"]["hits"].map do |r| - r["_id"] + results = { } + + begin + es_client = Elasticsearch::Client.new log: true + q = params[:q] + + # for now we do the basic query + results = es_client.search index: 'ribica', type: 'items', body: { query: { match: { _all: q } } } + rescue Exception => error + puts error.inspect + results = { "hits" => {"hits" => [ {"_id" => Item.first.id, "_score" => 2 }, {"_id" => Item.last.id, "_score" => 1 } ]}} + end + + ids = results["hits"]["hits"].map do |r| + r["_id"] end ids_with_score = {} results["hits"]["hits"].each do |r| - ids_with_score[r["_id"].to_i] = {:score => r["_score"], :item => nil} + ids_with_score[r["_id"].to_i] = {:score => r["_score"], :item => nil} end + if ids.length > 0 - res = Item.where(:id => ids).to_a - # make sure we have correct relevance order, since `where in` does not guarantee order - res.each do |ii| - ids_with_score[ii.id][:item] = ii - end - final = [] - ids_with_score.each do |k,v| - final << v - end - final.sort_by! {|v| -v[:score]} + res = Item.where(:id => ids).to_a + # make sure we have correct relevance order, since `where in` does not guarantee order + res.each do |ii| + ids_with_score[ii.id][:item] = ii + end + final = [] + ids_with_score.each do |k,v| + final << v + end + final.sort_by! {|v| -v[:score]} - final = final.map do |f| - f[:item] - end - prepare_items_for_mass_display(final) + final = final.map do |f| + f[:item] + end + prepare_items_for_mass_display(final) else - [].to_json + [].to_json end end diff --git a/front-api/db/migrate/20160111024541_add_payment_method_to_delivery_destination.rb b/front-api/db/migrate/20160111024541_add_payment_method_to_delivery_destination.rb new file mode 100644 index 0000000..436f912 --- /dev/null +++ b/front-api/db/migrate/20160111024541_add_payment_method_to_delivery_destination.rb @@ -0,0 +1,5 @@ +class AddPaymentMethodToDeliveryDestination < ActiveRecord::Migration + def change + add_column :delivery_destinations, :payment_method, :string + end +end diff --git a/front-api/db/migrate/20160111105607_add_gift_to_delivery_destination.rb b/front-api/db/migrate/20160111105607_add_gift_to_delivery_destination.rb new file mode 100644 index 0000000..2952bd9 --- /dev/null +++ b/front-api/db/migrate/20160111105607_add_gift_to_delivery_destination.rb @@ -0,0 +1,5 @@ +class AddGiftToDeliveryDestination < ActiveRecord::Migration + def change + add_column :delivery_destinations, :gift, :boolean, default: false + end +end diff --git a/front-api/db/migrate/20160112091903_add_recipient_destination_to_delivery_destination.rb b/front-api/db/migrate/20160112091903_add_recipient_destination_to_delivery_destination.rb new file mode 100644 index 0000000..a85dd88 --- /dev/null +++ b/front-api/db/migrate/20160112091903_add_recipient_destination_to_delivery_destination.rb @@ -0,0 +1,10 @@ +class AddRecipientDestinationToDeliveryDestination < ActiveRecord::Migration + def change + add_column :delivery_destinations, :recipient_name, :string + add_column :delivery_destinations, :recipient_address, :string + add_column :delivery_destinations, :recipient_place, :string + add_column :delivery_destinations, :recipient_postal_code, :string + add_column :delivery_destinations, :recipient_phone, :string + add_column :delivery_destinations, :recipient_email, :string + end +end diff --git a/front-api/db/schema.rb b/front-api/db/schema.rb index 8c9037a..529d977 100644 --- a/front-api/db/schema.rb +++ b/front-api/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151124061357) do +ActiveRecord::Schema.define(version: 20160122145944) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -67,6 +67,14 @@ ActiveRecord::Schema.define(version: 20151124061357) do t.integer "user_id" t.string "anonymous_id_string" t.boolean "instant_delivery", default: false + t.string "payment_method" + t.boolean "gift", default: false + t.string "recipient_name" + t.string "recipient_address" + t.string "recipient_place" + t.string "recipient_postal_code" + t.string "recipient_phone" + t.string "recipient_email" end create_table "delivery_time_estimations", force: :cascade do |t| diff --git a/front-api/helpers.rb b/front-api/helpers.rb index 1ad349e..247eac7 100644 --- a/front-api/helpers.rb +++ b/front-api/helpers.rb @@ -2,4 +2,16 @@ class Helper def self.money(amount) sprintf('%.2f KM', amount.to_f) end -end \ No newline at end of file + + def self.get_bam_to_euro_conversion_rate + RibicaConfig::BAM_TO_EURO_CONVERSION_RATE + end + + def self.bam_to_euro(amount) + self.get_bam_to_euro_conversion_rate * amount + end + + def self.do_not_parse_as_json + ['/payment/confirmation'] + end +end diff --git a/front-api/models/cart.rb b/front-api/models/cart.rb index 20b984c..44eb33b 100644 --- a/front-api/models/cart.rb +++ b/front-api/models/cart.rb @@ -1,5 +1,5 @@ class Cart < ActiveRecord::Base - has_many :item_in_carts, -> { order "created_at" } + has_many :item_in_carts, -> { order "created_at" } belongs_to :delivery_destination def self.find_or_create(anonymous_id, user_id) @@ -74,7 +74,7 @@ class Cart < ActiveRecord::Base def title number = id - name = delivery_destination.name + name = delivery_destination.name value = Helper.money(total) phone = "0#{delivery_destination.phone}" "BR: #{number} za #{name} (#{phone}) - #{value}" diff --git a/front-api/models/delivery_destination.rb b/front-api/models/delivery_destination.rb index 95437dc..f84e23c 100644 --- a/front-api/models/delivery_destination.rb +++ b/front-api/models/delivery_destination.rb @@ -1,4 +1,4 @@ -class DeliveryDestination < ActiveRecord::Base +class DeliveryDestination < ActiveRecord::Base has_one :cart belongs_to :user @@ -6,9 +6,32 @@ class DeliveryDestination < ActiveRecord::Base dd = DeliveryDestination.where(["user_id is not null and user_id = ?", user_id]).order("id desc").first dd ||= DeliveryDestination.where(["anonymous_id_string is not null and anonymous_id_string = ?", anonymous_id]).order("id desc").first dd ||= DeliveryDestination.new({user_id: user_id, anonymous_id_string: anonymous_id }) + + dd.payment_method ||= "cash_on_delivery" + dd.gift = false + dd.recipient_name = "" + dd.recipient_phone = "" + dd.recipient_email = "" + dd.recipient_address = "" + dd.recipient_place = "" + dd.recipient_postal_code = "" + attributes = dd.as_json attributes.delete("id") result = DeliveryDestination.create!(attributes) return result end + + def get_payment_string + case self.payment_method + when 'cash_on_delivery' + 'Prilikom preuzimanja' + when 'paypal' + 'Paypal' + when 'pikpay' + 'Pikpay' + else + 'Nepoznato' + end + end end diff --git a/front-api/views/cart_trello.erb b/front-api/views/cart_trello.erb index 8e1f0df..8f1bfa4 100644 --- a/front-api/views/cart_trello.erb +++ b/front-api/views/cart_trello.erb @@ -1,10 +1,10 @@ -<% +<% dd = @cart.delivery_destination c = @cart %> -**Ime:** <%= dd.name %> +**Ime:** <%= dd.name %> **Adresa:** <%= dd.address %> @@ -17,9 +17,23 @@ Bosna i Hercegovina **Telefon: ** +387 <%= dd.phone %> +**Plaćanje: ** <%= dd.get_payment_string %> + **Napomena: ** <%= dd.note %> +<% if dd.gift %> +**Poklon ** + +**Name: **<%= dd.recipient_name %> +**Postal code: **<%= dd.recipient_postal_code %> +**Place: **<%= dd.recipient_place %> +**Address: **<%= dd.recipient_address %> +**Phone: **<%= dd.recipient_phone %> +**Email: **<%= dd.recipient_email %> +<% end %> + +<% if dd.instant_delivery %> **Naručeno:** <%= @cart.updated_at.in_time_zone("Europe/Sarajevo").strftime("%A %d.%m.%Y. %H:%M") %> @@ -37,16 +51,7 @@ Bosna i Hercegovina Dostava 1 x <%= Helper.money(c.delivery_cost) %> = <%= Helper.money(c.delivery_cost) %> - + **UKUPNO:** <%= Helper.money(c.total) %> [Pogledati OVAJ LINK](https://www.ribica.ba/backoffice/carts/<%= @cart.id %>) - - - - - - - - - diff --git a/front-ui/app/actions/cartActions.js b/front-ui/app/actions/cartActions.js index 68c6f85..c85de07 100644 --- a/front-ui/app/actions/cartActions.js +++ b/front-ui/app/actions/cartActions.js @@ -75,4 +75,4 @@ var CartActions = { } }; -module.exports = CartActions; \ No newline at end of file +module.exports = CartActions; diff --git a/front-ui/app/components/cart/checkoutPage.js b/front-ui/app/components/cart/checkoutPage.js index 71e6ee5..cbd1af6 100644 --- a/front-ui/app/components/cart/checkoutPage.js +++ b/front-ui/app/components/cart/checkoutPage.js @@ -5,25 +5,54 @@ var React = require('react'), NavigationActions = require('../../actions/navigationActions'), SingleItem = require('../items/singleItem'), Globals = require('../../globals'), - CartTotal = require('./cartTotal'), + CartTotal = require('./cartTotal'), LinkBanner = require('../linkBanner/linkBanner'), - RibicaFormError = require('../shared/ribicaFormError'); - + PaymentSelect = require('../payment/paymentSelect'), + RibicaFormError = require('../shared/ribicaFormError'), + PaypalButton = require('../payment/paypalButton'), + PikpayButton = require('../payment/pikpayButton'), + CashOnDeliveryButton = require('../payment/cashOnDeliveryButton'); var Router = require('react-router'); var CheckoutPage = React.createClass({ - render: function() { + var choosePayment = ( + + + + ); + + var last_used_payment; + if(this.state.deliveryDestination.get('payment_method') == 'paypal') { + last_used_payment = ( + + ); + } else if(this.state.deliveryDestination.get('payment_method') == 'pikpay') { + last_used_payment = ( + + ); + } else { + last_used_payment = ( + + ); + } var supportedPlaceOptions = CartStore.getSupportedPlaces().map ( function (p) { return ({p.placeLabel})}); - + var content = ( - Dostava + Podaci o naručiocu Prezime i Ime @@ -41,15 +70,15 @@ var CheckoutPage = React.createClass({ adresa na koju će roba biti isporučena - + Mjesto - - {supportedPlaceOptions} - + + {supportedPlaceOptions} + @@ -78,22 +107,88 @@ var CheckoutPage = React.createClass({ - - - - Roba: - - Dostava: - Ukupno: + + + + Roba: + + Dostava: + Ukupno: - - Završi narudžbu - + + + + Poklon + - - - - ); + + + + + + + Podaci o dostavi + + + + + Prezime i Ime + + + + ime osobe koja prima pošiljku + + + + Adresa + + + + adresa na koju će roba biti isporučena + + + + + Mjesto + + + + + {supportedPlaceOptions} + + + + + + Telefon + + + + +387 + + + broj mobitela - mora biti sa jedne od mreža u BiH + + + + E - mail + + + + E - mail adresa na koju će vam biti poslano obavještenje o narudžbi + + + + + + + + + {choosePayment} + + + + ); if(CartStore.isAddressColapsed()) { @@ -109,16 +204,27 @@ var CheckoutPage = React.createClass({ Ukupno:
Codes to check: <%= text_area_tag "codes", @codes_to_check, rows: 20, columns: 50 %>
<%= submit_tag "Check" %>
<%= submit_tag "Delete Items" %>
Not deleted items
+ CSV content goes here: + + <%= @csv_content %>
Task: + <%= select_tag "task", options_for_select(@tasks, @task) %>
<%= submit_tag "Run task" %>
Output area: +
broj mobitela - mora biti sa jedne od mreža u BiH
- Završi narudžbu ili Promijeni adresu -
{last_used_payment} ili Promijeni način plaćanja ili adresu
, or , or using non-SVG elements ' + - 'in an parent. Try inspecting the child nodes of the element ' + - 'with React ID `%s`.', - updatedIndex, - parentID - ) : invariant(updatedChild)); - - initialChildren = initialChildren || {}; - initialChildren[parentID] = initialChildren[parentID] || []; - initialChildren[parentID][updatedIndex] = updatedChild; - - updatedChildren = updatedChildren || []; - updatedChildren.push(updatedChild); - } - } - - var renderedMarkup = Danger.dangerouslyRenderMarkup(markupList); - - // Remove updated children first so that `toIndex` is consistent. - if (updatedChildren) { - for (var j = 0; j < updatedChildren.length; j++) { - updatedChildren[j].parentNode.removeChild(updatedChildren[j]); - } - } - - for (var k = 0; k < updates.length; k++) { - update = updates[k]; - switch (update.type) { - case ReactMultiChildUpdateTypes.INSERT_MARKUP: - insertChildAt( - update.parentNode, - renderedMarkup[update.markupIndex], - update.toIndex - ); - break; - case ReactMultiChildUpdateTypes.MOVE_EXISTING: - insertChildAt( - update.parentNode, - initialChildren[update.parentID][update.fromIndex], - update.toIndex - ); - break; - case ReactMultiChildUpdateTypes.TEXT_CONTENT: - setTextContent( - update.parentNode, - update.textContent - ); - break; - case ReactMultiChildUpdateTypes.REMOVE_NODE: - // Already removed by the for-loop above. - break; - } - } - } - -}; - -module.exports = DOMChildrenOperations; - -},{"13":13,"150":150,"165":165,"79":79}],11:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule DOMProperty - * @typechecks static-only - */ - -/*jslint bitwise: true */ - -'use strict'; - -var invariant = _dereq_(150); - -function checkMask(value, bitmask) { - return (value & bitmask) === bitmask; -} - -var DOMPropertyInjection = { - /** - * Mapping from normalized, camelcased property names to a configuration that - * specifies how the associated DOM property should be accessed or rendered. - */ - MUST_USE_ATTRIBUTE: 0x1, - MUST_USE_PROPERTY: 0x2, - HAS_SIDE_EFFECTS: 0x4, - HAS_BOOLEAN_VALUE: 0x8, - HAS_NUMERIC_VALUE: 0x10, - HAS_POSITIVE_NUMERIC_VALUE: 0x20 | 0x10, - HAS_OVERLOADED_BOOLEAN_VALUE: 0x40, - - /** - * Inject some specialized knowledge about the DOM. This takes a config object - * with the following properties: - * - * isCustomAttribute: function that given an attribute name will return true - * if it can be inserted into the DOM verbatim. Useful for data-* or aria-* - * attributes where it's impossible to enumerate all of the possible - * attribute names, - * - * Properties: object mapping DOM property name to one of the - * DOMPropertyInjection constants or null. If your attribute isn't in here, - * it won't get written to the DOM. - * - * DOMAttributeNames: object mapping React attribute name to the DOM - * attribute name. Attribute names not specified use the **lowercase** - * normalized name. - * - * DOMPropertyNames: similar to DOMAttributeNames but for DOM properties. - * Property names not specified use the normalized name. - * - * DOMMutationMethods: Properties that require special mutation methods. If - * `value` is undefined, the mutation method should unset the property. - * - * @param {object} domPropertyConfig the config as described above. - */ - injectDOMPropertyConfig: function(domPropertyConfig) { - var Properties = domPropertyConfig.Properties || {}; - var DOMAttributeNames = domPropertyConfig.DOMAttributeNames || {}; - var DOMPropertyNames = domPropertyConfig.DOMPropertyNames || {}; - var DOMMutationMethods = domPropertyConfig.DOMMutationMethods || {}; - - if (domPropertyConfig.isCustomAttribute) { - DOMProperty._isCustomAttributeFunctions.push( - domPropertyConfig.isCustomAttribute - ); - } - - for (var propName in Properties) { - ("production" !== "development" ? invariant( - !DOMProperty.isStandardName.hasOwnProperty(propName), - 'injectDOMPropertyConfig(...): You\'re trying to inject DOM property ' + - '\'%s\' which has already been injected. You may be accidentally ' + - 'injecting the same DOM property config twice, or you may be ' + - 'injecting two configs that have conflicting property names.', - propName - ) : invariant(!DOMProperty.isStandardName.hasOwnProperty(propName))); - - DOMProperty.isStandardName[propName] = true; - - var lowerCased = propName.toLowerCase(); - DOMProperty.getPossibleStandardName[lowerCased] = propName; - - if (DOMAttributeNames.hasOwnProperty(propName)) { - var attributeName = DOMAttributeNames[propName]; - DOMProperty.getPossibleStandardName[attributeName] = propName; - DOMProperty.getAttributeName[propName] = attributeName; - } else { - DOMProperty.getAttributeName[propName] = lowerCased; - } - - DOMProperty.getPropertyName[propName] = - DOMPropertyNames.hasOwnProperty(propName) ? - DOMPropertyNames[propName] : - propName; - - if (DOMMutationMethods.hasOwnProperty(propName)) { - DOMProperty.getMutationMethod[propName] = DOMMutationMethods[propName]; - } else { - DOMProperty.getMutationMethod[propName] = null; - } - - var propConfig = Properties[propName]; - DOMProperty.mustUseAttribute[propName] = - checkMask(propConfig, DOMPropertyInjection.MUST_USE_ATTRIBUTE); - DOMProperty.mustUseProperty[propName] = - checkMask(propConfig, DOMPropertyInjection.MUST_USE_PROPERTY); - DOMProperty.hasSideEffects[propName] = - checkMask(propConfig, DOMPropertyInjection.HAS_SIDE_EFFECTS); - DOMProperty.hasBooleanValue[propName] = - checkMask(propConfig, DOMPropertyInjection.HAS_BOOLEAN_VALUE); - DOMProperty.hasNumericValue[propName] = - checkMask(propConfig, DOMPropertyInjection.HAS_NUMERIC_VALUE); - DOMProperty.hasPositiveNumericValue[propName] = - checkMask(propConfig, DOMPropertyInjection.HAS_POSITIVE_NUMERIC_VALUE); - DOMProperty.hasOverloadedBooleanValue[propName] = - checkMask(propConfig, DOMPropertyInjection.HAS_OVERLOADED_BOOLEAN_VALUE); - - ("production" !== "development" ? invariant( - !DOMProperty.mustUseAttribute[propName] || - !DOMProperty.mustUseProperty[propName], - 'DOMProperty: Cannot require using both attribute and property: %s', - propName - ) : invariant(!DOMProperty.mustUseAttribute[propName] || - !DOMProperty.mustUseProperty[propName])); - ("production" !== "development" ? invariant( - DOMProperty.mustUseProperty[propName] || - !DOMProperty.hasSideEffects[propName], - 'DOMProperty: Properties that have side effects must use property: %s', - propName - ) : invariant(DOMProperty.mustUseProperty[propName] || - !DOMProperty.hasSideEffects[propName])); - ("production" !== "development" ? invariant( - !!DOMProperty.hasBooleanValue[propName] + - !!DOMProperty.hasNumericValue[propName] + - !!DOMProperty.hasOverloadedBooleanValue[propName] <= 1, - 'DOMProperty: Value can be one of boolean, overloaded boolean, or ' + - 'numeric value, but not a combination: %s', - propName - ) : invariant(!!DOMProperty.hasBooleanValue[propName] + - !!DOMProperty.hasNumericValue[propName] + - !!DOMProperty.hasOverloadedBooleanValue[propName] <= 1)); - } - } -}; -var defaultValueCache = {}; - -/** - * DOMProperty exports lookup objects that can be used like functions: - * - * > DOMProperty.isValid['id'] - * true - * > DOMProperty.isValid['foobar'] - * undefined - * - * Although this may be confusing, it performs better in general. - * - * @see http://jsperf.com/key-exists - * @see http://jsperf.com/key-missing - */ -var DOMProperty = { - - ID_ATTRIBUTE_NAME: 'data-reactid', - - /** - * Checks whether a property name is a standard property. - * @type {Object} - */ - isStandardName: {}, - - /** - * Mapping from lowercase property names to the properly cased version, used - * to warn in the case of missing properties. - * @type {Object} - */ - getPossibleStandardName: {}, - - /** - * Mapping from normalized names to attribute names that differ. Attribute - * names are used when rendering markup or with `*Attribute()`. - * @type {Object} - */ - getAttributeName: {}, - - /** - * Mapping from normalized names to properties on DOM node instances. - * (This includes properties that mutate due to external factors.) - * @type {Object} - */ - getPropertyName: {}, - - /** - * Mapping from normalized names to mutation methods. This will only exist if - * mutation cannot be set simply by the property or `setAttribute()`. - * @type {Object} - */ - getMutationMethod: {}, - - /** - * Whether the property must be accessed and mutated as an object property. - * @type {Object} - */ - mustUseAttribute: {}, - - /** - * Whether the property must be accessed and mutated using `*Attribute()`. - * (This includes anything that fails ` in `.) - * @type {Object} - */ - mustUseProperty: {}, - - /** - * Whether or not setting a value causes side effects such as triggering - * resources to be loaded or text selection changes. We must ensure that - * the value is only set if it has changed. - * @type {Object} - */ - hasSideEffects: {}, - - /** - * Whether the property should be removed when set to a falsey value. - * @type {Object} - */ - hasBooleanValue: {}, - - /** - * Whether the property must be numeric or parse as a - * numeric and should be removed when set to a falsey value. - * @type {Object} - */ - hasNumericValue: {}, - - /** - * Whether the property must be positive numeric or parse as a positive - * numeric and should be removed when set to a falsey value. - * @type {Object} - */ - hasPositiveNumericValue: {}, - - /** - * Whether the property can be used as a flag as well as with a value. Removed - * when strictly equal to false; present without a value when strictly equal - * to true; present with a value otherwise. - * @type {Object} - */ - hasOverloadedBooleanValue: {}, - - /** - * All of the isCustomAttribute() functions that have been injected. - */ - _isCustomAttributeFunctions: [], - - /** - * Checks whether a property name is a custom attribute. - * @method - */ - isCustomAttribute: function(attributeName) { - for (var i = 0; i < DOMProperty._isCustomAttributeFunctions.length; i++) { - var isCustomAttributeFn = DOMProperty._isCustomAttributeFunctions[i]; - if (isCustomAttributeFn(attributeName)) { - return true; - } - } - return false; - }, - - /** - * Returns the default property value for a DOM property (i.e., not an - * attribute). Most default values are '' or false, but not all. Worse yet, - * some (in particular, `type`) vary depending on the type of element. - * - * TODO: Is it better to grab all the possible properties when creating an - * element to avoid having to create the same element twice? - */ - getDefaultValueForProperty: function(nodeName, prop) { - var nodeDefaults = defaultValueCache[nodeName]; - var testElement; - if (!nodeDefaults) { - defaultValueCache[nodeName] = nodeDefaults = {}; - } - if (!(prop in nodeDefaults)) { - testElement = document.createElement(nodeName); - nodeDefaults[prop] = testElement[prop]; - } - return nodeDefaults[prop]; - }, - - injection: DOMPropertyInjection -}; - -module.exports = DOMProperty; - -},{"150":150}],12:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule DOMPropertyOperations - * @typechecks static-only - */ - -'use strict'; - -var DOMProperty = _dereq_(11); - -var quoteAttributeValueForBrowser = _dereq_(163); -var warning = _dereq_(171); - -function shouldIgnoreValue(name, value) { - return value == null || - (DOMProperty.hasBooleanValue[name] && !value) || - (DOMProperty.hasNumericValue[name] && isNaN(value)) || - (DOMProperty.hasPositiveNumericValue[name] && (value < 1)) || - (DOMProperty.hasOverloadedBooleanValue[name] && value === false); -} - -if ("production" !== "development") { - var reactProps = { - children: true, - dangerouslySetInnerHTML: true, - key: true, - ref: true - }; - var warnedProperties = {}; - - var warnUnknownProperty = function(name) { - if (reactProps.hasOwnProperty(name) && reactProps[name] || - warnedProperties.hasOwnProperty(name) && warnedProperties[name]) { - return; - } - - warnedProperties[name] = true; - var lowerCasedName = name.toLowerCase(); - - // data-* attributes should be lowercase; suggest the lowercase version - var standardName = ( - DOMProperty.isCustomAttribute(lowerCasedName) ? - lowerCasedName : - DOMProperty.getPossibleStandardName.hasOwnProperty(lowerCasedName) ? - DOMProperty.getPossibleStandardName[lowerCasedName] : - null - ); - - // For now, only warn when we have a suggested correction. This prevents - // logging too much when using transferPropsTo. - ("production" !== "development" ? warning( - standardName == null, - 'Unknown DOM property %s. Did you mean %s?', - name, - standardName - ) : null); - - }; -} - -/** - * Operations for dealing with DOM properties. - */ -var DOMPropertyOperations = { - - /** - * Creates markup for the ID property. - * - * @param {string} id Unescaped ID. - * @return {string} Markup string. - */ - createMarkupForID: function(id) { - return DOMProperty.ID_ATTRIBUTE_NAME + '=' + - quoteAttributeValueForBrowser(id); - }, - - /** - * Creates markup for a property. - * - * @param {string} name - * @param {*} value - * @return {?string} Markup string, or null if the property was invalid. - */ - createMarkupForProperty: function(name, value) { - if (DOMProperty.isStandardName.hasOwnProperty(name) && - DOMProperty.isStandardName[name]) { - if (shouldIgnoreValue(name, value)) { - return ''; - } - var attributeName = DOMProperty.getAttributeName[name]; - if (DOMProperty.hasBooleanValue[name] || - (DOMProperty.hasOverloadedBooleanValue[name] && value === true)) { - return attributeName; - } - return attributeName + '=' + quoteAttributeValueForBrowser(value); - } else if (DOMProperty.isCustomAttribute(name)) { - if (value == null) { - return ''; - } - return name + '=' + quoteAttributeValueForBrowser(value); - } else if ("production" !== "development") { - warnUnknownProperty(name); - } - return null; - }, - - /** - * Sets the value for a property on a node. - * - * @param {DOMElement} node - * @param {string} name - * @param {*} value - */ - setValueForProperty: function(node, name, value) { - if (DOMProperty.isStandardName.hasOwnProperty(name) && - DOMProperty.isStandardName[name]) { - var mutationMethod = DOMProperty.getMutationMethod[name]; - if (mutationMethod) { - mutationMethod(node, value); - } else if (shouldIgnoreValue(name, value)) { - this.deleteValueForProperty(node, name); - } else if (DOMProperty.mustUseAttribute[name]) { - // `setAttribute` with objects becomes only `[object]` in IE8/9, - // ('' + value) makes it output the correct toString()-value. - node.setAttribute(DOMProperty.getAttributeName[name], '' + value); - } else { - var propName = DOMProperty.getPropertyName[name]; - // Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the - // property type before comparing; only `value` does and is string. - if (!DOMProperty.hasSideEffects[name] || - ('' + node[propName]) !== ('' + value)) { - // Contrary to `setAttribute`, object properties are properly - // `toString`ed by IE8/9. - node[propName] = value; - } - } - } else if (DOMProperty.isCustomAttribute(name)) { - if (value == null) { - node.removeAttribute(name); - } else { - node.setAttribute(name, '' + value); - } - } else if ("production" !== "development") { - warnUnknownProperty(name); - } - }, - - /** - * Deletes the value for a property on a node. - * - * @param {DOMElement} node - * @param {string} name - */ - deleteValueForProperty: function(node, name) { - if (DOMProperty.isStandardName.hasOwnProperty(name) && - DOMProperty.isStandardName[name]) { - var mutationMethod = DOMProperty.getMutationMethod[name]; - if (mutationMethod) { - mutationMethod(node, undefined); - } else if (DOMProperty.mustUseAttribute[name]) { - node.removeAttribute(DOMProperty.getAttributeName[name]); - } else { - var propName = DOMProperty.getPropertyName[name]; - var defaultValue = DOMProperty.getDefaultValueForProperty( - node.nodeName, - propName - ); - if (!DOMProperty.hasSideEffects[name] || - ('' + node[propName]) !== defaultValue) { - node[propName] = defaultValue; - } - } - } else if (DOMProperty.isCustomAttribute(name)) { - node.removeAttribute(name); - } else if ("production" !== "development") { - warnUnknownProperty(name); - } - } - -}; - -module.exports = DOMPropertyOperations; - -},{"11":11,"163":163,"171":171}],13:[function(_dereq_,module,exports){ -/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule Danger - * @typechecks static-only - */ - -/*jslint evil: true, sub: true */ - -'use strict'; - -var ExecutionEnvironment = _dereq_(22); - -var createNodesFromMarkup = _dereq_(126); -var emptyFunction = _dereq_(129); -var getMarkupWrap = _dereq_(142); -var invariant = _dereq_(150); - -var OPEN_TAG_NAME_EXP = /^(<[^ \/>]+)/; -var RESULT_INDEX_ATTR = 'data-danger-index'; - -/** - * Extracts the `nodeName` from a string of markup. - * - * NOTE: Extracting the `nodeName` does not require a regular expression match - * because we make assumptions about React-generated markup (i.e. there are no - * spaces surrounding the opening tag and there is at least one attribute). - * - * @param {string} markup String of markup. - * @return {string} Node name of the supplied markup. - * @see http://jsperf.com/extract-nodename - */ -function getNodeName(markup) { - return markup.substring(1, markup.indexOf(' ')); -} - -var Danger = { - - /** - * Renders markup into an array of nodes. The markup is expected to render - * into a list of root nodes. Also, the length of `resultList` and - * `markupList` should be the same. - * - * @param {array} markupList List of markup strings to render. - * @return {array} List of rendered nodes. - * @internal - */ - dangerouslyRenderMarkup: function(markupList) { - ("production" !== "development" ? invariant( - ExecutionEnvironment.canUseDOM, - 'dangerouslyRenderMarkup(...): Cannot render markup in a worker ' + - 'thread. Make sure `window` and `document` are available globally ' + - 'before requiring React when unit testing or use ' + - 'React.renderToString for server rendering.' - ) : invariant(ExecutionEnvironment.canUseDOM)); - var nodeName; - var markupByNodeName = {}; - // Group markup by `nodeName` if a wrap is necessary, else by '*'. - for (var i = 0; i < markupList.length; i++) { - ("production" !== "development" ? invariant( - markupList[i], - 'dangerouslyRenderMarkup(...): Missing markup.' - ) : invariant(markupList[i])); - nodeName = getNodeName(markupList[i]); - nodeName = getMarkupWrap(nodeName) ? nodeName : '*'; - markupByNodeName[nodeName] = markupByNodeName[nodeName] || []; - markupByNodeName[nodeName][i] = markupList[i]; - } - var resultList = []; - var resultListAssignmentCount = 0; - for (nodeName in markupByNodeName) { - if (!markupByNodeName.hasOwnProperty(nodeName)) { - continue; - } - var markupListByNodeName = markupByNodeName[nodeName]; - - // This for-in loop skips the holes of the sparse array. The order of - // iteration should follow the order of assignment, which happens to match - // numerical index order, but we don't rely on that. - var resultIndex; - for (resultIndex in markupListByNodeName) { - if (markupListByNodeName.hasOwnProperty(resultIndex)) { - var markup = markupListByNodeName[resultIndex]; - - // Push the requested markup with an additional RESULT_INDEX_ATTR - // attribute. If the markup does not start with a < character, it - // will be discarded below (with an appropriate console.error). - markupListByNodeName[resultIndex] = markup.replace( - OPEN_TAG_NAME_EXP, - // This index will be parsed back out below. - '$1 ' + RESULT_INDEX_ATTR + '="' + resultIndex + '" ' - ); - } - } - - // Render each group of markup with similar wrapping `nodeName`. - var renderNodes = createNodesFromMarkup( - markupListByNodeName.join(''), - emptyFunction // Do nothing special with