mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Merge branch 'master' into sm-makeover
This commit is contained in:
2
Gemfile
2
Gemfile
@@ -40,6 +40,8 @@ gem 'custom_error_message', :github => 'jeremydurham/custom-err-msg'
|
||||
gem 'foreigner'
|
||||
gem 'immigrant'
|
||||
|
||||
gem 'whenever', require: false
|
||||
|
||||
# Gems used only for assets and not required
|
||||
# in production environments by default.
|
||||
group :assets do
|
||||
|
||||
@@ -175,6 +175,7 @@ GEM
|
||||
xpath (~> 2.0)
|
||||
celluloid (0.15.2)
|
||||
timers (~> 1.1.0)
|
||||
chronic (0.10.2)
|
||||
chunky_png (1.3.0)
|
||||
climate_control (0.0.3)
|
||||
activesupport (>= 3.0)
|
||||
@@ -479,6 +480,9 @@ GEM
|
||||
addressable (>= 2.2.7)
|
||||
crack (>= 0.3.2)
|
||||
websocket-driver (0.3.2)
|
||||
whenever (0.9.2)
|
||||
activesupport (>= 2.3.4)
|
||||
chronic (>= 0.6.3)
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
zeus (0.13.3)
|
||||
@@ -552,3 +556,4 @@ DEPENDENCIES
|
||||
unicorn
|
||||
unicorn-rails
|
||||
webmock
|
||||
whenever
|
||||
|
||||
BIN
app/assets/images/loading.gif
Normal file
BIN
app/assets/images/loading.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -213,7 +213,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
if confirm("Are you sure?")
|
||||
$http(
|
||||
method: "DELETE"
|
||||
url: "/api/products/" + product.id
|
||||
url: "/api/products/" + product.id + "/soft_delete"
|
||||
).success (data) ->
|
||||
$scope.products.splice $scope.products.indexOf(product), 1
|
||||
DirtyProducts.deleteProduct product.id
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#= require angular-sanitize
|
||||
#= 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 ../shared/bindonce.min.js
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
Darkswarm.controller "LoginCtrl", ($scope, $http, AuthenticationService, Redirections) ->
|
||||
Darkswarm.controller "LoginCtrl", ($scope, $http, AuthenticationService, Redirections, Loading) ->
|
||||
$scope.path = "/login"
|
||||
|
||||
$scope.submit = ->
|
||||
Loading.message = "Hold on a moment, we're logging you in"
|
||||
$http.post("/user/spree_user/sign_in", {spree_user: $scope.spree_user}).success (data)->
|
||||
if Redirections.after_login
|
||||
location.href = location.origin + Redirections.after_login
|
||||
else
|
||||
location.href = location.origin + location.pathname # Strips out hash fragments
|
||||
.error (data) ->
|
||||
Loading.clear()
|
||||
$scope.errors = data.message
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.controller "HubNodeCtrl", ($scope, HashNavigation, Navigation, $location, $anchorScroll, $templateCache, CurrentHub) ->
|
||||
Darkswarm.controller "HubNodeCtrl", ($scope, HashNavigation, Navigation, $location, $templateCache, CurrentHub) ->
|
||||
$scope.toggle = ->
|
||||
HashNavigation.toggle $scope.hub.hash
|
||||
|
||||
@@ -7,6 +7,3 @@ Darkswarm.controller "HubNodeCtrl", ($scope, HashNavigation, Navigation, $locati
|
||||
|
||||
$scope.current = ->
|
||||
$scope.hub.id is CurrentHub.id
|
||||
|
||||
if $scope.open()
|
||||
$anchorScroll()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Darkswarm.controller "HubsCtrl", ($scope, Hubs, $anchorScroll, $rootScope, HashNavigation) ->
|
||||
Darkswarm.controller "HubsCtrl", ($scope, Hubs, $document, $rootScope, HashNavigation) ->
|
||||
$scope.Hubs = Hubs
|
||||
$scope.hubs = Hubs.hubs
|
||||
|
||||
$rootScope.$on "$locationChangeSuccess", (newRoute, oldRoute) ->
|
||||
if HashNavigation.active "hubs"
|
||||
$anchorScroll()
|
||||
$document.scrollTo $("#hubs"), 100, 200
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
Darkswarm.controller "MapCtrl", ($scope, MapConfiguration, OfnMap)->
|
||||
$scope.OfnMap = OfnMap
|
||||
$scope.map =
|
||||
center:
|
||||
latitude: -37.4713077
|
||||
longitude: 144.7851531
|
||||
zoom: 12
|
||||
styles: MapConfiguration.options
|
||||
$scope.map = MapConfiguration.options
|
||||
|
||||
@@ -7,6 +7,7 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource",
|
||||
'templates',
|
||||
'ngSanitize',
|
||||
'google-maps',
|
||||
'duScroll',
|
||||
'backstretch']).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')
|
||||
|
||||
@@ -5,12 +5,13 @@ Darkswarm.directive "ofnEmptiesCart", (CurrentHub, CurrentOrder, Navigation, sto
|
||||
template: "{{action}} <strong>{{hub.name}}</strong>"
|
||||
link: (scope, elm, attr)->
|
||||
# A hub is selected, we're changing to a different hub, and the cart isn't empty
|
||||
if CurrentHub.id and CurrentHub.id isnt scope.hub.id and not CurrentOrder.empty()
|
||||
if CurrentHub.id and CurrentHub.id isnt scope.hub.id
|
||||
scope.action = attr.change
|
||||
elm.bind 'click', (ev)->
|
||||
ev.preventDefault()
|
||||
if confirm "Are you sure? This will change your selected Hub and remove any items in you shopping cart."
|
||||
storage.clearAll() # One day this will have to be moar GRANULAR
|
||||
Navigation.go scope.hub.path
|
||||
unless CurrentOrder.empty()
|
||||
elm.bind 'click', (ev)->
|
||||
ev.preventDefault()
|
||||
if confirm "Are you sure? This will change your selected Hub and remove any items in you shopping cart."
|
||||
storage.clearAll() # One day this will have to be moar GRANULAR
|
||||
Navigation.go scope.hub.path
|
||||
else
|
||||
scope.action = attr.shop
|
||||
|
||||
@@ -16,7 +16,7 @@ Darkswarm.directive "ofnFlash", (flash, $timeout, RailsFlashLoader)->
|
||||
show = (message, type)=>
|
||||
if message
|
||||
$scope.flashes.push({message: message, type: typePairings[type]})
|
||||
$timeout($scope.delete, 5000)
|
||||
$timeout($scope.delete, 10000)
|
||||
|
||||
$scope.delete = ->
|
||||
$scope.flashes.shift()
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Darkswarm.directive "loading", (Loading)->
|
||||
scope: {}
|
||||
restrict: 'E'
|
||||
templateUrl: 'loading.html'
|
||||
controller: ($scope)->
|
||||
$scope.Loading = Loading
|
||||
$scope.show = ->
|
||||
$scope.Loading.message?
|
||||
|
||||
link: ($scope, element, attr)->
|
||||
@@ -8,10 +8,9 @@ Darkswarm.directive "ofnModal", ($modal)->
|
||||
link: (scope, elem, attrs, ctrl, transclude)->
|
||||
scope.title = attrs.title
|
||||
contents = null
|
||||
# We're using an isolate scope, which is a child of the original scope
|
||||
# We have to compile the transclude against the original scope, not the isolate
|
||||
transclude scope.$parent, (clone)->
|
||||
contents = clone
|
||||
|
||||
elem.on "click", =>
|
||||
# We're using an isolate scope, which is a child of the original scope
|
||||
# We have to compile the transclude against the original scope, not the isolate
|
||||
transclude scope.$parent, (clone)->
|
||||
contents = clone
|
||||
scope.modalInstance = $modal.open(controller: ctrl, template: contents, scope: scope.$parent)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Darkswarm.directive "renderSvg", ()->
|
||||
restrict: 'E'
|
||||
priority: 99
|
||||
template: "<svg-wrapper></svg-wrapper>"
|
||||
link: (scope, elem, attr)->
|
||||
if /.svg/.test attr.path # Only do this if we've got an svg
|
||||
$.ajax
|
||||
url: attr.path
|
||||
success: (html)->
|
||||
elem.html($(html).find("svg"))
|
||||
@@ -0,0 +1,10 @@
|
||||
Darkswarm.directive 'scrollAfterLoad', ($timeout, $location, $document)->
|
||||
restrict: "A"
|
||||
link: (scope, element, attr) ->
|
||||
if scope.$last is true
|
||||
$(window).load ->
|
||||
$timeout ->
|
||||
elem = $("##{$location.hash()}")
|
||||
if elem.length > 0
|
||||
$document.scrollTo elem , 100, 200, (x)->
|
||||
x * (2 - x)
|
||||
@@ -0,0 +1,5 @@
|
||||
Darkswarm.factory "Loading", ->
|
||||
new class Loading
|
||||
message: null
|
||||
clear: =>
|
||||
@message = null
|
||||
@@ -1,5 +1,11 @@
|
||||
Darkswarm.factory "MapConfiguration", ->
|
||||
new class MapConfiguration
|
||||
# From http://snazzymaps.com/style/15/subtle-grayscale
|
||||
options: [{"featureType":"landscape","stylers":[{"saturation":-100},{"lightness":65},{"visibility":"on"}]},{"featureType":"poi","stylers":[{"saturation":-100},{"lightness":51},{"visibility":"simplified"}]},{"featureType":"road.highway","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"road.arterial","stylers":[{"saturation":-100},{"lightness":30},{"visibility":"on"}]},{"featureType":"road.local","stylers":[{"saturation":-100},{"lightness":40},{"visibility":"on"}]},{"featureType":"transit","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"administrative.province","stylers":[{"visibility":"off"}]},{"featureType":"water","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":-25},{"saturation":-100}]},{"featureType":"water","elementType":"geometry","stylers":[{"hue":"#ffff00"},{"lightness":-25},{"saturation":-97}]}]
|
||||
options:
|
||||
center:
|
||||
latitude: -37.4713077
|
||||
longitude: 144.7851531
|
||||
zoom: 12
|
||||
additional_options: {}
|
||||
#mapTypeId: 'satellite'
|
||||
styles: [{"featureType":"landscape","stylers":[{"saturation":-100},{"lightness":65},{"visibility":"on"}]},{"featureType":"poi","stylers":[{"saturation":-100},{"lightness":51},{"visibility":"simplified"}]},{"featureType":"road.highway","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"road.arterial","stylers":[{"saturation":-100},{"lightness":30},{"visibility":"on"}]},{"featureType":"road.local","stylers":[{"saturation":-100},{"lightness":40},{"visibility":"on"}]},{"featureType":"transit","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"administrative.province","stylers":[{"visibility":"off"}]},{"featureType":"water","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":-25},{"saturation":-100}]},{"featureType":"water","elementType":"geometry","stylers":[{"hue":"#ffff00"},{"lightness":-25},{"saturation":-97}]}]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory 'Order', ($resource, order, $http, Navigation, storage, CurrentHub, RailsFlashLoader)->
|
||||
Darkswarm.factory 'Order', ($resource, order, $http, Navigation, storage, CurrentHub, RailsFlashLoader, Loading)->
|
||||
new class Order
|
||||
errors: {}
|
||||
secrets: {}
|
||||
@@ -20,9 +20,11 @@ Darkswarm.factory 'Order', ($resource, order, $http, Navigation, storage, Curren
|
||||
defaultValue: true
|
||||
|
||||
submit: ->
|
||||
Loading.message = "Submitting your order: please wait"
|
||||
$http.put('/checkout', {order: @preprocess()}).success (data, status)=>
|
||||
Navigation.go data.path
|
||||
.error (response, status)=>
|
||||
Loading.clear()
|
||||
@errors = response.errors
|
||||
RailsFlashLoader.loadFlash(response.flash)
|
||||
|
||||
|
||||
8
app/assets/javascripts/templates/loading.html.haml
Normal file
8
app/assets/javascripts/templates/loading.html.haml
Normal file
@@ -0,0 +1,8 @@
|
||||
#loading{"ng-show" => "show()"}
|
||||
%modal-backdrop
|
||||
#message
|
||||
%ul.loader
|
||||
%li
|
||||
%li
|
||||
%li
|
||||
%h1 {{ Loading.message }}
|
||||
@@ -11,7 +11,6 @@
|
||||
&:first-child
|
||||
cursor: pointer
|
||||
|
||||
|
||||
.active_table .active_table_node
|
||||
@include csstrans
|
||||
display: block
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
left: 26px
|
||||
top: 12px
|
||||
font-size: 1.6em
|
||||
z-index: 999
|
||||
z-index: 2
|
||||
color: #b2b2b2
|
||||
|
||||
input[type="text"]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import branding
|
||||
@import mixins
|
||||
|
||||
.hubs
|
||||
.active_table .active_table_node
|
||||
@@ -36,7 +37,21 @@
|
||||
//Open row
|
||||
&.open
|
||||
.fat-taxons
|
||||
@include csstrans
|
||||
margin-right: 0.5rem
|
||||
text-transform: uppercase
|
||||
font-size: 1rem
|
||||
line-height: 1
|
||||
color: $dark-grey
|
||||
// border: 1px solid $disabled-bright
|
||||
display: inline-block
|
||||
padding: 0.2rem 0.5rem 0.35rem 0.35rem
|
||||
|
||||
object.taxon
|
||||
height: 20px
|
||||
// &:hover, &.hover, &:active
|
||||
// background: rgba(255,255,255,0.5)
|
||||
|
||||
.active_table_row:first-child
|
||||
border-top: 1px solid $clr-brick
|
||||
border-left: 1px solid $clr-brick
|
||||
|
||||
68
app/assets/stylesheets/darkswarm/loading.sass
Normal file
68
app/assets/stylesheets/darkswarm/loading.sass
Normal file
@@ -0,0 +1,68 @@
|
||||
@import "compass/css3/user-interface"
|
||||
|
||||
#loading
|
||||
.reveal-modal-bg
|
||||
z-index: 101
|
||||
background: rgba(0, 0, 0, 0.85)
|
||||
#message
|
||||
width: 100%
|
||||
text-align: center
|
||||
position: absolute
|
||||
z-index: 102
|
||||
margin: auto
|
||||
top: 0
|
||||
left: 0
|
||||
bottom: 0
|
||||
right: 0
|
||||
@include user-select(none)
|
||||
h1
|
||||
color: white
|
||||
position: fixed
|
||||
text-align: center
|
||||
left: 0
|
||||
right: 0
|
||||
margin: 0 auto
|
||||
top: 55%
|
||||
width: 100%
|
||||
height: 400px
|
||||
overflow: visible
|
||||
|
||||
.loader
|
||||
position: fixed
|
||||
margin: 0 auto
|
||||
left: 0
|
||||
right: 0
|
||||
top: 50%
|
||||
margin-top: -30px
|
||||
width: 60px
|
||||
height: 60px
|
||||
list-style: none
|
||||
|
||||
li
|
||||
background-color: #FFFFFF
|
||||
width: 10px
|
||||
height: 10px
|
||||
float: right
|
||||
margin-right: 5px
|
||||
box-shadow: 0px 100px 20px rgba(0, 0, 0, 0.2)
|
||||
|
||||
li:first-child
|
||||
-webkit-animation: loadbars 0.6s cubic-bezier(0.645, 0.045, 0.355, 1) infinite 0s
|
||||
|
||||
li:nth-child(2)
|
||||
-webkit-animation: loadbars 0.6s ease-in-out infinite -0.2s
|
||||
|
||||
li:nth-child(3)
|
||||
-webkit-animation: loadbars 0.6s ease-in-out infinite -0.4s
|
||||
|
||||
@-webkit-keyframes 'loadbars'
|
||||
0%
|
||||
height: 10px
|
||||
margin-top: 25px
|
||||
50%
|
||||
height: 50px
|
||||
margin-top: 0px
|
||||
100%
|
||||
height: 10px
|
||||
margin-top: 25px
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
// Place all the styles related to the map controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
.ofn-map-container
|
||||
.map-container
|
||||
width: 100%
|
||||
map, .angular-google-map-container, google-map, .angular-google-map
|
||||
display: block
|
||||
height: 100%
|
||||
|
||||
img // https://github.com/zurb/foundation/issues/112
|
||||
max-width: none
|
||||
height: auto
|
||||
|
||||
@@ -31,35 +31,31 @@
|
||||
location, location + small
|
||||
display: block
|
||||
|
||||
#distributor_title
|
||||
float: left
|
||||
display: block
|
||||
min-width: 350px
|
||||
#distributor_title h3
|
||||
margin-top: 0
|
||||
@media all and (max-width: 768px)
|
||||
margin-bottom: 8px
|
||||
|
||||
|
||||
ordercycle
|
||||
@media all and (max-width: 768px)
|
||||
@media all and (max-width: 640px)
|
||||
float: left
|
||||
clear: left
|
||||
padding-bottom: 12px
|
||||
display: block
|
||||
padding: 12px 10px
|
||||
width: 100%
|
||||
margin-top: 10px
|
||||
background: #e5e5e5
|
||||
float: right
|
||||
form.custom
|
||||
//width: 400px
|
||||
text-align: right
|
||||
margin-right: 1em
|
||||
@media all and (max-width: 768px)
|
||||
padding-left: 1em
|
||||
padding-top: 1em
|
||||
& > strong
|
||||
line-height: 2.5
|
||||
font-size: 1.29em
|
||||
padding-right: 14px
|
||||
@media all and (max-width: 768px)
|
||||
font-size: 1.2em
|
||||
select
|
||||
select
|
||||
width: inherit
|
||||
display: inline-block
|
||||
vackground: transparent
|
||||
border-width: 1px
|
||||
border-color: #999
|
||||
color: #666
|
||||
@@ -67,13 +63,18 @@
|
||||
margin-bottom: 0
|
||||
padding: 8px 20px 8px 12px
|
||||
@media all and (max-width: 768px)
|
||||
font-size: 1.2em
|
||||
// width: 180px
|
||||
font-size: 0.875em
|
||||
closing
|
||||
@media all and (max-width: 768px)
|
||||
font-size: 1.2em
|
||||
padding-bottom: 10px
|
||||
color: black
|
||||
font-size: 1.5em
|
||||
display: block
|
||||
padding-bottom: 20px
|
||||
padding-bottom: 12px
|
||||
span
|
||||
@media all and (max-width: 768px)
|
||||
font-size: 0.875em
|
||||
|
||||
products
|
||||
display: block
|
||||
@@ -101,10 +102,12 @@
|
||||
input
|
||||
margin: 0
|
||||
width: 8em
|
||||
display: inline
|
||||
|
||||
.columns
|
||||
padding-top: 1em
|
||||
padding-bottom: 1em
|
||||
line-height: 2.4em
|
||||
|
||||
.row.summary, .row.variants
|
||||
@include csstrans
|
||||
@@ -116,8 +119,15 @@
|
||||
.row.summary
|
||||
@include csstrans
|
||||
background: #fff
|
||||
line-height: 1
|
||||
|
||||
.summary-header
|
||||
@include csstrans
|
||||
font-size: 1.15rem
|
||||
|
||||
object.taxon
|
||||
height: 18px
|
||||
|
||||
&, & *
|
||||
@include avenir
|
||||
color: $clr-brick
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
@font-face {
|
||||
font-family: 'OFN';
|
||||
src:url('/OFN.eot?-zgemuq');
|
||||
src:url('/OFN.eot?#iefix-zgemuq') format('embedded-opentype'),
|
||||
url('/OFN.woff?-zgemuq') format('woff'),
|
||||
url('/OFN.ttf?-zgemuq') format('truetype'),
|
||||
url('/OFN.svg?-zgemuq#OFN') format('svg');
|
||||
src:url('/OFN.eot?-g90m7b');
|
||||
src:url('/OFN.eot?#iefix-g90m7b') format('embedded-opentype'),
|
||||
url('/OFN.woff?-g90m7b') format('woff'),
|
||||
url('/OFN.ttf?-g90m7b') format('truetype'),
|
||||
url('/OFN.svg?-g90m7b#OFN') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -23,168 +23,174 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.ofn-i_055-point-left:before {
|
||||
.ofn-i_057-expand:before {
|
||||
content: "\e600";
|
||||
}
|
||||
.ofn-i_054-point-right:before {
|
||||
.ofn-i_056-bulk:before {
|
||||
content: "\e601";
|
||||
}
|
||||
.ofn-i_053-point-up:before {
|
||||
.ofn-i_055-point-left:before {
|
||||
content: "\e602";
|
||||
}
|
||||
.ofn-i_052-point-down:before {
|
||||
.ofn-i_054-point-right:before {
|
||||
content: "\e603";
|
||||
}
|
||||
.ofn-i_051-check-big:before {
|
||||
.ofn-i_053-point-up:before {
|
||||
content: "\e604";
|
||||
}
|
||||
.ofn-i_050-mail-circle:before {
|
||||
.ofn-i_052-point-down:before {
|
||||
content: "\e605";
|
||||
}
|
||||
.ofn-i_049-web:before {
|
||||
.ofn-i_051-check-big:before {
|
||||
content: "\e606";
|
||||
}
|
||||
.ofn-i_048-play-video:before {
|
||||
.ofn-i_050-mail-circle:before {
|
||||
content: "\e607";
|
||||
}
|
||||
.ofn-i_047-youtube:before {
|
||||
.ofn-i_049-web:before {
|
||||
content: "\e608";
|
||||
}
|
||||
.ofn-i_046-g:before {
|
||||
.ofn-i_048-play-video:before {
|
||||
content: "\e609";
|
||||
}
|
||||
.ofn-i_045-pintrest:before {
|
||||
.ofn-i_047-youtube:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
.ofn-i_044-facebook:before {
|
||||
.ofn-i_046-g:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
.ofn-i_043-instagram:before {
|
||||
.ofn-i_045-pintrest:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
.ofn-i_042-linkedin:before {
|
||||
.ofn-i_044-facebook:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
.ofn-i_041-twitter:before {
|
||||
.ofn-i_043-instagram:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
.ofn-i_040-hub:before {
|
||||
.ofn-i_042-linkedin:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
.ofn-i_039-delivery:before {
|
||||
.ofn-i_041-twitter:before {
|
||||
content: "\e610";
|
||||
}
|
||||
.ofn-i_038-takeaway:before {
|
||||
.ofn-i_040-hub:before {
|
||||
content: "\e611";
|
||||
}
|
||||
.ofn-i_037-map:before {
|
||||
.ofn-i_039-delivery:before {
|
||||
content: "\e612";
|
||||
}
|
||||
.ofn-i_036-producers:before {
|
||||
.ofn-i_038-takeaway:before {
|
||||
content: "\e613";
|
||||
}
|
||||
.ofn-i_035-groups:before {
|
||||
.ofn-i_037-map:before {
|
||||
content: "\e614";
|
||||
}
|
||||
.ofn-i_034-timer:before {
|
||||
.ofn-i_036-producers:before {
|
||||
content: "\e615";
|
||||
}
|
||||
.ofn-i_033-open-sign:before {
|
||||
.ofn-i_035-groups:before {
|
||||
content: "\e616";
|
||||
}
|
||||
.ofn-i_032-closed-sign:before {
|
||||
.ofn-i_034-timer:before {
|
||||
content: "\e617";
|
||||
}
|
||||
.ofn-i_031-alarm-clock:before {
|
||||
.ofn-i_033-open-sign:before {
|
||||
content: "\e618";
|
||||
}
|
||||
.ofn-i_030-money:before {
|
||||
.ofn-i_032-closed-sign:before {
|
||||
content: "\e619";
|
||||
}
|
||||
.ofn-i_029-shopping-basket:before {
|
||||
.ofn-i_031-alarm-clock:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
.ofn-i_028-barcode:before {
|
||||
.ofn-i_030-money:before {
|
||||
content: "\e61b";
|
||||
}
|
||||
.ofn-i_027-shopping-cart:before {
|
||||
.ofn-i_029-shopping-basket:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
.ofn-i_026-trash:before {
|
||||
.ofn-i_028-barcode:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
.ofn-i_025-notepad:before {
|
||||
.ofn-i_027-shopping-cart:before {
|
||||
content: "\e61e";
|
||||
}
|
||||
.ofn-i_024-mail:before {
|
||||
.ofn-i_026-trash:before {
|
||||
content: "\e61f";
|
||||
}
|
||||
.ofn-i_023-refresh:before {
|
||||
.ofn-i_025-notepad:before {
|
||||
content: "\e620";
|
||||
}
|
||||
.ofn-i_022-cog:before {
|
||||
.ofn-i_024-mail:before {
|
||||
content: "\e621";
|
||||
}
|
||||
.ofn-i_021-tools:before {
|
||||
.ofn-i_023-refresh:before {
|
||||
content: "\e622";
|
||||
}
|
||||
.ofn-i_020-search:before {
|
||||
.ofn-i_022-cog:before {
|
||||
content: "\e623";
|
||||
}
|
||||
.ofn-i_019-map-pin:before {
|
||||
.ofn-i_021-tools:before {
|
||||
content: "\e624";
|
||||
}
|
||||
.ofn-i_018-unlocked:before {
|
||||
.ofn-i_020-search:before {
|
||||
content: "\e625";
|
||||
}
|
||||
.ofn-i_017-locked:before {
|
||||
.ofn-i_019-map-pin:before {
|
||||
content: "\e626";
|
||||
}
|
||||
.ofn-i_016-group:before {
|
||||
.ofn-i_018-unlocked:before {
|
||||
content: "\e627";
|
||||
}
|
||||
.ofn-i_015-user:before {
|
||||
.ofn-i_017-locked:before {
|
||||
content: "\e628";
|
||||
}
|
||||
.ofn-i_014-menu:before {
|
||||
.ofn-i_016-group:before {
|
||||
content: "\e629";
|
||||
}
|
||||
.ofn-i_013-help:before {
|
||||
.ofn-i_015-user:before {
|
||||
content: "\e62a";
|
||||
}
|
||||
.ofn-i_012-warning:before {
|
||||
.ofn-i_014-menu:before {
|
||||
content: "\e62b";
|
||||
}
|
||||
.ofn-i_011-spinner:before {
|
||||
.ofn-i_013-help:before {
|
||||
content: "\e62c";
|
||||
}
|
||||
.ofn-i_010-bullet:before {
|
||||
.ofn-i_012-warning:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
.ofn-i_009-close:before {
|
||||
.ofn-i_011-spinner:before {
|
||||
content: "\e62e";
|
||||
}
|
||||
.ofn-i_008-caret-left:before {
|
||||
.ofn-i_010-bullet:before {
|
||||
content: "\e62f";
|
||||
}
|
||||
.ofn-i_007-caret-right:before {
|
||||
.ofn-i_009-close:before {
|
||||
content: "\e630";
|
||||
}
|
||||
.ofn-i_006-caret-up:before {
|
||||
.ofn-i_008-caret-left:before {
|
||||
content: "\e631";
|
||||
}
|
||||
.ofn-i_005-caret-down:before {
|
||||
.ofn-i_007-caret-right:before {
|
||||
content: "\e632";
|
||||
}
|
||||
.ofn-i_004-x:before {
|
||||
.ofn-i_006-caret-up:before {
|
||||
content: "\e633";
|
||||
}
|
||||
.ofn-i_003-check:before {
|
||||
.ofn-i_005-caret-down:before {
|
||||
content: "\e634";
|
||||
}
|
||||
.ofn-i_002-arrow-right:before {
|
||||
.ofn-i_004-x:before {
|
||||
content: "\e635";
|
||||
}
|
||||
.ofn-i_001-arrow-left:before {
|
||||
.ofn-i_003-check:before {
|
||||
content: "\e636";
|
||||
}
|
||||
.ofn-i_002-arrow-right:before {
|
||||
content: "\e637";
|
||||
}
|
||||
.ofn-i_001-arrow-left:before {
|
||||
content: "\e638";
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
border-top: 4px solid transparent
|
||||
a:after
|
||||
padding-left: 8px
|
||||
content: "\e632"
|
||||
content: "\e634"
|
||||
visibility: hidden
|
||||
@include icon-font
|
||||
dd:hover
|
||||
@@ -48,7 +48,7 @@
|
||||
a
|
||||
color: $clr-brick-bright
|
||||
a:after
|
||||
content: "\e631"
|
||||
content: "\e633"
|
||||
visibility: visible
|
||||
@include icon-font
|
||||
|
||||
|
||||
@@ -36,14 +36,14 @@ h1, h2, h3, h4, h5, h6, .avenir
|
||||
@include avenir
|
||||
padding: 0px
|
||||
|
||||
ul.ofn-list
|
||||
ul.bullet-list
|
||||
margin: 0
|
||||
li
|
||||
list-style: none
|
||||
line-height: 1.5
|
||||
height: inherit
|
||||
li:before
|
||||
content: "\e62d"
|
||||
content: "\e62f"
|
||||
font-family: "OFN"
|
||||
display: inline-block
|
||||
font-weight: normal
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
protect_from_forgery
|
||||
before_filter :require_certified_hostname
|
||||
|
||||
include EnterprisesHelper
|
||||
|
||||
@@ -17,6 +16,7 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def require_distributor_chosen
|
||||
@@ -42,17 +42,6 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
end
|
||||
|
||||
# There are several domains that point to the production server, but only one
|
||||
# (vic.openfoodnetwork.org) that has the SSL certificate. Redirect all requests to this
|
||||
# domain to avoid showing customers a scary invalid certificate error.
|
||||
def require_certified_hostname
|
||||
certified_host = "openfoodnetwork.org.au"
|
||||
|
||||
if Rails.env.production? && request.host != certified_host
|
||||
redirect_to "http://#{certified_host}#{request.fullpath}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# All render calls within the block will be performed with the specified format
|
||||
# Useful for rendering html within a JSON response, particularly if the specified
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
Spree::Admin::VariantsController.class_eval do
|
||||
helper 'spree/products'
|
||||
|
||||
def destroy
|
||||
@variant = Spree::Variant.find(params[:id])
|
||||
@variant.delete # This line changed, as well as removal of following conditional
|
||||
flash[:success] = I18n.t('notice_messages.variant_deleted')
|
||||
|
||||
respond_with(@variant) do |format|
|
||||
format.html { redirect_to admin_product_variants_url(params[:product_id]) }
|
||||
format.js { render_js_for_destroy }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
|
||||
|
||||
@@ -8,6 +8,15 @@ Spree::Api::ProductsController.class_eval do
|
||||
end
|
||||
|
||||
|
||||
def soft_delete
|
||||
authorize! :delete, Spree::Product
|
||||
@product = find_product(params[:product_id])
|
||||
authorize! :delete, @product
|
||||
@product.delete
|
||||
respond_with(@product, :status => 204)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# Copied and modified from Spree::Api::BaseController to allow
|
||||
|
||||
@@ -3,11 +3,7 @@ Spree::Api::VariantsController.class_eval do
|
||||
@variant = scope.find(params[:variant_id])
|
||||
authorize! :delete, @variant
|
||||
|
||||
@variant.deleted_at = Time.now()
|
||||
if @variant.save
|
||||
respond_with(@variant, :status => 204)
|
||||
else
|
||||
invalid_resource!(@variant)
|
||||
end
|
||||
@variant.delete
|
||||
respond_with @variant, status: 204
|
||||
end
|
||||
end
|
||||
|
||||
@@ -147,6 +147,15 @@ Spree::Product.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
def delete_with_delete_from_order_cycles
|
||||
transaction do
|
||||
delete_without_delete_from_order_cycles
|
||||
|
||||
ExchangeVariant.where('exchange_variants.variant_id IN (?)', self.variants_including_master_and_deleted).destroy_all
|
||||
end
|
||||
end
|
||||
alias_method_chain :delete, :delete_from_order_cycles
|
||||
|
||||
|
||||
private
|
||||
|
||||
|
||||
@@ -40,6 +40,20 @@ Spree::Variant.class_eval do
|
||||
values.to_sentence({ :words_connector => ", ", :two_words_connector => ", " })
|
||||
end
|
||||
|
||||
def delete_unit_option_values
|
||||
ovs = self.option_values.where(option_type_id: Spree::Product.all_variant_unit_option_types)
|
||||
self.option_values.destroy ovs
|
||||
end
|
||||
|
||||
def name_to_display
|
||||
display_name || product.name
|
||||
end
|
||||
|
||||
def unit_to_display
|
||||
display_as || options_text
|
||||
end
|
||||
|
||||
|
||||
def update_units
|
||||
delete_unit_option_values
|
||||
|
||||
@@ -51,17 +65,20 @@ Spree::Variant.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
def delete
|
||||
transaction do
|
||||
self.update_column(:deleted_at, Time.now)
|
||||
ExchangeVariant.where(variant_id: self).destroy_all
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def update_weight_from_unit_value
|
||||
self.weight = unit_value / 1000 if self.product.variant_unit == 'weight' && unit_value.present?
|
||||
end
|
||||
|
||||
def delete_unit_option_values
|
||||
ovs = self.option_values.where(option_type_id: Spree::Product.all_variant_unit_option_types)
|
||||
self.option_values.destroy ovs
|
||||
end
|
||||
|
||||
def option_value_name
|
||||
if display_as.present?
|
||||
display_as
|
||||
|
||||
10
app/views/admin/enterprises/_supplied_product.rabl
Normal file
10
app/views/admin/enterprises/_supplied_product.rabl
Normal file
@@ -0,0 +1,10 @@
|
||||
object @product
|
||||
|
||||
attributes :name
|
||||
node(:supplier_name) { |p| p.supplier.andand.name }
|
||||
node(:image_url) { |p| p.images.present? ? p.images.first.attachment.url(:mini) : nil }
|
||||
node(:master_id) { |p| p.master.id }
|
||||
child variants: :variants do |variant|
|
||||
attributes :id
|
||||
node(:label) { |v| v.options_text }
|
||||
end
|
||||
@@ -2,13 +2,8 @@ collection @collection
|
||||
|
||||
attributes :id, :name
|
||||
|
||||
child supplied_products: :supplied_products do |product|
|
||||
attributes :name
|
||||
node(:supplier_name) { |p| p.supplier.andand.name }
|
||||
node(:image_url) { |p| p.images.present? ? p.images.first.attachment.url(:mini) : nil }
|
||||
node(:master_id) { |p| p.master.id }
|
||||
child variants: :variants do |variant|
|
||||
attributes :id
|
||||
node(:label) { |v| v.options_text }
|
||||
node(:supplied_products) do |enterprise|
|
||||
enterprise.supplied_products.not_deleted.map do |product|
|
||||
partial 'admin/enterprises/supplied_product', object: product
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
Shop for
|
||||
%p.trans-sentence
|
||||
%span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"}
|
||||
%img{"bo-src" => "taxon.icon",
|
||||
name: "{{taxon.name}}", alt: "{{taxon.name}}"}
|
||||
%render-svg{path: "{{taxon.icon}}"}
|
||||
{{taxon.name}}
|
||||
.columns.small-4
|
||||
%h5 Delivery options
|
||||
@@ -21,7 +20,7 @@
|
||||
%h5
|
||||
%i.ofn-i_036-producers
|
||||
Our producers
|
||||
%ul.ofn-list
|
||||
%ul.bullet-list
|
||||
%li{"ng-repeat" => "producer in hub.producers"}
|
||||
= render partial: "modals/producer"
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
.active_table
|
||||
%hub.active_table_node.row{"ng-repeat" => "hub in filteredHubs = (hubs | hubs:query)",
|
||||
"ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
|
||||
"scroll-after-load" => true,
|
||||
"ng-controller" => "HubNodeCtrl",
|
||||
id: "{{hub.hash}}"}
|
||||
.small-12.columns
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
%h2 Map
|
||||
%h5 of all our food hubs and producers
|
||||
%p
|
||||
%button.neutral-btn.light
|
||||
%a.neutral-btn.light{href: "/map"}
|
||||
%i.ofn-i_037-map
|
||||
View map
|
||||
@@ -1,5 +1,16 @@
|
||||
attributes :id, :name, :description, :long_description, :website, :instagram, :facebook, :linkedin, :twitter
|
||||
|
||||
node :promo_image do |producer|
|
||||
producer.promo_image.url
|
||||
producer.promo_image(:large)
|
||||
end
|
||||
node :logo do |producer|
|
||||
producer.logo(:medium)
|
||||
end
|
||||
|
||||
node :path do |producer|
|
||||
main_app.producer_path(producer)
|
||||
end
|
||||
|
||||
node :hash do |producer|
|
||||
producer.to_param
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
attributes :name, :id, :permalink
|
||||
|
||||
node :icon do |taxon|
|
||||
taxon.icon.url
|
||||
taxon.icon(:original)
|
||||
end
|
||||
|
||||
@@ -27,9 +27,8 @@
|
||||
.inner-wrap
|
||||
= render partial: "shared/menu/menu"
|
||||
|
||||
%ofn-flash
|
||||
|
||||
%section{ role: "main" }
|
||||
= yield
|
||||
|
||||
#footer
|
||||
%loading
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
= inject_json "enterprisesForMap" , "enterprises_for_map", collection: @enterprises
|
||||
|
||||
.ofn-map-container{"fill-vertical" => true}
|
||||
.map-container{"fill-vertical" => true}
|
||||
%map{"ng-controller" => "MapCtrl"}
|
||||
%google-map{center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
|
||||
%google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
|
||||
%markers{models: "OfnMap.enterprises", fit: "true",
|
||||
coords: "'self'", icon: "'icon'", click: "'reveal'"}
|
||||
|
||||
|
||||
|
||||
@@ -9,41 +9,35 @@
|
||||
.row
|
||||
.small-12.large-6.columns
|
||||
%p{"ng-bind-html" => "producer.long_description"}
|
||||
.small-12.large-6.columns
|
||||
.small-8.large-4.columns
|
||||
%a{"ng-href" => '/producers##{{producer.hash}}'}
|
||||
Find my products
|
||||
%img.producer-logo{"ng-src" => "{{producer.logo}}", "ng-if" => "producer.logo"}
|
||||
%h5 Stay in touch with {{ producer.name }}
|
||||
|
||||
.small-4.large-2.columns
|
||||
%ul.small-block-grid-1{bindonce: true}
|
||||
|
||||
%li{"ng-if" => "producer.website"}
|
||||
%a{"ng-href" => "http://{{producer.website | stripUrl}}", target: "_blank" }
|
||||
%i.ofn-i_049-web
|
||||
{{ producer.website | stripUrl }}
|
||||
|
||||
%li{"ng-if" => "producer.email"}
|
||||
%a{"ng-href" => "mailto:{{producer.email | stripUrl}}", target: "_blank" }
|
||||
%i.ofn-i_050-mail-circle
|
||||
{{ producer.email | stripUrl }}
|
||||
|
||||
%li{"ng-if" => "producer.twitter"}
|
||||
%a{"ng-href" => "http://twitter.com/{{producer.twitter}}", target: "_blank"}
|
||||
%i.ofn-i_041-twitter
|
||||
{{ producer.twitter }}
|
||||
|
||||
%li{"ng-if" => "producer.facebook"}
|
||||
%a{"ng-href" => "http://{{producer.facebook | stripUrl}}", target: "_blank"}
|
||||
%i.ofn-i_044-facebook
|
||||
{{ producer.facebook | stripUrl }}
|
||||
|
||||
%li{"ng-if" => "producer.linkedin"}
|
||||
%a{"ng-href" => "http://{{producer.linkedin | stripUrl}}", target: "_blank"}
|
||||
%i.ofn-i_042-linkedin
|
||||
{{ producer.linkedin | stripUrl }}
|
||||
|
||||
%li{"ng-if" => "producer.instagram"}
|
||||
%a{"ng-href" => "http://instagram.com/{{producer.instagram}}", target: "_blank"}
|
||||
%i.ofn-i_043-instagram
|
||||
{{ producer.instagram }}
|
||||
|
||||
%a.close-reveal-modal{"ng-click" => "$close()"}
|
||||
%i.ofn-i_009-close
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
.small-12.columns
|
||||
.active_table
|
||||
%producer.active_table_node.row{id: "{{producer.path}}",
|
||||
"scroll-after-load" => true,
|
||||
"ng-repeat" => "producer in filteredProducers = (Producers.producers | filterProducers:query)",
|
||||
"ng-controller" => "ProducerNodeCtrl",
|
||||
"ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
LinkedIn
|
||||
.small-12.medium-4.columns.text-left
|
||||
%h4 Getting around
|
||||
%ul.ofn-list
|
||||
%ul.bullet-list
|
||||
%li
|
||||
%a{href: "/shop"} Shop
|
||||
%li
|
||||
|
||||
@@ -38,4 +38,4 @@
|
||||
%span.nav-primary.nav-branded {{ CurrentHub.name }}
|
||||
%li.divider
|
||||
%li.cart
|
||||
= render partial: "shared/menu/cart"
|
||||
= render partial: "shared/menu/cart"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.fixed
|
||||
= render partial: "shared/menu/large_menu"
|
||||
%ofn-flash
|
||||
= render partial: "shared/menu/mobile_menu"
|
||||
|
||||
@@ -25,7 +25,7 @@ child :primary_taxon => :primary_taxon do
|
||||
end
|
||||
|
||||
child :master => :master do
|
||||
attributes :id, :is_master, :count_on_hand, :options_text, :count_on_hand, :on_demand
|
||||
attributes :id, :is_master, :count_on_hand, :name_to_display, :unit_to_display, :count_on_hand, :on_demand
|
||||
child :images => :images do
|
||||
attributes :id, :alt
|
||||
node do |img|
|
||||
@@ -40,7 +40,8 @@ node :variants do |product|
|
||||
{id: v.id,
|
||||
is_master: v.is_master,
|
||||
count_on_hand: v.count_on_hand,
|
||||
options_text: v.options_text,
|
||||
name_to_display: v.name_to_display,
|
||||
unit_to_display: v.unit_to_display,
|
||||
on_demand: v.on_demand,
|
||||
price: v.price_with_fees(current_distributor, current_order_cycle),
|
||||
images: v.images.map { |i| {id: i.id, alt: i.alt, small_url: i.attachment.url(:small, false)} }
|
||||
|
||||
@@ -3,37 +3,40 @@
|
||||
|
||||
|
||||
.small-4.columns
|
||||
{{ product.master.options_text }}
|
||||
{{ product.master.name_to_display }}
|
||||
|
||||
-# WITHOUT GROUP BUY
|
||||
.small-5.columns{"bo-if" => "!product.group_buy"}
|
||||
%input{type: :number,
|
||||
min: 0,
|
||||
placeholder: "0",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{product.on_demand && 9999 || product.count_on_hand }}",
|
||||
name: "variants[{{product.master.id}}]",
|
||||
id: "variants_{{product.master.id}}",
|
||||
"ng-model" => "product.quantity"}
|
||||
{{ product.master.unit_to_display }}
|
||||
|
||||
-# WITH GROUP BUY
|
||||
.small-2.columns{"bo-if" => "product.group_buy"}
|
||||
%input{type: :number,
|
||||
min: 0,
|
||||
placeholder: "min",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{product.on_demand && 9999 || product.count_on_hand }}",
|
||||
name: "variants[{{product.master.id}}]",
|
||||
id: "variants_{{product.master.id}}",
|
||||
"ng-model" => "product.quantity"}
|
||||
(min)
|
||||
|
||||
.small-3.columns{"bo-if" => "product.group_buy"}
|
||||
%input{type: :number,
|
||||
min: 0,
|
||||
placeholder: "max",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{product.on_demand && 9999 || product.count_on_hand }}",
|
||||
name: "variant_attributes[{{product.master.id}}][max_quantity]",
|
||||
"ng-model" => "product.max_quantity"}
|
||||
(max)
|
||||
{{ product.master.unit_to_display }}
|
||||
|
||||
.small-2.columns.text-right
|
||||
{{ product.price | currency }}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
%img{"bo-src" => "product.master.images[0].small_url"}
|
||||
|
||||
.small-4.columns.summary-header
|
||||
%img{"bo-src" => "product.primary_taxon.icon",
|
||||
"ng-click" => "ordering.order = 'primary_taxon.name'",
|
||||
name: "{{product.primary_taxon.name}}"}
|
||||
%render-svg{path: "{{product.primary_taxon.icon}}"}
|
||||
= render partial: "modals/product"
|
||||
|
||||
.small-5.columns
|
||||
|
||||
@@ -6,37 +6,40 @@
|
||||
|
||||
|
||||
.small-4.columns
|
||||
{{ variant.options_text }}
|
||||
{{ variant.name_to_display }}
|
||||
|
||||
-# WITHOUT GROUP BUY
|
||||
.small-5.columns{"bo-if" => "!product.group_buy"}
|
||||
%input{type: :number,
|
||||
value: nil,
|
||||
min: 0,
|
||||
placeholder: "0",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}",
|
||||
"bo-model" => "variant.quantity"}
|
||||
{{ variant.unit_to_display }}
|
||||
|
||||
-# WITH GROUP BUY
|
||||
.small-2.columns{"bo-if" => "product.group_buy"}
|
||||
%input{type: :number,
|
||||
value: nil,
|
||||
min: 0,
|
||||
placeholder: "min",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}",
|
||||
"bo-model" => "variant.quantity"}
|
||||
(min)
|
||||
|
||||
.small-3.columns{"bo-if" => "product.group_buy"}
|
||||
%input{type: :number,
|
||||
min: 0,
|
||||
placeholder: "max",
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variant_attributes[{{variant.id}}][max_quantity]",
|
||||
"ng-model" => "variant.max_quantity"}
|
||||
(max)
|
||||
{{ variant.unit_to_display }}
|
||||
|
||||
.small-2.columns.text-right.price
|
||||
{{ variant.price | currency }}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
%navigation
|
||||
%distributor.details.row
|
||||
#distributor_title
|
||||
- if current_distributor.logo.exists?
|
||||
%img.left{src: current_distributor.logo.url(:thumb)}
|
||||
%h3
|
||||
= current_distributor.name
|
||||
%location= current_distributor.address.city
|
||||
/ Will this needs to be a drop-down to choose either pick-up point or delivery once shipping methods are implemented
|
||||
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
.small-12.medium-6.large-6.columns
|
||||
#distributor_title
|
||||
- if current_distributor.logo.exists?
|
||||
%img.left{src: current_distributor.logo.url(:thumb)}
|
||||
%h3
|
||||
= current_distributor.name
|
||||
%location= current_distributor.address.city
|
||||
/ Will this needs to be a drop-down to choose either pick-up point or delivery once shipping methods are implemented
|
||||
.small-12.medium-6.large-6.columns
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
|
||||
= render partial: "shopping_shared/tabs"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
%h5
|
||||
=current_distributor.name
|
||||
belongs to:
|
||||
%ul.ofn-list
|
||||
%ul.bullet-list
|
||||
- for group in current_distributor.groups
|
||||
%li
|
||||
%a{href: main_app.groups_path(anchor: "#/#group#{group.id}")}= group.name
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
.small-12.columns
|
||||
%h5
|
||||
= "#{current_distributor.name}'s producers:"
|
||||
%ul.ofn-list
|
||||
%ul.bullet-list
|
||||
%li{"ng-repeat" => "producer in CurrentHub.producers"}
|
||||
= render partial: "modals/producer"
|
||||
|
||||
@@ -3,7 +3,6 @@ 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
|
||||
@@ -120,6 +119,7 @@ Spree::Core::Engine.routes.prepend do
|
||||
|
||||
resources :products do
|
||||
get :managed, on: :collection
|
||||
delete :soft_delete
|
||||
|
||||
resources :variants do
|
||||
delete :soft_delete
|
||||
|
||||
12
config/schedule.rb
Normal file
12
config/schedule.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
require 'whenever'
|
||||
|
||||
# Learn more: http://github.com/javan/whenever
|
||||
|
||||
env "MAILTO", "rohan@rohanmitchell.com"
|
||||
|
||||
# If we use -e with a file containing specs, rspec interprets it and filters out our examples
|
||||
job_type :run_file, "cd :path; :environment_variable=:environment bundle exec script/rails runner :task :output"
|
||||
|
||||
every 1.day, at: '12:05am' do
|
||||
run_file "lib/open_food_network/integrity_checker.rb"
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
class RemoveDeletedVariantsFromOrderCycles < ActiveRecord::Migration
|
||||
def up
|
||||
evs = ExchangeVariant.joins(:variant).where('spree_variants.deleted_at IS NOT NULL')
|
||||
say "Removing #{evs.count} deleted variants from order cycles..."
|
||||
evs.destroy_all
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
||||
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20140604051248) do
|
||||
ActiveRecord::Schema.define(:version => 20140612020206) do
|
||||
|
||||
create_table "adjustment_metadata", :force => true do |t|
|
||||
t.integer "adjustment_id"
|
||||
@@ -681,7 +681,7 @@ ActiveRecord::Schema.define(:version => 20140604051248) do
|
||||
t.float "variant_unit_scale"
|
||||
t.string "variant_unit_name"
|
||||
t.text "notes"
|
||||
t.integer "primary_taxon_id"
|
||||
t.integer "primary_taxon_id", :null => false
|
||||
end
|
||||
|
||||
add_index "spree_products", ["available_on"], :name => "index_products_on_available_on"
|
||||
|
||||
23
lib/open_food_network/integrity_checker.rb
Normal file
23
lib/open_food_network/integrity_checker.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
require 'rspec/rails'
|
||||
require 'rspec/autorun'
|
||||
|
||||
# This spec file is one part of a two-part strategy to maintain data integrity. The first part
|
||||
# is to proactively protect data integrity using database constraints (not null, foreign keys,
|
||||
# etc) and ActiveRecord validations. As a backup to those two techniques, and particularly in
|
||||
# the cases where it's not possible to model an integrity concern with database constraints,
|
||||
# we can add a reactive integrity test here.
|
||||
|
||||
# These tests are run nightly and the results are emailed to the MAILTO address in
|
||||
# config/schedule.rb if any failures occur.
|
||||
|
||||
# Ref: http://pluralsight.com/training/Courses/TableOfContents/database-your-friend
|
||||
|
||||
|
||||
describe "data integrity" do
|
||||
it "has no deleted variants in order cycles" do
|
||||
# When a variant is soft deleted, it should be removed from all order cycles
|
||||
# via Spree::Product#delete or Spree::Variant#delete.
|
||||
evs = ExchangeVariant.joins(:variant).where('spree_variants.deleted_at IS NOT NULL')
|
||||
evs.count.should == 0
|
||||
end
|
||||
end
|
||||
BIN
public/OFN.eot
BIN
public/OFN.eot
Binary file not shown.
112
public/OFN.svg
112
public/OFN.svg
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 88 KiB |
BIN
public/OFN.ttf
BIN
public/OFN.ttf
Binary file not shown.
BIN
public/OFN.woff
BIN
public/OFN.woff
Binary file not shown.
@@ -7,9 +7,11 @@ module Spree
|
||||
render_views
|
||||
|
||||
let(:supplier) { FactoryGirl.create(:supplier_enterprise) }
|
||||
let(:supplier2) { FactoryGirl.create(:supplier_enterprise) }
|
||||
let!(:product1) { FactoryGirl.create(:product, supplier: supplier) }
|
||||
let!(:product2) { FactoryGirl.create(:product, supplier: supplier) }
|
||||
let!(:product3) { FactoryGirl.create(:product, supplier: supplier) }
|
||||
let(:product_other_supplier) { FactoryGirl.create(:product, supplier: supplier2) }
|
||||
let(:attributes) { [:id, :name, :supplier, :price, :on_hand, :available_on, :permalink_live] }
|
||||
let(:unit_attributes) { [:id, :name, :group_buy_unit_size, :variant_unit] }
|
||||
|
||||
@@ -39,6 +41,20 @@ module Spree
|
||||
keys = json_response.first.keys.map{ |key| key.to_sym }
|
||||
attributes.all?{ |attr| keys.include? attr }.should == true
|
||||
end
|
||||
|
||||
it "soft deletes my products" do
|
||||
spree_delete :soft_delete, {product_id: product1.to_param, format: :json}
|
||||
response.status.should == 204
|
||||
lambda { product1.reload }.should_not raise_error
|
||||
product1.deleted_at.should_not be_nil
|
||||
end
|
||||
|
||||
it "is denied access to soft deleting another enterprises' product" do
|
||||
spree_delete :soft_delete, {product_id: product_other_supplier.to_param, format: :json}
|
||||
assert_unauthorized!
|
||||
lambda { product_other_supplier.reload }.should_not raise_error
|
||||
product_other_supplier.deleted_at.should be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "as an administrator" do
|
||||
@@ -80,17 +96,23 @@ module Spree
|
||||
end
|
||||
|
||||
it "should allow available_on to be nil" do
|
||||
|
||||
spree_get :index, { :template => 'bulk_index', :format => :json }
|
||||
json_response.size.should == 3
|
||||
|
||||
product4 = FactoryGirl.create(:product)
|
||||
product4.available_on = nil
|
||||
product4.save!
|
||||
product5 = FactoryGirl.create(:product)
|
||||
product5.available_on = nil
|
||||
product5.save!
|
||||
|
||||
spree_get :index, { :template => 'bulk_index', :format => :json }
|
||||
json_response.size.should == 4
|
||||
end
|
||||
|
||||
it "soft deletes a product" do
|
||||
spree_delete :soft_delete, {product_id: product1.to_param, format: :json}
|
||||
response.status.should == 204
|
||||
lambda { product1.reload }.should_not raise_error
|
||||
product1.deleted_at.should_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,4 +83,18 @@ feature %q{
|
||||
page.should_not have_field "variant_unit_value"
|
||||
page.should_not have_field "variant_unit_description"
|
||||
end
|
||||
|
||||
it "soft-deletes variants", js: true do
|
||||
p = create(:simple_product)
|
||||
v = create(:variant, product: p)
|
||||
|
||||
login_to_admin_section
|
||||
visit spree.admin_product_variants_path p
|
||||
|
||||
page.find('a.delete-resource').click
|
||||
page.should_not have_content v.options_text
|
||||
|
||||
v.reload
|
||||
v.deleted_at.should_not be_nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,6 +36,7 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
exchange.variants << product.master
|
||||
|
||||
visit shop_path
|
||||
save_screenshot "/users/willmarshall/Desktop/wtsvg.png"
|
||||
find("#tab_producers a").click
|
||||
page.should have_content supplier.name
|
||||
end
|
||||
@@ -64,6 +65,8 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
end
|
||||
|
||||
it "shows products after selecting an order cycle" do
|
||||
product.master.update_attribute(:display_name, "kitten")
|
||||
product.master.update_attribute(:display_as, "rabbit")
|
||||
exchange1.variants << product.master ## add product to exchange
|
||||
visit shop_path
|
||||
page.should_not have_content product.name
|
||||
@@ -74,6 +77,9 @@ feature "As a consumer I want to shop with a distributor", js: true do
|
||||
page.should have_content "Next order closing in 2 days"
|
||||
Spree::Order.last.order_cycle.should == oc1
|
||||
page.should have_content product.name
|
||||
save_screenshot "/Users/willmarshall/Desktop/shop.png"
|
||||
page.should have_content product.master.display_name
|
||||
page.should have_content product.master.display_as
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -973,7 +973,7 @@ describe "AdminProductEditCtrl", ->
|
||||
|
||||
|
||||
describe "deleting products", ->
|
||||
it "deletes products with a http delete request to /api/products/id", ->
|
||||
it "deletes products with a http delete request to /api/products/id/soft_delete", ->
|
||||
spyOn(window, "confirm").andReturn true
|
||||
$scope.products = [
|
||||
{
|
||||
@@ -986,7 +986,7 @@ describe "AdminProductEditCtrl", ->
|
||||
}
|
||||
]
|
||||
$scope.dirtyProducts = {}
|
||||
$httpBackend.expectDELETE("/api/products/13").respond 200, "data"
|
||||
$httpBackend.expectDELETE("/api/products/13/soft_delete").respond 200, "data"
|
||||
$scope.deleteProduct $scope.products[1]
|
||||
$httpBackend.flush()
|
||||
|
||||
@@ -1005,7 +1005,7 @@ describe "AdminProductEditCtrl", ->
|
||||
DirtyProducts.addProductProperty 9, "someProperty", "something"
|
||||
DirtyProducts.addProductProperty 13, "name", "P1"
|
||||
|
||||
$httpBackend.expectDELETE("/api/products/13").respond 200, "data"
|
||||
$httpBackend.expectDELETE("/api/products/13/soft_delete").respond 200, "data"
|
||||
$scope.deleteProduct $scope.products[1]
|
||||
$httpBackend.flush()
|
||||
expect($scope.products).toEqual [
|
||||
@@ -1036,7 +1036,7 @@ describe "AdminProductEditCtrl", ->
|
||||
|
||||
|
||||
describe "when the variant has been saved", ->
|
||||
it "deletes variants with a http delete request to /api/products/product_permalink/variants/(variant_id)", ->
|
||||
it "deletes variants with a http delete request to /api/products/product_permalink/variants/(variant_id)/soft_delete", ->
|
||||
spyOn(window, "confirm").andReturn true
|
||||
$scope.products = [
|
||||
{
|
||||
|
||||
@@ -580,7 +580,7 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
describe "Taxons" do
|
||||
describe "taxons" do
|
||||
let(:taxon1) { create(:taxon) }
|
||||
let(:taxon2) { create(:taxon) }
|
||||
let(:product) { create(:simple_product) }
|
||||
@@ -589,5 +589,25 @@ module Spree
|
||||
product.taxons.should == [product.primary_taxon]
|
||||
end
|
||||
end
|
||||
|
||||
describe "deletion" do
|
||||
let(:p) { create(:simple_product) }
|
||||
let(:v) { create(:variant, product: p) }
|
||||
let(:oc) { create(:simple_order_cycle) }
|
||||
let(:s) { create(:supplier_enterprise) }
|
||||
let(:e) { create(:exchange, order_cycle: oc, incoming: true, sender: s, receiver: oc.coordinator) }
|
||||
|
||||
it "removes the master variant from all order cycles" do
|
||||
e.variants << p.master
|
||||
p.delete
|
||||
e.variants(true).should be_empty
|
||||
end
|
||||
|
||||
it "removes all other variants from order cycles" do
|
||||
e.variants << v
|
||||
p.delete
|
||||
e.variants(true).should be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -124,6 +124,31 @@ module Spree
|
||||
end
|
||||
|
||||
describe "unit value/description" do
|
||||
describe "getting name for display" do
|
||||
it "returns display_name if present" do
|
||||
v = create(:variant, display_name: "foo")
|
||||
v.name_to_display.should == "foo"
|
||||
end
|
||||
|
||||
it "returns product name if display_name is empty" do
|
||||
v = create(:variant, product: create(:product))
|
||||
v.name_to_display.should == v.product.name
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting unit for display" do
|
||||
it "returns display_as if present" do
|
||||
v = create(:variant, display_as: "foo")
|
||||
v.unit_to_display.should == "foo"
|
||||
end
|
||||
|
||||
it "returns options_text if display_as is empty" do
|
||||
v = create(:variant)
|
||||
v.stub(:options_text).and_return "ponies"
|
||||
v.unit_to_display.should == "ponies"
|
||||
end
|
||||
end
|
||||
|
||||
describe "setting the variant's weight from the unit value" do
|
||||
it "sets the variant's weight when unit is weight" do
|
||||
p = create(:simple_product, variant_unit: nil, variant_unit_scale: nil)
|
||||
@@ -209,6 +234,25 @@ module Spree
|
||||
end
|
||||
end
|
||||
end
|
||||
context "when the variant already has a value set (and all required option values exist)" do
|
||||
let!(:p0) { create(:simple_product, variant_unit: 'weight', variant_unit_scale: 1) }
|
||||
let!(:v0) { create(:variant, product: p0, unit_value: 10, unit_description: 'foo') }
|
||||
|
||||
let!(:p) { create(:simple_product, variant_unit: 'weight', variant_unit_scale: 1) }
|
||||
let!(:v) { create(:variant, product: p, unit_value: 5, unit_description: 'bar') }
|
||||
|
||||
it "removes the old option value and assigns the new one" do
|
||||
ov_orig = v.option_values.last
|
||||
ov_new = v0.option_values.last
|
||||
|
||||
expect {
|
||||
v.update_attributes!(unit_value: 10, unit_description: 'foo')
|
||||
}.to change(Spree::OptionValue, :count).by(0)
|
||||
|
||||
v.option_values.should_not include ov_orig
|
||||
v.option_values.should include ov_new
|
||||
end
|
||||
end
|
||||
|
||||
context "when the variant already has a value set (and all required option values exist)" do
|
||||
let!(:p0) { create(:simple_product, variant_unit: 'weight', variant_unit_scale: 1) }
|
||||
@@ -246,7 +290,7 @@ module Spree
|
||||
let!(:p) { create(:simple_product, variant_unit: 'weight', variant_unit_scale: 1) }
|
||||
let!(:v) { create(:variant, product: p, unit_value: 5, unit_description: 'bar', display_as: 'FOOS!') }
|
||||
|
||||
it "requests the name of the new option_value from OptionValueName" do
|
||||
it "does not request the name of the new option_value from OptionValueName" do
|
||||
OpenFoodNetwork::OptionValueNamer.any_instance.should_not_receive(:name)
|
||||
v.update_attributes!(unit_value: 10, unit_description: 'foo')
|
||||
ov = v.option_values.last
|
||||
@@ -264,13 +308,13 @@ module Spree
|
||||
|
||||
it "removes option value associations for unit option types" do
|
||||
expect {
|
||||
@v.send(:delete_unit_option_values)
|
||||
@v.delete_unit_option_values
|
||||
}.to change(@v.option_values, :count).by(-1)
|
||||
end
|
||||
|
||||
it "does not delete option values" do
|
||||
expect {
|
||||
@v.send(:delete_unit_option_values)
|
||||
@v.delete_unit_option_values
|
||||
}.to change(Spree::OptionValue, :count).by(0)
|
||||
end
|
||||
end
|
||||
|
||||
15
spec/views/admin/enterprises/index.rabl_spec.rb
Normal file
15
spec/views/admin/enterprises/index.rabl_spec.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe "admin/enterprises/index.rabl" do
|
||||
let(:enterprise) { create(:distributor_enterprise) }
|
||||
let!(:product) { create(:simple_product, supplier: enterprise) }
|
||||
let!(:deleted_product) { create(:simple_product, supplier: enterprise, deleted_at: 1.day.ago) }
|
||||
let(:render) { Rabl.render([enterprise], 'admin/enterprises/index', view_path: 'app/views', scope: RablHelper::FakeContext.instance) }
|
||||
|
||||
describe "supplied products" do
|
||||
it "does not render deleted products" do
|
||||
render.should have_json_size(1).at_path '0/supplied_products'
|
||||
render.should be_json_eql(product.master.id).at_path '0/supplied_products/0/master_id'
|
||||
end
|
||||
end
|
||||
end
|
||||
1
vendor/assets/javascripts/angular-scroll.min.js
vendored
Normal file
1
vendor/assets/javascripts/angular-scroll.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user