Merge in master

This commit is contained in:
Rob H
2014-05-30 15:10:13 +10:00
165 changed files with 3204 additions and 655 deletions

View File

@@ -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'

View File

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

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 153 KiB

View File

@@ -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, */*"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
Darkswarm.controller "GroupsCtrl", ($scope, Groups, $anchorScroll, $rootScope) ->
$scope.Groups = Groups
$scope.order = 'position'
$rootScope.$on "$locationChangeSuccess", (newRoute, oldRoute) ->
$anchorScroll()

View File

@@ -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")

View File

@@ -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)

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
Darkswarm.controller "ProducersTabCtrl", ($scope, CurrentHub) ->
$scope.CurrentHub = CurrentHub

View File

@@ -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')

View File

@@ -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)

View File

@@ -0,0 +1,6 @@
Darkswarm.filter "stripUrl", ->
stripper = /(https?:\/\/)?(www\.)?(.*)/
(url) ->
url.match(stripper).pop()

View File

@@ -1,4 +1,4 @@
Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location)->
Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redirections)->
new class AuthenticationService
selectedPath: "/login"

View File

@@ -0,0 +1,4 @@
Darkswarm.factory 'Groups', (groups) ->
new class Groups
constructor: ->
@groups = groups

View File

@@ -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: {}

View File

@@ -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()

View File

@@ -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()

View File

@@ -0,0 +1,3 @@
Darkswarm.factory "Redirections", ($location)->
new class Redirections
after_login: $location.search().after_login

View File

@@ -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" +
"");

View File

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

View File

@@ -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 {

View File

@@ -11,5 +11,4 @@
ofn-modal {
display: block;
}
}

View File

@@ -0,0 +1,5 @@
@import mixins
@import branding
fieldset
border: 0

View 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

View File

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

View File

@@ -0,0 +1,4 @@
.product_table
.row
border: 1px solid black
padding: 8px inherit

View File

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

View File

@@ -0,0 +1,6 @@
@import mixins
@import branding
#edit-cart
button, .button
margin: 0

View File

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

View 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/

View File

@@ -15,12 +15,10 @@ module Admin
redirect_to main_app.admin_enterprise_groups_path
end
private
def collection
EnterpriseGroup.by_position
end
end
end

View 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

View File

@@ -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 }

View File

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

View File

@@ -0,0 +1,7 @@
class GroupsController < BaseController
layout 'darkswarm'
def index
@groups = EnterpriseGroup.on_front_page.by_position
end
end

View File

@@ -11,4 +11,4 @@ Spree::Admin::BaseController.class_eval do
authorize! :admin, record
authorize! action, record
end
end
end

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
class Spree::StoreController
layout 'darkswarm'
def unauthorized
render 'shared/unauthorized', :status => 401
end
end

View File

@@ -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] }

View File

@@ -0,0 +1,2 @@
module GroupsHelper
end

View File

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

View File

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

View File

@@ -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)

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
/ insert_top "[data-hook='admin_product_form_right']"
= render 'spree/admin/products/primary_taxon_form', f: f

View File

@@ -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']);
});
});

View File

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

View 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});

View File

@@ -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)'}

View 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 }}

View 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'

View File

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

View File

@@ -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">

View 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

View File

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

View 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

View File

@@ -0,0 +1,2 @@
collection @enterprise_relationships
extends "admin/json/enterprise_relationship"

View File

@@ -0,0 +1,3 @@
collection @enterprises
attributes :id, :name

View File

@@ -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'}

View File

@@ -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'}

View 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'

View File

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

View 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"

View File

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

View File

@@ -5,6 +5,6 @@
%h2 Groups / Regions
%h5 See all the groups &amp; regions on the Open Food Network
%p
%button.neutral-btn.light
%a.neutral-btn.light{href: "/groups"}
%i.fi-torsos-all
View groups &amp; regions

View File

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

View File

@@ -1,2 +1,8 @@
object current_distributor
attributes :name, :id
if current_distributor
child suppliers: :producers do
extends "json/producer"
end
end

View File

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

View 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

View File

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

View File

@@ -0,0 +1,2 @@
:javascript
angular.module('Darkswarm').value("#{name.to_s}", #{render "json/#{partial.to_s}"})

View File

@@ -0,0 +1,5 @@
attributes :name, :id, :description, :long_description
node :promo_image do |producer|
producer.promo_image.url
end

View File

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

View File

@@ -0,0 +1,5 @@
attributes :name, :id, :permalink
node :icon do |taxon|
taxon.icon.url
end

View File

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

View File

@@ -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()"} &#215;
%a.close-reveal-modal{"ng-click" => "$close()"} &#215;

View 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()"} &#215;

View File

@@ -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()"} &#215;
%a.close-reveal-modal{"ng-click" => "$close()"} &#215;

View 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()"} &#215;

View 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()"} &#215;

View File

@@ -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"}

View File

@@ -1,2 +0,0 @@
:javascript
angular.module('Darkswarm').value('currentHub', #{render "json/current_hub"})

View File

@@ -1,2 +0,0 @@
:javascript
angular.module('Darkswarm').value('user', #{render "json/current_user"})

View File

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

View File

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

View File

@@ -0,0 +1 @@
Unauthorized

View File

@@ -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"}

View File

@@ -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 }}

View File

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