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 %>

+<% if dd.gift %> +

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") %> -
-
+
+

<% if dd.instant_delivery %>

@@ -47,7 +59,7 @@ OVO JE NARUDŽBA ZA INSTANT DOSTAVU Total <% @record.item_in_carts.each do |iic| %> - + <%= iic.item.code %> <%= iic.item.name %> <%= iic.count %> @@ -70,15 +82,6 @@ OVO JE NARUDŽBA ZA INSTANT DOSTAVU <%= money(c.total) %> - +

- - - - - - - - - diff --git a/back-office/app/views/items/check_availability.html.erb b/back-office/app/views/items/check_availability.html.erb index 6ca3489..ceb2ab1 100644 --- a/back-office/app/views/items/check_availability.html.erb +++ b/back-office/app/views/items/check_availability.html.erb @@ -2,33 +2,48 @@

<%= select_tag "supplier_id", options_from_collection_for_select(@suppliers, "id", "name", @selected_supplier.try(:id)) %>

-


<%= 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 %> +


+

+ +

+ <%= select_tag "task", options_for_select(@tasks, @task) %>

+ +

<%= submit_tag "Run task" %>

+ + <% if !@error_message.nil? && @error_message != "" %> +
Error message: <%= @error_message %>
+ <% end %> + +
+

+

<%= 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 ()}); - + var content = (
- Dostava + Podaci o naručiocu
@@ -41,15 +70,15 @@ var CheckoutPage = React.createClass({ adresa na koju će roba biti isporučena
- +
@@ -78,22 +107,88 @@ var CheckoutPage = React.createClass({
-
- -
-
Roba:
- - Dostava:
- Ukupno: +
+ +
+
Roba:
+ + Dostava:
+ Ukupno:
-
-
-
+
+
+
+ +
- -
-
- ); + + + + + +
+ Podaci o dostavi + + +
+ + +
+ + + ime osobe koja prima pošiljku +
+
+
+ +
+ + + adresa na koju će roba biti isporučena +
+
+ +
+ +
+ + +
+
+
+ +
+ +
+ +387 + +
+

broj mobitela - mora biti sa jedne od mreža u BiH

+
+
+
+ +
+ + + 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:

-

- ili -

+ +
+ +
+ +

{last_used_payment} ili

+
+ +
+ + {last_used_payment} + +
+
-
+ ); } @@ -144,10 +250,15 @@ var CheckoutPage = React.createClass({ }, _onFieldChange: function (event) { - CartActions.changeDeliveryDestinationProperty(event.target.name, event.target.value); + if(event.target.name === "gift") { + CartActions.changeDeliveryDestinationProperty(event.target.name, $(event.target).is(':checked')); + } else { + CartActions.changeDeliveryDestinationProperty(event.target.name, event.target.value); + } }, - + _onOrderClick: function (event) { + CartActions.changeDeliveryDestinationProperty("payment_method", "cash_on_delivery"); CartActions.confirmDelivery(); }, @@ -155,6 +266,16 @@ var CheckoutPage = React.createClass({ CartActions.setAddressColapsed(false); }, + _onGiftBtnClicked: function (event) { + CartActions.changeDeliveryDestinationProperty('gift', true); + CartActions.setAddressColapsed(false); + }, + + _handleOnSubmitPaypal: function(event) { + CartActions.changeDeliveryDestinationProperty('payment_method', 'paypal'); + return false; + }, + getInitialState: function () { return CartStore.getWholeCartState(); } @@ -162,4 +283,4 @@ var CheckoutPage = React.createClass({ }); -module.exports = CheckoutPage; +module.exports = CheckoutPage; diff --git a/front-ui/app/components/payment/cashOnDeliveryButton.js b/front-ui/app/components/payment/cashOnDeliveryButton.js new file mode 100644 index 0000000..3febebe --- /dev/null +++ b/front-ui/app/components/payment/cashOnDeliveryButton.js @@ -0,0 +1,17 @@ +var React = require('react'); +var ItemActions = require('../../actions/itemActions'); +var NavigationActions = require('../../actions/navigationActions'); +var NavigationStore = require('../../stores/navigationStore'); + +var Globals = require('../../globals'); +var Router = require('react-router'); + +var CashOnDeliveryButton = React.createClass({ + render: function() { + return ( + + ); + } +}); + +module.exports = CashOnDeliveryButton; diff --git a/front-ui/app/components/payment/paymentSelect.js b/front-ui/app/components/payment/paymentSelect.js new file mode 100644 index 0000000..9ebbba4 --- /dev/null +++ b/front-ui/app/components/payment/paymentSelect.js @@ -0,0 +1,36 @@ +var React = require('react'); +var ItemActions = require('../../actions/itemActions'); +var NavigationActions = require('../../actions/navigationActions'); +var NavigationStore = require('../../stores/navigationStore'); + +var Globals = require('../../globals'); +var Router = require('react-router'); +var PaypalButton = require('./paypalButton'); +var PikpaylButton = require('./pikpayButton'); +var CashOnDeliveryButton = require('./cashOnDeliveryButton'); + +var PaymentSelect = React.createClass({ + render: function() { + var cashOnDeliveryBtn = ( ); + var pikpayBtn = ( ); + var paypalBtn = ( ); + + return ( +
+
+ {cashOnDeliveryBtn} + {pikpayBtn} + {paypalBtn} +
+ +
+ {cashOnDeliveryBtn} + {pikpayBtn} + {paypalBtn} +
+
); + } +}); + + +module.exports = PaymentSelect; diff --git a/front-ui/app/components/payment/paypalButton.js b/front-ui/app/components/payment/paypalButton.js new file mode 100644 index 0000000..2e78154 --- /dev/null +++ b/front-ui/app/components/payment/paypalButton.js @@ -0,0 +1,62 @@ +var React = require('react'); +var ItemActions = require('../../actions/itemActions'); +var NavigationActions = require('../../actions/navigationActions'); +var NavigationStore = require('../../stores/navigationStore'); +var CartStore = require('../../stores/cartStore'); +var CartActions = require('../../actions/cartActions'); + +var Globals = require('../../globals'); +var Router = require('react-router'); + +var PaypalButton = React.createClass({ + render: function() { + var deliveryDestination = JSON.stringify( + { + 'anonymous_id_string': this.props.deliveryDestination.get('anonymous_id_string'), + 'user_id': this.props.deliveryDestination.get('user_id') + }); + var amount = Globals.ConvertToEuro(this.props.amount); + var deliveryCost = Globals.ConvertToEuro(this.props.deliveryCost); + var root = location.protocol + '//' + location.host; + var return_url = root + "/hvala"; + var cancel_return_url = root + "/dostava"; + var notifyUrl = Globals.IsUrlAbsolute(Globals.ApiUrl) ? Globals.ApiUrl : root + Globals.ApiUrl; + notifyUrl += "/payment/confirmation"; + + return ( + + ); + }, + _onPaypalClick: function(e) { + CartActions.changeDeliveryDestinationProperty('payment_method', 'paypal'); + CartStore.saveDeliveryDestinationAnd(function() { + $("#paypal_form").submit(); + }); + } +}); + + +module.exports = PaypalButton; diff --git a/front-ui/app/components/payment/pikpayButton.js b/front-ui/app/components/payment/pikpayButton.js new file mode 100644 index 0000000..c8e7ee5 --- /dev/null +++ b/front-ui/app/components/payment/pikpayButton.js @@ -0,0 +1,64 @@ +var React = require('react'); +var ItemActions = require('../../actions/itemActions'); +var NavigationActions = require('../../actions/navigationActions'); +var NavigationStore = require('../../stores/navigationStore'); +var CartStore = require('../../stores/cartStore'); +var CartActions = require('../../actions/cartActions'); + +var Globals = require('../../globals'); +var Router = require('react-router'); +var sha1 = require('sha1'); + +var PikpayButton = React.createClass({ + render: function() { + var total = this.props.amount + this.props.deliveryCost; + total = total.toFixed(2) * 100; + var order_info = "Info"; + var order_number = this.props.cartId; + var key = Globals.PikpayKey; + var authenticity_token = Globals.PikpayAuthenticityToken; + var digest = sha1(key + order_number + total + "BAM"); + var rebate_digest = sha1(key + order_number + total + total + "BAM"); + var deliveryDestination = this.props.deliveryDestination; + var city = CartStore.getNameOfThePlace(deliveryDestination.get('place')); + var custom = JSON.stringify( + { + 'anonymous_id_string': deliveryDestination.get('anonymous_id_string'), + 'user_id': deliveryDestination.get('user_id') + }); + + return ( /* + */ + ); + }, + _onPikpayClick: function(e) { + CartActions.changeDeliveryDestinationProperty('payment_method', 'pikpay'); + CartStore.saveDeliveryDestinationAnd(function() { + $("#pikpay_form").submit(); + }); + } +}); + +module.exports = PikpayButton; diff --git a/front-ui/app/components/shared/searchBox.js b/front-ui/app/components/shared/searchBox.js index 37adbf2..46128ad 100644 --- a/front-ui/app/components/shared/searchBox.js +++ b/front-ui/app/components/shared/searchBox.js @@ -34,7 +34,8 @@ var SearchBox = React.createClass({ }, onKeyPress: function(e) { var enterKeyCode = 13; - if(e.which == enterKeyCode) { + var whichKey = e.key || e.which; + if(whichKey == enterKeyCode) { this.doSearch(); e.preventDefault(); } diff --git a/front-ui/app/css/checkout.css b/front-ui/app/css/checkout.css index 9740cf8..890ea86 100644 --- a/front-ui/app/css/checkout.css +++ b/front-ui/app/css/checkout.css @@ -1,5 +1,33 @@ .checkout_form_margin { margin-right: 10px !important; margin-left: 10px !important; - +} + +.payment-select-container { + text-align: center; +} + +.payment-select-desktop .payment-btn { + margin-right: 10px !important;; +} + +.payment-select-mobile .payment-btn { + margin-bottom: 6px !important; +} + +.collapsed-address-container .mybutton { + margin-top: 0px !important; + width: 240px; +} + +.collapsed-address-container-mobile button { + margin-bottom: 6px !important; + margin-left: 10% !important; + margin-right: 10% !important; + width: 80% !important; +} + +.gift-btn { + width: 200px; + margin-bottom: 10px; } diff --git a/front-ui/app/css/main.css b/front-ui/app/css/main.css index 1edc183..2cd010a 100644 --- a/front-ui/app/css/main.css +++ b/front-ui/app/css/main.css @@ -1,95 +1,77 @@ @import url(http://fonts.googleapis.com/css?family=Roboto:400,300italic,300,100italic,100,400italic,500,500italic,700italic,700,900,900italic); - /* site */ - html { - overflow-y: scroll; +overflow-y: scroll; } - body{ - background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813560/bg_prp6pz.jpg") fixed 100% 100% ; - background-size: cover ; - background-position: center ; - background-repeat: no-repeat; - font-family: 'Roboto', sans-serif; - +background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813560/bg_prp6pz.jpg") fixed 100% 100% ; +background-size: cover ; +background-position: center ; +background-repeat: no-repeat; +font-family: 'Roboto', sans-serif; } - #left-nave { - background: blue; position: absolute; top: 0px; left: 0px; height: 100%; width: 250px; - } .beba{ - background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/beba-bg_wip7kj.png"); - background-size: cover; +background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/beba-bg_wip7kj.png"); +background-size: cover; } - html, body { height: 100%; width: 100%; margin: 0; } - h1 h2 h3 h4 h5{ - font-family: 'Roboto', sans-serif; +font-family: 'Roboto', sans-serif; } - .col-centered{ - float: none; - margin: 5% auto; +float: none; +margin: 5% auto; } - - .mybutton{ - background-color: #00a8a8; - padding: 6px 11px !important; - border-radius: 3px; - margin-top: 8px; - border: solid #06c3c3 1px; - color: #ffffff !important; +background-color: #00a8a8; +padding: 6px 11px !important; +border-radius: 3px; +margin-top: 8px; +border: solid #06c3c3 1px; +color: #ffffff !important; } - - .mybutton:hover { - background-color: #06c3c3 !important; - padding: 6px 11px !important; - color: black !important; - border-radius: 3px; - margin-top: 8px; - border: solid #06c3c3 1px; - color: #ffffff !important; +background-color: #06c3c3 !important; +padding: 6px 11px !important; +color: black !important; +border-radius: 3px; +margin-top: 8px; +border: solid #06c3c3 1px; +color: #ffffff !important; } - .add-to-cart-button { - background-color: #00a8a8; - padding: 6px 11px !important; - border-radius: 3px; - border: solid #06c3c3 1px; - color: #ffffff !important; +background-color: #00a8a8; +padding: 6px 11px !important; +border-radius: 3px; +border: solid #06c3c3 1px; +color: #ffffff !important; } - .add-to-cart-button:hover { - background-color: #06c3c3 !important; - padding: 6px 11px !important; - color: black !important; - border-radius: 3px; - border: solid #06c3c3 1px; - color: #ffffff !important; +background-color: #06c3c3 !important; +padding: 6px 11px !important; +color: black !important; +border-radius: 3px; +border: solid #06c3c3 1px; +color: #ffffff !important; } - a{ - font-family: 'Roboto', sans-serif; - font-weight: bold; +font-family: 'Roboto', sans-serif; +font-weight: bold; } .mynav li{ - display: inline-block; +display: inline-block; } .mynav { margin-top: 6px; - - text-align: center; +text-align: center; } .mynav a{ text-decoration: none; @@ -108,27 +90,24 @@ text-decoration: none; color: #06c3c3; background: none; } - .mynav li .mybutton { - background-color: #00a8a8; - padding: 4px 10px !important; - margin-top: 6px; - display: block; - border-radius: 3px; - border: solid #06c3c3 1px; - color: #ffffff; +background-color: #00a8a8; +padding: 4px 10px !important; +margin-top: 6px; +display: block; +border-radius: 3px; +border: solid #06c3c3 1px; +color: #ffffff; } .mynav li .mybutton:hover { - background-color: #06c3c3; - padding: 4px 10px !important; - margin-top: 6px; - display: block; - border-radius: 3px; - border: solid #06c3c3 1px; - color: #ffffff; +background-color: #06c3c3; +padding: 4px 10px !important; +margin-top: 6px; +display: block; +border-radius: 3px; +border: solid #06c3c3 1px; +color: #ffffff; } - - .productbox{ cursor: pointer; -moz-box-shadow: 1px 2px 2px #cccccc; @@ -143,219 +122,181 @@ margin-bottom: 35px; height: auto; border-bottom: solid gray 1px; width: 100%; - - } .productbox div{ padding: 4px 10px; - - } .productbox a{ color: black; - text-decoration: none; - - +text-decoration: none; } .productbox a:hover{ color: black; - text-decoration: none; +text-decoration: none; text-decoration: underline; - } .productbox div span{ color: black; - - - } #beba{ - padding-left: 45px; - padding-right: 45px; - margin-left: -23px; - margin-right: -23px; - +padding-left: 45px; +padding-right: 45px; +margin-left: -23px; +margin-right: -23px; } #beba:hover{ background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/beba-bg_wip7kj.png"); background-position: center ; background-size: 100% 100%; color: white; - background-repeat: no-repeat; +background-repeat: no-repeat; } #dijete:hover{ background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/dijete-bg_gff80o.png"); background-position: center ; background-size: 100% 100%; color: white; - background-repeat: no-repeat; +background-repeat: no-repeat; } #dijete:hover a{ - color: white; - } #mama:hover{ background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/mama-bg_rqbnuo.png"); background-position: center ; background-size: 100% 100%; color: white; - background-repeat: no-repeat; +background-repeat: no-repeat; } #mama:hover a{ - color: white; - } #madeinbih:hover{ background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/made-in-bih-bg_uusqpm.png"); background-position: center ; background-size: 100% 100%; color: white; - background-repeat: no-repeat; +background-repeat: no-repeat; } #madeinbih:hover a{ - color: white; - } #onama:hover{ background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/made-in-bih-bg_uusqpm.png"); background-position: center ; background-size: 100% 100%; color: white; - background-repeat: no-repeat; +background-repeat: no-repeat; } #onama:hover a{ - color: white; - } - - #marka:hover{ background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/marka-bg_cy8hpe.png"); background-position: center ; background-size: 100% 100%; color: white; - background-repeat: no-repeat; +background-repeat: no-repeat; } #marka:hover a{ - color: white; - } #akcija:hover{ background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/akcija-bg_xtawus.png"); background-position: center ; background-size: 100% 100%; color: white; - background-repeat: no-repeat; +background-repeat: no-repeat; } #akcija:hover a{ - color: white; - } - .mydropdown li:hover .sub-menu { - visibility: visible; - display: block; +visibility: visible; +display: block; } - - .mydropdown:hover .dropdown-menu { - display: block; - visibility: visible; +display: block; +visibility: visible; } - .navbar-default{ color: black; background-color: white; border-color: lightgray; } .navbar-default .navbar-nav > li > a{ - color:black; - +color:black; } - .navbar-default .navbar-nav > li{ padding-left: 16px; padding-right: 16px; - } - - - .navbar-default .navbar-nav > li > a:focus{ - background-color: rgba(255, 255, 255, 0.6) !important; - -moz-box-shadow: 0px 0px 2px #999; +background-color: rgba(255, 255, 255, 0.6) !important; +-moz-box-shadow: 0px 0px 2px #999; -webkit-box-shadow: 0px 0px 2px #999; box-shadow: 0px 0px 2px #999; - border: none !important; border-radius: 2px; } - .navbar-default .navbar-nav > .dropdown > a .caret{ - border-top-color: #fff; - border-bottom-color: #fff; +border-top-color: #fff; +border-bottom-color: #fff; } .navbar-default .navbar-brand{ - color:#fff; +color:#fff; } .menu-large { - position: static !important; +position: static !important; } .megamenu{ - padding: 20px 0px; - width:94%; - margin-left: 3%; - border-top: none !important; - -moz-box-shadow: 0px 1px 2px #999; +padding: 20px 0px; +width:94%; +margin-left: 3%; +border-top: none !important; +-moz-box-shadow: 0px 1px 2px #999; -webkit-box-shadow: 0px 1px 2px #999; box-shadow: 0px 1px 2px #999; } .megamenu> li > ul { - padding: 0; - margin: 0; +padding: 0; +margin: 0; } .megamenu> li > ul > li { - list-style: none; - padding-left: 10px; +list-style: none; +padding-left: 10px; } .megamenu> li > ul > li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 1.428571429; - color: black; - white-space: normal; +display: block; +padding: 3px 20px; +clear: both; +font-weight: normal; +line-height: 1.428571429; +color: black; +white-space: normal; } .megamenu> li ul > li > a:hover, .megamenu> li ul > li > a:focus { - text-decoration: none; - color: #262626; - background-color: #f5f5f5; +text-decoration: none; +color: #262626; +background-color: #f5f5f5; } .megamenu.disabled > a, .megamenu.disabled > a:hover, .megamenu.disabled > a:focus { - color: #999999; +color: #999999; } .megamenu.disabled > a:hover, .megamenu.disabled > a:focus { - text-decoration: none; - background-color: transparent; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - cursor: not-allowed; +text-decoration: none; +background-color: transparent; +background-image: none; +filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +cursor: not-allowed; } .megamenu.dropdown-header { - color: #428bca; - font-size: 18px; - +color: #428bca; +font-size: 18px; } .dropdown-header p { border-bottom: solid 1.5px lightgray !important; @@ -363,193 +304,188 @@ margin-bottom: 2px; text-transform: uppercase; color: black; font-family: 'Roboto', sans-serif; - font-weight: 500; - font-size: 16px; +font-weight: 500; +font-size: 16px; } .mycart{ - position: absolute; +position: absolute; background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/cart-icon_p9u63r.png"); background-position: center center; background-size: cover; - - background-repeat: no-repeat; - width: 42px; - height: 44px; - padding-bottom: 0px; - right:-14px; - bottom: -15px; - text-align: right; - +background-repeat: no-repeat; +width: 42px; +height: 44px; +padding-bottom: 0px; +right:-14px; +bottom: -15px; +text-align: right; } .mycart div { - text-align: right; +text-align: right; } .mycart span{ position: absolute; left: 13.4px; top: 4px; color: #ffffff; - } .mycart span:hover{ - color: white; } #myhome{ - border-bottom: solid #06c3c3 1px; +border-bottom: solid #06c3c3 1px; } - #myhome-hover:hover { - border-bottom: solid #06c3c3 1px; - +#myhome-hover:hover { +border-bottom: solid #06c3c3 1px; } - - - .sidebar ul{ - list-style: none; +list-style: none; } .sidebar li{ - margin-left:10px; - margin-bottom: 5px; - color:black; - padding: 10px; - border-bottom: solid 1px lightgray; - +margin-left:10px; +margin-bottom: 5px; +color:black; +padding: 10px; +border-bottom: solid 1px lightgray; } .sidebar li a{ text-decoration: none; - color:black; +color:black; } .sidebar li a:hover{ - - color:#06c3c3; +color:#06c3c3; } - @media (max-width: 768px) { - .megamenu{ - margin-left: 0 ; - margin-right: 0 ; - } - .megamenu> li { - margin-bottom: 30px; - } - .megamenu> li:last-child { - margin-bottom: 0; - } - .megamenu.dropdown-header { - padding: 3px 15px !important; - - } - .navbar-nav .open .dropdown-menu .dropdown-header{ - color:#fff; - } +.megamenu{ +margin-left: 0 ; +margin-right: 0 ; +} +.megamenu> li { +margin-bottom: 30px; +} +.megamenu> li:last-child { +margin-bottom: 0; +} +.megamenu.dropdown-header { +padding: 3px 15px !important; +} +.navbar-nav .open .dropdown-menu .dropdown-header{ +color:#fff; +} } - - @media (max-width: 1000px) { - .navbar-header { - float: none; - } - .navbar-toggle { - display: block; - - } - .navbar-collapse { - - box-shadow: inset 0 1px 0 rgba(255,255,255,0.1); - border-top:none !important; - } - .navbar-collapse.collapse { - display: none!important; - } - - .navbar-nav>li { - float: none; - - } - .navbar-nav>ul { - margin-bottom: 0px; - - } - .navbar-nav>li>a { - padding-top: 10px; - padding-bottom: 10px; - - } - .navbar-text { - float: none; - margin: 15px 0; - } - /* since 3.1.0 */ - .navbar-collapse.collapse.in { - display: block!important; - } - .collapsing { - overflow: hidden!important; - } +.navbar-header { +float: none; +} +.navbar-toggle { +display: block; +} +.navbar-collapse { +box-shadow: inset 0 1px 0 rgba(255,255,255,0.1); +border-top:none !important; +} +.navbar-collapse.collapse { +display: none!important; +} +.navbar-nav>li { +float: none; +} +.navbar-nav>ul { +margin-bottom: 0px; +} +.navbar-nav>li>a { +padding-top: 10px; +padding-bottom: 10px; +} +.navbar-text { +float: none; +margin: 15px 0; +} +/* since 3.1.0 */ +.navbar-collapse.collapse.in { +display: block!important; +} +.collapsing { +overflow: hidden!important; +} } - .error-message { - color: red; +color: red; } - .white_button{ - background-color: #f9f9f9; - width: 40px; - height: 40px; - padding-bottom: 7px; - /*display: block;*/ - border-radius: 1px; - margin-top: 8px; - border: solid #cccccc 1px; - color: #4d4d4d !important; - font-size: 16px; +background-color: #f9f9f9; +width: 40px; +height: 40px; +padding-bottom: 7px; +/*display: block;*/ +border-radius: 1px; +margin-top: 8px; +border: solid #cccccc 1px; +color: #4d4d4d !important; +font-size: 16px; } - .search-box { - background-color: #efefef; - border-radius: 0px; - border: 1px solid #cccccc; - padding-left: 10px; - padding-right: 10px; +background-color: #efefef; +border-radius: 0px; +border: 1px solid #cccccc; +padding-left: 10px; +padding-right: 10px; } - .search-button { - background-color: #f9f9f9; - color: #31b8b8; - border: 1px solid #cccccc; - border-radius: 0px; - margin-left: 5px; +background-color: #f9f9f9; +color: #31b8b8; +border: 1px solid #cccccc; +border-radius: 0px; +margin-left: 5px; } - .qty-box { - background-color: #efefef; - border-radius: 0px; - border: 1px solid #cccccc; +background-color: #efefef; +border-radius: 0px; +border: 1px solid #cccccc; } - .left-inner-addon { - position: relative; +position: relative; } .left-inner-addon input { - padding-left: 30px; +padding-left: 30px; } .left-inner-addon i { - position: absolute; - padding: 10px 12px; - pointer-events: none; +position: absolute; +padding: 10px 12px; +pointer-events: none; } - .cart-title { - font-size: 29px; - color: #06c3c3; +font-size: 29px; +color: #06c3c3; } .footer { - text-align: center; - width: 100%; - bottom: 20px; - margin-bottom: 20px; +text-align: center; +width: 100%; +bottom: 20px; +margin-bottom: 20px; +} + +.message { +font-size: 130%; +text-align: left; +margin-left: 20px; +margin-right: 20px; +} + +.payment { +box-sizing: border-box; +color: rgb(102, 102, 102); +display: block; +font-family: Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol'; +font-size: 13px; +font-style: normal; +font-variant: normal; +font-weight: normal; +height: 36px; +line-height: 18.2px; +width: 708px; +word-wrap: break-word; } .message { @@ -568,3 +504,4 @@ text-decoration: none; } + diff --git a/front-ui/app/globals.js b/front-ui/app/globals.js index e5b14ec..c4000ef 100644 --- a/front-ui/app/globals.js +++ b/front-ui/app/globals.js @@ -1,8 +1,11 @@ +var conversionRate = 0.51; + module.exports = { ApiUrl: '@@apiEndpoint', DefaultPageSize: 24, ItemGroupIdOfStartPage: "1", ItemGroupIdOfEmptyCartPage: "1", + BamToEuroConversionRate: conversionRate, FormatCurrency: function(amount_s) { var amount = parseFloat(amount_s); return (amount.toFixed(2) + " KM") @@ -11,7 +14,19 @@ module.exports = { var amount = parseFloat(amount_s); return (amount.toFixed(2) + "%") }, + ConvertToEuro: function(amount_s) { + amount = amount_s * conversionRate; + return amount.toFixed(2); + }, + IsUrlAbsolute: function(url) { + var r = new RegExp('^(?:[a-z]+:)?//', 'i'); + return r.test(url); + }, MaxNumberOfItemsToBeAdded: 1000, + PaypalId: "W7GKS2Q9ZGLGY", + PikpayFormUrl: "https://ipgtest.pikpay.ba/form", + PikpayAuthenticityToken: "1bb1eea16bd6492c01262636897c0c2e3291a1ab", + PikpayKey: "Ribica", Slugify: function(text) { return text.toString().toLowerCase() diff --git a/front-ui/app/models/deliveryDestination.js b/front-ui/app/models/deliveryDestination.js index 52712d2..5875372 100644 --- a/front-ui/app/models/deliveryDestination.js +++ b/front-ui/app/models/deliveryDestination.js @@ -14,9 +14,9 @@ var DeliveryDestination = Backbone.Model.extend({ }, url: Globals.ApiUrl + '/cart/delivery_destination', - defaults: { + defaults: { count: 0 } }); -module.exports = DeliveryDestination; \ No newline at end of file +module.exports = DeliveryDestination; diff --git a/front-ui/app/router.js b/front-ui/app/router.js index 1535f93..8946f9e 100644 --- a/front-ui/app/router.js +++ b/front-ui/app/router.js @@ -48,8 +48,8 @@ var router = Router.create({ location: Router.HistoryLocation }); -router.run(function (Handler, state) { - +router.run(function (Handler, state) { + }); diff --git a/front-ui/app/stores/cartStore.js b/front-ui/app/stores/cartStore.js index 7fc61a4..ad22b17 100644 --- a/front-ui/app/stores/cartStore.js +++ b/front-ui/app/stores/cartStore.js @@ -10,6 +10,7 @@ var DeliveryDestination = require('../models/deliveryDestination'); var OrderConfirmation = require('../models/orderConfirmation'); var Place = require('../models/place'); var Validation = require('../utils/validation'); +var Cart = require('../models/cart'); var _ = require('underscore'); @@ -23,11 +24,12 @@ var _deliveryDestination = new DeliveryDestination(); var _deliveryDestinationErrors = {}; var _deliveryCosts = new Place({ postalCode: _deliveryDestination.get('place') -}) +}); + +var _cart = new Cart(); var _addressColapsed = false; - var supportedPlaces = [{ "code": "-12", "placeLabel": "Izaberite mjesto" @@ -37,7 +39,7 @@ var supportedPlaces = [{ }, { "code": " 71000", "placeLabel": "Sarajevo" - }, + }, { "code": " 78000", "placeLabel": "Banja Luka" @@ -50,7 +52,7 @@ var supportedPlaces = [{ { "code": " 88000", "placeLabel": "Mostar" - }, + }, { "code": " 72000", @@ -2569,30 +2571,31 @@ var nameOfThePlace = function(code) { } var loadCart = function() { + _cart.fetch({success: function() { + _itemsInCart.fetch({ + success: function() { + states = {} + for (var i = 0; i < _itemsInCart.models.length; i++) { + var itemInCart = _itemsInCart.models[i]; + states[itemInCart.get('item_id')] = itemInCart; + } + _itemsForDisplay.fetch({ + success: function() { + CartActions.dataLoaded(); - _itemsInCart.fetch({ - success: function() { - states = {} - for (var i = 0; i < _itemsInCart.models.length; i++) { - var itemInCart = _itemsInCart.models[i]; - states[itemInCart.get('item_id')] = itemInCart; - } - _itemsForDisplay.fetch({ - success: function() { - CartActions.dataLoaded(); - - _deliveryDestination.fetch({ - success: function() { - validateDeliveryDestinationForm(); - collapseAddressIfNeeded(); - fetchPlace(); - CartActions.dataLoaded(); - } - }); - } - }); - } - }); + _deliveryDestination.fetch({ + success: function() { + validateDeliveryDestinationForm(); + collapseAddressIfNeeded(); + fetchPlace(); + CartActions.dataLoaded(); + } + }); + } + }); + } + }); + }}); _cartDataLoadCalled = true; }; @@ -2606,8 +2609,10 @@ var collapseAddressIfNeeded = function() { } var fetchPlace = function() { + postalCode = _deliveryDestination.get('gift') ? _deliveryDestination.get('recipient_place') : _deliveryDestination.get('place'); + _deliveryCosts = new Place({ - postalCode: _deliveryDestination.get('place') + postalCode: postalCode }) _deliveryCosts.fetch({ success: function() { @@ -2696,13 +2701,13 @@ var addNItems = function(item, count) { var changeDeliveryDestinationProperty = function(property, value) { _deliveryDestination.set(property, value); - if (property === 'place') { + if (property === 'place' || property === 'recipient_place' || property === 'gift') { fetchPlace(); } + validateDeliveryDestinationForm(); }; - var confirmOrder = function() { var oc = new OrderConfirmation({ @@ -2719,14 +2724,20 @@ var confirmOrder = function() { }); }; +var saveDeliveryDestinationAnd = function(successCallback) { + _deliveryDestination.save(null, { + success: function() { + successCallback(); + } + }) +}; var saveDeliveryDestination = function() { _deliveryDestination.save(null, { - success: function() { - - confirmOrder(); - } - }) + success: function() { + confirmOrder(); + } + }); }; var validateDeliveryDestinationForm = function() { @@ -2757,7 +2768,34 @@ var validateDeliveryDestinationForm = function() { _deliveryDestinationErrors['place'] = "Mjesto mora biti izabrano"; } + if(_deliveryDestination.get('gift')) { + if (Validation.safeString(_deliveryDestination.get('recipient_name')).search(nameRegex) < 0) { + _deliveryDestinationErrors['recipient_name'] = "I prezime i ime su obavezni"; + } + + if (Validation.safeString(_deliveryDestination.get('recipient_address')).search(addressRegex) < 0) { + _deliveryDestinationErrors['recipient_address'] = "Adresa mora biti ispravna"; + } + + if (_deliveryDestination.get('recipient_email') && Validation.safeString(_deliveryDestination.get('recipient_email')).search(emailRegex) < 0) { + _deliveryDestinationErrors['recipient_email'] = "Email mora biti ispravno upisan"; + } + + if (Validation.safeString(_deliveryDestination.get('recipient_phone')).search(phoneRegex) < 0) { + _deliveryDestinationErrors['recipient_phone'] = "Telefon mora biti ispravan"; + } + + if (Validation.safeString(_deliveryDestination.get('recipient_place')).search(placeRegex) < 0) { + _deliveryDestinationErrors['recipient_place'] = "Mjesto mora biti izabrano"; + } + } + var requiredFields = ["name", "email", "place", 'address', 'phone']; + + if(_deliveryDestination.get('gift')) { + requiredFields = requiredFields.concat(["recipient_name", "recipient_place", 'recipient_address', 'recipient_phone']); + } + for (var i in requiredFields) { var value = _deliveryDestination.get(requiredFields[i]); if (value === undefined || value === null || value === "") { @@ -2768,10 +2806,14 @@ var validateDeliveryDestinationForm = function() { } - var isDeliveryDestinationValid = function() { - return Object.getOwnPropertyNames(_deliveryDestinationErrors).length === 0; - } + return Object.getOwnPropertyNames(_deliveryDestinationErrors).length === 0; +} + +var getDeliveryCostTarget = function() { + return _deliveryDestination.get("gift") ? "recipient_place" : "place"; +} + // Extend CartStore with EventEmitter to add eventing capabilities var CartStore = _.extend({}, EventEmitter.prototype, { @@ -2792,6 +2834,29 @@ var CartStore = _.extend({}, EventEmitter.prototype, { return supportedPlaces; }, + getAmount: function() { + var counts = states; + + var total = 0; + + if (counts && _itemsForDisplay) { + var items = _itemsForDisplay.models; + for (var i = 0; i < items.length; i++) { + var item = items[i]; + var count = counts[item.get('id')].get('count'); + var price = item.get('list_price'); + total += (price * count) + }; + } + + return total; + }, + + getDeliveryCost: function(instantDelivery) { + return instantDelivery ? Number(_deliveryCosts.get('instant_delivery_price')) + : Number(_deliveryCosts.get('delivery_price')); + }, + getWholeCartState: function() { var numberOfItems = 0; @@ -2813,7 +2878,10 @@ var CartStore = _.extend({}, EventEmitter.prototype, { deliveryDestinationErrors: _deliveryDestinationErrors, isDeliveryDestinationValid: isDeliveryDestinationValid(), deliveryCosts: _deliveryCosts, - destinationValid: isDeliveryDestinationValid() + destinationValid: isDeliveryDestinationValid(), + address_colapsed: _addressColapsed, + deliveryCostsTarget: getDeliveryCostTarget(), + cart: _cart }; return state; }, @@ -2850,6 +2918,14 @@ var CartStore = _.extend({}, EventEmitter.prototype, { address.push(_deliveryDestination.get('email')) return address; + }, + + getNameOfThePlace: function(code) { + return nameOfThePlace(code); + }, + + saveDeliveryDestinationAnd: function(successCallback) { + saveDeliveryDestinationAnd(successCallback); } }); diff --git a/front-ui/build/index.html b/front-ui/build/index.html index d16df13..0d191d1 100644 --- a/front-ui/build/index.html +++ b/front-ui/build/index.html @@ -1,7 +1,7 @@ - ribica.ba - ispunjava zelje + ribica.ba - prvi online shop sa dostavom u BiH za mame i bebe - kupite igracke, pelene i ostalo diff --git a/front-ui/package.json b/front-ui/package.json index 17c3a04..596565c 100644 --- a/front-ui/package.json +++ b/front-ui/package.json @@ -31,6 +31,7 @@ "react": "~0.12.2", "react-google-analytics": "^0.2.0", "react-router": "~0.11.6", + "sha1": "^1.1.1", "superagent": "~0.21.0" } } diff --git a/node_modules/ga-react-router/README.md b/node_modules/ga-react-router/README.md deleted file mode 100644 index 041ef81..0000000 --- a/node_modules/ga-react-router/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Google analytics for react-router - -## How to use - -1. `npm install ga-react-router` -2. In your `webpack.config.js` add `new webpack.DefinePlugin({GA_TRACKING_CODE: JSON.stringify('XXXXXXXX')})` -3. Use analytics in your `Router.run` code. - -## Example - -```js -'use strict'; -var React = require('react'); -var Router = require('react-router'); -var analytics = require('ga-react-router'); - -var routes = require('./routes'); - -Router.run(routes, Router.HistoryLocation, function(Handler, state) { - React.render(, document.getElementById('content')); - analytics(state); -}); -``` diff --git a/node_modules/ga-react-router/package.json b/node_modules/ga-react-router/package.json deleted file mode 100644 index 3687066..0000000 --- a/node_modules/ga-react-router/package.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "name": "ga-react-router", - "version": "1.3.0", - "description": "Google analytics component for react-router", - "main": "src/index.js", - "peerDependencies": { - "react-router": "0.11.x - 0.13.x" - }, - "author": { - "name": "Thomas Coopman @tcoopman" - }, - "repository": { - "type": "git", - "url": "git+ssh://git@github.com/tcoopman/ga-react-router.git" - }, - "licenses": [ - { - "type": "MIT", - "url": "http://www.opensource.org/licenses/mit-license.php" - } - ], - "gitHead": "bd1a0c6d97aef76c38690de4278dec63da13fe9c", - "bugs": { - "url": "https://github.com/tcoopman/ga-react-router/issues" - }, - "homepage": "https://github.com/tcoopman/ga-react-router#readme", - "_id": "ga-react-router@1.3.0", - "scripts": {}, - "_shasum": "28f51f27d5b0339db55499adf82265033cedc939", - "_from": "ga-react-router@*", - "_npmVersion": "2.8.3", - "_nodeVersion": "1.8.1", - "_npmUser": { - "name": "tcoopman", - "email": "thomas.coopman@gmail.com" - }, - "maintainers": [ - { - "name": "tcoopman", - "email": "thomas.coopman@gmail.com" - } - ], - "dist": { - "shasum": "28f51f27d5b0339db55499adf82265033cedc939", - "tarball": "http://registry.npmjs.org/ga-react-router/-/ga-react-router-1.3.0.tgz" - }, - "directories": {}, - "_resolved": "https://registry.npmjs.org/ga-react-router/-/ga-react-router-1.3.0.tgz", - "readme": "ERROR: No README data found!" -} diff --git a/node_modules/ga-react-router/src/ga.js b/node_modules/ga-react-router/src/ga.js deleted file mode 100644 index 1d02120..0000000 --- a/node_modules/ga-react-router/src/ga.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; -if(typeof window !== 'undefined' && typeof GA_TRACKING_CODE !== 'undefined') { - (function(window, document, script, url, r, tag, firstScriptTag) { - window['GoogleAnalyticsObject']=r; - window[r] = window[r] || function() { - (window[r].q = window[r].q || []).push(arguments) - }; - window[r].l = 1*new Date(); - tag = document.createElement(script), - firstScriptTag = document.getElementsByTagName(script)[0]; - tag.async = 1; - tag.src = url; - firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); - })( - window, - document, - 'script', - '//www.google-analytics.com/analytics.js', - 'ga' - ); - - var ga = window.ga; - - ga('create', GA_TRACKING_CODE, 'auto'); - - module.exports = function() { - return window.ga.apply(window.ga, arguments); - }; -} else { - module.exports = function() {console.log(arguments)}; -} diff --git a/node_modules/ga-react-router/src/index.js b/node_modules/ga-react-router/src/index.js deleted file mode 100644 index 08c2da4..0000000 --- a/node_modules/ga-react-router/src/index.js +++ /dev/null @@ -1,12 +0,0 @@ -'use strict'; -var ga = require('./ga'); - - -function analytics(state) { - ga('send', 'pageview', { - 'page': state.path - }); -} - - -module.exports = analytics; diff --git a/node_modules/react-router/README.md b/node_modules/react-router/README.md deleted file mode 100644 index 985c291..0000000 --- a/node_modules/react-router/README.md +++ /dev/null @@ -1,140 +0,0 @@ -[![npm package](https://img.shields.io/npm/v/react-router.svg?style=flat-square)](https://www.npmjs.org/package/react-router) -[![build status](https://img.shields.io/travis/rackt/react-router/master.svg?style=flat-square)](https://travis-ci.org/rackt/react-router) -[![dependency status](https://img.shields.io/david/rackt/react-router.svg?style=flat-square)](https://david-dm.org/rackt/react-router) -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/rackt/react-router?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - -React Router -============ - -A complete routing library for React. - -Docs ----- - -- [Guide: Overview](/docs/guides/overview.md) -- [API](/docs/api/) - -Important Notes ---------------- - -### SemVer - -Before our `1.0` release, breaking API changes will cause a bump to -`0.x`. For example, `0.4.1` and `0.4.8` will have the same API, but -`0.5.0` will have breaking changes. - -Please refer to the [upgrade guide](/UPGRADE_GUIDE.md) and -[changelog](/CHANGELOG.md) when upgrading. - -Installation ------------- - -```sh -npm install react-router -# or -bower install react-router -``` - -This library is written with CommonJS modules. If you are using -browserify, webpack, or similar, you can consume it like anything else -installed from npm. - -There is also a global build available on bower, find the library on -`window.ReactRouter`. - -The library is also available on the popular CDN [cdnjs](https://cdnjs.com/libraries/react-router). - -Features --------- - -- Nested views mapped to nested routes -- Modular construction of route hierarchy -- Sync and async transition hooks -- Transition abort / redirect / retry -- Dynamic segments -- Query parameters -- Links with automatic `.active` class when their route is active -- Multiple root routes -- Hash or HTML5 history (with fallback) URLs -- Declarative Redirect routes -- Declarative NotFound routes -- Browser scroll behavior with transitions - -Check out the `examples` directory to see how simple previously complex UI -and workflows are to create. - -What's it look like? --------------------- - -```js -var routes = ( - - - - - - - - - - - -); - -Router.run(routes, function (Handler) { - React.render(, document.body); -}); - -// Or, if you'd like to use the HTML5 history API for cleaner URLs: - -Router.run(routes, Router.HistoryLocation, function (Handler) { - React.render(, document.body); -}); -``` - -See more in the [overview guide](/docs/guides/overview.md). - -Benefits of this Approach -------------------------- - -1. **Incredible screen-creation productivity** - There is only one - use-case when a user visits a route: render something. Every user - interface has layers (or nesting) whether it's a simple navbar or - multiple levels of master-detail. Coupling nested routes to these - nested views gets rid of a ton of work for the developer to wire all - of it together when the user switches routes. Adding new screens - could not get faster. - -2. **Immediate understanding of application structure** - When routes - are declared in one place, developers can easily construct a mental - image of the application. It's essentially a sitemap. There's not a - better way to get so much information about your app this quickly. - -3. **Code tractability** - When a developer gets a ticket to fix a bug - at as specific url they simply 1) look at the route config, then 2) - go find the handler for that route. Every entry point into your - application is represented by these routes. - -4. **URLs are your first thought, not an after-thought** - With React - Router, you don't get UI on the page without configuring a url first. - Fortunately, it's wildly productive this way, too. - -Related Modules ---------------- - -- [rnr-constrained-route](https://github.com/bjyoungblood/rnr-constrained-route) - validate paths - and parameters on route handlers. -- [react-router-bootstrap](https://github.com/mtscout6/react-router-bootstrap) - Integration with [react-bootstrap](https://github.com/react-bootstrap/react-bootstrap) components. -- [react-router-proxy-loader](https://github.com/odysseyscience/react-router-proxy-loader) - A Webpack loader to dynamically load react-router components on-demand - -Contributing ------------- - -Please see [CONTRIBUTING](CONTRIBUTING.md) - -Thanks, Ember -------------- - -This library is highly inspired by the Ember.js routing API. In general, -it's a translation of the Ember router api to React. Huge thanks to the -Ember team for solving the hardest part already. diff --git a/node_modules/react-router/lib/Cancellation.js b/node_modules/react-router/lib/Cancellation.js deleted file mode 100644 index bcce202..0000000 --- a/node_modules/react-router/lib/Cancellation.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Represents a cancellation caused by navigating away - * before the previous transition has fully resolved. - */ -"use strict"; - -function Cancellation() {} - -module.exports = Cancellation; \ No newline at end of file diff --git a/node_modules/react-router/lib/History.js b/node_modules/react-router/lib/History.js deleted file mode 100644 index f6194ad..0000000 --- a/node_modules/react-router/lib/History.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; - -var invariant = require('react/lib/invariant'); -var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM; - -var History = { - - /** - * The current number of entries in the history. - * - * Note: This property is read-only. - */ - length: 1, - - /** - * Sends the browser back one entry in the history. - */ - back: function back() { - invariant(canUseDOM, 'Cannot use History.back without a DOM'); - - // Do this first so that History.length will - // be accurate in location change listeners. - History.length -= 1; - - window.history.back(); - } - -}; - -module.exports = History; \ No newline at end of file diff --git a/node_modules/react-router/lib/Match.js b/node_modules/react-router/lib/Match.js deleted file mode 100644 index ec5f8bc..0000000 --- a/node_modules/react-router/lib/Match.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }; - -var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - -/* jshint -W084 */ -var PathUtils = require('./PathUtils'); - -function deepSearch(route, pathname, query) { - // Check the subtree first to find the most deeply-nested match. - var childRoutes = route.childRoutes; - if (childRoutes) { - var match, childRoute; - for (var i = 0, len = childRoutes.length; i < len; ++i) { - childRoute = childRoutes[i]; - - if (childRoute.isDefault || childRoute.isNotFound) continue; // Check these in order later. - - if (match = deepSearch(childRoute, pathname, query)) { - // A route in the subtree matched! Add this route and we're done. - match.routes.unshift(route); - return match; - } - } - } - - // No child routes matched; try the default route. - var defaultRoute = route.defaultRoute; - if (defaultRoute && (params = PathUtils.extractParams(defaultRoute.path, pathname))) { - return new Match(pathname, params, query, [route, defaultRoute]); - } // Does the "not found" route match? - var notFoundRoute = route.notFoundRoute; - if (notFoundRoute && (params = PathUtils.extractParams(notFoundRoute.path, pathname))) { - return new Match(pathname, params, query, [route, notFoundRoute]); - } // Last attempt: check this route. - var params = PathUtils.extractParams(route.path, pathname); - if (params) { - return new Match(pathname, params, query, [route]); - }return null; -} - -var Match = (function () { - function Match(pathname, params, query, routes) { - _classCallCheck(this, Match); - - this.pathname = pathname; - this.params = params; - this.query = query; - this.routes = routes; - } - - _createClass(Match, null, [{ - key: 'findMatch', - - /** - * Attempts to match depth-first a route in the given route's - * subtree against the given path and returns the match if it - * succeeds, null if no match can be made. - */ - value: function findMatch(routes, path) { - var pathname = PathUtils.withoutQuery(path); - var query = PathUtils.extractQuery(path); - var match = null; - - for (var i = 0, len = routes.length; match == null && i < len; ++i) match = deepSearch(routes[i], pathname, query); - - return match; - } - }]); - - return Match; -})(); - -module.exports = Match; \ No newline at end of file diff --git a/node_modules/react-router/lib/Navigation.js b/node_modules/react-router/lib/Navigation.js deleted file mode 100644 index 023720c..0000000 --- a/node_modules/react-router/lib/Navigation.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -var PropTypes = require('./PropTypes'); - -/** - * A mixin for components that modify the URL. - * - * Example: - * - * var MyLink = React.createClass({ - * mixins: [ Router.Navigation ], - * handleClick(event) { - * event.preventDefault(); - * this.transitionTo('aRoute', { the: 'params' }, { the: 'query' }); - * }, - * render() { - * return ( - * Click me! - * ); - * } - * }); - */ -var Navigation = { - - contextTypes: { - router: PropTypes.router.isRequired - }, - - /** - * Returns an absolute URL path created from the given route - * name, URL parameters, and query values. - */ - makePath: function makePath(to, params, query) { - return this.context.router.makePath(to, params, query); - }, - - /** - * Returns a string that may safely be used as the href of a - * link to the route with the given name. - */ - makeHref: function makeHref(to, params, query) { - return this.context.router.makeHref(to, params, query); - }, - - /** - * Transitions to the URL specified in the arguments by pushing - * a new URL onto the history stack. - */ - transitionTo: function transitionTo(to, params, query) { - this.context.router.transitionTo(to, params, query); - }, - - /** - * Transitions to the URL specified in the arguments by replacing - * the current URL in the history stack. - */ - replaceWith: function replaceWith(to, params, query) { - this.context.router.replaceWith(to, params, query); - }, - - /** - * Transitions to the previous URL. - */ - goBack: function goBack() { - return this.context.router.goBack(); - } - -}; - -module.exports = Navigation; \ No newline at end of file diff --git a/node_modules/react-router/lib/PathUtils.js b/node_modules/react-router/lib/PathUtils.js deleted file mode 100644 index c39d210..0000000 --- a/node_modules/react-router/lib/PathUtils.js +++ /dev/null @@ -1,153 +0,0 @@ -'use strict'; - -var invariant = require('react/lib/invariant'); -var assign = require('object-assign'); -var qs = require('qs'); - -var paramCompileMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|[*.()\[\]\\+|{}^$]/g; -var paramInjectMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$?]*[?]?)|[*]/g; -var paramInjectTrailingSlashMatcher = /\/\/\?|\/\?\/|\/\?/g; -var queryMatcher = /\?(.*)$/; - -var _compiledPatterns = {}; - -function compilePattern(pattern) { - if (!(pattern in _compiledPatterns)) { - var paramNames = []; - var source = pattern.replace(paramCompileMatcher, function (match, paramName) { - if (paramName) { - paramNames.push(paramName); - return '([^/?#]+)'; - } else if (match === '*') { - paramNames.push('splat'); - return '(.*?)'; - } else { - return '\\' + match; - } - }); - - _compiledPatterns[pattern] = { - matcher: new RegExp('^' + source + '$', 'i'), - paramNames: paramNames - }; - } - - return _compiledPatterns[pattern]; -} - -var PathUtils = { - - /** - * Returns true if the given path is absolute. - */ - isAbsolute: function isAbsolute(path) { - return path.charAt(0) === '/'; - }, - - /** - * Joins two URL paths together. - */ - join: function join(a, b) { - return a.replace(/\/*$/, '/') + b; - }, - - /** - * Returns an array of the names of all parameters in the given pattern. - */ - extractParamNames: function extractParamNames(pattern) { - return compilePattern(pattern).paramNames; - }, - - /** - * Extracts the portions of the given URL path that match the given pattern - * and returns an object of param name => value pairs. Returns null if the - * pattern does not match the given path. - */ - extractParams: function extractParams(pattern, path) { - var _compilePattern = compilePattern(pattern); - - var matcher = _compilePattern.matcher; - var paramNames = _compilePattern.paramNames; - - var match = path.match(matcher); - - if (!match) { - return null; - }var params = {}; - - paramNames.forEach(function (paramName, index) { - params[paramName] = match[index + 1]; - }); - - return params; - }, - - /** - * Returns a version of the given route path with params interpolated. Throws - * if there is a dynamic segment of the route path for which there is no param. - */ - injectParams: function injectParams(pattern, params) { - params = params || {}; - - var splatIndex = 0; - - return pattern.replace(paramInjectMatcher, function (match, paramName) { - paramName = paramName || 'splat'; - - // If param is optional don't check for existence - if (paramName.slice(-1) === '?') { - paramName = paramName.slice(0, -1); - - if (params[paramName] == null) return ''; - } else { - invariant(params[paramName] != null, 'Missing "%s" parameter for path "%s"', paramName, pattern); - } - - var segment; - if (paramName === 'splat' && Array.isArray(params[paramName])) { - segment = params[paramName][splatIndex++]; - - invariant(segment != null, 'Missing splat # %s for path "%s"', splatIndex, pattern); - } else { - segment = params[paramName]; - } - - return segment; - }).replace(paramInjectTrailingSlashMatcher, '/'); - }, - - /** - * Returns an object that is the result of parsing any query string contained - * in the given path, null if the path contains no query string. - */ - extractQuery: function extractQuery(path) { - var match = path.match(queryMatcher); - return match && qs.parse(match[1]); - }, - - /** - * Returns a version of the given path without the query string. - */ - withoutQuery: function withoutQuery(path) { - return path.replace(queryMatcher, ''); - }, - - /** - * Returns a version of the given path with the parameters in the given - * query merged into the query string. - */ - withQuery: function withQuery(path, query) { - var existingQuery = PathUtils.extractQuery(path); - - if (existingQuery) query = query ? assign(existingQuery, query) : existingQuery; - - var queryString = qs.stringify(query, { arrayFormat: 'brackets' }); - - if (queryString) { - return PathUtils.withoutQuery(path) + '?' + queryString; - }return PathUtils.withoutQuery(path); - } - -}; - -module.exports = PathUtils; \ No newline at end of file diff --git a/node_modules/react-router/lib/PropTypes.js b/node_modules/react-router/lib/PropTypes.js deleted file mode 100644 index fdee9be..0000000 --- a/node_modules/react-router/lib/PropTypes.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -var assign = require('react/lib/Object.assign'); -var ReactPropTypes = require('react').PropTypes; -var Route = require('./Route'); - -var PropTypes = assign({}, ReactPropTypes, { - - /** - * Indicates that a prop should be falsy. - */ - falsy: function falsy(props, propName, componentName) { - if (props[propName]) { - return new Error('<' + componentName + '> should not have a "' + propName + '" prop'); - } - }, - - /** - * Indicates that a prop should be a Route object. - */ - route: ReactPropTypes.instanceOf(Route), - - /** - * Indicates that a prop should be a Router object. - */ - //router: ReactPropTypes.instanceOf(Router) // TODO - router: ReactPropTypes.func - -}); - -module.exports = PropTypes; \ No newline at end of file diff --git a/node_modules/react-router/lib/Redirect.js b/node_modules/react-router/lib/Redirect.js deleted file mode 100644 index 7cbf688..0000000 --- a/node_modules/react-router/lib/Redirect.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Encapsulates a redirect to the given route. - */ -"use strict"; - -function Redirect(to, params, query) { - this.to = to; - this.params = params; - this.query = query; -} - -module.exports = Redirect; \ No newline at end of file diff --git a/node_modules/react-router/lib/Route.js b/node_modules/react-router/lib/Route.js deleted file mode 100644 index b2d4ac1..0000000 --- a/node_modules/react-router/lib/Route.js +++ /dev/null @@ -1,200 +0,0 @@ -'use strict'; - -var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }; - -var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - -var assign = require('react/lib/Object.assign'); -var invariant = require('react/lib/invariant'); -var warning = require('react/lib/warning'); -var PathUtils = require('./PathUtils'); - -var _currentRoute; - -var Route = (function () { - function Route(name, path, ignoreScrollBehavior, isDefault, isNotFound, onEnter, onLeave, handler) { - _classCallCheck(this, Route); - - this.name = name; - this.path = path; - this.paramNames = PathUtils.extractParamNames(this.path); - this.ignoreScrollBehavior = !!ignoreScrollBehavior; - this.isDefault = !!isDefault; - this.isNotFound = !!isNotFound; - this.onEnter = onEnter; - this.onLeave = onLeave; - this.handler = handler; - } - - _createClass(Route, [{ - key: 'appendChild', - - /** - * Appends the given route to this route's child routes. - */ - value: function appendChild(route) { - invariant(route instanceof Route, 'route.appendChild must use a valid Route'); - - if (!this.childRoutes) this.childRoutes = []; - - this.childRoutes.push(route); - } - }, { - key: 'toString', - value: function toString() { - var string = ''; - - return string; - } - }], [{ - key: 'createRoute', - - /** - * Creates and returns a new route. Options may be a URL pathname string - * with placeholders for named params or an object with any of the following - * properties: - * - * - name The name of the route. This is used to lookup a - * route relative to its parent route and should be - * unique among all child routes of the same parent - * - path A URL pathname string with optional placeholders - * that specify the names of params to extract from - * the URL when the path matches. Defaults to `/${name}` - * when there is a name given, or the path of the parent - * route, or / - * - ignoreScrollBehavior True to make this route (and all descendants) ignore - * the scroll behavior of the router - * - isDefault True to make this route the default route among all - * its siblings - * - isNotFound True to make this route the "not found" route among - * all its siblings - * - onEnter A transition hook that will be called when the - * router is going to enter this route - * - onLeave A transition hook that will be called when the - * router is going to leave this route - * - handler A React component that will be rendered when - * this route is active - * - parentRoute The parent route to use for this route. This option - * is automatically supplied when creating routes inside - * the callback to another invocation of createRoute. You - * only ever need to use this when declaring routes - * independently of one another to manually piece together - * the route hierarchy - * - * The callback may be used to structure your route hierarchy. Any call to - * createRoute, createDefaultRoute, createNotFoundRoute, or createRedirect - * inside the callback automatically uses this route as its parent. - */ - value: function createRoute(options, callback) { - options = options || {}; - - if (typeof options === 'string') options = { path: options }; - - var parentRoute = _currentRoute; - - if (parentRoute) { - warning(options.parentRoute == null || options.parentRoute === parentRoute, 'You should not use parentRoute with createRoute inside another route\'s child callback; it is ignored'); - } else { - parentRoute = options.parentRoute; - } - - var name = options.name; - var path = options.path || name; - - if (path && !(options.isDefault || options.isNotFound)) { - if (PathUtils.isAbsolute(path)) { - if (parentRoute) { - invariant(path === parentRoute.path || parentRoute.paramNames.length === 0, 'You cannot nest path "%s" inside "%s"; the parent requires URL parameters', path, parentRoute.path); - } - } else if (parentRoute) { - // Relative paths extend their parent. - path = PathUtils.join(parentRoute.path, path); - } else { - path = '/' + path; - } - } else { - path = parentRoute ? parentRoute.path : '/'; - } - - if (options.isNotFound && !/\*$/.test(path)) path += '*'; // Auto-append * to the path of not found routes. - - var route = new Route(name, path, options.ignoreScrollBehavior, options.isDefault, options.isNotFound, options.onEnter, options.onLeave, options.handler); - - if (parentRoute) { - if (route.isDefault) { - invariant(parentRoute.defaultRoute == null, '%s may not have more than one default route', parentRoute); - - parentRoute.defaultRoute = route; - } else if (route.isNotFound) { - invariant(parentRoute.notFoundRoute == null, '%s may not have more than one not found route', parentRoute); - - parentRoute.notFoundRoute = route; - } - - parentRoute.appendChild(route); - } - - // Any routes created in the callback - // use this route as their parent. - if (typeof callback === 'function') { - var currentRoute = _currentRoute; - _currentRoute = route; - callback.call(route, route); - _currentRoute = currentRoute; - } - - return route; - } - }, { - key: 'createDefaultRoute', - - /** - * Creates and returns a route that is rendered when its parent matches - * the current URL. - */ - value: function createDefaultRoute(options) { - return Route.createRoute(assign({}, options, { isDefault: true })); - } - }, { - key: 'createNotFoundRoute', - - /** - * Creates and returns a route that is rendered when its parent matches - * the current URL but none of its siblings do. - */ - value: function createNotFoundRoute(options) { - return Route.createRoute(assign({}, options, { isNotFound: true })); - } - }, { - key: 'createRedirect', - - /** - * Creates and returns a route that automatically redirects the transition - * to another route. In addition to the normal options to createRoute, this - * function accepts the following options: - * - * - from An alias for the `path` option. Defaults to * - * - to The path/route/route name to redirect to - * - params The params to use in the redirect URL. Defaults - * to using the current params - * - query The query to use in the redirect URL. Defaults - * to using the current query - */ - value: function createRedirect(options) { - return Route.createRoute(assign({}, options, { - path: options.path || options.from || '*', - onEnter: function onEnter(transition, params, query) { - transition.redirect(options.to, options.params || params, options.query || query); - } - })); - } - }]); - - return Route; -})(); - -module.exports = Route; \ No newline at end of file diff --git a/node_modules/react-router/lib/ScrollHistory.js b/node_modules/react-router/lib/ScrollHistory.js deleted file mode 100644 index 52a585f..0000000 --- a/node_modules/react-router/lib/ScrollHistory.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -var invariant = require('react/lib/invariant'); -var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM; -var getWindowScrollPosition = require('./getWindowScrollPosition'); - -function shouldUpdateScroll(state, prevState) { - if (!prevState) { - return true; - } // Don't update scroll position when only the query has changed. - if (state.pathname === prevState.pathname) { - return false; - }var routes = state.routes; - var prevRoutes = prevState.routes; - - var sharedAncestorRoutes = routes.filter(function (route) { - return prevRoutes.indexOf(route) !== -1; - }); - - return !sharedAncestorRoutes.some(function (route) { - return route.ignoreScrollBehavior; - }); -} - -/** - * Provides the router with the ability to manage window scroll position - * according to its scroll behavior. - */ -var ScrollHistory = { - - statics: { - - /** - * Records curent scroll position as the last known position for the given URL path. - */ - recordScrollPosition: function recordScrollPosition(path) { - if (!this.scrollHistory) this.scrollHistory = {}; - - this.scrollHistory[path] = getWindowScrollPosition(); - }, - - /** - * Returns the last known scroll position for the given URL path. - */ - getScrollPosition: function getScrollPosition(path) { - if (!this.scrollHistory) this.scrollHistory = {}; - - return this.scrollHistory[path] || null; - } - - }, - - componentWillMount: function componentWillMount() { - invariant(this.constructor.getScrollBehavior() == null || canUseDOM, 'Cannot use scroll behavior without a DOM'); - }, - - componentDidMount: function componentDidMount() { - this._updateScroll(); - }, - - componentDidUpdate: function componentDidUpdate(prevProps, prevState) { - this._updateScroll(prevState); - }, - - _updateScroll: function _updateScroll(prevState) { - if (!shouldUpdateScroll(this.state, prevState)) { - return; - }var scrollBehavior = this.constructor.getScrollBehavior(); - - if (scrollBehavior) scrollBehavior.updateScrollPosition(this.constructor.getScrollPosition(this.state.path), this.state.action); - } - -}; - -module.exports = ScrollHistory; \ No newline at end of file diff --git a/node_modules/react-router/lib/State.js b/node_modules/react-router/lib/State.js deleted file mode 100644 index 8cad99e..0000000 --- a/node_modules/react-router/lib/State.js +++ /dev/null @@ -1,74 +0,0 @@ -'use strict'; - -var PropTypes = require('./PropTypes'); - -/** - * A mixin for components that need to know the path, routes, URL - * params and query that are currently active. - * - * Example: - * - * var AboutLink = React.createClass({ - * mixins: [ Router.State ], - * render() { - * var className = this.props.className; - * - * if (this.isActive('about')) - * className += ' is-active'; - * - * return React.DOM.a({ className: className }, this.props.children); - * } - * }); - */ -var State = { - - contextTypes: { - router: PropTypes.router.isRequired - }, - - /** - * Returns the current URL path. - */ - getPath: function getPath() { - return this.context.router.getCurrentPath(); - }, - - /** - * Returns the current URL path without the query string. - */ - getPathname: function getPathname() { - return this.context.router.getCurrentPathname(); - }, - - /** - * Returns an object of the URL params that are currently active. - */ - getParams: function getParams() { - return this.context.router.getCurrentParams(); - }, - - /** - * Returns an object of the query params that are currently active. - */ - getQuery: function getQuery() { - return this.context.router.getCurrentQuery(); - }, - - /** - * Returns an array of the routes that are currently active. - */ - getRoutes: function getRoutes() { - return this.context.router.getCurrentRoutes(); - }, - - /** - * A helper method to determine if a given route, params, and query - * are active. - */ - isActive: function isActive(to, params, query) { - return this.context.router.isActive(to, params, query); - } - -}; - -module.exports = State; \ No newline at end of file diff --git a/node_modules/react-router/lib/TestUtils.js b/node_modules/react-router/lib/TestUtils.js deleted file mode 100644 index 60b706b..0000000 --- a/node_modules/react-router/lib/TestUtils.js +++ /dev/null @@ -1,171 +0,0 @@ -'use strict'; - -var React = require('react'); -var RouteHandler = require('./components/RouteHandler'); -var PropTypes = require('./PropTypes'); - -exports.Nested = React.createClass({ - displayName: 'Nested', - - render: function render() { - return React.createElement( - 'div', - null, - React.createElement( - 'h1', - { className: 'Nested' }, - 'Nested' - ), - React.createElement(RouteHandler, null) - ); - } -}); - -exports.Foo = React.createClass({ - displayName: 'Foo', - - render: function render() { - return React.createElement( - 'div', - { className: 'Foo' }, - 'Foo' - ); - } -}); - -exports.Bar = React.createClass({ - displayName: 'Bar', - - render: function render() { - return React.createElement( - 'div', - { className: 'Bar' }, - 'Bar' - ); - } -}); - -exports.Baz = React.createClass({ - displayName: 'Baz', - - render: function render() { - return React.createElement( - 'div', - { className: 'Baz' }, - 'Baz' - ); - } -}); - -exports.Async = React.createClass({ - displayName: 'Async', - - statics: { - delay: 10, - - willTransitionTo: function willTransitionTo(transition, params, query, callback) { - setTimeout(callback, exports.Async.delay); - } - }, - - render: function render() { - return React.createElement( - 'div', - { className: 'Async' }, - 'Async' - ); - } -}); - -exports.RedirectToFoo = React.createClass({ - displayName: 'RedirectToFoo', - - statics: { - willTransitionTo: function willTransitionTo(transition) { - transition.redirect('/foo'); - } - }, - - render: function render() { - return null; - } -}); - -exports.RedirectToFooAsync = React.createClass({ - displayName: 'RedirectToFooAsync', - - statics: { - delay: 10, - - willTransitionTo: function willTransitionTo(transition, params, query, callback) { - setTimeout(function () { - transition.redirect('/foo'); - callback(); - }, exports.RedirectToFooAsync.delay); - } - }, - - render: function render() { - return null; - } -}); - -exports.Abort = React.createClass({ - displayName: 'Abort', - - statics: { - willTransitionTo: function willTransitionTo(transition) { - transition.abort(); - } - }, - - render: function render() { - return null; - } -}); - -exports.AbortAsync = React.createClass({ - displayName: 'AbortAsync', - - statics: { - delay: 10, - - willTransitionTo: function willTransitionTo(transition, params, query, callback) { - setTimeout(function () { - transition.abort(); - callback(); - }, exports.AbortAsync.delay); - } - }, - - render: function render() { - return null; - } -}); - -exports.EchoFooProp = React.createClass({ - displayName: 'EchoFooProp', - - render: function render() { - return React.createElement( - 'div', - null, - this.props.foo - ); - } -}); - -exports.EchoBarParam = React.createClass({ - displayName: 'EchoBarParam', - - contextTypes: { - router: PropTypes.router.isRequired - }, - render: function render() { - return React.createElement( - 'div', - { className: 'EchoBarParam' }, - this.context.router.getCurrentParams().bar - ); - } -}); \ No newline at end of file diff --git a/node_modules/react-router/lib/Transition.js b/node_modules/react-router/lib/Transition.js deleted file mode 100644 index d58c311..0000000 --- a/node_modules/react-router/lib/Transition.js +++ /dev/null @@ -1,75 +0,0 @@ -/* jshint -W058 */ - -'use strict'; - -var Cancellation = require('./Cancellation'); -var Redirect = require('./Redirect'); - -/** - * Encapsulates a transition to a given path. - * - * The willTransitionTo and willTransitionFrom handlers receive - * an instance of this class as their first argument. - */ -function Transition(path, retry) { - this.path = path; - this.abortReason = null; - // TODO: Change this to router.retryTransition(transition) - this.retry = retry.bind(this); -} - -Transition.prototype.abort = function (reason) { - if (this.abortReason == null) this.abortReason = reason || 'ABORT'; -}; - -Transition.prototype.redirect = function (to, params, query) { - this.abort(new Redirect(to, params, query)); -}; - -Transition.prototype.cancel = function () { - this.abort(new Cancellation()); -}; - -Transition.from = function (transition, routes, components, callback) { - routes.reduce(function (callback, route, index) { - return function (error) { - if (error || transition.abortReason) { - callback(error); - } else if (route.onLeave) { - try { - route.onLeave(transition, components[index], callback); - - // If there is no callback in the argument list, call it automatically. - if (route.onLeave.length < 3) callback(); - } catch (e) { - callback(e); - } - } else { - callback(); - } - }; - }, callback)(); -}; - -Transition.to = function (transition, routes, params, query, callback) { - routes.reduceRight(function (callback, route) { - return function (error) { - if (error || transition.abortReason) { - callback(error); - } else if (route.onEnter) { - try { - route.onEnter(transition, params, query, callback); - - // If there is no callback in the argument list, call it automatically. - if (route.onEnter.length < 4) callback(); - } catch (e) { - callback(e); - } - } else { - callback(); - } - }; - }, callback)(); -}; - -module.exports = Transition; \ No newline at end of file diff --git a/node_modules/react-router/lib/actions/LocationActions.js b/node_modules/react-router/lib/actions/LocationActions.js deleted file mode 100644 index 8970c10..0000000 --- a/node_modules/react-router/lib/actions/LocationActions.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Actions that modify the URL. - */ -'use strict'; - -var LocationActions = { - - /** - * Indicates a new location is being pushed to the history stack. - */ - PUSH: 'push', - - /** - * Indicates the current location should be replaced. - */ - REPLACE: 'replace', - - /** - * Indicates the most recent entry should be removed from the history stack. - */ - POP: 'pop' - -}; - -module.exports = LocationActions; \ No newline at end of file diff --git a/node_modules/react-router/lib/behaviors/ImitateBrowserBehavior.js b/node_modules/react-router/lib/behaviors/ImitateBrowserBehavior.js deleted file mode 100644 index e7782f9..0000000 --- a/node_modules/react-router/lib/behaviors/ImitateBrowserBehavior.js +++ /dev/null @@ -1,29 +0,0 @@ -'use strict'; - -var LocationActions = require('../actions/LocationActions'); - -/** - * A scroll behavior that attempts to imitate the default behavior - * of modern browsers. - */ -var ImitateBrowserBehavior = { - - updateScrollPosition: function updateScrollPosition(position, actionType) { - switch (actionType) { - case LocationActions.PUSH: - case LocationActions.REPLACE: - window.scrollTo(0, 0); - break; - case LocationActions.POP: - if (position) { - window.scrollTo(position.x, position.y); - } else { - window.scrollTo(0, 0); - } - break; - } - } - -}; - -module.exports = ImitateBrowserBehavior; \ No newline at end of file diff --git a/node_modules/react-router/lib/behaviors/ScrollToTopBehavior.js b/node_modules/react-router/lib/behaviors/ScrollToTopBehavior.js deleted file mode 100644 index 92c81e9..0000000 --- a/node_modules/react-router/lib/behaviors/ScrollToTopBehavior.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * A scroll behavior that always scrolls to the top of the page - * after a transition. - */ -"use strict"; - -var ScrollToTopBehavior = { - - updateScrollPosition: function updateScrollPosition() { - window.scrollTo(0, 0); - } - -}; - -module.exports = ScrollToTopBehavior; \ No newline at end of file diff --git a/node_modules/react-router/lib/components/ActiveHandler.js b/node_modules/react-router/lib/components/ActiveHandler.js deleted file mode 100644 index 13844be..0000000 --- a/node_modules/react-router/lib/components/ActiveHandler.js +++ /dev/null @@ -1,108 +0,0 @@ -'use strict'; - -var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }; - -var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); - -var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; - -var React = require('react'); -var ContextWrapper = require('./ContextWrapper'); -var assign = require('react/lib/Object.assign'); -var PropTypes = require('../PropTypes'); - -var REF_NAME = '__routeHandler__'; - -/** - * A component renders the active child route handler - * when routes are nested. - */ - -var RouteHandler = (function (_React$Component) { - function RouteHandler() { - _classCallCheck(this, RouteHandler); - - if (_React$Component != null) { - _React$Component.apply(this, arguments); - } - } - - _inherits(RouteHandler, _React$Component); - - _createClass(RouteHandler, [{ - key: 'getChildContext', - value: function getChildContext() { - return { - routeDepth: this.context.routeDepth + 1 - }; - } - }, { - key: 'componentDidMount', - value: function componentDidMount() { - this._updateRouteComponent(this.refs[REF_NAME]); - } - }, { - key: 'componentDidUpdate', - value: function componentDidUpdate() { - this._updateRouteComponent(this.refs[REF_NAME]); - } - }, { - key: 'componentWillUnmount', - value: function componentWillUnmount() { - this._updateRouteComponent(null); - } - }, { - key: '_updateRouteComponent', - value: function _updateRouteComponent(component) { - this.context.router.setRouteComponentAtDepth(this.getRouteDepth(), component); - } - }, { - key: 'getRouteDepth', - value: function getRouteDepth() { - return this.context.routeDepth; - } - }, { - key: 'createChildRouteHandler', - value: function createChildRouteHandler(props) { - var route = this.context.router.getRouteAtDepth(this.getRouteDepth()); - - if (route == null) { - return null; - }var childProps = assign({}, props || this.props, { - ref: REF_NAME, - params: this.context.router.getCurrentParams(), - query: this.context.router.getCurrentQuery() - }); - - return React.createElement(route.handler, childProps); - } - }, { - key: 'render', - value: function render() { - var handler = this.createChildRouteHandler(); - //