mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Merge in master
This commit is contained in:
1
Gemfile
1
Gemfile
@@ -35,6 +35,7 @@ gem 'geocoder'
|
||||
gem 'gmaps4rails'
|
||||
gem 'spinjs-rails'
|
||||
gem 'rack-ssl', :require => 'rack/ssl'
|
||||
gem 'custom_error_message', :github => 'jeremydurham/custom-err-msg'
|
||||
|
||||
gem 'foreigner'
|
||||
gem 'immigrant'
|
||||
|
||||
@@ -6,6 +6,12 @@ GIT
|
||||
actionpack (~> 3.0)
|
||||
activemodel (~> 3.0)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/jeremydurham/custom-err-msg.git
|
||||
revision: 3a8ec9dddc7a5b0aab7c69a6060596de300c68f4
|
||||
specs:
|
||||
custom_error_message (1.1.1)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/openfoodfoundation/spree.git
|
||||
revision: da651b40f5c6cdd32e00b060729eb9aefd4f615f
|
||||
@@ -492,6 +498,7 @@ DEPENDENCIES
|
||||
coffee-rails (~> 3.2.1)
|
||||
comfortable_mexican_sofa
|
||||
compass-rails
|
||||
custom_error_message!
|
||||
database_cleaner (= 0.7.1)
|
||||
db2fog
|
||||
debugger-linecache
|
||||
|
||||
1565
app/assets/images/groups.svg
Normal file
1565
app/assets/images/groups.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 153 KiB |
@@ -1,3 +1,3 @@
|
||||
angular.module("ofn.admin", ["ngResource","ofn.dropdown"]).config ($httpProvider) ->
|
||||
angular.module("ofn.admin", ["ngResource", "ngAnimate", "ofn.dropdown"]).config ($httpProvider) ->
|
||||
$httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content")
|
||||
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"
|
||||
@@ -11,6 +11,7 @@
|
||||
//= require shared/jquery-ui-timepicker-addon
|
||||
//= require angular
|
||||
//= require angular-resource
|
||||
//= require angular-animate
|
||||
//= require admin/spree_core
|
||||
//= require admin/spree_auth
|
||||
//= require admin/spree_promo
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
angular.module("ofn.admin").controller "AdminEnterpriseRelationshipsCtrl", ($scope, EnterpriseRelationships, Enterprises) ->
|
||||
$scope.EnterpriseRelationships = EnterpriseRelationships
|
||||
$scope.Enterprises = Enterprises
|
||||
|
||||
$scope.create = ->
|
||||
$scope.EnterpriseRelationships.create($scope.parent_id, $scope.child_id)
|
||||
|
||||
$scope.delete = (enterprise_relationship) ->
|
||||
if confirm("Are you sure?")
|
||||
$scope.EnterpriseRelationships.delete enterprise_relationship
|
||||
@@ -0,0 +1,18 @@
|
||||
angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterprise_relationships) ->
|
||||
new class EnterpriseRelationships
|
||||
create_errors: ""
|
||||
|
||||
constructor: ->
|
||||
@enterprise_relationships = enterprise_relationships
|
||||
|
||||
create: (parent_id, child_id) ->
|
||||
$http.post('/admin/enterprise_relationships', {enterprise_relationship: {parent_id: parent_id, child_id: child_id}}).success (data, status) =>
|
||||
@enterprise_relationships.unshift(data)
|
||||
@create_errors = ""
|
||||
|
||||
.error (response, status) =>
|
||||
@create_errors = response.errors
|
||||
|
||||
delete: (er) ->
|
||||
$http.delete('/admin/enterprise_relationships/' + er.id).success (data) =>
|
||||
@enterprise_relationships.splice @enterprise_relationships.indexOf(er), 1
|
||||
@@ -0,0 +1,5 @@
|
||||
angular.module("ofn.admin").factory 'Enterprises', (my_enterprises, all_enterprises) ->
|
||||
new class Enterprises
|
||||
constructor: ->
|
||||
@my_enterprises = my_enterprises
|
||||
@all_enterprises = all_enterprises
|
||||
@@ -5,6 +5,7 @@
|
||||
#
|
||||
#= require angular
|
||||
#= require angular-cookies
|
||||
#= require angular-sanitize
|
||||
#= require angular-resource
|
||||
#= require ../shared/mm-foundation-tpls-0.2.0-SNAPSHOT
|
||||
#= require ../shared/bindonce.min.js
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
Darkswarm.controller "LoginCtrl", ($scope, $http, $location, AuthenticationService) ->
|
||||
Darkswarm.controller "LoginCtrl", ($scope, $http, AuthenticationService, Redirections) ->
|
||||
$scope.path = "/login"
|
||||
|
||||
$scope.submit = ->
|
||||
$http.post("/user/spree_user/sign_in", {spree_user: $scope.spree_user}).success (data)->
|
||||
location.href = location.origin + location.pathname # Strips out hash fragments
|
||||
if Redirections.after_login
|
||||
location.href = location.origin + Redirections.after_login
|
||||
else
|
||||
location.href = location.origin + location.pathname # Strips out hash fragments
|
||||
.error (data) ->
|
||||
$scope.errors = data.message
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Darkswarm.controller "GroupsCtrl", ($scope, Groups, $anchorScroll, $rootScope) ->
|
||||
$scope.Groups = Groups
|
||||
$scope.order = 'position'
|
||||
|
||||
$rootScope.$on "$locationChangeSuccess", (newRoute, oldRoute) ->
|
||||
$anchorScroll()
|
||||
@@ -1,15 +1,17 @@
|
||||
Darkswarm.controller "OrderCycleCtrl", ($scope, $rootScope, OrderCycle, $timeout) ->
|
||||
Darkswarm.controller "OrderCycleCtrl", ($scope, OrderCycle, $timeout) ->
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
$scope.OrderCycle = OrderCycle
|
||||
|
||||
$scope.changeOrderCycle = ->
|
||||
OrderCycle.push_order_cycle()
|
||||
$timeout ->
|
||||
$("#order_cycle_id").trigger("closeTrigger")
|
||||
|
||||
# Timeout forces this to be evaluated after everything is loaded
|
||||
# This is a hack. We should probably write our own "popover" directive
|
||||
# That takes an expression instead of a trigger, and binds to that
|
||||
$timeout =>
|
||||
if !$scope.OrderCycle.selected()
|
||||
$("#order_cycle_id").trigger("openTrigger")
|
||||
|
||||
|
||||
Darkswarm.controller "OrderCycleChangeCtrl", ($scope, OrderCycle, Product, $timeout) ->
|
||||
$scope.changeOrderCycle = ->
|
||||
OrderCycle.push_order_cycle Product.update
|
||||
$timeout ->
|
||||
$("#order_cycle_id").trigger("closeTrigger")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Darkswarm.controller "ProducerNodeCtrl", ($scope, HashNavigation, $anchorScroll) ->
|
||||
$scope.toggle = ->
|
||||
HashNavigation.navigate $scope.producer.hash
|
||||
HashNavigation.toggle $scope.producer.hash
|
||||
|
||||
$scope.open = ->
|
||||
HashNavigation.active($scope.producer.hash)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Darkswarm.controller "ProductNodeCtrl", ($scope) ->
|
||||
|
||||
$scope.price = ->
|
||||
if $scope.product.variants.length > 0
|
||||
prices = (v.price for v in $scope.product.variants)
|
||||
Math.min.apply(null, prices)
|
||||
else
|
||||
$scope.product.price
|
||||
|
||||
$scope.producer = $scope.product.supplier
|
||||
$scope.hasVariants = $scope.product.variants.length > 0
|
||||
@@ -1,8 +1,8 @@
|
||||
Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Product, OrderCycle) ->
|
||||
$scope.data = Product.data
|
||||
$scope.limit = 3
|
||||
$scope.ordering = {order: "name"}
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
Product.update()
|
||||
|
||||
$scope.incrementLimit = ->
|
||||
if $scope.limit < $scope.data.products.length
|
||||
@@ -12,10 +12,3 @@ Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Product, OrderCycle) -
|
||||
code = e.keyCode || e.which
|
||||
if code == 13
|
||||
e.preventDefault()
|
||||
|
||||
$scope.productPrice = (product) ->
|
||||
if product.variants.length > 0
|
||||
prices = (v.price for v in product.variants)
|
||||
Math.min.apply(null, prices)
|
||||
else
|
||||
product.price
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Darkswarm.controller "ProducersTabCtrl", ($scope, CurrentHub) ->
|
||||
$scope.CurrentHub = CurrentHub
|
||||
@@ -5,6 +5,7 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource",
|
||||
'infinite-scroll',
|
||||
'angular-flash.service',
|
||||
'templates',
|
||||
'ngSanitize',
|
||||
'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')
|
||||
|
||||
@@ -2,13 +2,16 @@ Darkswarm.directive "ofnModal", ($modal)->
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
transclude: true
|
||||
scope: {}
|
||||
template: "<a>{{title}}</a>"
|
||||
|
||||
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
|
||||
|
||||
scope.cancel = ->
|
||||
scope.modalInstance.dismiss("cancel")
|
||||
|
||||
elem.on "click", ->
|
||||
scope.modalInstance = $modal.open(controller: ctrl, template: transclude())
|
||||
elem.on "click", =>
|
||||
scope.modalInstance = $modal.open(controller: ctrl, template: contents, scope: scope.$parent)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Darkswarm.filter "stripUrl", ->
|
||||
stripper = /(https?:\/\/)?(www\.)?(.*)/
|
||||
(url) ->
|
||||
url.match(stripper).pop()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location)->
|
||||
Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redirections)->
|
||||
new class AuthenticationService
|
||||
selectedPath: "/login"
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
Darkswarm.factory 'Groups', (groups) ->
|
||||
new class Groups
|
||||
constructor: ->
|
||||
@groups = groups
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory 'Order', ($resource, Product, order, $http, CheckoutFormState, flash, Navigation)->
|
||||
Darkswarm.factory 'Order', ($resource, order, $http, CheckoutFormState, flash, Navigation)->
|
||||
new class Order
|
||||
errors: {}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
Darkswarm.factory 'OrderCycle', ($resource, Product, orderCycleData) ->
|
||||
Darkswarm.factory 'OrderCycle', ($resource, orderCycleData) ->
|
||||
class OrderCycle
|
||||
@order_cycle = orderCycleData # Object or {} due to RABL
|
||||
@push_order_cycle: ->
|
||||
@push_order_cycle: (callback) ->
|
||||
new $resource("/shop/order_cycle").save {order_cycle_id: @order_cycle.order_cycle_id}, (order_data)->
|
||||
OrderCycle.order_cycle.orders_close_at = order_data.orders_close_at
|
||||
Product.update()
|
||||
callback()
|
||||
|
||||
@orders_close_at: ->
|
||||
@order_cycle.orders_close_at if @selected()
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
Darkswarm.factory 'Product', ($resource) ->
|
||||
new class Product
|
||||
data: {
|
||||
constructor: ->
|
||||
@update()
|
||||
|
||||
# TODO: don't need to scope this into object
|
||||
# Already on object as far as controller scope is concerned
|
||||
data:
|
||||
products: null
|
||||
loading: true
|
||||
}
|
||||
update: ->
|
||||
|
||||
update: =>
|
||||
@data.products = $resource("/shop/products").query =>
|
||||
@data.loading = false
|
||||
@data
|
||||
all: ->
|
||||
@data.products || @update()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Darkswarm.factory "Redirections", ($location)->
|
||||
new class Redirections
|
||||
after_login: $location.search().after_login
|
||||
@@ -2477,7 +2477,7 @@ angular.module("template/modal/window.html", []).run(["$templateCache", function
|
||||
$templateCache.put("template/modal/window.html",
|
||||
"<div tabindex=\"-1\" class=\"reveal-modal fade {{ windowClass }}\"\n" +
|
||||
" ng-class=\"{in: animate}\" ng-click=\"close($event)\"\n" +
|
||||
" style=\"display: block; position: fixed; visibility: visible\">\n" +
|
||||
" style=\"display: block; visibility: visible\">\n" +
|
||||
" <div ng-transclude></div>\n" +
|
||||
"</div>\n" +
|
||||
"");
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// TODO: Provide -moz- and -o- directives
|
||||
@-webkit-keyframes alert-flash
|
||||
0%
|
||||
background-color: #f9f1ae
|
||||
|
||||
100%
|
||||
background-color: #fff
|
||||
|
||||
|
||||
table#enterprise-relationships
|
||||
th.actions, td.actions
|
||||
width: 16%
|
||||
.errors
|
||||
color: #f00
|
||||
|
||||
tr.ng-enter
|
||||
-webkit-animation-name: alert-flash
|
||||
-webkit-animation-duration: 1200ms
|
||||
-webkit-animation-iteration-count: 1
|
||||
-webkit-animation-timing-function: ease-in-out
|
||||
@@ -19,9 +19,11 @@ table .blank-action {
|
||||
}
|
||||
|
||||
|
||||
input.search {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
#new_enterprise_fee_set input.search {
|
||||
float: right;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.ng .ng-invalid.ng-dirty {
|
||||
|
||||
@@ -11,5 +11,4 @@
|
||||
|
||||
ofn-modal {
|
||||
display: block;
|
||||
}
|
||||
|
||||
}
|
||||
5
app/assets/stylesheets/darkswarm/forms.css.sass
Normal file
5
app/assets/stylesheets/darkswarm/forms.css.sass
Normal file
@@ -0,0 +1,5 @@
|
||||
@import mixins
|
||||
@import branding
|
||||
|
||||
fieldset
|
||||
border: 0
|
||||
42
app/assets/stylesheets/darkswarm/groups.css.sass
Normal file
42
app/assets/stylesheets/darkswarm/groups.css.sass
Normal file
@@ -0,0 +1,42 @@
|
||||
@import branding
|
||||
@import mixins
|
||||
|
||||
#groups
|
||||
background-color: $clr-brick-light
|
||||
background-image: url("/assets/groups.svg")
|
||||
background-position: center 15px
|
||||
background-repeat: no-repeat
|
||||
padding-bottom: 20px
|
||||
|
||||
.group
|
||||
padding-bottom: 40px
|
||||
hr
|
||||
border-bottom: 10px solid white
|
||||
outline: 0
|
||||
border-top: 0
|
||||
margin: 0
|
||||
|
||||
.group-hero
|
||||
position: relative
|
||||
padding: 0
|
||||
border: 10px solid white
|
||||
background: white
|
||||
|
||||
h3.group-name
|
||||
margin-top: 0.5em
|
||||
margin-bottom: 0.15em
|
||||
|
||||
img.group-logo
|
||||
max-width: 220px
|
||||
max-height: 86px
|
||||
float: right
|
||||
padding-top: 10px
|
||||
|
||||
|
||||
img.group-hero-img
|
||||
background-color: black
|
||||
width: 100%
|
||||
height: inherit
|
||||
max-height: 260px
|
||||
min-height: 120px
|
||||
overflow: hidden
|
||||
@@ -12,7 +12,7 @@
|
||||
border: 1px solid #999
|
||||
font-size: 18px
|
||||
@extend .avenir
|
||||
padding: 22px 18px
|
||||
padding: 0.75em 1em
|
||||
height: auto
|
||||
margin-bottom: 1em
|
||||
|
||||
@@ -85,10 +85,10 @@
|
||||
&, & *
|
||||
color: $clr-turquoise
|
||||
a
|
||||
color: white
|
||||
color: $clr-turquoise
|
||||
&:hover
|
||||
text-decoration: none
|
||||
color: $clr-turquoise-light
|
||||
color: $clr-turquoise-bright
|
||||
|
||||
@mixin fullbg
|
||||
background-position: center center
|
||||
|
||||
4
app/assets/stylesheets/darkswarm/product_table.css.sass
Normal file
4
app/assets/stylesheets/darkswarm/product_table.css.sass
Normal file
@@ -0,0 +1,4 @@
|
||||
.product_table
|
||||
.row
|
||||
border: 1px solid black
|
||||
padding: 8px inherit
|
||||
@@ -1,8 +1,7 @@
|
||||
@import mixins
|
||||
@import variables
|
||||
@import branding
|
||||
|
||||
product
|
||||
display: block
|
||||
|
||||
.darkswarm
|
||||
#search
|
||||
@@ -77,68 +76,58 @@ product
|
||||
|
||||
products
|
||||
display: block
|
||||
padding-top: 2.3em
|
||||
padding-top: 2.3em
|
||||
@media all and (max-width: 768px)
|
||||
padding-top: 1em
|
||||
input.button.right
|
||||
float: left
|
||||
table
|
||||
table-layout: fixed
|
||||
width: 100%
|
||||
border-collapse: collapse
|
||||
border: none
|
||||
th
|
||||
line-height: 50px
|
||||
&.name
|
||||
width: 330px
|
||||
//&.notes
|
||||
//width: 140px
|
||||
&.variant
|
||||
width: 180px
|
||||
&.quantity, &.bulk, &.price
|
||||
width: 90px
|
||||
.notes
|
||||
max-width: 300px
|
||||
td, th
|
||||
|
||||
|
||||
product:hover, product:focus, product:active
|
||||
border-color: $clr-brick
|
||||
@include box-shadow(0 0 3px 0 $clr-brick-bright)
|
||||
|
||||
.row.variants
|
||||
border-top: 1px solid $clr-brick-light
|
||||
background: $clr-brick-ultra-light
|
||||
|
||||
product
|
||||
@include csstrans
|
||||
border: 1px solid #989898
|
||||
display: block
|
||||
margin-bottom: 1em !important
|
||||
|
||||
input
|
||||
margin: 0
|
||||
width: 8em
|
||||
|
||||
.columns
|
||||
padding-top: 1em
|
||||
padding-bottom: 1em
|
||||
|
||||
.row.summary, .row.variants
|
||||
@include csstrans
|
||||
margin-left: 0
|
||||
margin-right: 0
|
||||
background: #f7f7f7
|
||||
border-top: 1px solid #dfdfdf
|
||||
|
||||
.row.summary
|
||||
@include csstrans
|
||||
background: #fff
|
||||
> span
|
||||
min-width: 50px
|
||||
display: block
|
||||
tbody
|
||||
border: 1px solid #cccccc
|
||||
border-left: 0px
|
||||
border-right: 0px
|
||||
td
|
||||
padding: 20px 0px
|
||||
&.name
|
||||
img
|
||||
float: left
|
||||
margin-right: 30px
|
||||
@media all and (max-width: 768px)
|
||||
margin-right: 1em
|
||||
div
|
||||
min-width: 150px
|
||||
tr.product-description
|
||||
display: none
|
||||
|
||||
.summary-header
|
||||
&, & *
|
||||
@include avenir
|
||||
color: $clr-brick
|
||||
|
||||
.summary-price
|
||||
&, & *
|
||||
@include avenir
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Responsive
|
||||
@media all and (max-width: 768px)
|
||||
td.notes, th.notes
|
||||
display: none
|
||||
img
|
||||
width: 20px
|
||||
height: auto
|
||||
tr.product-description
|
||||
display: table-row
|
||||
td:empty
|
||||
display: none
|
||||
|
||||
|
||||
input[type=number]
|
||||
width: 60px
|
||||
margin: 0px
|
||||
display: block
|
||||
float: right
|
||||
padding-top: 14px
|
||||
|
||||
|
||||
6
app/assets/stylesheets/darkswarm/shopping-cart.css.sass
Normal file
6
app/assets/stylesheets/darkswarm/shopping-cart.css.sass
Normal file
@@ -0,0 +1,6 @@
|
||||
@import mixins
|
||||
@import branding
|
||||
|
||||
#edit-cart
|
||||
button, .button
|
||||
margin: 0
|
||||
@@ -26,7 +26,7 @@ a
|
||||
text-decoration: none
|
||||
color: $clr-brick-bright
|
||||
|
||||
small
|
||||
small, .small
|
||||
font-size: 0.75rem
|
||||
|
||||
@mixin avenir
|
||||
@@ -56,6 +56,9 @@ ul.ofn-list
|
||||
.pad-top
|
||||
padding-top: 1em
|
||||
|
||||
.not-bold
|
||||
font-weight: normal
|
||||
|
||||
strong.avenir
|
||||
font-weight: normal // Avenir is basically bold anyway
|
||||
|
||||
|
||||
3
app/assets/stylesheets/groups.css.scss
Normal file
3
app/assets/stylesheets/groups.css.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
// Place all the styles related to the groups controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
@@ -15,12 +15,10 @@ module Admin
|
||||
redirect_to main_app.admin_enterprise_groups_path
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def collection
|
||||
EnterpriseGroup.by_position
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
25
app/controllers/admin/enterprise_relationships_controller.rb
Normal file
25
app/controllers/admin/enterprise_relationships_controller.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
module Admin
|
||||
class EnterpriseRelationshipsController < ResourceController
|
||||
def index
|
||||
@my_enterprises = Enterprise.managed_by(spree_current_user).by_name
|
||||
@all_enterprises = Enterprise.by_name
|
||||
@enterprise_relationships = EnterpriseRelationship.by_name.involving_enterprises @my_enterprises
|
||||
end
|
||||
|
||||
def create
|
||||
@enterprise_relationship = EnterpriseRelationship.new params[:enterprise_relationship]
|
||||
|
||||
if @enterprise_relationship.save
|
||||
render partial: "admin/json/enterprise_relationship", locals: {enterprise_relationship: @enterprise_relationship}
|
||||
else
|
||||
render status: 400, json: {errors: @enterprise_relationship.errors.full_messages.join(', ')}
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@enterprise_relationship = EnterpriseRelationship.find params[:id]
|
||||
@enterprise_relationship.destroy
|
||||
render nothing: true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -23,7 +23,7 @@ module Admin
|
||||
|
||||
respond_to do |format|
|
||||
if @order_cycle.save
|
||||
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle).go!
|
||||
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, managed_enterprises).go!
|
||||
|
||||
flash[:notice] = 'Your order cycle has been created.'
|
||||
format.html { redirect_to admin_order_cycles_path }
|
||||
@@ -40,7 +40,7 @@ module Admin
|
||||
|
||||
respond_to do |format|
|
||||
if @order_cycle.update_attributes(params[:order_cycle])
|
||||
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle).go!
|
||||
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, managed_enterprises).go!
|
||||
|
||||
flash[:notice] = 'Your order cycle has been updated.'
|
||||
format.html { redirect_to admin_order_cycles_path }
|
||||
|
||||
@@ -26,7 +26,11 @@ class CheckoutController < Spree::CheckoutController
|
||||
if @order.next
|
||||
state_callback(:after)
|
||||
else
|
||||
flash[:error] = t(:payment_processing_failed)
|
||||
unless @order.errors.empty?
|
||||
flash[:error] = @order.errors.full_messages.to_sentence
|
||||
else
|
||||
flash[:error] = t(:payment_processing_failed)
|
||||
end
|
||||
update_failed
|
||||
return
|
||||
end
|
||||
|
||||
7
app/controllers/groups_controller.rb
Normal file
7
app/controllers/groups_controller.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class GroupsController < BaseController
|
||||
layout 'darkswarm'
|
||||
|
||||
def index
|
||||
@groups = EnterpriseGroup.on_front_page.by_position
|
||||
end
|
||||
end
|
||||
@@ -11,4 +11,4 @@ Spree::Admin::BaseController.class_eval do
|
||||
authorize! :admin, record
|
||||
authorize! action, record
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,4 +4,16 @@ Spree::Admin::OverviewController.class_eval do
|
||||
@product_count = Spree::Product.active.managed_by(spree_current_user).count
|
||||
@order_cycle_count = OrderCycle.active.managed_by(spree_current_user).count
|
||||
end
|
||||
end
|
||||
|
||||
# This is in Spree::Core::ControllerHelpers::Auth
|
||||
# But you can't easily reopen modules in Ruby
|
||||
def unauthorized
|
||||
if try_spree_current_user
|
||||
flash[:error] = t(:authorization_failure)
|
||||
redirect_to '/unauthorized'
|
||||
else
|
||||
store_location
|
||||
redirect_to root_path(anchor: "login?after_login=#{spree.admin_path}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,6 +11,16 @@ Spree::OrdersController.class_eval do
|
||||
include OrderCyclesHelper
|
||||
layout 'darkswarm'
|
||||
|
||||
# Patching to redirect to shop if order is empty
|
||||
def edit
|
||||
@order = current_order(true)
|
||||
if @order.line_items.empty?
|
||||
redirect_to main_app.shop_path
|
||||
else
|
||||
associate_user
|
||||
end
|
||||
end
|
||||
|
||||
# Patch Orders#populate to populate multi_cart (if enabled)
|
||||
def populate
|
||||
if OpenFoodNetwork::FeatureToggle.enabled? :multi_cart
|
||||
@@ -18,6 +28,7 @@ Spree::OrdersController.class_eval do
|
||||
end
|
||||
populator = Spree::OrderPopulator.new(current_order(true), current_currency)
|
||||
if populator.populate(params.slice(:products, :variants, :quantity))
|
||||
|
||||
fire_event('spree.cart.add')
|
||||
fire_event('spree.order.contents_changed')
|
||||
respond_with(@order) do |format|
|
||||
|
||||
7
app/controllers/spree/store_controller_decorator.rb
Normal file
7
app/controllers/spree/store_controller_decorator.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class Spree::StoreController
|
||||
layout 'darkswarm'
|
||||
|
||||
def unauthorized
|
||||
render 'shared/unauthorized', :status => 401
|
||||
end
|
||||
end
|
||||
@@ -2,6 +2,10 @@ module EnterprisesHelper
|
||||
def current_distributor
|
||||
@current_distributor ||= current_order(false).andand.distributor
|
||||
end
|
||||
|
||||
def managed_enterprises
|
||||
Enterprise.managed_by(spree_current_user)
|
||||
end
|
||||
|
||||
def enterprises_options enterprises
|
||||
enterprises.map { |enterprise| [enterprise.name + ": " + enterprise.address.address1 + ", " + enterprise.address.city, enterprise.id.to_i] }
|
||||
|
||||
2
app/helpers/groups_helper.rb
Normal file
2
app/helpers/groups_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module GroupsHelper
|
||||
end
|
||||
@@ -1,4 +1,9 @@
|
||||
module SharedHelper
|
||||
|
||||
def inject_json(name, partial)
|
||||
render "json/injection", name: name, partial: partial
|
||||
end
|
||||
|
||||
def distributor_link_class(distributor)
|
||||
cart = current_order(true)
|
||||
@active_distributors ||= Enterprise.distributors_with_active_order_cycles
|
||||
@@ -9,17 +14,11 @@ module SharedHelper
|
||||
klass
|
||||
end
|
||||
|
||||
# all suppliers of current distributor's products
|
||||
def current_producers
|
||||
if current_distributor && current_order_cycle
|
||||
variants = current_order_cycle.variants_distributed_by(current_distributor)
|
||||
Enterprise.supplying_variant_in(variants)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def enterprise_user?
|
||||
spree_current_user.andand.enterprises.count > 0
|
||||
spree_current_user.andand.enterprises.andand.count.to_i > 0
|
||||
end
|
||||
|
||||
def admin_user?
|
||||
spree_current_user.andand.has_spree_role? 'admin'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,8 +18,8 @@ class Enterprise < ActiveRecord::Base
|
||||
delegate :latitude, :longitude, :city, :state_name, :to => :address
|
||||
|
||||
accepts_nested_attributes_for :address
|
||||
has_attached_file :logo, :styles => { :medium => "300x300>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
|
||||
has_attached_file :promo_image, :styles => { :large => "570x380>", :thumb => "100x100>" }, :default_url => "/images/:style/missing.png"
|
||||
has_attached_file :logo, :styles => { :medium => "300x300>", :thumb => "100x100>" }
|
||||
has_attached_file :promo_image, :styles => { :large => "260x1200#", :thumb => "100x100>" }
|
||||
|
||||
validates_presence_of :name
|
||||
validates_presence_of :address
|
||||
|
||||
@@ -4,6 +4,17 @@ class EnterpriseGroup < ActiveRecord::Base
|
||||
has_and_belongs_to_many :enterprises
|
||||
|
||||
validates :name, presence: true
|
||||
validates :description, presence: true
|
||||
|
||||
attr_accessible :name, :description, :long_description, :on_front_page, :enterprise_ids
|
||||
|
||||
attr_accessible :promo_image
|
||||
has_attached_file :promo_image, styles: {large: "260x1200#"}
|
||||
validates_attachment_content_type :promo_image, :content_type => /\Aimage\/.*\Z/
|
||||
|
||||
attr_accessible :logo
|
||||
has_attached_file :logo, styles: {medium: "100x100"}
|
||||
validates_attachment_content_type :logo, :content_type => /\Aimage\/.*\Z/
|
||||
|
||||
scope :by_position, order('position ASC')
|
||||
scope :on_front_page, where(on_front_page: true)
|
||||
|
||||
@@ -3,5 +3,14 @@ class EnterpriseRelationship < ActiveRecord::Base
|
||||
belongs_to :child, class_name: 'Enterprise'
|
||||
|
||||
validates_presence_of :parent_id, :child_id
|
||||
validates_uniqueness_of :child_id, scope: :parent_id
|
||||
validates_uniqueness_of :child_id, scope: :parent_id, message: "^That relationship is already established."
|
||||
|
||||
scope :with_enterprises,
|
||||
joins('LEFT JOIN enterprises AS parent_enterprises ON parent_enterprises.id = enterprise_relationships.parent_id').
|
||||
joins('LEFT JOIN enterprises AS child_enterprises ON child_enterprises.id = enterprise_relationships.child_id')
|
||||
scope :by_name, with_enterprises.order('parent_enterprises.name, child_enterprises.name')
|
||||
|
||||
scope :involving_enterprises, ->(enterprises) {
|
||||
where('parent_id IN (?) OR child_id IN (?)', enterprises, enterprises)
|
||||
}
|
||||
end
|
||||
|
||||
@@ -54,6 +54,10 @@ class Exchange < ActiveRecord::Base
|
||||
incoming? ? 'supplier' : 'distributor'
|
||||
end
|
||||
|
||||
def participant
|
||||
incoming? ? sender : receiver
|
||||
end
|
||||
|
||||
def to_h(core_only=false)
|
||||
h = attributes.merge({ 'variant_ids' => variant_ids.sort, 'enterprise_fee_ids' => enterprise_fee_ids.sort })
|
||||
h.reject! { |k| %w(id order_cycle_id created_at updated_at).include? k } if core_only
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
class AbilityDecorator
|
||||
include CanCan::Ability
|
||||
|
||||
def initialize(user)
|
||||
if user.enterprises.count > 0
|
||||
|
||||
@@ -53,6 +53,11 @@ class AbilityDecorator
|
||||
(user.enterprises & shipping_method.distributors).any?
|
||||
end
|
||||
|
||||
can [:admin, :index, :create], EnterpriseRelationship
|
||||
can [:destroy], EnterpriseRelationship do |enterprise_relationship|
|
||||
user.enterprises.include? enterprise_relationship.parent
|
||||
end
|
||||
|
||||
can [:create], OrderCycle
|
||||
can [:admin, :index, :read, :edit, :update, :bulk_update, :clone], OrderCycle do |order_cycle|
|
||||
user.enterprises.include? order_cycle.coordinator
|
||||
|
||||
@@ -124,6 +124,10 @@ Spree::Product.class_eval do
|
||||
order_cycle.variants_distributed_by(distributor).where(product_id: self)
|
||||
end
|
||||
|
||||
def primary_taxon
|
||||
self.taxons.order.first
|
||||
end
|
||||
|
||||
# Build a product distribution for each distributor
|
||||
def build_product_distributions_for_user user
|
||||
Enterprise.is_distributor.managed_by(user).each do |distributor|
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/ insert_top "[data-hook='admin_product_form_right']"
|
||||
|
||||
= render 'spree/admin/products/primary_taxon_form', f: f
|
||||
@@ -35,27 +35,24 @@
|
||||
= f.label :product_variant_unit_name, :unit_name
|
||||
%input.fullwidth{ id: 'product_variant_unit_name','ng-model' => 'product.variant_unit_name', :name => 'product[variant_unit_name]', :placeholder => 'eg. bunches', :type => 'text' }
|
||||
.twelve.columns.alpha
|
||||
.three.columns.alpha
|
||||
.six.columns.alpha
|
||||
= render 'spree/admin/products/primary_taxon_form', f: f
|
||||
.three.columns
|
||||
= f.field_container :price do
|
||||
= f.label :price, t(:price)
|
||||
%span.required *
|
||||
%br/
|
||||
= f.text_field :price, class: 'fullwidth'
|
||||
= f.error_message_on :price
|
||||
.three.columns
|
||||
.three.columns.omega
|
||||
= f.field_container :on_hand do
|
||||
= f.label :on_hand, t(:on_hand)
|
||||
%span.required *
|
||||
%br/
|
||||
= f.text_field :on_hand, class: 'fullwidth'
|
||||
= f.error_message_on :on_hand
|
||||
.six.columns.omega
|
||||
= f.field_container :primary_taxon do
|
||||
= f.label :product_category
|
||||
%br/
|
||||
= text_field_tag :primary_taxon, nil, class: 'fullwidth'
|
||||
.twelve.columns.alpha
|
||||
= f.field_container :on_hand do
|
||||
= f.field_container :description do
|
||||
= f.label :product_description, t(:product_description)
|
||||
%br/
|
||||
= f.text_area :description, class: 'fullwidth', rows: 3
|
||||
@@ -82,4 +79,4 @@
|
||||
:javascript
|
||||
angular.element(document.getElementById("new_product")).ready(function() {
|
||||
angular.bootstrap(document.getElementById("new_product"), ['admin.products']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
%br/
|
||||
= f.text_field :name
|
||||
|
||||
= f.field_container :description do
|
||||
= f.label :description
|
||||
%br/
|
||||
= f.text_field :description
|
||||
|
||||
= f.field_container :long_description do
|
||||
= f.label :long_description
|
||||
%br/
|
||||
= f.text_area :long_description
|
||||
|
||||
= f.field_container :on_front_page do
|
||||
= f.label :on_front_page, 'On front page?'
|
||||
%br/
|
||||
@@ -12,3 +22,22 @@
|
||||
= f.label :enterprise_ids, 'Enterprises'
|
||||
%br/
|
||||
= f.collection_select :enterprise_ids, Enterprise.all, :id, :name, {}, {class: "select2 fullwidth", multiple: true}
|
||||
|
||||
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= f.label :logo, class: 'with-tip', 'data-powertip' => 'This is the logo'
|
||||
.with-tip{'data-powertip' => 'This is the logo'}
|
||||
%a What's this?
|
||||
.omega.eight.columns
|
||||
= image_tag @object.logo.url if @object.logo.present?
|
||||
= f.file_field :logo
|
||||
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= f.label :promo_image, class: 'with-tip', 'data-powertip' => 'This image is displayed at the top of the Group profile'
|
||||
.with-tip{'data-powertip' => 'This image is displayed at the top of the Group profile'}
|
||||
%a What's this?
|
||||
.omega.eight.columns
|
||||
= image_tag @object.promo_image.url if @object.promo_image.present?
|
||||
= f.file_field :promo_image
|
||||
|
||||
4
app/views/admin/enterprise_relationships/_data.html.haml
Normal file
4
app/views/admin/enterprise_relationships/_data.html.haml
Normal file
@@ -0,0 +1,4 @@
|
||||
:javascript
|
||||
angular.module('ofn.admin').value('enterprise_relationships', #{render partial: "admin/json/enterprise_relationships", object: @enterprise_relationships});
|
||||
angular.module('ofn.admin').value('my_enterprises', #{render partial: "admin/json/enterprises", object: @my_enterprises});
|
||||
angular.module('ofn.admin').value('all_enterprises', #{render partial: "admin/json/enterprises", object: @all_enterprises});
|
||||
@@ -0,0 +1,5 @@
|
||||
%td {{ enterprise_relationship.parent_name }}
|
||||
%td permits
|
||||
%td {{ enterprise_relationship.child_name }}
|
||||
%td.actions
|
||||
%a.delete-enterprise-relationship.icon-trash.no-text{'ng-click' => 'delete(enterprise_relationship)'}
|
||||
9
app/views/admin/enterprise_relationships/_form.html.haml
Normal file
9
app/views/admin/enterprise_relationships/_form.html.haml
Normal file
@@ -0,0 +1,9 @@
|
||||
%tr
|
||||
%td
|
||||
%select{name: "enterprise_relationship_parent_id", "ng-model" => "parent_id", "ng-options" => "e.id as e.name for e in Enterprises.my_enterprises"}
|
||||
%td permits
|
||||
%td
|
||||
%select{name: "enterprise_relationship_child_id", "ng-model" => "child_id", "ng-options" => "e.id as e.name for e in Enterprises.all_enterprises"}
|
||||
%td.actions
|
||||
%input{type: "button", value: "Create", "ng-click" => "create()"}
|
||||
.errors {{ EnterpriseRelationships.create_errors }}
|
||||
15
app/views/admin/enterprise_relationships/index.html.haml
Normal file
15
app/views/admin/enterprise_relationships/index.html.haml
Normal file
@@ -0,0 +1,15 @@
|
||||
- content_for :page_title do
|
||||
Enterprise Relationships
|
||||
|
||||
= render 'admin/shared/enterprises_sub_menu'
|
||||
|
||||
%div{"ng-app" => "ofn.admin", "ng-controller" => "AdminEnterpriseRelationshipsCtrl"}
|
||||
= render 'data'
|
||||
|
||||
%input.search{"ng-model" => "query", "placeholder" => "Search"}
|
||||
|
||||
%table#enterprise-relationships
|
||||
%tbody
|
||||
= render 'form'
|
||||
%tr{"ng-repeat" => "enterprise_relationship in EnterpriseRelationships.enterprise_relationships | filter:query"}
|
||||
= render 'enterprise_relationship'
|
||||
@@ -116,12 +116,21 @@
|
||||
= f.label :website
|
||||
.omega.eight.columns
|
||||
= f.text_field :website, { placeholder: "eg. www.truffles.com"}
|
||||
-# TODO: Facebook model field
|
||||
-#.row
|
||||
-# .alpha.two.columns
|
||||
-# = f.label :facebook, 'Facebook'
|
||||
-# .omega.four.columns
|
||||
-# = f.text_field :facebook
|
||||
.row
|
||||
.alpha.two.columns
|
||||
= f.label :facebook, 'Facebook'
|
||||
.omega.four.columns
|
||||
= f.text_field :facebook
|
||||
.row
|
||||
.alpha.two.columns
|
||||
= f.label :instagram, 'Instagram'
|
||||
.omega.four.columns
|
||||
= f.text_field :instagram
|
||||
.row
|
||||
.alpha.two.columns
|
||||
= f.label :linkedin, 'LinkedIn'
|
||||
.omega.four.columns
|
||||
= f.text_field :linkedin
|
||||
.row
|
||||
.alpha.three.columns
|
||||
= f.label :twitter
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<%= render 'admin/shared/enterprises_sub_menu' %>
|
||||
|
||||
|
||||
<%= form_for @enterprise_set, :url => main_app.bulk_update_admin_enterprises_path do |f| %>
|
||||
<table class="index" id="listing_enterprises">
|
||||
|
||||
14
app/views/admin/enterprises/index.rabl
Normal file
14
app/views/admin/enterprises/index.rabl
Normal file
@@ -0,0 +1,14 @@
|
||||
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 }
|
||||
end
|
||||
end
|
||||
@@ -1,15 +0,0 @@
|
||||
r.list_of :enterprises, @collection do
|
||||
r.element :id
|
||||
r.element :name
|
||||
|
||||
r.list_of :supplied_products do |product|
|
||||
r.element :name
|
||||
r.element :supplier_name, product.supplier.andand.name
|
||||
r.element :image_url, product.images.present? ? product.images.first.attachment.url(:mini) : nil
|
||||
r.element :master_id, product.master.id
|
||||
r.list_of :variants do |variant|
|
||||
r.element :id
|
||||
r.element :label, variant.options_text
|
||||
end
|
||||
end
|
||||
end
|
||||
11
app/views/admin/json/_enterprise_relationship.rabl
Normal file
11
app/views/admin/json/_enterprise_relationship.rabl
Normal file
@@ -0,0 +1,11 @@
|
||||
object @enterprise_relationship
|
||||
|
||||
attributes :id, :parent_id, :child_id
|
||||
|
||||
node :parent_name do |enterprise_relationship|
|
||||
enterprise_relationship.parent.name
|
||||
end
|
||||
|
||||
node :child_name do |enterprise_relationship|
|
||||
enterprise_relationship.child.name
|
||||
end
|
||||
2
app/views/admin/json/_enterprise_relationships.rabl
Normal file
2
app/views/admin/json/_enterprise_relationships.rabl
Normal file
@@ -0,0 +1,2 @@
|
||||
collection @enterprise_relationships
|
||||
extends "admin/json/enterprise_relationship"
|
||||
3
app/views/admin/json/_enterprises.rabl
Normal file
3
app/views/admin/json/_enterprises.rabl
Normal file
@@ -0,0 +1,3 @@
|
||||
collection @enterprises
|
||||
|
||||
attributes :id, :name
|
||||
@@ -1,7 +1,7 @@
|
||||
%td{:colspan => 3}
|
||||
.exchange-select-all-variants
|
||||
%label
|
||||
= check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants', 1, 1, 'ng-model' => 'exchange.select_all_variants', 'ng-click' => 'setExchangeVariants(exchange, incomingExchangesVariants(), exchange.select_all_variants)', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants'
|
||||
= check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants', 1, 1, 'ng-model' => 'exchange.select_all_variants', 'ng-change' => 'setExchangeVariants(exchange, incomingExchangesVariants(), exchange.select_all_variants)', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants'
|
||||
Select all
|
||||
|
||||
.exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle'}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%td{:colspan => 3}
|
||||
.exchange-select-all-variants
|
||||
%label
|
||||
= check_box_tag 'order_cycle_incoming_exchange_{{ $parent.$index }}_select_all_variants', 1, 1, 'ng-model' => 'exchange.select_all_variants', 'ng-click' => 'setExchangeVariants(exchange, suppliedVariants(exchange.enterprise_id), exchange.select_all_variants)', 'id' => 'order_cycle_incoming_exchange_{{ $parent.$index }}_select_all_variants'
|
||||
= check_box_tag 'order_cycle_incoming_exchange_{{ $parent.$index }}_select_all_variants', 1, 1, 'ng-model' => 'exchange.select_all_variants', 'ng-change' => 'setExchangeVariants(exchange, suppliedVariants(exchange.enterprise_id), exchange.select_all_variants)', 'id' => 'order_cycle_incoming_exchange_{{ $parent.$index }}_select_all_variants'
|
||||
Select all
|
||||
|
||||
.exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products'}
|
||||
|
||||
4
app/views/admin/shared/_enterprises_sub_menu.html.haml
Normal file
4
app/views/admin/shared/_enterprises_sub_menu.html.haml
Normal file
@@ -0,0 +1,4 @@
|
||||
= content_for :sub_menu do
|
||||
%ul#sub_nav.inline-menu{"data-hook" => "admin_order_sub_tabs"}
|
||||
= tab :enterprises, url: main_app.admin_enterprises_path
|
||||
= tab :relationships, url: main_app.admin_enterprise_relationships_path, match_path: '/enterprise_relationships'
|
||||
@@ -18,8 +18,8 @@ child current_order.ship_address => :ship_address do
|
||||
end
|
||||
|
||||
node :shipping_methods do
|
||||
Hash[current_order.distributor.shipping_methods.collect {
|
||||
|method| [method.id, {
|
||||
Hash[current_order.available_shipping_methods("front_end").collect { |method|
|
||||
[method.id, {
|
||||
require_ship_address: method.require_ship_address,
|
||||
price: method.compute_amount(current_order).to_f,
|
||||
name: method.name
|
||||
|
||||
44
app/views/groups/index.html.haml
Normal file
44
app/views/groups/index.html.haml
Normal file
@@ -0,0 +1,44 @@
|
||||
#groups{"ng-controller" => "GroupsCtrl"}
|
||||
:javascript
|
||||
angular.module('Darkswarm').value('groups', #{render partial: "json/groups", object: @groups})
|
||||
.row.pad-top
|
||||
.small-12.columns.text-center
|
||||
%h1 Groups / Regions
|
||||
%div
|
||||
Check out our
|
||||
%ofn-modal{title: "food groups"}
|
||||
= render partial: "modals/groups"
|
||||
below
|
||||
%p
|
||||
|
||||
%input{type: :text,
|
||||
"ng-model" => "query",
|
||||
placeholder: "Search group name",
|
||||
"ng-debounce" => "150",
|
||||
"ofn-disable-enter" => true}
|
||||
|
||||
.group{"ng-repeat" => "group in Groups.groups | filter:query | orderBy:order",
|
||||
name: "group{{group.id}}",
|
||||
id: "group{{group.id}}"}
|
||||
.row.pad-top{bindonce: true}
|
||||
.small-12.columns
|
||||
.group-hero
|
||||
%img.group-hero-img{"bo-src" => "group.promo_image"}
|
||||
%img.group-logo{"bo-src" => "group.logo", "bo-if" => "group.logo"}
|
||||
%h3.group-name {{ group.name }}
|
||||
%h5.group-description {{ group.description }}
|
||||
|
||||
.row.pad-top{bindonce: true}
|
||||
.small-6.columns
|
||||
%p {{ group.long_description }}
|
||||
.small-6.columns
|
||||
%h5 Our hubs & producers
|
||||
%ul.small-block-grid-2
|
||||
%li{"ng-repeat" => "enterprise in group.enterprises"}
|
||||
%a{"bo-href" => "enterprise.path"} {{ enterprise.name }}
|
||||
|
||||
.row.group_footer
|
||||
.small-12.columns
|
||||
%hr
|
||||
|
||||
= render partial: "shared/footer"
|
||||
@@ -2,7 +2,8 @@
|
||||
.columns.small-4
|
||||
%strong Shop for
|
||||
%p.trans-sentence
|
||||
{{ hub.taxons | printArrayOfObjects }}
|
||||
%img{"ng-repeat" => "taxon in hub.taxons", "bo-src" => "taxon.icon",
|
||||
name: "{{taxon.name}}", alt: "{{taxon.name}}"}
|
||||
.columns.small-4
|
||||
%strong Delivery options
|
||||
%ol
|
||||
@@ -10,8 +11,9 @@
|
||||
%li.delivery{"bo-if" => "hub.delivery"} Delivery
|
||||
.columns.small-4
|
||||
%strong Our producers
|
||||
%p
|
||||
Go to our shop to see our current producers
|
||||
%ul
|
||||
%li{"ng-repeat" => "producer in hub.producers"}
|
||||
= render partial: "modals/producer"
|
||||
|
||||
.row.active_table_row.link{"ng-show" => "open()", "ng-if" => "hub.active"}
|
||||
.columns.small-11
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
%h2 Groups / Regions
|
||||
%h5 See all the groups & regions on the Open Food Network
|
||||
%p
|
||||
%button.neutral-btn.light
|
||||
%a.neutral-btn.light{href: "/groups"}
|
||||
%i.fi-torsos-all
|
||||
View groups & regions
|
||||
@@ -5,6 +5,6 @@
|
||||
%h2 Producers
|
||||
%h5 Looking for a specific producer or farmer?
|
||||
%p
|
||||
%button.neutral-btn.turquoise
|
||||
%a.neutral-btn.turquoise{href: "/producers"}
|
||||
%i.fi-trees
|
||||
View all producers
|
||||
@@ -1,2 +1,8 @@
|
||||
object current_distributor
|
||||
attributes :name, :id
|
||||
|
||||
if current_distributor
|
||||
child suppliers: :producers do
|
||||
extends "json/producer"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,3 +4,11 @@ attributes :name, :id, :description
|
||||
child :address do
|
||||
extends "json/partials/address"
|
||||
end
|
||||
|
||||
node :path do |enterprise|
|
||||
shop_enterprise_path(enterprise)
|
||||
end
|
||||
|
||||
node :hash do |enterprise|
|
||||
enterprise.to_param
|
||||
end
|
||||
|
||||
14
app/views/json/_groups.rabl
Normal file
14
app/views/json/_groups.rabl
Normal file
@@ -0,0 +1,14 @@
|
||||
collection @groups
|
||||
attributes :id, :name, :position, :description, :long_description
|
||||
|
||||
child enterprises: :enterprises do
|
||||
extends 'json/enterprises'
|
||||
end
|
||||
|
||||
node :logo do |group|
|
||||
group.logo(:medium) if group.logo.exists?
|
||||
end
|
||||
|
||||
node :promo_image do |group|
|
||||
group.promo_image(:large) if group.promo_image.exists?
|
||||
end
|
||||
@@ -2,11 +2,11 @@ collection Enterprise.visible.is_distributor
|
||||
extends 'json/enterprises'
|
||||
|
||||
child distributed_taxons: :taxons do
|
||||
attributes :name, :id
|
||||
extends "json/taxon"
|
||||
end
|
||||
|
||||
child suppliers: :producers do
|
||||
attributes :name, :id
|
||||
extends "json/producer"
|
||||
end
|
||||
|
||||
node :pickup do |hub|
|
||||
@@ -17,14 +17,6 @@ node :delivery do |hub|
|
||||
not hub.shipping_methods.where(:require_ship_address => true).empty?
|
||||
end
|
||||
|
||||
node :path do |hub|
|
||||
shop_enterprise_path(hub)
|
||||
end
|
||||
|
||||
node :hash do |hub|
|
||||
hub.to_param
|
||||
end
|
||||
|
||||
node :active do |hub|
|
||||
@active_distributors.include?(hub)
|
||||
end
|
||||
|
||||
2
app/views/json/_injection.html.haml
Normal file
2
app/views/json/_injection.html.haml
Normal file
@@ -0,0 +1,2 @@
|
||||
:javascript
|
||||
angular.module('Darkswarm').value("#{name.to_s}", #{render "json/#{partial.to_s}"})
|
||||
5
app/views/json/_producer.rabl
Normal file
5
app/views/json/_producer.rabl
Normal file
@@ -0,0 +1,5 @@
|
||||
attributes :name, :id, :description, :long_description
|
||||
|
||||
node :promo_image do |producer|
|
||||
producer.promo_image.url
|
||||
end
|
||||
@@ -2,7 +2,7 @@ collection @producers
|
||||
extends 'json/enterprises'
|
||||
|
||||
child supplied_taxons: :taxons do
|
||||
attributes :name, :id
|
||||
extends 'json/taxon'
|
||||
end
|
||||
|
||||
child distributors: :distributors do
|
||||
|
||||
5
app/views/json/_taxon.rabl
Normal file
5
app/views/json/_taxon.rabl
Normal file
@@ -0,0 +1,5 @@
|
||||
attributes :name, :id, :permalink
|
||||
|
||||
node :icon do |taxon|
|
||||
taxon.icon.url
|
||||
end
|
||||
@@ -15,16 +15,16 @@
|
||||
= csrf_meta_tags
|
||||
|
||||
%body.off-canvas{"ng-app" => "Darkswarm"}
|
||||
= inject_json "currentHub", "current_hub"
|
||||
= inject_json "user", "current_user"
|
||||
|
||||
.off-canvas-wrap{offcanvas: true}
|
||||
.inner-wrap
|
||||
= render partial: "shared/current_hub"
|
||||
= render partial: "shared/current_user"
|
||||
= render partial: "shared/menu/menu"
|
||||
|
||||
= display_flash_messages
|
||||
%ofn-flash
|
||||
|
||||
-#= render "shared/sidebar"
|
||||
|
||||
%section{ role: "main" }
|
||||
= yield
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
%h5 Our food hubs are the point of contact between you and the people who make your food!
|
||||
%p You can search for a convenient hub by location or name. Some hubs have multiple points where you can pick-up your purchases, and some will also provide delivery options. Each food hub is a sales point with independent business operations and logisitics - so variations between hubs are to be expected.
|
||||
%p You can only shop one food hub at a time.
|
||||
%a.close-reveal-modal{"ng-click" => "cancel()"} ×
|
||||
%a.close-reveal-modal{"ng-click" => "$close()"} ×
|
||||
|
||||
4
app/views/modals/_groups.html.haml
Normal file
4
app/views/modals/_groups.html.haml
Normal file
@@ -0,0 +1,4 @@
|
||||
%h2 Groups / Regions
|
||||
%p These are the organisations and relationships between hubs which make up the Open Food Network.
|
||||
%p Some groups are clustered by location or council, others by non-geographic similarities.
|
||||
%a.close-reveal-modal{"ng-click" => "cancel()"} ×
|
||||
@@ -6,4 +6,4 @@
|
||||
%h5 Learn more
|
||||
%p If you want to learn more about the Open Food Network, how it works, and get involved, check out:
|
||||
%a.button.neutral-btn.dark{:href => "http://www.openfoodnetwork.org" , :target => "_blank" } Open Food Network
|
||||
%a.close-reveal-modal{"ng-click" => "cancel()"} ×
|
||||
%a.close-reveal-modal{"ng-click" => "$close()"} ×
|
||||
|
||||
39
app/views/modals/_producer.html.haml
Normal file
39
app/views/modals/_producer.html.haml
Normal file
@@ -0,0 +1,39 @@
|
||||
%ofn-modal{title: "{{producer.name}}"}
|
||||
.row
|
||||
.columns.small-12.producer-hero
|
||||
%img.producer-hero-img{"ng-src" => "{{producer.promo_image}}"}
|
||||
/ Will - scale large images down to 1200px wide, crop in to img aspect ratio 60W:13H
|
||||
%h3.producer-name {{ producer.name }}
|
||||
.row
|
||||
.columns.small-12.large-6{"ng-bind-html" => "producer.long_description"}
|
||||
.columns.small-12.large-6
|
||||
%img.producer-logo{"ng-src" => "{{producer.logo}}", "ng-if" => "producer.logo"}
|
||||
%h4 Stay in touch with {{ producer.name }}
|
||||
%ul.small-block-grid-1{bindonce: true}
|
||||
%li{"ng-if" => "producer.website"}
|
||||
%a{"ng-href" => "http://{{producer.website | stripUrl}}", target: "_blank" }
|
||||
%i.fi-web
|
||||
{{ producer.website | stripUrl }}
|
||||
|
||||
%li{"ng-if" => "producer.twitter"}
|
||||
%a{"ng-href" => "http://twitter.com/{{producer.twitter}}", target: "_blank"}
|
||||
%i.fi-social-twitter
|
||||
{{ producer.twitter }}
|
||||
|
||||
%li{"ng-if" => "producer.facebook"}
|
||||
%a{"ng-href" => "http://{{producer.facebook | stripUrl}}", target: "_blank"}
|
||||
%i.fi-social-facebook
|
||||
{{ producer.facebook | stripUrl }}
|
||||
|
||||
%li{"ng-if" => "producer.linkedin"}
|
||||
%a{"ng-href" => "http://{{producer.linkedin | stripUrl}}", target: "_blank"}
|
||||
%i.fi-social-linkedin
|
||||
{{ producer.linkedin | stripUrl }}
|
||||
|
||||
%li{"ng-if" => "producer.instagram"}
|
||||
%a{"ng-href" => "http://instagram.com/{{producer.instagram}}", target: "_blank"}
|
||||
%i.fi-social-instagram
|
||||
{{ producer.instagram }}
|
||||
|
||||
|
||||
%a.close-reveal-modal{"ng-click" => "$close()"} ×
|
||||
10
app/views/modals/_product.html.haml
Normal file
10
app/views/modals/_product.html.haml
Normal file
@@ -0,0 +1,10 @@
|
||||
%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
|
||||
%h2
|
||||
%img{"ng-src" => "{{product.primary_taxon.icon}}"}
|
||||
{{product.name}}
|
||||
%p {{product.description}}
|
||||
%a.close-reveal-modal{"ng-click" => "$close()"} ×
|
||||
@@ -5,7 +5,7 @@
|
||||
{{ producer.taxons | printArrayOfObjects }}
|
||||
.columns.small-8
|
||||
%strong About us
|
||||
%p.trans-sentence
|
||||
%p
|
||||
{{ producer.description }}
|
||||
|
||||
.row.active_table_row.link{"ng-show" => "open()", "ng-repeat" => "hub in producer.distributors"}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
:javascript
|
||||
angular.module('Darkswarm').value('currentHub', #{render "json/current_hub"})
|
||||
@@ -1,2 +0,0 @@
|
||||
:javascript
|
||||
angular.module('Darkswarm').value('user', #{render "json/current_user"})
|
||||
@@ -21,10 +21,10 @@
|
||||
%span.nav-primary Producers
|
||||
%li.divider
|
||||
%li
|
||||
%a{href: ""}
|
||||
%a{href: main_app.groups_path}
|
||||
%span.nav-primary Groups
|
||||
%li.divider
|
||||
- if spree_current_user.andand.has_spree_role? 'admin'
|
||||
- if admin_user? or enterprise_user?
|
||||
%li
|
||||
%a{href: spree.admin_path}
|
||||
%span.nav-primary Admin
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
%ul.off-canvas-list
|
||||
%li= link_to image_tag("ofn_logo_small.png"), root_path
|
||||
|
||||
- if spree_current_user.andand.has_spree_role? 'admin'
|
||||
- if admin_user? or enterprise_user?
|
||||
%li
|
||||
%a{href: spree.admin_path}
|
||||
%span.nav-primary Admin
|
||||
@@ -36,17 +36,14 @@
|
||||
%li
|
||||
%a{href: root_path + "#/#hubs"}
|
||||
%span.nav-primary Hubs
|
||||
|
||||
%li
|
||||
%a{href: ""}
|
||||
%span.nav-primary Map
|
||||
|
||||
%li
|
||||
%a{href: main_app.producers_path}
|
||||
%span.nav-primary Producers
|
||||
|
||||
%li
|
||||
%a{href: ""}
|
||||
%a{href: main_app.groups_path}
|
||||
%span.nav-primary Groups
|
||||
|
||||
|
||||
|
||||
1
app/views/shared/unauthorized.html.haml
Normal file
1
app/views/shared/unauthorized.html.haml
Normal file
@@ -0,0 +1 @@
|
||||
Unauthorized
|
||||
@@ -1,79 +0,0 @@
|
||||
%products{"ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id != null",
|
||||
"infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1"}
|
||||
|
||||
= form_for :order, :url => populate_orders_path, html: {:class => "custom"} do
|
||||
|
||||
%input#search.text{"ng-model" => "query",
|
||||
placeholder: "Search",
|
||||
"ng-debounce" => "150",
|
||||
"ng-keypress" => "searchKeypress($event)"}
|
||||
%input.button.right{type: :submit, value: "Add to Cart"}
|
||||
|
||||
%table
|
||||
%thead
|
||||
%th.name Item
|
||||
%th.notes Notes
|
||||
%th.variant Unit
|
||||
%th.quantity QTY
|
||||
%th.bulk Bulk
|
||||
%th.price.text-right Price
|
||||
%tbody{"ng-show" => "data.loading"}
|
||||
%tr
|
||||
%td{colspan: 6}
|
||||
%h3.text-center Loading Products
|
||||
%tbody{"ng-repeat" => "product in data.products | filter:query | limitTo: limit track by product.id"}
|
||||
%tr{"class" => "product product-{{ product.id }}"}
|
||||
|
||||
%td.name{bindonce: "product"}
|
||||
%img{"bo-src" => "product.master.images[0].small_url"}
|
||||
%div
|
||||
%h5
|
||||
{{ product.name }}
|
||||
%a{"data-reveal-id" => "producer_details_{{product.supplier.id}}", "data-reveal" => ""}
|
||||
{{ product.supplier.name }}
|
||||
|
||||
%td.notes{bindonce: ""} {{ product.notes | truncate:80 }}
|
||||
|
||||
%td{bindonce: ""}
|
||||
%span{"ng-hide" => "product.variants.length > 0"} {{ product.master.options_text }}
|
||||
%span{"ng-show" => "product.variants.length > 0"}
|
||||
%img.collapse{src: "/assets/collapse.png",
|
||||
"ng-show" => "product.show_variants",
|
||||
"ng-click" => "product.show_variants = !product.show_variants"}
|
||||
|
||||
%img.expand{src: "/assets/expand.png",
|
||||
"ng-show" => "!product.show_variants",
|
||||
"ng-click" => "product.show_variants = !product.show_variants"}
|
||||
%td
|
||||
%span{"ng-show" => "(product.variants.length == 0)"}
|
||||
%input{type: :number,
|
||||
value: nil,
|
||||
min: 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"}
|
||||
|
||||
%td.group_buy
|
||||
%span{"ng-show" => "product.group_buy && (product.variants.length == 0)"}
|
||||
%input{type: :number,
|
||||
min: 0,
|
||||
"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"}
|
||||
|
||||
%td.price.text-right{bindonce: ""}
|
||||
%small{"ng-show" => "(product.variants.length > 0)"} from
|
||||
{{ productPrice(product) | currency }}
|
||||
|
||||
%tr.product-description{bindonce: ""}
|
||||
%td{colspan: 2}{{ product.notes | truncate:80 }}
|
||||
|
||||
%tr.variant{"ng-repeat" => "variant in product.variants", "ng-if" => "product.show_variants"}
|
||||
= render partial: "shop/variant"
|
||||
|
||||
%input.button.right{type: :submit, value: "Add to Cart"}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
%td
|
||||
%td.notes
|
||||
%td{bindonce: ""} {{variant.options_text}}
|
||||
%td
|
||||
%input{type: :number,
|
||||
value: nil,
|
||||
min: 0,
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}",
|
||||
"ng-model" => "variant.quantity"}
|
||||
%td.group_buy
|
||||
%span{"ng-show" => "product.group_buy"}
|
||||
%input{type: :number,
|
||||
min: 0,
|
||||
"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"}
|
||||
%td.price.text-right{bindonce: ""}
|
||||
{{ variant.price | currency }}
|
||||
@@ -1,10 +1,6 @@
|
||||
collection @products
|
||||
attributes :id, :name, :permalink, :count_on_hand, :on_demand, :group_buy
|
||||
|
||||
node :show_variants do
|
||||
true
|
||||
end
|
||||
|
||||
node do |product|
|
||||
{
|
||||
notes: strip_tags(product.notes),
|
||||
@@ -14,7 +10,18 @@ node do |product|
|
||||
end
|
||||
|
||||
child :supplier => :supplier do
|
||||
attributes :id, :name, :description
|
||||
attributes :id, :name, :description, :long_description, :website, :instagram, :facebook, :linkedin, :twitter
|
||||
|
||||
node :logo do |supplier|
|
||||
supplier.logo(:medium) if supplier.logo.exists?
|
||||
end
|
||||
node :promo_image do |supplier|
|
||||
supplier.promo_image(:large) if supplier.promo_image.exists?
|
||||
end
|
||||
end
|
||||
|
||||
child :primary_taxon => :primary_taxon do
|
||||
extends 'json/taxon'
|
||||
end
|
||||
|
||||
child :master => :master do
|
||||
@@ -22,7 +29,8 @@ child :master => :master do
|
||||
child :images => :images do
|
||||
attributes :id, :alt
|
||||
node do |img|
|
||||
{:small_url => img.attachment.url(:small, false)}
|
||||
{:small_url => img.attachment.url(:small, false),
|
||||
:large_url => img.attachment.url(:large, false)}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user