diff --git a/Gemfile b/Gemfile
index e958a9389e..cb6f7a8f96 100644
--- a/Gemfile
+++ b/Gemfile
@@ -28,6 +28,7 @@ gem 'andand'
gem 'truncate_html'
gem 'representative_view'
gem 'rabl'
+gem "active_model_serializers"
gem 'oj'
gem 'deface', :github => 'spree/deface', :ref => '1110a13'
gem 'paperclip'
diff --git a/Gemfile.lock b/Gemfile.lock
index dd09513503..9afad20d05 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -118,6 +118,8 @@ GEM
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
active_link_to (1.0.0)
+ active_model_serializers (0.8.1)
+ activemodel (>= 3.0)
active_utils (2.0.2)
activesupport (>= 2.3.11)
i18n
@@ -324,7 +326,7 @@ GEM
money (5.0.0)
i18n (~> 0.4)
json
- multi_json (1.10.0)
+ multi_json (1.10.1)
multi_xml (0.5.5)
net-scp (1.1.2)
net-ssh (>= 2.6.5)
@@ -492,6 +494,7 @@ PLATFORMS
ruby
DEPENDENCIES
+ active_model_serializers
andand
angular-rails-templates
angularjs-rails
diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee
index a19bd1294f..45f91366ba 100644
--- a/app/assets/javascripts/darkswarm/all.js.coffee
+++ b/app/assets/javascripts/darkswarm/all.js.coffee
@@ -6,11 +6,13 @@
#= require angular
#= require angular-cookies
#= require angular-sanitize
+#= require angular-animate
#= require angular-resource
#= require lodash.underscore.js
#= require angular-scroll.min.js
#= require angular-google-maps.min.js
-#= require ../shared/mm-foundation-tpls-0.2.0-SNAPSHOT
+#= require angular-timer.min.js
+#= require ../shared/mm-foundation-tpls-0.2.2.min.js
#= require ../shared/bindonce.min.js
#= require ../shared/ng-infinite-scroll.min.js
#= require ../shared/angular-local-storage.js
diff --git a/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee
index 850f58b33e..4bb856ace4 100644
--- a/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee
@@ -2,5 +2,7 @@ Darkswarm.controller "GroupsCtrl", ($scope, Groups, $anchorScroll, $rootScope) -
$scope.Groups = Groups
$scope.order = 'position'
- $rootScope.$on "$locationChangeSuccess", (newRoute, oldRoute) ->
- $anchorScroll()
+ #$rootScope.$on "$locationChangeSuccess", (newRoute, oldRoute) ->
+ #$anchorScroll()
+ #
+ #
diff --git a/app/assets/javascripts/darkswarm/controllers/hubs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/hubs_controller.js.coffee
index ca7d86f5cf..acc71b1172 100644
--- a/app/assets/javascripts/darkswarm/controllers/hubs_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/hubs_controller.js.coffee
@@ -1,6 +1,6 @@
Darkswarm.controller "HubsCtrl", ($scope, Hubs, $document, $rootScope, HashNavigation) ->
$scope.Hubs = Hubs
- $scope.hubs = Hubs.hubs
+ $scope.hubs = Hubs.visible
$rootScope.$on "$locationChangeSuccess", (newRoute, oldRoute) ->
if HashNavigation.active "hubs"
diff --git a/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee
index 92cf8dedfc..6db73f7af3 100644
--- a/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/tabs/producers_controller.js.coffee
@@ -1,2 +1,3 @@
-Darkswarm.controller "ProducersTabCtrl", ($scope, CurrentHub) ->
+Darkswarm.controller "ProducersTabCtrl", ($scope, CurrentHub, Enterprises) ->
+ # Injecting Enterprises so CurrentHub.producers is dereferenced
$scope.CurrentHub = CurrentHub
diff --git a/app/assets/javascripts/darkswarm/darkswarm.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee
index b04487b330..c497ea3cfd 100644
--- a/app/assets/javascripts/darkswarm/darkswarm.js.coffee
+++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee
@@ -5,10 +5,11 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource",
'infinite-scroll',
'angular-flash.service',
'templates',
+ 'timer',
'ngSanitize',
'google-maps',
'duScroll',
- 'backstretch']).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) ->
+ ]).config ($httpProvider, $tooltipProvider, $locationProvider, $anchorScrollProvider) ->
$httpProvider.defaults.headers.post['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
$httpProvider.defaults.headers.put['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content')
$httpProvider.defaults.headers['common']['X-Requested-With'] = 'XMLHttpRequest'
diff --git a/app/assets/javascripts/darkswarm/directives/active_table_hub_link.js.coffee b/app/assets/javascripts/darkswarm/directives/active_table_hub_link.js.coffee
index ff220d0d5e..4369fced34 100644
--- a/app/assets/javascripts/darkswarm/directives/active_table_hub_link.js.coffee
+++ b/app/assets/javascripts/darkswarm/directives/active_table_hub_link.js.coffee
@@ -6,7 +6,7 @@ Darkswarm.directive "activeTableHubLink", (CurrentHub, CurrentOrder) ->
link: (scope, elm, attr)->
# Swap out the text of the hub link depending on whether it'll change current hub
# To be used with ofnEmptiesCart
- if CurrentHub.hub.id and CurrentHub.hub.id isnt scope.hub.id
+ if CurrentHub.hub?.id and CurrentHub.hub.id isnt scope.hub.id
scope.action = attr.change
else
scope.action = attr.shop
diff --git a/app/assets/javascripts/darkswarm/directives/empties_cart.js.coffee b/app/assets/javascripts/darkswarm/directives/empties_cart.js.coffee
index b71196c40c..75e88f3682 100644
--- a/app/assets/javascripts/darkswarm/directives/empties_cart.js.coffee
+++ b/app/assets/javascripts/darkswarm/directives/empties_cart.js.coffee
@@ -3,7 +3,7 @@ Darkswarm.directive "ofnEmptiesCart", (CurrentHub, CurrentOrder, Navigation, sto
link: (scope, elm, attr)->
hub = scope.$eval(attr.ofnEmptiesCart)
# A hub is selected, we're changing to a different hub, and the cart isn't empty
- if CurrentHub.hub.id and CurrentHub.hub.id isnt hub.id
+ if CurrentHub.hub?.id and CurrentHub.hub.id isnt hub.id
unless CurrentOrder.empty()
elm.bind 'click', (ev)->
ev.preventDefault()
diff --git a/app/assets/javascripts/darkswarm/directives/hub_modal.js.coffee b/app/assets/javascripts/darkswarm/directives/hub_modal.js.coffee
new file mode 100644
index 0000000000..4810cdbcf0
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/directives/hub_modal.js.coffee
@@ -0,0 +1,7 @@
+Darkswarm.directive "hubModal", ($modal)->
+ restrict: 'E'
+ replace: true
+ template: "{{enterprise.name}}"
+ link: (scope, elem, attrs, ctrl)->
+ elem.on "click", =>
+ scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'hub_modal.html', scope: scope)
diff --git a/app/assets/javascripts/darkswarm/directives/offcanvas.js.coffee b/app/assets/javascripts/darkswarm/directives/offcanvas.js.coffee
deleted file mode 100644
index 4ff4f300da..0000000000
--- a/app/assets/javascripts/darkswarm/directives/offcanvas.js.coffee
+++ /dev/null
@@ -1,6 +0,0 @@
-Darkswarm.directive "offcanvas", ->
- restrict: "A"
-
- link: (scope, el, attr) ->
- el.find(".left-off-canvas-toggle").bind 'click', ->
- el.toggleClass 'move-right'
diff --git a/app/assets/javascripts/darkswarm/directives/producer_modal.js.coffee b/app/assets/javascripts/darkswarm/directives/producer_modal.js.coffee
new file mode 100644
index 0000000000..a8d8253289
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/directives/producer_modal.js.coffee
@@ -0,0 +1,9 @@
+Darkswarm.directive "producerModal", ($modal)->
+ restrict: 'E'
+ replace: true
+ template: ""
+ transclude: true
+ link: (scope, elem, attrs, ctrl)->
+ elem.on "click", =>
+ scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'producer_modal.html', scope: scope)
+
diff --git a/app/assets/javascripts/darkswarm/directives/product_modal.js.coffee b/app/assets/javascripts/darkswarm/directives/product_modal.js.coffee
new file mode 100644
index 0000000000..428fde3633
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/directives/product_modal.js.coffee
@@ -0,0 +1,9 @@
+Darkswarm.directive "productModal", ($modal)->
+ restrict: 'E'
+ replace: true
+ template: ""
+ transclude: true
+ link: (scope, elem, attrs, ctrl)->
+ elem.on "click", =>
+ scope.modalInstance = $modal.open(controller: ctrl, templateUrl: 'product_modal.html', scope: scope)
+
diff --git a/app/assets/javascripts/darkswarm/filters/filter_groups.js.coffee b/app/assets/javascripts/darkswarm/filters/filter_groups.js.coffee
new file mode 100644
index 0000000000..b209068dcf
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/filters/filter_groups.js.coffee
@@ -0,0 +1,10 @@
+Darkswarm.filter "groups", (Matcher)->
+ (groups, text)->
+ groups ||= []
+ text ?= ""
+
+ groups.filter (group)=>
+ Matcher.match([
+ group.name, group.long_description
+ ], text) || group.enterprises.some (e)->
+ Matcher.match [e.name], text
diff --git a/app/assets/javascripts/darkswarm/filters/visible.js.coffee b/app/assets/javascripts/darkswarm/filters/visible.js.coffee
new file mode 100644
index 0000000000..7430c2e553
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/filters/visible.js.coffee
@@ -0,0 +1,4 @@
+Darkswarm.filter "visible", ->
+ (objects)->
+ objects.filter (obj)->
+ obj.visible
diff --git a/app/assets/javascripts/darkswarm/services/dereferencer.js.coffee b/app/assets/javascripts/darkswarm/services/dereferencer.js.coffee
new file mode 100644
index 0000000000..2061f95ea1
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/services/dereferencer.js.coffee
@@ -0,0 +1,6 @@
+Darkswarm.factory 'Dereferencer', ->
+ new class Dereferencer
+ dereference: (array, data)->
+ if array
+ for object, i in array
+ array[i] = data[object.id]
diff --git a/app/assets/javascripts/darkswarm/services/enterprises.js.coffee b/app/assets/javascripts/darkswarm/services/enterprises.js.coffee
index c3721f4896..b442773d31 100644
--- a/app/assets/javascripts/darkswarm/services/enterprises.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/enterprises.js.coffee
@@ -1,4 +1,4 @@
-Darkswarm.factory 'Enterprises', (enterprises, CurrentHub)->
+Darkswarm.factory 'Enterprises', (enterprises, CurrentHub, Dereferencer)->
new class Enterprises
enterprises_by_id: {} # id/object pairs for lookup
constructor: ->
@@ -10,12 +10,6 @@ Darkswarm.factory 'Enterprises', (enterprises, CurrentHub)->
dereference: ->
if CurrentHub.hub?.id
CurrentHub.hub = @enterprises_by_id[CurrentHub.hub.id]
-
for enterprise in @enterprises
- if enterprise.hubs
- for hub, i in enterprise.hubs
- enterprise.hubs[i] = @enterprises_by_id[hub.id]
-
- if enterprise.producers
- for producer, i in enterprise.producers
- enterprise.producers[i] = @enterprises_by_id[producer.id]
+ Dereferencer.dereference enterprise.hubs, @enterprises_by_id
+ Dereferencer.dereference enterprise.producers, @enterprises_by_id
diff --git a/app/assets/javascripts/darkswarm/services/groups.js.coffee b/app/assets/javascripts/darkswarm/services/groups.js.coffee
index e5e50615e8..e07d6c2055 100644
--- a/app/assets/javascripts/darkswarm/services/groups.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/groups.js.coffee
@@ -1,4 +1,14 @@
-Darkswarm.factory 'Groups', (groups) ->
+Darkswarm.factory 'Groups', (groups, Enterprises, Dereferencer) ->
new class Groups
+ groups: groups
+ groups_by_id: {}
constructor: ->
- @groups = groups
+ for group in @groups
+ @groups_by_id[group.id] = group
+ @dereference()
+ dereference: ->
+ for group in @groups
+ Dereferencer.dereference group.enterprises, Enterprises.enterprises_by_id
+ for enterprise in Enterprises.enterprises
+ Dereferencer.dereference enterprise.groups, @groups_by_id
+
diff --git a/app/assets/javascripts/darkswarm/services/hubs.js.coffee b/app/assets/javascripts/darkswarm/services/hubs.js.coffee
index 5d55fa7de9..ac7dc3a0eb 100644
--- a/app/assets/javascripts/darkswarm/services/hubs.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/hubs.js.coffee
@@ -1,8 +1,9 @@
-Darkswarm.factory 'Hubs', ($filter, Enterprises) ->
+Darkswarm.factory 'Hubs', ($filter, Enterprises, visibleFilter) ->
new class Hubs
constructor: ->
@hubs = @order Enterprises.enterprises.filter (hub)->
hub.is_distributor
+ @visible = visibleFilter @hubs
order: (hubs)->
$filter('orderBy')(hubs, ['-active', '+orders_close_at'])
diff --git a/app/assets/javascripts/darkswarm/services/map.js.coffee b/app/assets/javascripts/darkswarm/services/map.js.coffee
index ed4bc8dae4..43750acdb2 100644
--- a/app/assets/javascripts/darkswarm/services/map.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/map.js.coffee
@@ -1,7 +1,7 @@
-Darkswarm.factory "OfnMap", (Enterprises, MapModal)->
+Darkswarm.factory "OfnMap", (Enterprises, MapModal, visibleFilter)->
new class OfnMap
constructor: ->
- @enterprises = (@extend(enterprise) for enterprise in Enterprises.enterprises)
+ @enterprises = (@extend(enterprise) for enterprise in visibleFilter(Enterprises.enterprises))
# Adding methods to each enterprise
diff --git a/app/assets/javascripts/darkswarm/services/map_modal.js.coffee b/app/assets/javascripts/darkswarm/services/map_modal.js.coffee
index 1c9bf13bb3..c9ed30f558 100644
--- a/app/assets/javascripts/darkswarm/services/map_modal.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/map_modal.js.coffee
@@ -6,7 +6,7 @@ Darkswarm.factory "MapModal", ($modal, $rootScope)->
scope.enterprise = enterprise
if enterprise.is_distributor
scope.hub = enterprise
- $modal.open(templateUrl: "map_modal_hub.html", scope: scope)
+ $modal.open(templateUrl: "hub_modal.html", scope: scope)
else
scope.producer = enterprise
$modal.open(templateUrl: "map_modal_producer.html", scope: scope)
diff --git a/app/assets/javascripts/darkswarm/services/navigation.js.coffee b/app/assets/javascripts/darkswarm/services/navigation.js.coffee
index cf64cd1666..fd59d0f348 100644
--- a/app/assets/javascripts/darkswarm/services/navigation.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/navigation.js.coffee
@@ -1,4 +1,4 @@
-Darkswarm.factory 'Navigation', ($location) ->
+Darkswarm.factory 'Navigation', ($location, $window) ->
new class Navigation
path: null
@@ -10,7 +10,6 @@ Darkswarm.factory 'Navigation', ($location) ->
$location.path(@path)
toggle: (path = false)=>
- console.log "toggling"
@path = path || @path
if $location.path() == @path
$location.path("/")
@@ -18,4 +17,7 @@ Darkswarm.factory 'Navigation', ($location) ->
@navigate(path)
go: (path)->
- window.location.pathname = path
+ if path.match /^http/
+ $window.location.href = path
+ else
+ $window.location.pathname = path
diff --git a/app/assets/javascripts/darkswarm/services/producers.js.coffee b/app/assets/javascripts/darkswarm/services/producers.js.coffee
index ac8354c101..65d8e42c5d 100644
--- a/app/assets/javascripts/darkswarm/services/producers.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/producers.js.coffee
@@ -1,6 +1,7 @@
-Darkswarm.factory 'Producers', (Enterprises) ->
+Darkswarm.factory 'Producers', (Enterprises, visibleFilter) ->
new class Producers
constructor: ->
@producers = Enterprises.enterprises.filter (enterprise)->
enterprise.is_primary_producer
+ @visible = visibleFilter @producers
diff --git a/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js b/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js
new file mode 100644
index 0000000000..be82613518
--- /dev/null
+++ b/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js
@@ -0,0 +1,9 @@
+/*
+ * angular-mm-foundation
+ * http://madmimi.github.io/angular-foundation/
+
+ * Version: 0.2.2 - 2014-06-25
+ * License: MIT
+ */
+angular.module("mm.foundation",["mm.foundation.tpls","mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.dropdownToggle","mm.foundation.transition","mm.foundation.modal","mm.foundation.offcanvas","mm.foundation.pagination","mm.foundation.tooltip","mm.foundation.popover","mm.foundation.progressbar","mm.foundation.rating","mm.foundation.tabs","mm.foundation.tour","mm.foundation.typeahead"]),angular.module("mm.foundation.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/tour/tour.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("mm.foundation.accordion",[]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(this.groups.indexOf(a),1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse",function(a){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(b,c,d,e){var f,g;e.addGroup(b),b.isOpen=!1,d.isOpen&&(f=a(d.isOpen),g=f.assign,b.$parent.$watch(f,function(a){b.isOpen=!!a})),b.$watch("isOpen",function(a){a&&e.closeOthers(b),g&&g(b.$parent,a)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(a,b,c){return function(a,b,d,e){e.setHeading(c(a,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("mm.foundation.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"}}}),angular.module("mm.foundation.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("mm.foundation.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass,this.toggleEvent=a.toggleEvent}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){b.hasClass(e.activeClass)||a.$apply(function(){f.$setViewValue(a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("mm.foundation.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("mm.foundation.dropdownToggle",["mm.foundation.position"]).directive("dropdownToggle",["$document","$location","$position",function(a,b,c){var d=null,e=angular.noop;return{restrict:"CA",scope:{dropdownToggle:"@"},link:function(b,f){var g=angular.element(a[0].querySelector(b.dropdownToggle));b.$watch("$location.path",function(){e()}),f.bind("click",function(h){g=angular.element(a[0].querySelector(b.dropdownToggle));var i=f===d;if(h.preventDefault(),h.stopPropagation(),d&&e(),!i&&!f.hasClass("disabled")&&!f.prop("disabled")){g.css("display","block");var j=c.offset(f),k=c.offset(angular.element(g[0].offsetParent));g.css({left:j.left-k.left+"px",top:j.top-k.top+j.height+"px"}),d=f,e=function(){a.unbind("click",e),g.css("display","none"),e=angular.noop,d=null},a.bind("click",e)}}),g&&g.css("display","none")}}}]),angular.module("mm.foundation.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("mm.foundation.modal",["mm.foundation.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0)}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g,0)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&e.$apply(function(){o.dismiss(b.key)}))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();h>=0&&!k&&(l=e.$new(!0),l.index=h,k=d("")(l),f.append(k));var i=angular.element("");i.attr("window-class",b.windowClass),i.attr("index",n.length()-1),i.attr("animate","animate"),i.html(b.content);var j=d(i)(b.scope);n.top().value.modalDomEl=j,f.append(j),f.addClass(m)},o.close=function(a,b){var c=n.get(a).value;c&&(c.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a).value;c&&(c.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("mm.foundation.offcanvas",[]).directive("offCanvasWrap",["$window",function(a){return{scope:{},restrict:"C",link:function(b,c){var d=angular.element(a),e=b.sidebar=c;b.hide=function(){e.removeClass("move-left"),e.removeClass("move-right")},d.bind("resize.body",b.hide),b.$on("$destroy",function(){d.unbind("resize.body",b.hide)})},controller:["$scope",function(a){this.leftToggle=function(){a.sidebar.toggleClass("move-right")},this.rightToggle=function(){a.sidebar.toggleClass("move-left")},this.hide=function(){a.hide()}}]}}]).directive("leftOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.leftToggle()})}}}]).directive("rightOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.rightToggle()})}}}]).directive("exitOffCanvas",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]).directive("offCanvasList",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]),angular.module("mm.foundation.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse","$interpolate",function(a,b,c,d){var e=this,f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d){b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){e.itemsPerPage=parseInt(b,10),a.totalPages=e.calculateTotalPages()}):this.itemsPerPage=d},this.noPrevious=function(){return 1===this.page},this.noNext=function(){return this.page===a.totalPages},this.isActive=function(a){return this.page===a},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.getAttributeValue=function(b,c,e){return angular.isDefined(b)?e?d(b)(a.$parent):a.$parent.$eval(b):c},this.render=function(){this.page=parseInt(a.page,10)||1,this.page>0&&this.page<=a.totalPages&&(a.pages=this.getPages(this.page,a.totalPages))},a.selectPage=function(b){!e.isActive(b)&&b>0&&b<=a.totalPages&&(a.page=b,a.onSelectPage({page:b}))},a.$watch("page",function(){e.render()}),a.$watch("totalItems",function(){a.totalPages=e.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),e.page>b?a.selectPage(b):e.render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c,d){return{number:a,text:b,active:c,disabled:d}}var h,i=f.getAttributeValue(e.boundaryLinks,b.boundaryLinks),j=f.getAttributeValue(e.directionLinks,b.directionLinks),k=f.getAttributeValue(e.firstText,b.firstText,!0),l=f.getAttributeValue(e.previousText,b.previousText,!0),m=f.getAttributeValue(e.nextText,b.nextText,!0),n=f.getAttributeValue(e.lastText,b.lastText,!0),o=f.getAttributeValue(e.rotate,b.rotate);f.init(b.itemsPerPage),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){h=parseInt(a,10),f.render()}),f.getPages=function(a,b){var c=[],d=1,e=b,p=angular.isDefined(h)&&b>h;p&&(o?(d=Math.max(a-Math.floor(h/2),1),e=d+h-1,e>b&&(e=b,d=e-h+1)):(d=(Math.ceil(a/h)-1)*h+1,e=Math.min(d+h-1,b)));for(var q=d;e>=q;q++){var r=g(q,q,f.isActive(q),!1);c.push(r)}if(p&&!o){if(d>1){var s=g(d-1,"...",!1,!1);c.unshift(s)}if(b>e){var t=g(e+1,"...",!1,!1);c.push(t)}}if(j){var u=g(a-1,l,!1,f.noPrevious());c.unshift(u);var v=g(a+1,m,!1,f.noNext());c.push(v)}if(i){var w=g(1,k,!1,f.noPrevious());c.unshift(w);var x=g(b,n,!1,f.noNext());c.push(x)}return c}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){function f(a,b,c,d,e){return{number:a,text:b,disabled:c,previous:i&&d,next:i&&e}}var g=e.getAttributeValue(d.previousText,a.previousText,!0),h=e.getAttributeValue(d.nextText,a.nextText,!0),i=e.getAttributeValue(d.align,a.align);e.init(a.itemsPerPage),e.getPages=function(a){return[f(a-1,g,e.noPrevious(),!0,!1),f(a+1,h,e.noNext(),!1,!0)]}}}}]),angular.module("mm.foundation.tooltip",["mm.foundation.position","mm.foundation.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!z||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return b.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),A(),b.tt_isOpen=!0,b.$digest(),A):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),b.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var a,d,e,f;switch(a=w?j.offset(c):j.position(c),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),b.tt_placement){case"right":f={top:a.top+a.height/2-e/2,left:a.left+a.width+10};break;case"bottom":f={top:a.top+a.height+10,left:a.left};break;case"left":f={top:a.top+a.height/2-e/2,left:a.left-d-10};break;default:f={top:a.top-e-10,left:a.left}}f.top+="px",f.left+="px",t.css(f)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(c.unbind(x.show,k),c.unbind(x.hide,m))},C=function(){};d.$observe(l+"Trigger",function(a){B(),C(),x=n(a),angular.isFunction(x.show)?C=b.$watch(function(){return x.show(b,c,d)},function(a){return g(a?p:q)}):x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m)),y=!0});var D=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(D)?!!D:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),C(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("mm.foundation.popover",["mm.foundation.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("mm.foundation.progressbar",["mm.foundation.transition"]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig","$transition",function(a,b,c,d){var e=this,f=[],g=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,h=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.addBar=function(a,b){var c=0,d=a.$parent.$index;angular.isDefined(d)&&f[d]&&(c=f[d].value),f.push(a),this.update(b,a.value,c),a.$watch("value",function(a,c){a!==c&&e.update(b,a,c)}),a.$on("$destroy",function(){e.removeBar(a)})},this.update=function(a,b,c){var e=this.getPercentage(b);h?(a.css("width",this.getPercentage(c)+"%"),d(a,{width:e+"%"})):a.css({transition:"none",width:e+"%"})},this.removeBar=function(a){f.splice(f.indexOf(a),1)},this.getPercentage=function(a){return Math.round(100*a/g)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},template:''}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("mm.foundation.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","$parse","ratingConfig",function(a,b,c,d){this.maxRange=angular.isDefined(b.max)?a.$parent.$eval(b.max):d.max,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):d.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):d.stateOff,this.createRateObjects=function(a){for(var b={stateOn:this.stateOn,stateOff:this.stateOff},c=0,d=a.length;d>c;c++)a[c]=angular.extend({index:c},b,a[c]);return a},a.range=this.createRateObjects(angular.isDefined(b.ratingStates)?angular.copy(a.$parent.$eval(b.ratingStates)):new Array(this.maxRange)),a.rate=function(b){a.value===b||a.readonly||(a.value=b)},a.enter=function(b){a.readonly||(a.val=b),a.onHover({value:b})},a.reset=function(){a.val=angular.copy(a.value),a.onLeave()},a.$watch("value",function(b){a.val=b}),a.readonly=!1,b.readonly&&a.$parent.$watch(c(b.readonly),function(b){a.readonly=!!b})}]).directive("rating",function(){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0}}),angular.module("mm.foundation.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){a.selectExpression(a.$parent)},b.addTab=function(a){c.push(a)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1,a.type=angular.isDefined(c.type)?a.$parent.$eval(c.type):"tabs"}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){var g,h;e.select&&(b.selectExpression=a(e.select)),e.active?(g=a(e.active),h=g.assign,b.$parent.$watch(g,function(a,c){a!==c&&(b.active=!!a)}),b.active=g(b.$parent)):h=g=angular.noop,b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||b.selectExpression(b.$parent)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("mm.foundation.tour",["mm.foundation.position","mm.foundation.tooltip"]).service("$tour",["$window",function(a){function b(){return parseInt(a.localStorage.getItem("mm.tour.step"),10)}function c(b){d=b,a.localStorage.setItem("mm.tour.step",b)}var d=b(),e={};this.add=function(a,b){e[a]=b},this.has=function(a){return!!e[a]},this.isActive=function(){return d>0},this.current=function(a){return a?void c(d):d},this.start=function(){c(1)},this.next=function(){c(d+1)},this.end=function(){c(0)}}]).directive("stepTextPopup",["$tour",function(a){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tour/tour.html",link:function(b,c){b.isLastStep=function(){return!a.has(a.current()+1)},b.endTour=function(){c.remove(),a.end()},b.nextStep=function(){c.remove(),a.next()}}}}]).directive("stepText",["$position","$tooltip","$tour","$window",function(a,b,c,d){function e(a){var b=a[0].getBoundingClientRect();return b.top>=0&&b.left>=0&&b.bottom<=d.innerHeight-80&&b.right<=d.innerWidth}function f(b,f,g){var h=parseInt(g.stepIndex,10);if(c.isActive()&&h&&(c.add(h,g),h===c.current())){if(!e(f)){var i=a.offset(f);d.scrollTo(0,i.top-d.innerHeight/2)}return!0}return!1}return b("stepText","step",f)}]),angular.module("mm.foundation.typeahead",["mm.foundation.position","mm.foundation.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+c+"'.");return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?b(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=angular.element("");w.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&w.attr("template-url",k.typeaheadTemplateUrl);var x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y=function(){x.matches=[],x.activeIdx=-1},z=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){if(a===l.$viewValue&&m){if(c.length>0){x.activeIdx=0,x.matches.length=0;for(var d=0;d=n?o>0?(A&&d.cancel(A),A=d(function(){z(a)},o)):z(a):(q(i,!1),y()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,d={};d[v.itemName]=c=x.matches[a].model,b=v.modelMapper(i,d),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,d)}),y(),j[0].focus()},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),y(),x.$digest()))}),j.bind("blur",function(){m=!1});var B=function(a){j[0]!==a.target&&(y(),x.$digest())};e.bind("click",B),i.$on("$destroy",function(){e.unbind("click",B)});var C=a(w)(x);t?e.find("body").append(C):j.after(C)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?b.replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'\n {{heading}}\n \n\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
\n')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html","\n")}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'\n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'\n \n \n\n')
+}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'\n \n \n\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'\n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'\n')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'\n')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'\n \n
\n')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n\n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'\n {{heading}}\n\n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'\n')}]),angular.module("template/tour/tour.html",[]).run(["$templateCache",function(a){a.put("template/tour/tour.html",'\n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html","\n')}]);
diff --git a/app/assets/javascripts/templates/hub_modal.html.haml b/app/assets/javascripts/templates/hub_modal.html.haml
new file mode 100644
index 0000000000..6b13f6ee78
--- /dev/null
+++ b/app/assets/javascripts/templates/hub_modal.html.haml
@@ -0,0 +1,4 @@
+%ng-include{src: "'partials/enterprise_header.html'"}
+%ng-include{src: "'partials/enterprise_details.html'"}
+%ng-include{src: "'partials/hub_details.html'"}
+%ng-include{src: "'partials/close.html'"}
diff --git a/app/assets/javascripts/templates/map_modal_producer.html.haml b/app/assets/javascripts/templates/map_modal_producer.html.haml
index 23ae109b3e..dff26519d3 100644
--- a/app/assets/javascripts/templates/map_modal_producer.html.haml
+++ b/app/assets/javascripts/templates/map_modal_producer.html.haml
@@ -1,37 +1,4 @@
-.highlight
- .highlight-top
- %p.right
- {{ [enterprise.address.city, enterprise.address.state] | printArray}}
- %h3
- %i.ofn-i_036-producers
- {{ enterprise.name }}
- %img.hero-img{"ng-src" => "{{enterprise.promo_image}}"}
-
-.row{bindonce: true}
- .small-12.large-8.columns
- %div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"}
- %h5.modal-header About
- .about-container
- %img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"}
- %p.text-small{"ng-bind-html" => "enterprise.long_description"}
- .small-12.large-4.columns
- %ng-include{src: "'partials/contact.html'"}
- %ng-include{src: "'partials/follow.html'"}
-
-.row.pad-top{bindonce: true, "ng-if" => "enterprise.hubs.length > 0"}
- .cta-container.small-12.columns
- %h5
- %i.ofn-i_029-shopping-basket
- Shop for {{enterprise.name}} products at:
- %a.button.hub{"ng-repeat" => "hub in enterprise.hubs",
- "bo-href" => "hub.path",
- "bo-class" => "{primary: hub.active, secondary: !hub.active}",
- "ofn-empties-cart" => "hub"}
- %i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
- %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
- {{hub.name}}
- .button-address {{ hub.address.city }} , {{hub.address.state}}
- %i.ofn-i_007-caret-right
-
-%a.close-reveal-modal.outside{"ng-click" => "$close()"}
- %i.ofn-i_009-close
+%ng-include{src: "'partials/enterprise_header.html'"}
+%ng-include{src: "'partials/enterprise_details.html'"}
+%ng-include{src: "'partials/hub_actions.html'"}
+%ng-include{src: "'partials/close.html'"}
diff --git a/app/assets/javascripts/templates/partials/close.html.haml b/app/assets/javascripts/templates/partials/close.html.haml
new file mode 100644
index 0000000000..1facd8edeb
--- /dev/null
+++ b/app/assets/javascripts/templates/partials/close.html.haml
@@ -0,0 +1,2 @@
+%a.close-reveal-modal.outside{"ng-click" => "$close()"}
+ %i.ofn-i_009-close
diff --git a/app/assets/javascripts/templates/partials/contact.html.haml b/app/assets/javascripts/templates/partials/contact.html.haml
index 3a64615be1..8831a8436e 100644
--- a/app/assets/javascripts/templates/partials/contact.html.haml
+++ b/app/assets/javascripts/templates/partials/contact.html.haml
@@ -5,8 +5,9 @@
{{ enterprise.phone }}
%p{"ng-if" => "enterprise.email"}
- %a{"ng-href" => "mailto:{{enterprise.email | stripUrl}}", target: "_blank" }
- {{ enterprise.email | stripUrl }}
+ %a{"ng-href" => "{{enterprise.email | stripUrl}}", target: "_blank", mailto: true}
+ %span.email
+ {{ enterprise.email | stripUrl }}
%p{"ng-if" => "enterprise.website"}
%a{"ng-href" => "http://{{enterprise.website | stripUrl}}", target: "_blank" }
diff --git a/app/assets/javascripts/templates/partials/enterprise_details.html.haml b/app/assets/javascripts/templates/partials/enterprise_details.html.haml
new file mode 100644
index 0000000000..2f88fe51f3
--- /dev/null
+++ b/app/assets/javascripts/templates/partials/enterprise_details.html.haml
@@ -0,0 +1,10 @@
+.row{bindonce: true}
+ .small-12.large-8.columns
+ %div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"}
+ %h5.modal-header About
+ .about-container
+ %img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"}
+ %p.text-small{"ng-bind-html" => "enterprise.long_description"}
+ .small-12.large-4.columns
+ %ng-include{src: "'partials/contact.html'"}
+ %ng-include{src: "'partials/follow.html'"}
diff --git a/app/assets/javascripts/templates/partials/enterprise_header.html.haml b/app/assets/javascripts/templates/partials/enterprise_header.html.haml
new file mode 100644
index 0000000000..698a5c6be6
--- /dev/null
+++ b/app/assets/javascripts/templates/partials/enterprise_header.html.haml
@@ -0,0 +1,9 @@
+.highlight
+ .highlight-top
+ %p.right
+ {{ [enterprise.address.city, enterprise.address.state] | printArray}}
+ %h3
+ %i.ofn-i_036-producers{"ng-show" => "!enterprise.is_distributor"}
+ %i.ofn-i_040-hub{"ng-show" => "enterprise.is_distributor"}
+ {{ enterprise.name }}
+ %img.hero-img{"ng-src" => "{{enterprise.promo_image}}"}
diff --git a/app/assets/javascripts/templates/partials/hub_actions.html.haml b/app/assets/javascripts/templates/partials/hub_actions.html.haml
new file mode 100644
index 0000000000..499f4f7ec8
--- /dev/null
+++ b/app/assets/javascripts/templates/partials/hub_actions.html.haml
@@ -0,0 +1,15 @@
+.row.pad-top{bindonce: true, "ng-if" => "enterprise.hubs.length > 0"}
+ .cta-container.small-12.columns
+ %h5
+ %i.ofn-i_029-shopping-basket
+ Shop for {{enterprise.name}} products at:
+ %a.button.hub{"ng-repeat" => "hub in enterprise.hubs",
+ "bo-href" => "hub.path",
+ "bo-class" => "{primary: hub.active, secondary: !hub.active}",
+ "ofn-empties-cart" => "hub"}
+ %i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
+ %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
+ {{hub.name}}
+ .button-address {{ hub.address.city }} , {{hub.address.state}}
+ %i.ofn-i_007-caret-right
+
diff --git a/app/assets/javascripts/templates/map_modal_hub.html.haml b/app/assets/javascripts/templates/partials/hub_details.html.haml
similarity index 56%
rename from app/assets/javascripts/templates/map_modal_hub.html.haml
rename to app/assets/javascripts/templates/partials/hub_details.html.haml
index 79301798c4..b3de842a3f 100644
--- a/app/assets/javascripts/templates/map_modal_hub.html.haml
+++ b/app/assets/javascripts/templates/partials/hub_details.html.haml
@@ -1,23 +1,3 @@
-.highlight
- .highlight-top
- %p.right
- {{ [enterprise.address.city, enterprise.address.state] | printArray}}
- %h3
- %i.ofn-i_040-hub
- {{ enterprise.name }}
- %img.hero-img{"ng-src" => "{{enterprise.promo_image}}"}
-
-.row{bindonce: true}
- .small-12.large-8.columns
- %div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"}
- %h5.modal-header About
- .about-container
- %img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"}
- %p.text-small{"ng-bind-html" => "enterprise.long_description"}
- .small-12.large-4.columns
- %ng-include{src: "'partials/contact.html'"}
- %ng-include{src: "'partials/follow.html'"}
-
.row.pad-top{bindonce: true}
.cta-container.small-12.columns
.row
@@ -26,7 +6,6 @@
%i.ofn-i_029-shopping-basket
%span{"active-table-hub-link" => "enterprise", change: "Change hub to", shop: "Shop at"}
.small-12.large-6.columns.right
- / Needs logic to hide if nothing populated:
.right{"bo-if" => "enterprise.pickup || enterprise.delivery"}
Delivery options:
%span{"bo-if" => "enterprise.pickup"}
@@ -45,7 +24,3 @@
{{enterprise.name}}
.button-address {{ enterprise.address.city }} , {{enterprise.address.state}}
%i.ofn-i_007-caret-right
-
-%a.close-reveal-modal.outside{"ng-click" => "$close()"}
- %i.ofn-i_009-close
-
diff --git a/app/assets/javascripts/templates/producer_modal.html.haml b/app/assets/javascripts/templates/producer_modal.html.haml
new file mode 100644
index 0000000000..db6f927e21
--- /dev/null
+++ b/app/assets/javascripts/templates/producer_modal.html.haml
@@ -0,0 +1,3 @@
+%ng-include{src: "'partials/enterprise_header.html'"}
+%ng-include{src: "'partials/enterprise_details.html'"}
+%ng-include{src: "'partials/close.html'"}
diff --git a/app/assets/javascripts/templates/product_modal.html.haml b/app/assets/javascripts/templates/product_modal.html.haml
new file mode 100644
index 0000000000..1f0c1a7564
--- /dev/null
+++ b/app/assets/javascripts/templates/product_modal.html.haml
@@ -0,0 +1,9 @@
+.row
+ .columns.small-12.large-6
+ %img.product-img{"ng-src" => "{{product.master.images[0].large_url}}", "ng-if" => "product.master.images[0]"}
+ .columns.small-12.large-6.product-header
+ %h2
+ %render-svg{path: "{{product.primary_taxon.icon}}"}
+ {{product.name}}
+ %p {{product.description}}
+%ng-include{src: "'partials/close.html'"}
diff --git a/app/assets/stylesheets/darkswarm/animations.sass b/app/assets/stylesheets/darkswarm/animations.sass
new file mode 100644
index 0000000000..fc1db55bb5
--- /dev/null
+++ b/app/assets/stylesheets/darkswarm/animations.sass
@@ -0,0 +1,30 @@
+.fade
+ opacity: 0
+ -webkit-transition: opacity .15s linear
+ transition: opacity .15s linear
+
+.fade.in
+ opacity: 1
+
+.reveal-modal.fade
+ -webkit-transition: -webkit-transform .3s ease-out
+ -moz-transition: -moz-transform .3s ease-out
+ -o-transition: -o-transform .3s ease-out
+ transition: transform .3s ease-out
+ -webkit-transform: translate(0, -25%)
+ -ms-transform: translate(0, -25%)
+ transform: translate(0, -25%)
+
+.reveal-modal.in
+ -webkit-transform: translate(0, 0)
+ -ms-transform: translate(0, 0)
+ transform: translate(0, 0)
+
+.reveal-modal-bg.fade
+ filter: alpha(opacity = 0)
+ opacity: 0
+
+.reveal-modal-bg.in
+ filter: alpha(opacity = 50)
+ opacity: .5
+
diff --git a/app/assets/stylesheets/darkswarm/home_tagline.css.sass b/app/assets/stylesheets/darkswarm/home_tagline.css.sass
index 3d00cb7a96..2c109a2703 100644
--- a/app/assets/stylesheets/darkswarm/home_tagline.css.sass
+++ b/app/assets/stylesheets/darkswarm/home_tagline.css.sass
@@ -7,19 +7,24 @@
background-color: black
background-image: url("/assets/home/tagline-bg.jpg")
@include fullbg
- height: 400px
+ height: 500px
padding: 40px 0px
- h1, h2, p
+ h1, h2, span, small, timer
color: white
+ p
+ color: $clr-brick-light
h1
- margin-bottom: 1em
+ margin-bottom: 3rem
h2
font-size: 1.6875rem
max-width: 610px
margin: 0 auto
+ padding-bottom: 0.5rem
a
color: $clr-brick-bright
&:hover, &:active, &:focus
color: $clr-brick-light-bright
- @include textsoftpress
\ No newline at end of file
+ @include textsoftpress
+ a.button.primary
+ color: white
\ No newline at end of file
diff --git a/app/assets/stylesheets/darkswarm/images.css.sass b/app/assets/stylesheets/darkswarm/images.css.sass
index 5c4d4ff8e5..39a86a93d1 100644
--- a/app/assets/stylesheets/darkswarm/images.css.sass
+++ b/app/assets/stylesheets/darkswarm/images.css.sass
@@ -21,7 +21,7 @@
.hero-img-small
background-color: #333
width: 100%
- min-height: 60px
+ // min-height: 60px
height: inherit
overflow: hidden
margin: 0 0 1rem 0 !important
diff --git a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass
index d3e9d1af22..0560cb742c 100644
--- a/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass
+++ b/app/assets/stylesheets/darkswarm/modal-enterprises.css.sass
@@ -29,7 +29,8 @@
max-height: 200px
min-height: 20px
margin-bottom: 0.5rem
- overflow: scroll
+ overflow-y: scroll
+ overflow-x: hidden
border-bottom: 1px solid #999
@include box-shadow(0 2px 2px -2px #999)
diff --git a/app/assets/stylesheets/darkswarm/producers.css.sass b/app/assets/stylesheets/darkswarm/producers.css.sass
index 3ad4e7de3a..8f0e7edfcb 100644
--- a/app/assets/stylesheets/darkswarm/producers.css.sass
+++ b/app/assets/stylesheets/darkswarm/producers.css.sass
@@ -4,4 +4,11 @@
.producers
@include fullwidthbg
background-image: url("/assets/producers/producers-pg-bg.jpg")
- background-repeat: no-repeat
\ No newline at end of file
+ background-repeat: no-repeat
+ a
+ color: $clr-turquoise
+ &:hover, &:active, &:focus
+ color: $clr-turquoise-bright
+ a.button.primary
+ &:hover, &:active, &:focus
+ color: white
\ No newline at end of file
diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass
index 312efb2188..3407916bfc 100644
--- a/app/assets/stylesheets/darkswarm/shop.css.sass
+++ b/app/assets/stylesheets/darkswarm/shop.css.sass
@@ -38,6 +38,8 @@
ordercycle
+ p.text-right
+ max-width: 400px
@media all and (max-width: 640px)
float: left
clear: left
@@ -45,6 +47,8 @@
width: 100%
margin-top: 10px
background: #e5e5e5
+ p.text-right
+ max-width: 100%
float: right
form.custom
text-align: right
diff --git a/app/assets/stylesheets/darkswarm/tabs.css.sass b/app/assets/stylesheets/darkswarm/tabs.css.sass
index ccf1743b9f..a8e3ef4a22 100644
--- a/app/assets/stylesheets/darkswarm/tabs.css.sass
+++ b/app/assets/stylesheets/darkswarm/tabs.css.sass
@@ -2,10 +2,27 @@
@import mixins
@import branding
+// Foundation overrides
+#tabs .tabs-content > .content p
+ max-width: 100% !important
+
+.tabs-content > .content
+ padding-top: 0 !important
+
+// Tabs styling
+
#tabs
background: url("/assets/gray_jean.png") top left repeat
@include box-shadow(inset 0 2px 3px 0 rgba(0,0,0,0.15))
display: block
+ color: $dark-grey
+
+ .panel
+ border-color: rgba(219, 88, 61, 0.5)
+ background-color: rgba(255, 255, 255, 0)
+ // @include box-shadow( 0 1px 1px 0 rgba(255,255,255,1))
+
+
dl dd a
@include avenir
diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb
index 884a5da2f4..77137f5940 100644
--- a/app/controllers/checkout_controller.rb
+++ b/app/controllers/checkout_controller.rb
@@ -138,9 +138,8 @@ class CheckoutController < Spree::CheckoutController
render :edit and return
end
- redirect_to(main_app.shop_paypal_payment_url(@order, :payment_method_id => payment_method.id))
+ render json: {path: main_app.paypal_payment_url(@order, :payment_method_id => payment_method.id)}, status: 200
true
-
end
# Overriding to customize the cancel url
diff --git a/app/helpers/shared_helper.rb b/app/helpers/shared_helper.rb
index b552ccd554..f555a9f695 100644
--- a/app/helpers/shared_helper.rb
+++ b/app/helpers/shared_helper.rb
@@ -1,12 +1,17 @@
module SharedHelper
def inject_enterprises
- inject_json "enterprises" , "enterprises"
+ inject_json_ams "enterprises", Enterprise.all, Api::EnterpriseSerializer, active_distributors: @active_distributors
end
def inject_json(name, partial, opts = {})
render partial: "json/injection", locals: {name: name, partial: partial}.merge(opts)
end
+ def inject_json_ams(name, data, serializer, opts = {})
+ json = ActiveModel::ArraySerializer.new(data, {each_serializer: serializer}.merge(opts)).to_json
+ render partial: "json/injection_ams", locals: {name: name, json: json}
+ end
+
def distributor_link_class(distributor)
cart = current_order(true)
@active_distributors ||= Enterprise.distributors_with_active_order_cycles
diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb
index cc8640c8aa..5b922b15e4 100644
--- a/app/models/enterprise.rb
+++ b/app/models/enterprise.rb
@@ -156,7 +156,7 @@ class Enterprise < ActiveRecord::Base
end
def distributors
- self.relatives.is_distributor.visible
+ self.relatives.is_distributor
end
def website
@@ -170,7 +170,7 @@ class Enterprise < ActiveRecord::Base
end
def suppliers
- self.relatives.is_primary_producer.visible
+ self.relatives.is_primary_producer
end
def distributed_variants
@@ -187,15 +187,17 @@ class Enterprise < ActiveRecord::Base
# Return all taxons for all distributed products
def distributed_taxons
- Spree::Product.in_distributor(self).map do |p|
- p.taxons
- end.flatten.uniq
+ Spree::Taxon.
+ joins(:products).
+ where('spree_products.id IN (?)', Spree::Product.in_distributor(self)).
+ select('DISTINCT spree_taxons.*')
end
# Return all taxons for all supplied products
def supplied_taxons
- Spree::Product.in_supplier(self).map do |p|
- p.taxons
- end.flatten.uniq
+ Spree::Taxon.
+ joins(:products).
+ where('spree_products.id IN (?)', Spree::Product.in_supplier(self)).
+ select('DISTINCT spree_taxons.*')
end
private
diff --git a/app/serializers/api/address_serializer.rb b/app/serializers/api/address_serializer.rb
new file mode 100644
index 0000000000..a4dfcb0f5f
--- /dev/null
+++ b/app/serializers/api/address_serializer.rb
@@ -0,0 +1,7 @@
+class Api::AddressSerializer < ActiveModel::Serializer
+ attributes :id, :zipcode, :city, :state
+
+ def state
+ object.state.abbr
+ end
+end
diff --git a/app/serializers/api/enterprise_serializer.rb b/app/serializers/api/enterprise_serializer.rb
new file mode 100644
index 0000000000..a74606dd79
--- /dev/null
+++ b/app/serializers/api/enterprise_serializer.rb
@@ -0,0 +1,62 @@
+class Api::EnterpriseSerializer < ActiveModel::Serializer
+ attributes :name, :id, :description, :latitude, :longitude,
+ :long_description, :website, :instagram, :linkedin, :twitter,
+ :facebook, :is_primary_producer, :is_distributor, :phone, :visible,
+ :email, :hash, :logo, :promo_image, :icon, :path,
+ :pickup, :delivery, :active, :orders_close_at
+
+ has_many :distributed_taxons, key: :taxons, serializer: Api::TaxonSerializer
+ has_many :supplied_taxons, serializer: Api::TaxonSerializer
+ has_many :distributors, key: :hubs, serializer: Api::IdSerializer
+ has_many :suppliers, key: :producers, serializer: Api::IdSerializer
+
+ has_one :address, serializer: Api::AddressSerializer
+
+ def pickup
+ object.shipping_methods.where(:require_ship_address => false).present?
+ end
+
+ def delivery
+ object.shipping_methods.where(:require_ship_address => true).present?
+ end
+
+ def active
+ @options[:active_distributors].andand.include?(object)
+ end
+
+ def orders_close_at
+ OrderCycle.first_closing_for(object).andand.orders_close_at
+ end
+
+ def email
+ object.email.to_s.reverse
+ end
+
+ def hash
+ object.to_param
+ end
+
+ def logo
+ object.logo(:medium) if object.logo.exists?
+ end
+
+ def promo_image
+ object.promo_image(:large) if object.promo_image.exists?
+ end
+
+ def icon
+ if object.is_primary_producer? and object.is_distributor?
+ "/assets/map-icon-both.svg"
+ elsif object.is_primary_producer?
+ "/assets/map-icon-producer.svg"
+ else
+ "/assets/map-icon-hub.svg"
+ end
+ end
+
+ # TODO when ActiveSerializers supports URL helpers
+ # Then refactor. See readme https://github.com/rails-api/active_model_serializers
+ def path
+ "/enterprises/#{object.to_param}/shop"
+ end
+end
diff --git a/app/serializers/api/id_serializer.rb b/app/serializers/api/id_serializer.rb
new file mode 100644
index 0000000000..0093338e6e
--- /dev/null
+++ b/app/serializers/api/id_serializer.rb
@@ -0,0 +1,3 @@
+class Api::IdSerializer < ActiveModel::Serializer
+ attributes :id
+end
diff --git a/app/serializers/api/taxon_serializer.rb b/app/serializers/api/taxon_serializer.rb
new file mode 100644
index 0000000000..a907532a6b
--- /dev/null
+++ b/app/serializers/api/taxon_serializer.rb
@@ -0,0 +1,7 @@
+class Api::TaxonSerializer < ActiveModel::Serializer
+ attributes :id, :name, :permalink, :icon
+
+ def icon
+ object.icon(:original)
+ end
+end
diff --git a/app/views/groups/index.html.haml b/app/views/groups/index.html.haml
index 7872bef239..f2ae6e550a 100644
--- a/app/views/groups/index.html.haml
+++ b/app/views/groups/index.html.haml
@@ -1,4 +1,5 @@
= inject_enterprises
+
:javascript
angular.module('Darkswarm').value('groups', #{render partial: "json/groups", object: @groups})
@@ -19,7 +20,7 @@
"ng-debounce" => "150",
"ofn-disable-enter" => true}
- .group{"ng-repeat" => "group in Groups.groups | filter:query | orderBy:order",
+ .group{"ng-repeat" => "group in Groups.groups | groups:query | orderBy:order",
name: "group{{group.id}}",
id: "group{{group.id}}"}
.row.pad-top{bindonce: true}
@@ -36,10 +37,12 @@
.small-6.columns
%p {{ group.long_description }}
.small-6.columns
- %h5 Our hubs & producers
+ %h5 Our hubs & producers
%ul.small-block-grid-2
- %li{"ng-repeat" => "enterprise in group.enterprises"}
- %a{"bo-href" => "enterprise.path"} {{ enterprise.name }}
+ %li{"ng-repeat" => "enterprise in group.enterprises", "scroll-after-load" => true}
+ %hub-modal{"ng-if" => "enterprise.is_distributor"}
+ %producer-modal{"ng-if" => "!enterprise.is_distributor", "show-hub-actions" => 'true'}
+ {{ enterprise.name }}
.row.group_footer
.small-12.columns
diff --git a/app/views/home/_fat.html.haml b/app/views/home/_fat.html.haml
index f9c0db6810..16dc3eaafb 100644
--- a/app/views/home/_fat.html.haml
+++ b/app/views/home/_fat.html.haml
@@ -28,9 +28,9 @@
Our producers
%ul.bullet-list
%li{"ng-repeat" => "enterprise in hub.producers"}
- = render partial: "modals/producer"
+ %producer-modal {{ enterprise.name }}
-.row.active_table_row.link{"ng-show" => "open()", "bo-if" => "hub.active"}
+.row.active_table_row.link{"ng-show" => "open()"}
.cta-container.columns.small-12
.row
.columns.small-12
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index b441ac647f..ea283f1490 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -2,10 +2,16 @@
.row
.small-12.text-center.columns
%h1= image_tag "ofn_logo_beta.png", title: "Open Food Network (beta)"
- %h2 An open marketplace that makes it easy to find, buy, sell and move sustainable local food.
-
- %ofn-modal{title: "Learn more"}
- = render partial: "modals/learn_more"
+ %h2 We're crowdfunding right now!
+ %h5
+ %timer{"end-time" => '1407679200000'}
+ {{days}} days, {{hours}} hrs, {{minutes}} mins & {{seconds}} secs to go
+ %p Help us make Open Food Network the best it can be:
+ %a.button.primary{href: "http://startsomegood.com/openfoodnetwork", target:"_blank"} Support now
+ / %h2 An open marketplace that makes it easy to find, buy, sell and move sustainable local food.
+ %br
+ %ofn-modal{title: "Learn more"}
+ = render partial: "modals/learn_more"
= render partial: "home/hubs"
diff --git a/app/views/json/_groups.rabl b/app/views/json/_groups.rabl
index 27325bb850..691d36f42f 100644
--- a/app/views/json/_groups.rabl
+++ b/app/views/json/_groups.rabl
@@ -2,7 +2,7 @@ collection @groups
attributes :id, :name, :position, :description, :long_description
child enterprises: :enterprises do
- extends 'json/enterprises'
+ attributes :id
end
node :logo do |group|
diff --git a/app/views/json/_injection_ams.html.haml b/app/views/json/_injection_ams.html.haml
new file mode 100644
index 0000000000..40635189c8
--- /dev/null
+++ b/app/views/json/_injection_ams.html.haml
@@ -0,0 +1,2 @@
+:javascript
+ angular.module('Darkswarm').value("#{name.to_s}", #{json})
diff --git a/app/views/json/partials/_hub.rabl b/app/views/json/partials/_hub.rabl
index 2df71001cb..ed9520b43e 100644
--- a/app/views/json/partials/_hub.rabl
+++ b/app/views/json/partials/_hub.rabl
@@ -13,8 +13,10 @@ end
node :delivery do |hub|
hub.shipping_methods.where(:require_ship_address => true).present?
end
-node :active do |hub|
- @active_distributors.include?(hub)
+if @active_distributors
+ node :active do |hub|
+ @active_distributors.include?(hub)
+ end
end
node :orders_close_at do |hub|
OrderCycle.first_closing_for(hub).andand.orders_close_at
diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml
index 0bc90f9234..3d246acab8 100644
--- a/app/views/layouts/darkswarm.html.haml
+++ b/app/views/layouts/darkswarm.html.haml
@@ -4,8 +4,11 @@
%meta{name: 'viewport', content: "width=device-width,initial-scale=1.0"}/
%title= content_for?(:title) ? yield(:title) : 'Welcome to Open Food Network'
- = favicon_link_tag "favicon.png"
- %link{href: "http://fonts.googleapis.com/css?family=Open+Sans:400,700", rel: "stylesheet", type: "text/css"}/
+ - if Rails.env.production?
+ = favicon_link_tag
+ - else
+ = favicon_link_tag "/favicon-staging.ico"
+ %link{href: "https://fonts.googleapis.com/css?family=Open+Sans:400,700", rel: "stylesheet", type: "text/css"}/
= yield :scripts
%script{src: "//maps.googleapis.com/maps/api/js?sensor=false"}
diff --git a/app/views/modals/_producer.html.haml b/app/views/modals/_producer.html.haml
index 044cf3cdf0..6f8d8951f1 100644
--- a/app/views/modals/_producer.html.haml
+++ b/app/views/modals/_producer.html.haml
@@ -23,8 +23,9 @@
{{ enterprise.phone }}
%p{"bo-if" => "enterprise.email"}
- %a{"ng-href" => "mailto:{{enterprise.email | stripUrl}}", target: "_blank" }
- {{ enterprise.email | stripUrl }}
+ %a{"ng-href" => "{{enterprise.email | stripUrl}}", target: "_blank", mailto: true }
+ %span.email
+ {{ enterprise.email | stripUrl }}
%p{"bo-show" => "enterprise.website"}
%a{"ng-href" => "http://{{enterprise.website}}", target: "_blank" }
diff --git a/app/views/modals/_product.html.haml b/app/views/modals/_product.html.haml
deleted file mode 100644
index 6d50f9c09d..0000000000
--- a/app/views/modals/_product.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-%ofn-modal{title: "{{product.name}}"}
- .row
- .columns.small-12.large-6
- %img.product-img{"ng-src" => "{{product.master.images[0].large_url}}", "ng-if" => "product.master.images[0]"}
- .columns.small-12.large-6.product-header
- %h2
- %render-svg{path: "{{product.primary_taxon.icon}}"}
- {{product.name}}
- %p {{product.description}}
- %a.close-reveal-modal{"ng-click" => "$close()"}
- %i.ofn-i_009-close
diff --git a/app/views/producers/index.haml b/app/views/producers/index.haml
index b71013c425..5cde67a512 100644
--- a/app/views/producers/index.haml
+++ b/app/views/producers/index.haml
@@ -14,16 +14,16 @@
%i.ofn-i_020-search
%input{type: :text,
"ng-model" => "query",
- placeholder: "Search postcode, suburb or hub name...",
+ placeholder: "Search postcode, suburb or producer name...",
"ng-debounce" => "150",
"ofn-disable-enter" => true}
.row{bindonce: true}
.small-12.columns
.active_table
- %producer.active_table_node.row{id: "{{producer.path}}",
+ %producer.active_table_node.row.animate-repeat{id: "{{producer.path}}",
"scroll-after-load" => true,
- "ng-repeat" => "producer in filteredProducers = (Producers.producers | filterProducers:query)",
+ "ng-repeat" => "producer in filteredProducers = (Producers.visible | filterProducers:query)",
"ng-controller" => "ProducerNodeCtrl",
"ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}",
id: "{{producer.hash}}"}
diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml
index b7681e2764..eee0e16332 100644
--- a/app/views/shop/products/_form.html.haml
+++ b/app/views/shop/products/_form.html.haml
@@ -12,7 +12,7 @@
%input.button.primary.right{type: :submit, value: "Add to Cart"}
%div{bindonce: true}
- %product{"ng-controller" => "ProductNodeCtrl",
+ %product.animate-repeat{"ng-controller" => "ProductNodeCtrl",
"ng-repeat" => "product in Product.products | products:query | orderBy:ordering.order | limitTo: limit track by product.id"}
%div
= render partial: "shop/products/summary"
diff --git a/app/views/shop/products/_master.html.haml b/app/views/shop/products/_master.html.haml
index c06acb5cb1..92a17be43d 100644
--- a/app/views/shop/products/_master.html.haml
+++ b/app/views/shop/products/_master.html.haml
@@ -1,5 +1,5 @@
.small-1.columns
- %span.bulk{"bo-if" => "product.group_buy"} bulk
+ %i.ofn-i_056-bulk{"bo-if" => "product.group_buy"}
.small-4.columns
@@ -15,7 +15,7 @@
name: "variants[{{product.master.id}}]",
id: "variants_{{product.master.id}}",
"ng-model" => "product.quantity"}
- %small {{ product.master.unit_to_display }}
+ %small x {{ product.master.unit_to_display }}
-# WITH GROUP BUY
.small-2.columns{"bo-if" => "product.group_buy"}
@@ -36,7 +36,7 @@
max: "{{product.on_demand && 9999 || product.count_on_hand }}",
name: "variant_attributes[{{product.master.id}}][max_quantity]",
"ng-model" => "product.max_quantity"}
- {{ product.master.unit_to_display }}
+ %small x {{ product.master.unit_to_display }}
.small-2.columns.text-right
{{ product.price | currency }}
diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml
index ec018065c8..b21b535315 100644
--- a/app/views/shop/products/_summary.html.haml
+++ b/app/views/shop/products/_summary.html.haml
@@ -1,14 +1,15 @@
.row.summary
.small-1.columns
- %img{"bo-src" => "product.master.images[0].small_url"}
+ %product-modal
+ %img{"bo-src" => "product.master.images[0].small_url"}
.small-4.columns.summary-header
%render-svg{path: "{{product.primary_taxon.icon}}"}
- = render partial: "modals/product"
+ %product-modal {{ product.name }}
.small-5.columns
%i.ofn-i_036-producers
- = render partial: "modals/producer"
+ %producer-modal {{ enterprise.name }}
.small-2.columns.summary-price.text-right.price
%span{"ng-if" => "hasVariants"}
diff --git a/app/views/shop/products/_variants.html.haml b/app/views/shop/products/_variants.html.haml
index 9faaa5e8b1..5d6a59535e 100644
--- a/app/views/shop/products/_variants.html.haml
+++ b/app/views/shop/products/_variants.html.haml
@@ -2,7 +2,7 @@
"ng-repeat" => "variant in product.variants"}
.small-1.columns
- %span.bulk{"bo-if" => "product.group_buy"} bulk
+ %i.ofn-i_056-bulk{"bo-if" => "product.group_buy"}
.small-4.columns
@@ -18,7 +18,7 @@
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}",
"bo-model" => "variant.quantity"}
- %small {{ variant.unit_to_display }}
+ %small x {{ variant.unit_to_display }}
-# WITH GROUP BUY
.small-2.columns{"bo-if" => "product.group_buy"}
@@ -39,7 +39,7 @@
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
name: "variant_attributes[{{variant.id}}][max_quantity]",
"ng-model" => "variant.max_quantity"}
- %small {{ variant.unit_to_display }}
+ %small x {{ variant.unit_to_display }}
.small-2.columns.text-right.price
{{ variant.price | currency }}
diff --git a/app/views/shopping_shared/_about.html.haml b/app/views/shopping_shared/_about.html.haml
index 1cd8f91c49..0e71125f65 100644
--- a/app/views/shopping_shared/_about.html.haml
+++ b/app/views/shopping_shared/_about.html.haml
@@ -1,9 +1,7 @@
.content#about{"ng-controller" => "AboutUsCtrl", bindonce: true}
.row
- .small-12.large-6.columns
- %img.hero-img-small{"bo-src" => "CurrentHub.hub.promo_image"}
- %p.small-text{"bo-html" => "CurrentHub.hub.long_description"}
- .small-12.large-3.columns
+ .small-12.large-8.columns.panel
+ %img.hero-img-small{"bo-src" => "CurrentHub.hub.promo_image", "bo-if" => "CurrentHub.hub.promo_image"}
+ %p{"bo-html" => "CurrentHub.hub.long_description"}
+ .small-12.large-4.columns
- .small-12.large-3.columns
- %img{"bo-src" => "CurrentHub.hub.logo", "bo-if" => "CurrentHub.hub.logo"}
diff --git a/app/views/shopping_shared/_contact.html.haml b/app/views/shopping_shared/_contact.html.haml
index 249f0092c8..f5dc7c85bd 100644
--- a/app/views/shopping_shared/_contact.html.haml
+++ b/app/views/shopping_shared/_contact.html.haml
@@ -4,52 +4,57 @@
.panel
.row
.small-12.large-4.columns
- %h4=current_distributor.name
- %p
- = current_distributor.address.address1
- - unless current_distributor.address.address2.blank?
- %br
- = current_distributor.address.address2
- %br
- = current_distributor.address.city
- = current_distributor.address.state
- = current_distributor.address.zipcode
+ - if current_distributor.address.address1 || current_distributor.address.address2 || current_distributor.address.city || current_distributor.address.state || current_distributor.address.zipcode
+ %div.modal-centered
+ %h5.modal-header=current_distributor.name
+ %p
+ = current_distributor.address.address1
+ - unless current_distributor.address.address2.blank?
+ %br
+ = current_distributor.address.address2
+ %br
+ = current_distributor.address.city
+ = current_distributor.address.state
+ = current_distributor.address.zipcode
- .small-12.large-8.columns
- %ul.small-block-grid-1.large-block-grid-2{bindonce: true}
- - unless current_distributor.website.blank?
- %li
- %a{href: "http://#{current_distributor.website}", target: "_blank" }
- %i.ofn-i_049-web
- = current_distributor.website
+ .small-12.large-4.columns
+ - if current_distributor.website || current_distributor.email
+ %div.modal-centered
+ %h5.modal-header Contact
+ - unless current_distributor.website.blank?
+ %p
+ %a{href: "http://#{current_distributor.website}", target: "_blank" }
+ = current_distributor.website
+ - unless current_distributor.email.blank?
+ %p
+ %a{href: current_distributor.email.reverse, mailto: true}
+ %span.email
+ = current_distributor.email.reverse
- - unless current_distributor.email.blank?
- %li
- %a{href: current_distributor.email.reverse, mailto: true }
- %i.ofn-i_050-mail-circle
- %span.email
- = current_distributor.email.reverse
+ .small-12.large-4.columns
+ - if current_distributor.twitter.present? || current_distributor.facebook.present? || current_distributor.linkedin.present? || current_distributor.instagram.present?
+ %div.modal-centered
+ %h5.modal-header Follow
+ %div.follow-icons
+ - unless current_distributor.twitter.blank?
+ %span
+ %a{href: "http://twitter.com/#{current_distributor.twitter}", target: "_blank" }
+ %i.ofn-i_041-twitter
- - unless current_distributor.twitter.blank?
- %li
- %a{href: "http://twitter.com/#{current_distributor.twitter}", target: "_blank" }
- %i.ofn-i_041-twitter
- = current_distributor.twitter
+ - unless current_distributor.facebook.blank?
+ %span
+ %a{href: "http://#{current_distributor.facebook}", target: "_blank" }
+ %i.ofn-i_044-facebook
+ = current_distributor.facebook
- - unless current_distributor.facebook.blank?
- %li
- %a{href: "http://#{current_distributor.facebook}", target: "_blank" }
- %i.ofn-i_044-facebook
- = current_distributor.facebook
+ - unless current_distributor.linkedin.blank?
+ %span
+ %a{href: "http://#{current_distributor.linkedin}", target: "_blank" }
+ %i.ofn-i_042-linkedin
+ = current_distributor.linkedin
- - unless current_distributor.linkedin.blank?
- %li
- %a{href: "http://#{current_distributor.linkedin}", target: "_blank" }
- %i.ofn-i_042-linkedin
- = current_distributor.linkedin
-
- - unless current_distributor.instagram.blank?
- %li
- %a{href: "http://instagram.com.#{current_distributor.instagram}", target: "_blank" }
- %i.ofn-i_043-instagram
- = current_distributor.instagram
+ - unless current_distributor.instagram.blank?
+ %span
+ %a{href: "http://instagram.com.#{current_distributor.instagram}", target: "_blank" }
+ %i.ofn-i_043-instagram
+ = current_distributor.instagram
diff --git a/app/views/shopping_shared/_groups.html.haml b/app/views/shopping_shared/_groups.html.haml
index 5fd1a1f9b3..7e6b48997e 100644
--- a/app/views/shopping_shared/_groups.html.haml
+++ b/app/views/shopping_shared/_groups.html.haml
@@ -1,10 +1,12 @@
.content
.row
- .small-12.columns
- %h5
- =current_distributor.name
- belongs to:
- %ul.bullet-list
- - for group in current_distributor.groups
- %li
- %a{href: main_app.groups_path(anchor: "#/#group#{group.id}")}= group.name
+ .small-12.columns.panel
+ - if current_distributor.groups.length > 0
+ %h5
+ =current_distributor.name
+ is part of:
+ %ul.bullet-list
+ - for group in current_distributor.groups
+ %li
+ %a{href: main_app.groups_path + "/#/#group#{group.id}"}
+ = group.name
diff --git a/app/views/shopping_shared/_order_cycles.html.haml b/app/views/shopping_shared/_order_cycles.html.haml
index 195389df91..cfc77091d3 100644
--- a/app/views/shopping_shared/_order_cycles.html.haml
+++ b/app/views/shopping_shared/_order_cycles.html.haml
@@ -3,13 +3,15 @@
angular.module('Darkswarm').value('orderCycleData', #{render "json/order_cycle"})
- if @order_cycles and @order_cycles.empty?
- Orders are currently closed for this hub
- %p
- Please contact your hub directly to see if they accept late orders,
- or wait until the next cycle opens.
-
- = render partial: "shopping_shared/next_order_cycle"
- = render partial: "shopping_shared/last_order_cycle"
+ %h4.text-right
+ %i.ofn-i_032-closed-sign
+ Orders are closed
+ %p.text-right Please wait until the next cycle opens (or contact us directly to see if we can accept any late orders)
+ .text-right
+ %small
+ %em
+ = render partial: "shopping_shared/next_order_cycle"
+ = render partial: "shopping_shared/last_order_cycle"
- else
%form.custom
diff --git a/app/views/shopping_shared/_producers.html.haml b/app/views/shopping_shared/_producers.html.haml
index 3ccaaf72ab..4c34ae917d 100644
--- a/app/views/shopping_shared/_producers.html.haml
+++ b/app/views/shopping_shared/_producers.html.haml
@@ -1,8 +1,8 @@
.content#producers{"ng-controller" => "ProducersTabCtrl"}
.row
- .small-12.columns
- %h5
- = "#{current_distributor.name}'s producers:"
+ .small-12.columns.panel
+ %h5
+ {{CurrentHub.hub.name}}'s producers:
%ul.bullet-list
%li{"ng-repeat" => "enterprise in CurrentHub.hub.producers"}
= render partial: "modals/producer"
diff --git a/app/views/spree/checkout/payment/_paypalexpress.html.haml b/app/views/spree/checkout/payment/_paypalexpress.html.haml
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/app/views/spree/orders/edit.html.haml b/app/views/spree/orders/edit.html.haml
index 76b22aeb36..4fdeea348b 100644
--- a/app/views/spree/orders/edit.html.haml
+++ b/app/views/spree/orders/edit.html.haml
@@ -1,4 +1,5 @@
-- @body_id = 'cart'
+= inject_enterprises
+
.darkswarm
- content_for :order_cycle_form do
%strong.avenir
diff --git a/app/views/spree/orders/show.html.haml b/app/views/spree/orders/show.html.haml
index f0fe766696..1f020f8c1f 100644
--- a/app/views/spree/orders/show.html.haml
+++ b/app/views/spree/orders/show.html.haml
@@ -1,3 +1,5 @@
+= inject_enterprises
+
.darkswarm
- content_for :order_cycle_form do
%strong.avenir
diff --git a/config/initializers/serializers.rb b/config/initializers/serializers.rb
new file mode 100644
index 0000000000..50f03c6591
--- /dev/null
+++ b/config/initializers/serializers.rb
@@ -0,0 +1,2 @@
+ActiveModel::ArraySerializer.root = false
+ActiveModel::Serializer.root = false
diff --git a/config/routes.rb b/config/routes.rb
index b151e4bae8..51826d6777 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -3,11 +3,7 @@ Openfoodnetwork::Application.routes.draw do
get "/#/login", to: "home#index", as: :spree_login
- if Rails.env.production?
- get "/map", to: "home#index", as: :map
- else
- get "/map", to: "map#index", as: :map
- end
+ get "/map", to: "map#index", as: :map
resource :shop, controller: "shop" do
get :products
@@ -20,7 +16,7 @@ Openfoodnetwork::Application.routes.draw do
get '/checkout', :to => 'checkout#edit' , :as => :checkout
put '/checkout', :to => 'checkout#update' , :as => :update_checkout
- get "/checkout/paypal_payment", to: 'checkout#paypal_payment', as: :paypal_payment
+ get '/checkout/paypal_payment/:order_id', to: 'checkout#paypal_payment', as: :paypal_payment
resources :enterprises do
collection do
diff --git a/public/favicon-staging.ico b/public/favicon-staging.ico
new file mode 100644
index 0000000000..c54a7c4528
Binary files /dev/null and b/public/favicon-staging.ico differ
diff --git a/public/favicon.ico b/public/favicon.ico
index e69de29bb2..9e0f9a2681 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 4005d82a2a..67193cb9a0 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -1,9 +1,17 @@
require 'spec_helper'
describe GroupsController do
+ render_views
+ let(:enterprise) { create(:distributor_enterprise) }
+ let!(:group) { create(:enterprise_group, enterprises: [enterprise], on_front_page: true) }
it "gets all visible groups" do
EnterpriseGroup.stub_chain :on_front_page, :by_position
EnterpriseGroup.should_receive :on_front_page
get :index
end
+
+ it "loads all enterprises for group" do
+ get :index
+ response.body.should have_text enterprise.id
+ end
end
diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb
index babb1c84e6..5cd2b0cda8 100644
--- a/spec/controllers/home_controller_spec.rb
+++ b/spec/controllers/home_controller_spec.rb
@@ -14,14 +14,15 @@ describe HomeController do
assigns[:active_distributors].should == [distributor]
end
- it "does not show invisible hubs" do
+ # Exclusion from actual rendered view handled in features/consumer/home
+ it "shows JSON for invisible hubs" do
get :index
- response.body.should_not have_content invisible_distributor.name
+ response.body.should have_content invisible_distributor.name
end
- # This is done inside the json/hubs RABL template
+ # This is done inside the json/hubs Serializer
it "gets the next order cycle for each hub" do
- OrderCycle.should_receive(:first_closing_for).with(distributor)
+ OrderCycle.should_receive(:first_closing_for).twice
get :index
end
end
diff --git a/spec/controllers/producers_controller_spec.rb b/spec/controllers/producers_controller_spec.rb
index d5a90a77d3..812b08892f 100644
--- a/spec/controllers/producers_controller_spec.rb
+++ b/spec/controllers/producers_controller_spec.rb
@@ -5,7 +5,7 @@ describe ProducersController do
before do
Enterprise.stub(:distributors_with_active_order_cycles).and_return [distributor]
- Enterprise.stub(:visible).and_return [distributor]
+ Enterprise.stub(:all).and_return [distributor]
end
it "sets active distributors" do
diff --git a/spec/features/consumer/groups_spec.rb b/spec/features/consumer/groups_spec.rb
new file mode 100644
index 0000000000..7baa5a2807
--- /dev/null
+++ b/spec/features/consumer/groups_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+feature 'Groups', js: true do
+ include AuthenticationWorkflow
+ include UIComponentHelper
+
+ let(:enterprise) { create(:distributor_enterprise) }
+ let!(:group) { create(:enterprise_group, enterprises: [enterprise], on_front_page: true) }
+
+ it "renders groups" do
+ visit groups_path
+ page.should have_content group.name
+ end
+
+ it "renders enterprise modals for groups" do
+ visit groups_path
+ page.should have_content enterprise.name
+ open_enterprise_modal enterprise
+ modal_should_be_open_for enterprise
+ end
+end
diff --git a/spec/features/consumer/home_spec.rb b/spec/features/consumer/home_spec.rb
index 1b28af1489..520923d251 100644
--- a/spec/features/consumer/home_spec.rb
+++ b/spec/features/consumer/home_spec.rb
@@ -5,20 +5,27 @@ feature 'Home', js: true do
include UIComponentHelper
let!(:distributor) { create(:distributor_enterprise) }
+ let!(:invisible_distributor) { create(:distributor_enterprise, visible: false) }
let(:d1) { create(:distributor_enterprise) }
let(:d2) { create(:distributor_enterprise) }
let!(:order_cycle) { create(:order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise)) }
+ let!(:producer) { create(:supplier_enterprise) }
+ let!(:er) { create(:enterprise_relationship, parent: distributor, child: producer) }
before do
visit "/"
end
- it "shows all hubs" do
+ it "shows hubs" do
page.should have_content distributor.name
expand_active_table_node distributor.name
page.should have_content "Shop at #{distributor.name}"
end
+ it "does not show invisible hubs" do
+ page.should_not have_content invisible_distributor.name
+ end
+
it "should grey out hubs that are not in an order cycle" do
create(:simple_product, distributors: [d1, d2])
visit root_path
@@ -30,4 +37,11 @@ feature 'Home', js: true do
follow_active_table_node distributor.name
current_path.should == "/shop"
end
+
+ it "should show hub producer modals" do
+ expand_active_table_node distributor.name
+ page.should have_content producer.name
+ open_enterprise_modal producer
+ modal_should_be_open_for producer
+ end
end
diff --git a/spec/features/consumer/producers_spec.rb b/spec/features/consumer/producers_spec.rb
index 7a7bf333a3..8e834aaeb3 100644
--- a/spec/features/consumer/producers_spec.rb
+++ b/spec/features/consumer/producers_spec.rb
@@ -7,6 +7,7 @@ feature %q{
}, js: true do
include UIComponentHelper
let!(:producer) { create(:supplier_enterprise) }
+ let!(:invisible_producer) { create(:supplier_enterprise, visible: false) }
before do
visit producers_path
@@ -17,4 +18,8 @@ feature %q{
expand_active_table_node producer.name
page.should have_content producer.supplied_taxons.join(', ')
end
+
+ it "doesn't show invisible producers" do
+ page.should_not have_content invisible_producer.name
+ end
end
diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb
index e219e970a2..377f56c102 100644
--- a/spec/features/consumer/shopping/shopping_spec.rb
+++ b/spec/features/consumer/shopping/shopping_spec.rb
@@ -79,6 +79,9 @@ feature "As a consumer I want to shop with a distributor", js: true do
page.should have_content product.name
page.should have_content product.master.display_name
page.should have_content product.master.display_as
+
+ open_product_modal product
+ modal_should_be_open_for product
end
end
end
@@ -196,7 +199,7 @@ feature "As a consumer I want to shop with a distributor", js: true do
context "when no order cycles are available" do
it "tells us orders are closed" do
visit shop_path
- page.should have_content "Orders are currently closed for this hub"
+ page.should have_content "Orders are closed"
end
it "shows the last order cycle" do
oc1 = create(:simple_order_cycle, distributors: [distributor], orders_close_at: 10.days.ago)
diff --git a/spec/helpers/shared_helper_spec.rb b/spec/helpers/shared_helper_spec.rb
index 92c688d1ae..76d2e6e859 100644
--- a/spec/helpers/shared_helper_spec.rb
+++ b/spec/helpers/shared_helper_spec.rb
@@ -23,4 +23,18 @@ describe SharedHelper do
helper.stub(:current_order) { order }
helper.distributor_link_class(d1).should =~ /empties-cart/
end
+
+ describe "Injecting json" do
+ let(:enterprise) { create(:distributor_enterprise, facebook: "roger") }
+
+ it "Will inject via AMS" do
+ helper.inject_json_ams("test", [enterprise], Api::EnterpriseSerializer).should match enterprise.name
+ end
+
+ it "injects enterprises" do
+ Enterprise.stub(:visible).and_return [enterprise]
+ helper.inject_enterprises().should match enterprise.name
+ helper.inject_enterprises().should match enterprise.facebook
+ end
+ end
end
diff --git a/spec/javascripts/unit/darkswarm/filters/filter_groups_spec.js.coffee b/spec/javascripts/unit/darkswarm/filters/filter_groups_spec.js.coffee
new file mode 100644
index 0000000000..e7e2614f7f
--- /dev/null
+++ b/spec/javascripts/unit/darkswarm/filters/filter_groups_spec.js.coffee
@@ -0,0 +1,34 @@
+describe "filtering Groups", ->
+ filterGroups = null
+ groups = [{
+ name: "test"
+ long_description: "roger"
+ enterprises: [{
+ name: "kittens"
+ }, {
+ name: "kittens"
+ }]
+ }, {
+ name: "blankness"
+ long_description: "in the sky"
+ enterprises: [{
+ name: "ponies"
+ }, {
+ name: "ponies"
+ }]
+ }
+ ]
+
+ beforeEach ->
+ module 'Darkswarm'
+ inject ($filter) ->
+ filterGroups = $filter('groups')
+
+ it "filters by name", ->
+ expect(filterGroups(groups, "test")[0]).toBe groups[0]
+
+ it "filters by description", ->
+ expect(filterGroups(groups, "sky")[0]).toBe groups[1]
+
+ it "filters by enterprise name", ->
+ expect(filterGroups(groups, "ponies")[0]).toBe groups[1]
diff --git a/spec/javascripts/unit/darkswarm/navigation.js.coffee b/spec/javascripts/unit/darkswarm/navigation.js.coffee
deleted file mode 100644
index 3701a7bded..0000000000
--- a/spec/javascripts/unit/darkswarm/navigation.js.coffee
+++ /dev/null
@@ -1,11 +0,0 @@
-describe 'Navigation service', ->
- Navigation = null
-
- beforeEach ->
- module 'Darkswarm'
- inject ($injector)->
- Navigation = $injector.get("Navigation")
-
- it "caches the path provided", ->
- Navigation.navigate "/foo"
- expect(Navigation.path).toEqual "/foo"
diff --git a/spec/javascripts/unit/darkswarm/services/groups_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/groups_spec.js.coffee
new file mode 100644
index 0000000000..abd9c5c617
--- /dev/null
+++ b/spec/javascripts/unit/darkswarm/services/groups_spec.js.coffee
@@ -0,0 +1,33 @@
+describe "Groups service", ->
+ Groups = null
+ Enterprises = null
+ CurrentHubMock = {}
+ groups = [{
+ id: 1
+ name: "Test Group"
+ enterprises: [
+ {id: 1},
+ {id: 2}
+ ]
+ }]
+ enterprises = [
+ {id: 1, name: "Test 1", groups: [{id: 1}]},
+ {id: 2, name: "Test 2", groups: [{id: 1}]}
+ ]
+
+ beforeEach ->
+ module 'Darkswarm'
+ angular.module('Darkswarm').value('groups', groups)
+ angular.module('Darkswarm').value('enterprises', enterprises)
+ module ($provide)->
+ $provide.value "CurrentHub", CurrentHubMock
+ null
+ inject (_Groups_, _Enterprises_)->
+ Groups = _Groups_
+ Enterprises = _Enterprises_
+
+ it "dereferences group enterprises", ->
+ expect(Groups.groups[0].enterprises[0]).toBe enterprises[0]
+
+ it "dereferences enterprise groups", ->
+ expect(Enterprises.enterprises[0].groups[0]).toBe groups[0]
diff --git a/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee
index c10c2c22f8..3ef21705d3 100644
--- a/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee
+++ b/spec/javascripts/unit/darkswarm/services/map_spec.js.coffee
@@ -7,6 +7,7 @@ describe "Hubs service", ->
active: false
orders_close_at: new Date()
type: "hub"
+ visible: true
}
]
diff --git a/spec/javascripts/unit/darkswarm/services/navigation.js.coffee b/spec/javascripts/unit/darkswarm/services/navigation.js.coffee
new file mode 100644
index 0000000000..8b5912bff8
--- /dev/null
+++ b/spec/javascripts/unit/darkswarm/services/navigation.js.coffee
@@ -0,0 +1,28 @@
+describe 'Navigation service', ->
+ Navigation = null
+ window =
+ location:
+ href: null
+ pathname: null
+
+ beforeEach ->
+ module 'Darkswarm', ($provide) ->
+ $provide.value "$window", window
+ null
+ inject ($injector)->
+ Navigation = $injector.get("Navigation")
+
+
+ it "caches the path provided", ->
+ Navigation.navigate "/foo"
+ expect(Navigation.path).toEqual "/foo"
+
+ describe "redirecting", ->
+ it "redirects to full URLs", ->
+ Navigation.go "http://google.com"
+ expect(window.location.href).toEqual "http://google.com"
+
+ it "redirects to paths", ->
+ Navigation.go "/woo/yeah"
+ expect(window.location.pathname).toEqual "/woo/yeah"
+
\ No newline at end of file
diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb
index 0354f6b41d..0fa57d19d3 100644
--- a/spec/models/enterprise_spec.rb
+++ b/spec/models/enterprise_spec.rb
@@ -42,14 +42,12 @@ describe Enterprise do
it "scopes relatives to visible distributors" do
e.should_receive(:relatives).and_return(relatives = [])
relatives.should_receive(:is_distributor).and_return relatives
- relatives.should_receive(:visible)
e.distributors
end
it "scopes relatives to visible producers" do
e.should_receive(:relatives).and_return(relatives = [])
relatives.should_receive(:is_primary_producer).and_return relatives
- relatives.should_receive(:visible)
e.suppliers
end
end
@@ -439,12 +437,12 @@ describe Enterprise do
it "gets all taxons of all distributed products" do
Spree::Product.stub(:in_distributor).and_return [product1, product2]
- distributor.distributed_taxons.should == [taxon1, taxon2]
+ distributor.distributed_taxons.sort.should == [taxon1, taxon2].sort
end
it "gets all taxons of all supplied products" do
Spree::Product.stub(:in_supplier).and_return [product1, product2]
- supplier.supplied_taxons.should == [taxon1, taxon2]
+ supplier.supplied_taxons.sort.should == [taxon1, taxon2].sort
end
end
diff --git a/spec/serializers/enterprise_serializer.rb b/spec/serializers/enterprise_serializer.rb
new file mode 100644
index 0000000000..4e9248be53
--- /dev/null
+++ b/spec/serializers/enterprise_serializer.rb
@@ -0,0 +1,21 @@
+#require 'spec_helper'
+
+describe Api::EnterpriseSerializer do
+ let(:enterprise) { create(:distributor_enterprise) }
+ let(:taxon) { create(:taxon) }
+ it "serializes an enterprise" do
+ serializer = Api::EnterpriseSerializer.new enterprise
+ serializer.to_json.should match enterprise.name
+ end
+
+ it "includes distributed taxons" do
+ enterprise.stub(:distributed_taxons).and_return [taxon]
+ serializer = Api::EnterpriseSerializer.new enterprise
+ serializer.to_json.should match taxon.name
+ end
+
+ it "will render urls" do
+ serializer = Api::EnterpriseSerializer.new enterprise
+ serializer.to_json.should match "map-icon-hub.svg"
+ end
+end
diff --git a/spec/support/request/ui_component_helper.rb b/spec/support/request/ui_component_helper.rb
index 269f28cca1..32d6abd77e 100644
--- a/spec/support/request/ui_component_helper.rb
+++ b/spec/support/request/ui_component_helper.rb
@@ -38,6 +38,20 @@ module UIComponentHelper
have_selector ".login-modal"
end
+ def open_product_modal(product)
+ find("a", text: product.name).click
+ end
+
+ def open_enterprise_modal(enterprise)
+ find("a", text: enterprise.name).click
+ end
+
+ def modal_should_be_open_for(object)
+ within ".reveal-modal" do
+ page.should have_content object.name
+ end
+ end
+
def have_reset_password
have_content "An email with instructions on resetting your password has been sent!"
end
diff --git a/vendor/assets/javascripts/angular-timer.min.js b/vendor/assets/javascripts/angular-timer.min.js
new file mode 100755
index 0000000000..9fdc966a93
--- /dev/null
+++ b/vendor/assets/javascripts/angular-timer.min.js
@@ -0,0 +1,8 @@
+/**
+ * angular-timer - v1.1.6 - 2014-07-01 7:37 AM
+ * https://github.com/siddii/angular-timer
+ *
+ * Copyright (c) 2014 Siddique Hameed
+ * Licensed MIT
+ */
+var timerModule=angular.module("timer",[]).directive("timer",["$compile",function(a){return{restrict:"EAC",replace:!1,scope:{interval:"=interval",startTimeAttr:"=startTime",endTimeAttr:"=endTime",countdownattr:"=countdown",finishCallback:"&finishCallback",autoStart:"&autoStart",maxTimeUnit:"="},controller:["$scope","$element","$attrs","$timeout",function(b,c,d,e){function f(){b.timeoutId&&clearTimeout(b.timeoutId)}function g(){b.maxTimeUnit&&"day"!==b.maxTimeUnit?"second"===b.maxTimeUnit?(b.seconds=Math.floor(b.millis/1e3),b.minutes=0,b.hours=0,b.days=0,b.months=0,b.years=0):"minute"===b.maxTimeUnit?(b.seconds=Math.floor(b.millis/1e3%60),b.minutes=Math.floor(b.millis/6e4),b.hours=0,b.days=0,b.months=0,b.years=0):"hour"===b.maxTimeUnit?(b.seconds=Math.floor(b.millis/1e3%60),b.minutes=Math.floor(b.millis/6e4%60),b.hours=Math.floor(b.millis/36e5),b.days=0,b.months=0,b.years=0):"month"===b.maxTimeUnit?(b.seconds=Math.floor(b.millis/1e3%60),b.minutes=Math.floor(b.millis/6e4%60),b.hours=Math.floor(b.millis/36e5%24),b.days=Math.floor(b.millis/36e5/24%30),b.months=Math.floor(b.millis/36e5/24/30),b.years=0):"year"===b.maxTimeUnit&&(b.seconds=Math.floor(b.millis/1e3%60),b.minutes=Math.floor(b.millis/6e4%60),b.hours=Math.floor(b.millis/36e5%24),b.days=Math.floor(b.millis/36e5/24%30),b.months=Math.floor(b.millis/36e5/24/30%12),b.years=Math.floor(b.millis/36e5/24/365)):(b.seconds=Math.floor(b.millis/1e3%60),b.minutes=Math.floor(b.millis/6e4%60),b.hours=Math.floor(b.millis/36e5%24),b.days=Math.floor(b.millis/36e5/24),b.months=0,b.years=0),b.secondsS=1==b.seconds?"":"s",b.minutesS=1==b.minutes?"":"s",b.hoursS=1==b.hours?"":"s",b.daysS=1==b.days?"":"s",b.monthsS=1==b.months?"":"s",b.yearsS=1==b.years?"":"s",b.sseconds=b.seconds<10?"0"+b.seconds:b.seconds,b.mminutes=b.minutes<10?"0"+b.minutes:b.minutes,b.hhours=b.hours<10?"0"+b.hours:b.hours,b.ddays=b.days<10?"0"+b.days:b.days,b.mmonths=b.months<10?"0"+b.months:b.months,b.yyears=b.years<10?"0"+b.years:b.years}"function"!=typeof String.prototype.trim&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),b.autoStart=d.autoStart||d.autostart,c.append(0===c.html().trim().length?a("{{millis}}")(b):a(c.contents())(b)),b.startTime=null,b.endTime=null,b.timeoutId=null,b.countdown=b.countdownattr&&parseInt(b.countdownattr,10)>=0?parseInt(b.countdownattr,10):void 0,b.isRunning=!1,b.$on("timer-start",function(){b.start()}),b.$on("timer-resume",function(){b.resume()}),b.$on("timer-stop",function(){b.stop()}),b.$on("timer-clear",function(){b.clear()}),b.$on("timer-set-countdown",function(a,c){b.countdown=c}),b.start=c[0].start=function(){b.startTime=b.startTimeAttr?new Date(b.startTimeAttr):new Date,b.endTime=b.endTimeAttr?new Date(b.endTimeAttr):null,b.countdown||(b.countdown=b.countdownattr&&parseInt(b.countdownattr,10)>0?parseInt(b.countdownattr,10):void 0),f(),h(),b.isRunning=!0},b.resume=c[0].resume=function(){f(),b.countdownattr&&(b.countdown+=1),b.startTime=new Date-(b.stoppedTime-b.startTime),h(),b.isRunning=!0},b.stop=b.pause=c[0].stop=c[0].pause=function(){var a=b.timeoutId;b.clear(),b.$emit("timer-stopped",{timeoutId:a,millis:b.millis,seconds:b.seconds,minutes:b.minutes,hours:b.hours,days:b.days})},b.clear=c[0].clear=function(){b.stoppedTime=new Date,f(),b.timeoutId=null,b.isRunning=!1},c.bind("$destroy",function(){f(),b.isRunning=!1}),b.countdownattr?(b.millis=1e3*b.countdownattr,b.addCDSeconds=c[0].addCDSeconds=function(a){b.countdown+=a,b.$digest(),b.isRunning||b.start()},b.$on("timer-add-cd-seconds",function(a,c){e(function(){b.addCDSeconds(c)})}),b.$on("timer-set-countdown-seconds",function(a,c){b.isRunning||b.clear(),b.countdown=c,b.millis=1e3*c,g()})):b.millis=0,g();var h=function(){b.millis=new Date-b.startTime;var a=b.millis%1e3;return b.endTimeAttr&&(b.millis=b.endTime-new Date,a=b.interval-b.millis%1e3),b.countdownattr&&(b.millis=1e3*b.countdown),b.millis<0?(b.stop(),b.millis=0,g(),void(b.finishCallback&&b.$eval(b.finishCallback))):(g(),b.timeoutId=setTimeout(function(){h(),b.$digest()},b.interval-a),b.$emit("timer-tick",{timeoutId:b.timeoutId,millis:b.millis}),void(b.countdown>0?b.countdown--:b.countdown<=0&&(b.stop(),b.finishCallback&&b.$eval(b.finishCallback))))};(void 0===b.autoStart||b.autoStart===!0)&&b.start()}]}}]);"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports=timerModule);
\ No newline at end of file