Auto-merged master into uk/trial-length on deployment.

This commit is contained in:
Maikel
2016-05-25 18:56:17 +10:00
260 changed files with 3296 additions and 2029 deletions

1
.gitignore vendored
View File

@@ -39,3 +39,4 @@ NERD_tree*
coverage
libpeerconnection.log
/config/application.yml
node_modules

View File

@@ -26,13 +26,16 @@ before_script:
- cp config/database.travis.yml config/database.yml
- cp config/application.yml.example config/application.yml
- RAILS_ENV=test bundle exec rake db:create db:schema:load
# Only install PhantomJS if it is not already present (ie. cached)
- npm list -g phantomjs-prebuilt@~2.1.7 --depth=0 || npm install -g phantomjs-prebuilt@~2.1.7
- export PATH=`npm bin -g`:$PATH
- >
if [ "$KARMA" = "true" ]; then
npm install karma@0.12.31
npm install karma-jasmine@0.1.5
npm install karma-phantomjs-launcher@0.1.4
npm install karma-coffee-preprocessor@0.2.1
npm install -g karma-cli@0.0.4
npm install -g npm@'3.8.8'
npm install
npm install -g karma-cli@0.1.2
fi
script:

View File

@@ -28,7 +28,7 @@ gem 'comfortable_mexican_sofa'
gem 'simple_form', :github => 'RohanM/simple_form'
gem 'unicorn'
gem 'angularjs-rails', '1.2.13'
gem 'angularjs-rails', '1.5.5'
gem 'bugsnag'
gem 'newrelic_rpm'
gem 'haml'

View File

@@ -153,7 +153,7 @@ GEM
sprockets (~> 2)
tilt
angularjs-file-upload-rails (1.1.0)
angularjs-rails (1.2.13)
angularjs-rails (1.5.5)
ansi (1.4.2)
arel (3.0.3)
atomic (1.1.99)
@@ -176,7 +176,8 @@ GEM
columnize (~> 0.3)
debugger-linecache (~> 1.2)
cancan (1.6.8)
capybara (2.5.0)
capybara (2.7.1)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
@@ -455,7 +456,7 @@ GEM
railties (>= 3.1)
money (5.1.1)
i18n (~> 0.6.0)
multi_json (1.11.2)
multi_json (1.12.0)
multi_xml (0.5.5)
newrelic_rpm (3.12.0.288)
nokogiri (1.6.7.2)
@@ -479,7 +480,7 @@ GEM
paypal-sdk-merchant (1.106.1)
paypal-sdk-core (~> 0.2.3)
pg (0.13.2)
poltergeist (1.7.0)
poltergeist (1.9.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
@@ -626,7 +627,7 @@ GEM
webmock (1.13.0)
addressable (>= 2.2.7)
crack (>= 0.3.2)
websocket-driver (0.6.2)
websocket-driver (0.6.3)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
whenever (0.9.2)
@@ -650,7 +651,7 @@ DEPENDENCIES
andand
angular-rails-templates (~> 0.2.0)
angularjs-file-upload-rails (~> 1.1.0)
angularjs-rails (= 1.2.13)
angularjs-rails (= 1.5.5)
atomic
awesome_print
aws-sdk
@@ -734,4 +735,4 @@ DEPENDENCIES
wkhtmltopdf-binary
BUNDLED WITH
1.10.6
1.11.2

View File

@@ -43,9 +43,9 @@
//= require ./utils/utils
//= require ./users/users
//= require ./variant_overrides/variant_overrides
//= require textAngular.min.js
//= require textAngular-rangy.min.js
//= require textAngular-sanitize.min.js
//= require ../shared/bindonce.min.js
//= require textAngular.min.js
//= require darkswarm/i18n.js
//= require darkswarm/i18n.translate.js

View File

@@ -1,222 +0,0 @@
angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [
"$scope", "$http", "$filter", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey", "Columns"
($scope, $http, $filter, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey, Columns) ->
$scope.loading = true
$scope.initialiseVariables = ->
start = daysFromToday -7
end = daysFromToday 1
$scope.lineItems = []
$scope.filteredLineItems = []
$scope.confirmDelete = true
$scope.startDate = formatDate start
$scope.endDate = formatDate end
$scope.quickSearch = ""
$scope.bulkActions = [ { name: t("bom_actions_delete"), callback: $scope.deleteLineItems } ]
$scope.selectedBulkAction = $scope.bulkActions[0]
$scope.selectedUnitsProduct = {};
$scope.selectedUnitsVariant = {};
$scope.sharedResource = false
$scope.columns = Columns.setColumns
order_no: { name: t("bom_no"), visible: false }
full_name: { name: t("name"), visible: true }
email: { name: t("email"), visible: false }
phone: { name: t("phone"), visible: false }
order_date: { name: t("bom_date"), visible: true }
producer: { name: t("producer"), visible: true }
order_cycle: { name: t("bom_cycle"), visible: false }
hub: { name: t("bom_hub"), visible: false }
variant: { name: t("bom_variant"), visible: true }
quantity: { name: t("bom_quantity"), visible: true }
max: { name: t("bom_max"), visible: true }
final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false }
price: { name: t("price"), visible: false }
$scope.initialise = ->
$scope.initialiseVariables()
authorise_api_reponse = ""
dataFetcher("/api/users/authorise_api?token=" + SpreeApiKey).then (data) ->
authorise_api_reponse = data
$scope.spree_api_key_ok = data.hasOwnProperty("success") and data["success"] == "Use of API Authorised"
if $scope.spree_api_key_ok
$http.defaults.headers.common["X-Spree-Token"] = SpreeApiKey
dataFetcher("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").then (data) ->
$scope.suppliers = $filter('orderBy')(data, 'name')
$scope.suppliers.unshift blankOption()
dataFetcher("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").then (data) ->
$scope.distributors = $filter('orderBy')(data, 'name')
$scope.distributors.unshift blankOption()
ocFetcher = dataFetcher("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=#{formatDate(daysFromToday(-90))}").then (data) ->
$scope.orderCycles = data
$scope.orderCyclesByID = []
$scope.orderCyclesByID[oc.id] = oc for oc in $scope.orderCycles
$scope.orderCycles.unshift blankOption()
$scope.fetchOrders()
ocFetcher.then ->
$scope.resetSelectFilters()
else if authorise_api_reponse.hasOwnProperty("error")
$scope.api_error_msg = authorise_api_reponse("error")
else
api_error_msg = "You don't have an API key yet. An attempt was made to generate one, but you are currently not authorised, please contact your site administrator for access."
$scope.fetchOrders = ->
$scope.loading = true
dataFetcher("/admin/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=#{$scope.startDate};q[completed_at_lt]=#{$scope.endDate}").then (data) ->
$scope.resetOrders data
$scope.loading = false
$scope.resetOrders = (data) ->
$scope.orders = data
$scope.resetLineItems()
pendingChanges.removeAll()
$scope.resetLineItems = ->
$scope.lineItems = $scope.orders.reduce (lineItems,order) ->
orderWithoutLineItems = $scope.lineItemOrder order
for i,line_item of order.line_items
line_item.checked = false
line_item.supplier = $scope.matchObject $scope.suppliers, line_item.supplier, null
line_item.order = orderWithoutLineItems
line_item.original_final_weight_volume = line_item.final_weight_volume
line_item.original_quantity = line_item.quantity
line_item.original_price = line_item.price
lineItems.concat order.line_items
, []
$scope.lineItemOrder = (order) ->
lineItemOrder = angular.copy(order)
delete lineItemOrder.line_items
lineItemOrder.distributor = $scope.matchObject $scope.distributors, order.distributor, null
lineItemOrder.order_cycle = $scope.matchObject $scope.orderCycles, order.order_cycle, null
lineItemOrder
$scope.matchObject = (list, testObject, noMatch) ->
for i, object of list
if angular.equals(object, testObject)
return object
return noMatch
$scope.deleteLineItem = (lineItem) ->
if ($scope.confirmDelete && confirm("Are you sure?")) || !$scope.confirmDelete
$http(
method: "DELETE"
url: "/api/orders/" + lineItem.order.number + "/line_items/" + lineItem.id
).success (data) ->
$scope.lineItems.splice $scope.lineItems.indexOf(lineItem), 1
$scope.deleteLineItems = (lineItems) ->
existingState = $scope.confirmDelete
$scope.confirmDelete = false
$scope.deleteLineItem lineItem for lineItem in lineItems when lineItem.checked
$scope.confirmDelete = existingState
$scope.submit = ->
if $scope.bulk_order_form.$valid
pendingChanges.submitAll()
else
alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors."
$scope.allBoxesChecked = ->
checkedCount = $scope.filteredLineItems.reduce (count,lineItem) ->
count + (if lineItem.checked then 1 else 0 )
, 0
checkedCount == $scope.filteredLineItems.length
$scope.toggleAllCheckboxes = ->
changeTo = !$scope.allBoxesChecked()
lineItem.checked = changeTo for lineItem in $scope.filteredLineItems
$scope.setSelectedUnitsVariant = (unitsProduct,unitsVariant) ->
$scope.selectedUnitsProduct = unitsProduct
$scope.selectedUnitsVariant = unitsVariant
$scope.sumUnitValues = ->
sum = $scope.filteredLineItems.reduce (sum,lineItem) ->
sum = sum + lineItem.final_weight_volume
, 0
$scope.sumMaxUnitValues = ->
sum = $scope.filteredLineItems.reduce (sum,lineItem) ->
sum = sum + Math.max(lineItem.max_quantity,lineItem.original_quantity) * lineItem.units_variant.unit_value
, 0
$scope.allFinalWeightVolumesPresent = ->
for i,lineItem of $scope.filteredLineItems
return false if !lineItem.hasOwnProperty('final_weight_volume') || !(lineItem.final_weight_volume > 0)
true
# How is this different to OptionValueNamer#name?
# Should it be extracted to that class or VariantUnitManager?
$scope.formattedValueWithUnitName = (value, unitsProduct, unitsVariant) ->
# A Units Variant is an API object which holds unit properies of a variant
if unitsProduct.hasOwnProperty("variant_unit") && (unitsProduct.variant_unit == "weight" || unitsProduct.variant_unit == "volume") && value > 0
scale = VariantUnitManager.getScale(value, unitsProduct.variant_unit)
Math.round(value/scale * 1000)/1000 + " " + VariantUnitManager.getUnitName(scale, unitsProduct.variant_unit)
else
''
$scope.fulfilled = (sumOfUnitValues) ->
# A Units Variant is an API object which holds unit properies of a variant
if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size") && $scope.selectedUnitsProduct.group_buy_unit_size > 0 &&
$scope.selectedUnitsProduct.hasOwnProperty("variant_unit") &&
( $scope.selectedUnitsProduct.variant_unit == "weight" || $scope.selectedUnitsProduct.variant_unit == "volume" )
Math.round( sumOfUnitValues / $scope.selectedUnitsProduct.group_buy_unit_size * 1000)/1000
else
''
$scope.unitsVariantSelected = ->
!angular.equals($scope.selectedUnitsVariant,{})
$scope.resetSelectFilters = ->
$scope.distributorFilter = $scope.distributors[0].id
$scope.supplierFilter = $scope.suppliers[0].id
$scope.orderCycleFilter = $scope.orderCycles[0].id
$scope.quickSearch = ""
$scope.weightAdjustedPrice = (lineItem) ->
if lineItem.final_weight_volume > 0
unit_value = lineItem.final_weight_volume / lineItem.quantity
original_unit_value = lineItem.original_final_weight_volume / lineItem.original_quantity
lineItem.price = lineItem.original_price * (unit_value / original_unit_value)
$scope.unitValueLessThanZero = (lineItem) ->
if lineItem.units_variant.unit_value <= 0
true
else
false
$scope.updateOnQuantity = (lineItem) ->
if lineItem.quantity > 0
lineItem.final_weight_volume = lineItem.original_final_weight_volume * lineItem.quantity / lineItem.original_quantity
$scope.weightAdjustedPrice(lineItem)
$scope.$watch "orderCycleFilter", (newVal, oldVal) ->
unless $scope.orderCycleFilter == "0" || angular.equals(newVal, oldVal)
$scope.startDate = $scope.orderCyclesByID[$scope.orderCycleFilter].first_order
$scope.endDate = $scope.orderCyclesByID[$scope.orderCycleFilter].last_order
]
daysFromToday = (days) ->
now = new Date
now.setHours(0)
now.setMinutes(0)
now.setSeconds(0)
now.setDate( now.getDate() + days )
now
formatDate = (date) ->
year = date.getFullYear()
month = twoDigitNumber date.getMonth() + 1
day = twoDigitNumber date.getDate()
return year + "-" + month + "-" + day
formatTime = (date) ->
hours = twoDigitNumber date.getHours()
mins = twoDigitNumber date.getMinutes()
secs = twoDigitNumber date.getSeconds()
return hours + ":" + mins + ":" + secs
twoDigitNumber = (number) ->
twoDigits = "" + number
twoDigits = ("0" + number) if number < 10
twoDigits

View File

@@ -3,18 +3,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$scope.StatusMessage = StatusMessage
$scope.columns = Columns.setColumns
producer: {name: t("products_producer"), visible: true}
sku: {name: t("products_sku"), visible: false}
name: {name: t("products_name"), visible: true}
unit: {name: t("products_unit"), visible: true}
price: {name: t("products_price"), visible: true}
on_hand: {name: t("products_on_hand"), visible: true}
on_demand: {name: t("products_on_demand"), visible: false}
category: {name: t("products_category"), visible: false}
tax_category: {name: t("products_tax_category"), visible: false}
inherits_properties: {name: t("products_inherits_properties"), visible: false}
available_on: {name: t("products_available_on"), visible: false}
$scope.columns = Columns.columns
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()

View File

@@ -1,33 +1,24 @@
angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, Columns, pendingChanges, shops) ->
$scope.shop = {}
angular.module("admin.customers").controller "customersCtrl", ($scope, $q, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, pendingChanges, shops) ->
$scope.shops = shops
$scope.CurrentShop = CurrentShop
$scope.RequestMonitor = RequestMonitor
$scope.submitAll = pendingChanges.submitAll
$scope.add = Customers.add
$scope.deleteCustomer = Customers.remove
$scope.customerLimit = 20
$scope.columns = Columns.columns
$scope.columns = Columns.setColumns
email: { name: "Email", visible: true }
code: { name: "Code", visible: true }
tags: { name: "Tags", visible: true }
$scope.$watch "CurrentShop.shop", ->
if $scope.CurrentShop.shop.id?
Customers.index({enterprise_id: $scope.CurrentShop.shop.id}).then (data) ->
$scope.customers = data
$scope.$watch "shop.id", ->
if $scope.shop.id?
$scope.customers = index {enterprise_id: $scope.shop.id}
$scope.add = (email) ->
$scope.findTags = (query) ->
defer = $q.defer()
params =
enterprise_id: $scope.shop.id
email: email
CustomerResource.create params, (customer) =>
if customer.id
$scope.customers.push customer
$scope.quickSearch = customer.email
$scope.deleteCustomer = (customer) ->
params = id: customer.id
CustomerResource.destroy params, ->
i = $scope.customers.indexOf customer
$scope.customers.splice i, 1 unless i < 0
index = (params) ->
$scope.loaded = false
CustomerResource.index params, =>
$scope.loaded = true
enterprise_id: $scope.CurrentShop.shop.id
TagRuleResource.mapByTag params, (data) =>
filtered = data.filter (tag) ->
tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1
defer.resolve filtered
defer.promise

View File

@@ -1 +1 @@
angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.utils', 'admin.dropdown'])
angular.module("admin.customers", ['ngResource', 'admin.tagRules', 'admin.indexUtils', 'admin.utils', 'admin.dropdown'])

View File

@@ -0,0 +1,46 @@
angular.module("admin.customers").directive 'newCustomerDialog', ($compile, $injector, $templateCache, $window, CurrentShop, Customers) ->
restrict: 'A'
scope: true
link: (scope, element, attr) ->
scope.CurrentShop = CurrentShop
scope.submitted = null
scope.email = ""
scope.errors = []
scope.addCustomer = (valid) ->
scope.submitted = scope.email
scope.errors = []
if valid
Customers.add(scope.email).$promise.then (data) ->
if data.id
scope.email = ""
scope.submitted = null
template.dialog('close')
, (response) ->
if response.data.errors
scope.errors.push(error) for error in response.data.errors
else
scope.errors.push("Sorry! Could not create '#{scope.email}'")
return
# Compile modal template
template = $compile($templateCache.get('admin/new_customer_dialog.html'))(scope)
# Set Dialog options
template.dialog
show: { effect: "fade", duration: 400 }
hide: { effect: "fade", duration: 300 }
autoOpen: false
resizable: false
width: $window.innerWidth * 0.4;
modal: true
open: (event, ui) ->
$('.ui-widget-overlay').bind 'click', ->
$(this).siblings('.ui-dialog').find('.ui-dialog-content').dialog('close')
# Link opening of dialog to click event on element
element.bind 'click', (e) ->
if CurrentShop.shop.id
template.dialog('open')
else
alert('Please select a shop first')

View File

@@ -0,0 +1,3 @@
angular.module("admin.customers").factory "CurrentShop", ->
new class CurrentShop
shop: {}

View File

@@ -0,0 +1,21 @@
angular.module("admin.customers").factory "Customers", ($q, RequestMonitor, CustomerResource, CurrentShop) ->
new class Customers
customers: []
add: (email) ->
params =
enterprise_id: CurrentShop.shop.id
email: email
CustomerResource.create params, (customer) =>
@customers.unshift customer if customer.id
remove: (customer) ->
params = id: customer.id
CustomerResource.destroy params, =>
i = @customers.indexOf customer
@customers.splice i, 1 unless i < 0
index: (params) ->
request = CustomerResource.index(params, (data) => @customers = data)
RequestMonitor.load(request.$promise)
request.$promise

View File

@@ -1,4 +1,4 @@
angular.module("ofn.admin").directive "ofnTrackMaster", ["DirtyProducts", (DirtyProducts) ->
angular.module("ofn.admin").directive "ofnTrackMaster", (DirtyProducts) ->
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
ngModel.$parsers.push (viewValue) ->
@@ -6,4 +6,3 @@ angular.module("ofn.admin").directive "ofnTrackMaster", ["DirtyProducts", (Dirty
DirtyProducts.addMasterProperty scope.product.id, scope.product.master.id, attrs.ofnTrackMaster, viewValue
scope.displayDirtyProducts()
viewValue
]

View File

@@ -0,0 +1,10 @@
angular.module("admin.dropdown").controller "ColumnsDropdownCtrl", ($scope, Columns) ->
$scope.columns = Columns.columns
$scope.toggle = Columns.toggleColumn
$scope.saved = Columns.preferencesSaved
$scope.saving = false
$scope.saveColumnPreferences = (action_name) ->
$scope.saving = true
Columns.savePreferences(action_name).then ->
$scope.saving = false

View File

@@ -0,0 +1,6 @@
angular.module("admin.dropdown").directive 'columnsDropdown', ->
restrict: 'E'
templateUrl: 'admin/columns_dropdown.html'
controller: 'ColumnsDropdownCtrl'
scope:
action: '@'

View File

@@ -5,9 +5,4 @@ angular.module("admin.enterprises").controller 'enterprisesCtrl', ($scope, $q, E
$q.all(requests).then ->
$scope.loaded = true
$scope.columns = Columns.setColumns
name: { name: "Name", visible: true }
producer: { name: "Producer", visible: true }
package: { name: "Package", visible: true }
status: { name: "Status", visible: true }
manage: { name: "Manage", visible: true }
$scope.columns = Columns.columns

View File

@@ -1 +1 @@
angular.module("admin.enterprises", [
angular.module("admin.enterprises", [

View File

@@ -1,7 +0,0 @@
angular.module('ofn.admin').filter "translate", ->
(key, options) ->
t(key, options)
angular.module('ofn.admin').filter "t", ->
(key, options) ->
t(key, options)

View File

@@ -1,4 +1,4 @@
angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout) ->
angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout, $filter) ->
require: 'ngModel'
restrict: 'C'
scope:
@@ -6,15 +6,19 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout)
minSearch: "@?"
text: "@?"
blank: "=?"
filter: "=?"
link: (scope, element, attrs, ngModel) ->
$timeout ->
scope.text ||= 'name'
scope.filter ||= -> true
scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object"
item.name = $sanitize(item.name) for item in scope.data
element.select2
minimumResultsForSearch: scope.minSearch || 0
data: { results: scope.data, text: scope.text }
data: ->
filtered = $filter('filter')(scope.data,scope.filter)
{ results: filtered, text: scope.text }
formatSelection: (item) ->
item[scope.text]
formatResult: (item) ->

View File

@@ -0,0 +1,12 @@
angular.module("admin.indexUtils").component 'showMore',
templateUrl: 'admin/show_more.html'
bindings:
data: "="
limit: "="
increment: "="
# For now, this component is not being used.
# Something about binding "data" to a variable on the parent scope that is continually refreshed by
# being assigned within an ng-repeat means that we get $digest iteration errors. Seems to be solved
# by using the new "as" syntax for ng-repeat to assign and alias the outcome of the filters, but this
# has the limitation of not being able to be limited AFTER the assignment has been made, which we need

View File

@@ -1,8 +0,0 @@
angular.module("admin.indexUtils").directive "toggleColumn", (Columns) ->
link: (scope, element, attrs) ->
element.addClass "selected" if scope.column.visible
element.click "click", ->
scope.$apply ->
Columns.toggleColumn(scope.column)
element.toggleClass "selected"

View File

@@ -1 +1 @@
angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates']).config ($httpProvider) ->
angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates', 'admin.utils']).config ($httpProvider) ->

View File

@@ -1,13 +1,15 @@
angular.module("admin.indexUtils").factory 'Columns', ($rootScope) ->
angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, columns) ->
new class Columns
savedColumns: {}
columns: {}
visibleCount: 0
setColumns: (columns) =>
constructor: ->
@columns = {}
@columns[name] = column for name, column of columns
for column in columns
@columns[column.column_name] = column
@savedColumns[column.column_name] = angular.copy(column)
@calculateVisibleCount()
@columns
toggleColumn: (column) =>
column.visible = !column.visible
@@ -16,3 +18,18 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope) ->
calculateVisibleCount: =>
@visibleCount = (column for name, column of @columns when column.visible).length
$rootScope.$broadcast "columnCount:changed", @visibleCount
preferencesSaved: =>
angular.equals(@columns, @savedColumns)
savePreferences: (action_name) =>
$http
method: "PUT"
url: "/admin/column_preferences/bulk_update"
data:
action_name: action_name
column_preferences: (preference for column_name, preference of @columns)
.success (data) =>
for column in data
angular.extend(@columns[column.column_name], column)
angular.extend(@savedColumns[column.column_name], column)

View File

@@ -1,10 +1,12 @@
angular.module("admin.indexUtils").factory "pendingChanges", (resources) ->
angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, StatusMessage) ->
new class pendingChanges
pendingChanges: {}
errors: []
add: (id, attr, change) =>
@pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}")
@pendingChanges["#{id}"]["#{attr}"] = change
StatusMessage.display('notice', "You have made #{@changeCount(@pendingChanges)} unsaved changes")
removeAll: =>
@pendingChanges = {}
@@ -14,11 +16,19 @@ angular.module("admin.indexUtils").factory "pendingChanges", (resources) ->
delete @pendingChanges["#{id}"]["#{attr}"]
delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1
submitAll: =>
submitAll: (form=null) =>
all = []
@errors = []
StatusMessage.display('progress', "Saving...")
for id, objectChanges of @pendingChanges
for attrName, change of objectChanges
all.push @submit(change)
$q.all(all).then =>
if @errors.length == 0
StatusMessage.display('success', "All changes saved successfully")
form.$setPristine() if form?
else
StatusMessage.display('failure', "Oh no! I was unable to save your changes")
all
submit: (change) ->
@@ -26,7 +36,8 @@ angular.module("admin.indexUtils").factory "pendingChanges", (resources) ->
@remove change.object.id, change.attr
change.scope.reset( data["#{change.attr}"] )
change.scope.success()
, (error) ->
, (error) =>
@errors.push error
change.scope.error()
changeCount: (objectChanges) ->

View File

@@ -5,27 +5,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.confirmDelete = true
$scope.startDate = formatDate daysFromToday -7
$scope.endDate = formatDate daysFromToday 1
$scope.bulkActions = [ { name: t("bom_actions_delete"), callback: 'deleteLineItems' } ]
$scope.bulkActions = [ { name: t("admin.orders.bulk_management.actions_delete"), callback: 'deleteLineItems' } ]
$scope.selectedUnitsProduct = {}
$scope.selectedUnitsVariant = {}
$scope.sharedResource = false
$scope.columns = Columns.setColumns
order_no: { name: t("bom_no"), visible: false }
full_name: { name: t("name"), visible: true }
email: { name: t("email"), visible: false }
phone: { name: t("phone"), visible: false }
order_date: { name: t("bom_date"), visible: true }
producer: { name: t("producer"), visible: true }
order_cycle: { name: t("bom_cycle"), visible: false }
hub: { name: t("bom_hub"), visible: false }
variant: { name: t("bom_variant"), visible: true }
quantity: { name: t("bom_quantity"), visible: true }
max: { name: t("bom_max"), visible: true }
final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false }
price: { name: t("price"), visible: false }
$scope.columns = Columns.columns
$scope.confirmRefresh = ->
LineItems.allSaved() || confirm(t "unsaved_changes_warning")
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
$scope.resetSelectFilters = ->
$scope.distributorFilter = blankOption().id

View File

@@ -12,7 +12,7 @@ angular.module('admin.orderCycles')
$scope.StatusMessage = StatusMessage
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
$scope.suppliedVariants = (enterprise_id) ->
Enterprise.suppliedVariants(enterprise_id)
@@ -79,5 +79,6 @@ angular.module('admin.orderCycles')
$scope.removeDistributionOfVariant = (variant_id) ->
OrderCycle.removeDistributionOfVariant(variant_id)
$scope.submit = (destination) ->
$scope.submit = ($event, destination) ->
$event.preventDefault()
OrderCycle.create(destination)

View File

@@ -1,5 +1,5 @@
angular.module('admin.orderCycles')
.controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) ->
.controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) ->
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
$scope.supplier_enterprises = Enterprise.producer_enterprises
@@ -12,6 +12,9 @@ angular.module('admin.orderCycles')
$scope.StatusMessage = StatusMessage
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
@@ -60,6 +63,7 @@ angular.module('admin.orderCycles')
$scope.removeExchange = ($event, exchange) ->
$event.preventDefault()
OrderCycle.removeExchange(exchange)
$scope.order_cycle_form.$dirty = true
$scope.addCoordinatorFee = ($event) ->
$event.preventDefault()
@@ -81,4 +85,13 @@ angular.module('admin.orderCycles')
OrderCycle.removeDistributionOfVariant(variant_id)
$scope.submit = (destination) ->
OrderCycle.update(destination)
$event.preventDefault()
StatusMessage.display 'progress', "Saving..."
$scope.submit = ($event, destination) ->
$event.preventDefault()
StatusMessage.display 'progress', "Saving..."
OrderCycle.update(destination, $scope.order_cycle_form)
$scope.cancel = (destination) ->
$window.location = destination

View File

@@ -20,7 +20,7 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl"
OrderCycle.order_cycle.coordinator_id = enterprise.id
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
$scope.removeDistributionOfVariant = angular.noop
@@ -41,6 +41,7 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl"
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
$scope.submit = (destination) ->
$scope.submit = ($event, destination) ->
$event.preventDefault()
OrderCycle.mirrorIncomingToOutgoingProducts()
OrderCycle.create(destination)

View File

@@ -9,6 +9,9 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
$scope.order_cycle = OrderCycle.load $scope.orderCycleId(), (order_cycle) =>
$scope.init()
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
@@ -34,6 +37,8 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
$event.preventDefault()
OrderCycle.removeCoordinatorFee(index)
$scope.submit = (destination) ->
$scope.submit = ($event, destination) ->
$event.preventDefault()
StatusMessage.display 'progress', "Saving..."
OrderCycle.mirrorIncomingToOutgoingProducts()
OrderCycle.update(destination)
OrderCycle.update(destination, $scope.order_cycle_form)

View File

@@ -154,11 +154,12 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S
else
console.log('Failed to create order cycle')
update: (destination) ->
update: (destination, form) ->
return unless @confirmNoDistributors()
oc = new OrderCycleResource({order_cycle: this.dataForSubmit()})
oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) =>
if data['success']
form.$setPristine() if form
if destination?
$window.location = destination
else

View File

@@ -2,13 +2,11 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attr
$scope.$compile = $compile
$scope.shops = shops
$scope.orderCycles = orderCycles
for oc in $scope.orderCycles
oc.name_and_status = "#{oc.name} (#{oc.status})"
$scope.distributor_id = $attrs.ofnDistributorId
$scope.order_cycle_id = $attrs.ofnOrderCycleId
$scope.distributor_id = parseInt($attrs.ofnDistributorId)
$scope.order_cycle_id = parseInt($attrs.ofnOrderCycleId)
$scope.validOrderCycle = (oc, index, array) ->
$scope.validOrderCycle = (oc) ->
$scope.orderCycleHasDistributor oc, parseInt($scope.distributor_id)
$scope.distributorHasOrderCycles = (distributor) ->
@@ -20,3 +18,9 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attr
$scope.distributionChosen = ->
$scope.distributor_id && $scope.order_cycle_id
for oc in $scope.orderCycles
oc.name_and_status = "#{oc.name} (#{oc.status})"
for shop in $scope.shops
shop.disabled = !$scope.distributorHasOrderCycles(shop)

View File

@@ -1 +1 @@
angular.module("admin.orders", ['ngResource'])
angular.module("admin.orders", ['admin.indexUtils', 'ngResource'])

View File

@@ -1,2 +1,10 @@
angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod) ->
angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod, TagRuleResource, $q) ->
$scope.shippingMethod = shippingMethod
$scope.findTags = (query) ->
defer = $q.defer()
TagRuleResource.mapByTag (data) =>
filtered = data.filter (tag) ->
tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1
defer.resolve filtered
defer.promise

View File

@@ -1 +1 @@
angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils'])
angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'admin.tagRules', 'templates'])

View File

@@ -0,0 +1,10 @@
angular.module("admin.tagRules").factory 'TagRuleResource', ($resource) ->
$resource('/admin/tag_rules/:action.json', {}, {
'mapByTag':
method: 'GET'
isArray: true
cache: true
params:
action: 'map_by_tag'
enterprise_id: '@enterprise_id'
})

View File

@@ -1 +1 @@
angular.module("admin.tagRules", ['ngTagsInput'])
angular.module("admin.tagRules", ['ngResource', 'ngTagsInput'])

View File

@@ -1,8 +1,9 @@
angular.module("admin.utils").directive "saveBar", (StatusMessage) ->
restrict: "E"
transclude: true
scope:
save: "&"
form: "="
dirty: "="
persist: "=?"
templateUrl: "admin/save_bar.html"
link: (scope, element, attrs) ->
scope.StatusMessage = StatusMessage

View File

@@ -1,10 +1,11 @@
angular.module("admin.utils").directive "tagsWithTranslation", ($timeout) ->
restrict: "E"
template: "<tags-input ng-model='object[tagsAttr]'>"
templateUrl: "admin/tags_input.html"
scope:
object: "="
tagsAttr: "@?"
tagListAttr: "@?"
findTags: "&"
link: (scope, element, attrs) ->
$timeout ->
scope.tagsAttr ||= "tags"

View File

@@ -0,0 +1,7 @@
angular.module("admin.utils").filter "translate", ->
(key, options) ->
t(key, options)
angular.module("admin.utils").filter "t", ->
(key, options) ->
t(key, options)

View File

@@ -19,19 +19,10 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
hidden: { name: "Hidden Products", visible: false }
new: { name: "New Products", visible: false }
$scope.columns = Columns.setColumns
producer: { name: "Producer", visible: true }
product: { name: "Product", visible: true }
sku: { name: "SKU", visible: false }
price: { name: "Price", visible: true }
on_hand: { name: "On Hand", visible: true }
on_demand: { name: "On Demand", visible: false }
reset: { name: "Reset Stock Level", visible: false }
inheritance: { name: "Inheritance", visible: false }
visibility: { name: "Hide", visible: false }
$scope.bulkActions = [ name: "Reset Stock Levels To Defaults", callback: 'resetStock' ]
$scope.columns = Columns.columns
$scope.resetSelectFilters = ->
$scope.producerFilter = 0
$scope.query = ''

View File

@@ -1 +1 @@
angular.module("admin.variantOverrides", ["pasvaz.bindonce", "admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems"])
angular.module("admin.variantOverrides", ["admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems"])

View File

@@ -11,8 +11,7 @@
#= require lodash.underscore.js
#= require angular-scroll.min.js
#= require angular-google-maps.min.js
#= require ../shared/mm-foundation-tpls-0.2.2.min.js
#= require ../shared/bindonce.min.js
#= require ../shared/mm-foundation-tpls-0.8.0.min.js
#= require ../shared/ng-infinite-scroll.min.js
#= require ../shared/angular-local-storage.js
#= require ../shared/angular-slideables.js

View File

@@ -1,5 +1,6 @@
Darkswarm.controller "SignupCtrl", ($scope, $http, $window, $location, Redirections, AuthenticationService) ->
$scope.path = "/signup"
$scope.errors =
email: null
password: null

View File

@@ -3,5 +3,10 @@ Darkswarm.controller "AuthenticationCtrl", ($scope, AuthenticationService, Spree
$scope.toggle = AuthenticationService.toggle
$scope.spree_user = SpreeUser.spree_user
$scope.active = AuthenticationService.active
$scope.isActive = AuthenticationService.isActive
$scope.select = AuthenticationService.select
$scope.tabs =
login: { active: $scope.isActive('/login') }
signup: { active: $scope.isActive('/signup') }
forgot: { active: $scope.isActive('/forgot') }

View File

@@ -1,4 +1,4 @@
Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter) ->
Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter, Navigation) ->
$scope.Enterprises = Enterprises
all_enterprises_by_id = Enterprises.enterprises_by_id
@@ -19,4 +19,3 @@ Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, M
$scope.map = angular.copy MapConfiguration.options
$scope.mapMarkers = OfnMap.enterprise_markers visible_enterprises

View File

@@ -0,0 +1,8 @@
Darkswarm.controller "GroupTabsCtrl", ($scope, $controller, Navigation) ->
angular.extend this, $controller('TabsCtrl', {$scope: $scope})
$scope.tabs =
map: { active: Navigation.isActive('/map') }
about: { active: Navigation.isActive('/about') }
producers: { active: Navigation.isActive('/producers') }
hubs: { active: Navigation.isActive('/hubs') }

View File

@@ -1,3 +1,3 @@
Darkswarm.controller "GroupsCtrl", ($scope, Groups, $anchorScroll, $rootScope) ->
Darkswarm.controller "GroupsCtrl", ($scope, Groups) ->
$scope.Groups = Groups
$scope.order = 'position'

View File

@@ -1,6 +1,7 @@
Darkswarm.controller "ProductNodeCtrl", ($scope, $modal) ->
Darkswarm.controller "ProductNodeCtrl", ($scope, $modal, FilterSelectorsService) ->
$scope.enterprise = $scope.product.supplier # For the modal, so it's consistent
$scope.triggerProductModal = ->
$scope.productTaxonSelectors = FilterSelectorsService.createSelectors()
$scope.productPropertySelectors = FilterSelectorsService.createSelectors()
$modal.open(templateUrl: "product_modal.html", scope: $scope)

View File

@@ -0,0 +1,8 @@
Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation) ->
angular.extend this, $controller('TabsCtrl', {$scope: $scope})
$scope.tabs =
about: { active: Navigation.isActive('/about') }
producers: { active: Navigation.isActive('/producers') }
contact: { active: Navigation.isActive('/contact') }
groups: { active: Navigation.isActive('/groups') }

View File

@@ -1,15 +1,6 @@
Darkswarm.controller "TabsCtrl", ($scope, $rootScope, $location) ->
# Return active if supplied path matches url hash path.
$scope.active = (path)->
$location.hash() == path
Darkswarm.controller "TabsCtrl", ($scope, Navigation) ->
$scope.isActive = Navigation.isActive
# Select tab by setting the url hash path.
$scope.select = (path)->
$location.hash path
# Toggle tab selected status by setting the url hash path.
$scope.toggle = (path)->
if $scope.active(path)
$location.hash ""
else
$location.hash path
$scope.select = (path) ->
Navigation.navigate path

View File

@@ -1,7 +1,6 @@
window.Darkswarm = angular.module("Darkswarm", ["ngResource",
'mm.foundation',
'angularLocalStorage',
'pasvaz.bindonce',
'infinite-scroll',
'angular-flash.service',
'templates',

View File

@@ -7,6 +7,22 @@ Darkswarm.directive 'mapSearch', ($timeout)->
link: (scope, elem, attrs, ctrl)->
$timeout =>
map = ctrl.getMap()
# Use OSM tiles server
map.mapTypes.set 'OSM', new (google.maps.ImageMapType)(
getTileUrl: (coord, zoom) ->
# "Wrap" x (logitude) at 180th meridian properly
# NB: Don't touch coord.x because coord param is by reference, and changing its x property breakes something in Google's lib
tilesPerGlobe = 1 << zoom
x = coord.x % tilesPerGlobe
if x < 0
x = tilesPerGlobe + x
# Wrap y (latitude) in a like manner if you want to enable vertical infinite scroll
'http://tile.openstreetmap.org/' + zoom + '/' + x + '/' + coord.y + '.png'
tileSize: new (google.maps.Size)(256, 256)
name: 'OpenStreetMap'
maxZoom: 18)
input = (document.getElementById("pac-input"))
map.controls[google.maps.ControlPosition.TOP_LEFT].push input
searchBox = new google.maps.places.SearchBox((input))
@@ -21,7 +37,7 @@ Darkswarm.directive 'mapSearch', ($timeout)->
#map.setCenter place.geometry.location
map.fitBounds place.geometry.viewport
#map.fitBounds bounds
# Bias the SearchBox results towards places that are within the bounds of the
# current map's viewport.
google.maps.event.addListener map, "bounds_changed", ->

View File

@@ -0,0 +1,19 @@
Darkswarm.directive "ofnOnHand", ->
restrict: 'A'
require: "ngModel"
link: (scope, elem, attr, ngModel) ->
# In cases where this field gets its value from the HTML element rather than the model,
# initialise the model with the HTML value.
if scope.$eval(attr.ngModel) == undefined
ngModel.$setViewValue elem.val()
ngModel.$parsers.push (viewValue) ->
on_hand = parseInt(attr.ofnOnHand)
if parseInt(viewValue) > on_hand
alert t('insufficient_stock', {on_hand: on_hand})
viewValue = on_hand
ngModel.$setViewValue viewValue
ngModel.$render()
viewValue

View File

@@ -22,7 +22,7 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir
@selectedPath = path
Navigation.navigate @selectedPath
active: Navigation.active
isActive: Navigation.isActive
close: ->
if location.pathname in ["/", "/checkout"]

View File

@@ -1,4 +1,4 @@
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, storage)->
# Handles syncing of current cart/order state to server
new class Cart
dirty: false
@@ -28,15 +28,39 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
update: =>
@update_running = true
$http.post('/orders/populate', @data()).success (data, status)=>
@saved()
@update_running = false
@compareAndNotifyStockLevels data.stock_levels
@popQueue() if @update_enqueued
.error (response, status)=>
@scheduleRetry(status)
@update_running = false
compareAndNotifyStockLevels: (stockLevels) =>
scope = $rootScope.$new(true)
scope.variants = []
# TODO: These changes to quantity/max_quantity trigger another cart update, which
# is unnecessary.
for li in @line_items_present()
if stockLevels[li.variant.id]?
li.variant.count_on_hand = stockLevels[li.variant.id].on_hand
if li.quantity > li.variant.count_on_hand
li.quantity = li.variant.count_on_hand
scope.variants.push li.variant
if li.variant.count_on_hand == 0 && li.max_quantity > li.variant.count_on_hand
li.max_quantity = li.variant.count_on_hand
scope.variants.push(li.variant) unless li.variant in scope.variants
if scope.variants.length > 0
$modal.open(templateUrl: "out_of_stock.html", scope: scope, windowClass: 'out-of-stock-modal')
popQueue: =>
@update_enqueued = false
@scheduleUpdate()

View File

@@ -10,9 +10,12 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h
$http.put('/checkout', {order: @preprocess()}).success (data, status)=>
Navigation.go data.path
.error (response, status)=>
Loading.clear()
@errors = response.errors
RailsFlashLoader.loadFlash(response.flash)
if response.path
Navigation.go response.path
else
Loading.clear()
@errors = response.errors
RailsFlashLoader.loadFlash(response.flash)
# Rails wants our Spree::Address data to be provided with _attributes
preprocess: ->

View File

@@ -2,6 +2,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService,
new class EnterpriseRegistrationService
enterprise:
user_ids: [CurrentUser.id]
email: CurrentUser.email
email_address: CurrentUser.email
address: {}
country: availableCountries[0]

View File

@@ -1,11 +1,14 @@
Darkswarm.factory "MapConfiguration", ->
new class MapConfiguration
options:
center:
center:
latitude: -37.4713077
longitude: 144.7851531
zoom: 12
additional_options: {}
#mapTypeId: 'satellite'
additional_options:
# mapTypeId: 'satellite'
mapTypeId: 'OSM'
mapTypeControl: false
streetViewControl: false
styles: [{"featureType":"landscape","stylers":[{"saturation":-100},{"lightness":65},{"visibility":"on"}]},{"featureType":"poi","stylers":[{"saturation":-100},{"lightness":51},{"visibility":"simplified"}]},{"featureType":"road.highway","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"road.arterial","stylers":[{"saturation":-100},{"lightness":30},{"visibility":"on"}]},{"featureType":"road.local","stylers":[{"saturation":-100},{"lightness":40},{"visibility":"on"}]},{"featureType":"transit","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"administrative.province","stylers":[{"visibility":"off"}]},{"featureType":"water","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":-25},{"saturation":-100}]},{"featureType":"water","elementType":"geometry","stylers":[{"hue":"#ffff00"},{"lightness":-25},{"saturation":-97}]},{"featureType":"road","elementType": "labels.icon","stylers":[{"visibility":"off"}]}]

View File

@@ -1,9 +1,9 @@
Darkswarm.factory 'Navigation', ($location, $window) ->
new class Navigation
path: null
path: null
active: (path)->
$location.path() == path
isActive: (path)->
$location.path() == path
navigate: (path)=>
@path = path

View File

@@ -1 +0,0 @@
!function(){"use strict";var e=angular.module("pasvaz.bindonce",[]);e.directive("bindonce",function(){var e=function(e){if(e&&0!==e.length){var t=angular.lowercase(""+e);e=!("f"===t||"0"===t||"false"===t||"no"===t||"n"===t||"[]"===t)}else e=!1;return e},t=parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10);isNaN(t)&&(t=parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10));var r={restrict:"AM",controller:["$scope","$element","$attrs","$interpolate",function(r,a,i,n){var c=function(t,r,a){var i="show"===r?"":"none",n="hide"===r?"":"none";t.css("display",e(a)?i:n)},o=function(e,t){if(angular.isObject(t)&&!angular.isArray(t)){var r=[];angular.forEach(t,function(e,t){e&&r.push(t)}),t=r}t&&e.addClass(angular.isArray(t)?t.join(" "):t)},s=function(e,t){e.transclude(t,function(t){var r=e.element.parent(),a=e.element&&e.element[e.element.length-1],i=r&&r[0]||a&&a.parentNode,n=a&&a.nextSibling||null;angular.forEach(t,function(e){i.insertBefore(e,n)})})},l={watcherRemover:void 0,binders:[],group:i.boName,element:a,ran:!1,addBinder:function(e){this.binders.push(e),this.ran&&this.runBinders()},setupWatcher:function(e){var t=this;this.watcherRemover=r.$watch(e,function(e){void 0!==e&&(t.removeWatcher(),t.checkBindonce(e))},!0)},checkBindonce:function(e){var t=this,r=e.$promise?e.$promise.then:e.then;"function"==typeof r?r(function(){t.runBinders()}):t.runBinders()},removeWatcher:function(){void 0!==this.watcherRemover&&(this.watcherRemover(),this.watcherRemover=void 0)},runBinders:function(){for(;this.binders.length>0;){var r=this.binders.shift();if(!this.group||this.group==r.group){var a=r.scope.$eval(r.interpolate?n(r.value):r.value);switch(r.attr){case"boIf":e(a)&&s(r,r.scope.$new());break;case"boSwitch":var i,l=r.controller[0];(i=l.cases["!"+a]||l.cases["?"])&&(r.scope.$eval(r.attrs.change),angular.forEach(i,function(e){s(e,r.scope.$new())}));break;case"boSwitchWhen":var u=r.controller[0];u.cases["!"+r.attrs.boSwitchWhen]=u.cases["!"+r.attrs.boSwitchWhen]||[],u.cases["!"+r.attrs.boSwitchWhen].push({transclude:r.transclude,element:r.element});break;case"boSwitchDefault":var u=r.controller[0];u.cases["?"]=u.cases["?"]||[],u.cases["?"].push({transclude:r.transclude,element:r.element});break;case"hide":case"show":c(r.element,r.attr,a);break;case"class":o(r.element,a);break;case"text":r.element.text(a);break;case"html":r.element.html(a);break;case"style":r.element.css(a);break;case"src":r.element.attr(r.attr,a),t&&r.element.prop("src",a);break;case"attr":angular.forEach(r.attrs,function(e,t){var a,i;t.match(/^boAttr./)&&r.attrs[t]&&(a=t.replace(/^boAttr/,"").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),i=r.scope.$eval(r.attrs[t]),r.element.attr(a,i))});break;case"href":case"alt":case"title":case"id":case"value":r.element.attr(r.attr,a)}}}this.ran=!0}};return l}],link:function(e,t,r,a){var i=r.bindonce&&e.$eval(r.bindonce);void 0!==i?a.checkBindonce(i):(a.setupWatcher(r.bindonce),t.bind("$destroy",a.removeWatcher))}};return r}),angular.forEach([{directiveName:"boShow",attribute:"show"},{directiveName:"boHide",attribute:"hide"},{directiveName:"boClass",attribute:"class"},{directiveName:"boText",attribute:"text"},{directiveName:"boBind",attribute:"text"},{directiveName:"boHtml",attribute:"html"},{directiveName:"boSrcI",attribute:"src",interpolate:!0},{directiveName:"boSrc",attribute:"src"},{directiveName:"boHrefI",attribute:"href",interpolate:!0},{directiveName:"boHref",attribute:"href"},{directiveName:"boAlt",attribute:"alt"},{directiveName:"boTitle",attribute:"title"},{directiveName:"boId",attribute:"id"},{directiveName:"boStyle",attribute:"style"},{directiveName:"boValue",attribute:"value"},{directiveName:"boAttr",attribute:"attr"},{directiveName:"boIf",transclude:"element",terminal:!0,priority:1e3},{directiveName:"boSwitch",require:"boSwitch",controller:function(){this.cases={}}},{directiveName:"boSwitchWhen",transclude:"element",priority:800,require:"^boSwitch"},{directiveName:"boSwitchDefault",transclude:"element",priority:800,require:"^boSwitch"}],function(t){var r=200;return e.directive(t.directiveName,function(){var e={priority:t.priority||r,transclude:t.transclude||!1,terminal:t.terminal||!1,require:["^bindonce"].concat(t.require||[]),controller:t.controller,compile:function(e,r,a){return function(e,r,i,n){var c=n[0],o=i.boParent;if(o&&c.group!==o){var s=c.element.parent();c=void 0;for(var l;9!==s[0].nodeType&&s.length;){if((l=s.data("$bindonceController"))&&l.group===o){c=l;break}s=s.parent()}if(!c)throw new Error("No bindonce controller: "+o)}c.addBinder({element:r,attr:t.attribute||t.directiveName,attrs:i,value:i[t.directiveName],interpolate:t.interpolate,group:o,transclude:a,controller:n.slice(1),scope:e})}}};return e})})}();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
.ofn-drop-down.right#columns-dropdown{ ng: { controller: 'ColumnsDropdownCtrl' } }
%span{ :class => 'icon-reorder' }= "&nbsp; #{t('admin.columns')}".html_safe
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%div.menu_item{ ng: { repeat: "column in columns", click: "toggle(column)", class: "{selected: column.visible}" } }
%span.check
%span.name {{column.name }}
%hr
%div.menu_item.text-center
%input.fullwidth.orange{ type: "button", ng: { value: "saved() ? 'Saved': 'Saving'", show: "saved() || saving", disabled: "saved()" } }
%input.fullwidth.red{ type: "button", value: 'Save As Default', ng: { show: "!saved() && !saving", click: "saveColumnPreferences(action)"} }

View File

@@ -0,0 +1,15 @@
#new-customer-dialog
.text-normal.margin-bottom-30.text-center
= t('admin.customers.index.add_a_new_customer_for', shop_name: "{{ CurrentShop.shop.name }}:")
%form{ name: 'new_customer_form', novalidate: true }
.text-center.margin-bottom-30
%input.fullwidth{ type: 'email', name: 'email', required: true, placeholder: t('admin.customers.index.customer_placeholder'), ng: { model: "email" } }
%div{ ng: { show: "email == submitted" } }
.error{ ng: { show: "(new_customer_form.email.$error.email || new_customer_form.email.$error.required)" } }
= t('admin.customers.index.valid_email_error')
.error{ ng: { repeat: "error in errors", bind: "error" } }
.text-center
%input.button.red.icon-plus{ type: 'submit', value: t('admin.customers.index.add_customer'), ng: { click: 'addCustomer(new_customer_form.email.$valid)' } }

View File

@@ -17,13 +17,13 @@
%td.severity
%i.icon-warning-sign.issue
%td.description
%span{ bo: { bind: "issue.description" } }
%span{ ng: { bind: "::issue.description" } }
%td.resolve
%div{ ng: { bind: { html: "issue.link" } } }
%tr{ ng: { repeat: "warning in warnings"} }
%td.severity
%i.icon-warning-sign.warning
%td.description
%span{ bo: { bind: "warning.description" } }
%span{ ng: { bind: "::warning.description" } }
%td.resolve
%div{ ng: { bind: { html: "warning.link" } } }

View File

@@ -1,6 +1,6 @@
#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } }
.twelve.columns.alpha
%h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } }
{{ StatusMessage.statusMessage.text || "&nbsp;" }}
.four.columns.omega.text-right
%input.red{type: "button", value: "Save Changes", ng: { disabled: '!form.$dirty', click: "save()" } }
#save-bar.animate-show{ ng: { show: 'dirty || persist || StatusMessage.active()' } }
.container
.eight.columns.alpha
%h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } }
{{ StatusMessage.statusMessage.text || "&nbsp;" }}
.eight.columns.omega.text-right{ ng: { transclude: true } }

View File

@@ -0,0 +1,4 @@
%div{ ng: { show: "data.length > limit" } }
%input{ type: 'button', value: 'Show More', ng: { click: 'limit = limit + increment' } }
or
%input{ type: 'button', value: "Show All ({{ data.length - limit }} More)", ng: { click: 'limit = data.length' } }

View File

@@ -0,0 +1,8 @@
.tag-template
%div
%span.tag-with-rules{ ng: { if: "data.rules" }, "ofn-with-tip" => "{{ 'admin.tag_has_rules' | t:{num: data.rules} }}" }
{{$getDisplayText()}}
%span{ ng: { if: "!data.rules" } }
{{$getDisplayText()}}
%a.remove-button{ ng: {click: "$removeTag()"} }
&#10006;

View File

@@ -0,0 +1,11 @@
.autocomplete-template
%span.tag-with-rules{ ng: { if: "data.rules" } }
{{$getDisplayText()}}
%span.tag-with-rules{ ng: { if: "data.rules == 1" } }
&mdash;
= t 'admin.has_one_rule'
%span.tag-with-rules{ ng: { if: "data.rules > 1" } }
&mdash;
= t 'admin.has_n_rules', { num: '{{data.rules}}' }
%span{ ng: { if: "!data.rules" } }
{{$getDisplayText()}}

View File

@@ -0,0 +1,7 @@
%tags-input{ template: 'admin/tag.html', ng: { model: 'object[tagsAttr]' } }
%auto-complete{source: "findTags({query: $query})",
template: "admin/tag_autocomplete.html",
"min-length" => "0",
"load-on-focus" => "true",
"load-on-empty" => "true",
"max-results-to-show" => "32"}

View File

@@ -1,4 +1,4 @@
%ul{ bindonce: true }
%ul
%active-selector{ ng: { repeat: "selector in allSelectors", show: "ifDefined(selector.fits, true)" } }
%render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"} }
%span{"bo-text" => "selector.object.name"}
%span{"ng-bind" => "::selector.object.name"}

View File

@@ -1,10 +1,5 @@
%tab#forgot{"ng-controller" => "ForgotCtrl",
heading: "{{'forgot_password' | t}}",
active: "active(path)",
select: "select(path)"}
%form{"ng-submit" => "submit()"}
%tab#forgot{ heading: "{{'forgot_password' | t}}", active: "tabs.forgot.active", select: "select(path)"}
%form{ ng: { controller: "ForgotCtrl", submit: "submit()" } }
.row
.large-12.columns
.alert-box.success.radius{"ng-show" => "sent"}
@@ -13,18 +8,18 @@
%div{"ng-show" => "!sent"}
.alert-box.alert{"ng-show" => "errors != null"}
{{ errors }}
.row
.large-12.columns
%label{for: "email"} {{'signup_email' | t}}
%input.title.input-text{name: "email",
%input.title.input-text{name: "email",
type: "email",
id: "email",
tabindex: 1,
"ng-model" => "spree_user.email"}
.row
.large-12.columns
%input.button.primary{name: "commit",
tabindex: "3",
type: "submit",
%input.button.primary{name: "commit",
tabindex: "3",
type: "submit",
value: "{{'reset_password' | t}}"}

View File

@@ -1,8 +1,5 @@
%tab#login-content{"ng-controller" => "LoginCtrl",
heading: "{{'label_login' | t}}",
active: "active(path)",
select: "select(path)"}
%form{"ng-submit" => "submit()"}
%tab#login-content{ heading: "{{'label_login' | t}}", active: "tabs.login.active", select: "select(path)"}
%form{ ng: { controller: "LoginCtrl", submit: "submit()" } }
.row
.large-12.columns
.alert-box.alert{"ng-show" => "errors != null"}
@@ -10,7 +7,7 @@
.row
.large-12.columns
%label{for: "email"} {{'email' | t}}
%input.title.input-text{name: "email",
%input.title.input-text{name: "email",
type: "email",
id: "email",
tabindex: 1,
@@ -18,7 +15,7 @@
.row
.large-12.columns
%label{for: "password"} {{'password' | t}}
%input.title.input-text{name: "password",
%input.title.input-text{name: "password",
type: "password",
id: "password",
autocomplete: "off",
@@ -26,15 +23,15 @@
"ng-model" => "spree_user.password"}
.row
.large-12.columns
%input{name: "remember_me",
type: "checkbox",
id: "remember_me",
%input{name: "remember_me",
type: "checkbox",
id: "remember_me",
value: "1",
"ng-model" => "spree_user.remember_me"}
%label{for: "remember_me"} {{'remember_me' | t}}
.row
.large-12.columns
%input.button.primary{name: "commit",
tabindex: "3",
type: "submit",
%input.button.primary{name: "commit",
tabindex: "3",
type: "submit",
value: "{{'label_login' | t}}"}

View File

@@ -0,0 +1,13 @@
%a.close-reveal-modal{"ng-click" => "$close()"}
%i.ofn-i_009-close
%h3 Reduced stock available
%p While you've been shopping, the stock levels for one or more of the products in your cart have reduced. Here's what's changed:
%p{'ng-repeat' => "v in variants"}
%em {{ v.name_to_display }} - {{ v.unit_to_display }}
%span{'ng-if' => "v.count_on_hand == 0"}
is now out of stock.
%span{'ng-if' => "v.count_on_hand > 0"}
now only has {{ v.count_on_hand }} remaining.

View File

@@ -1,11 +1,11 @@
%div.contact-container{bindonce: true}
%div.modal-centered{"bo-if" => "enterprise.email_address || enterprise.website || enterprise.phone"}
%div.contact-container
%div.modal-centered{"ng-if" => "::enterprise.email_address || enterprise.website || enterprise.phone"}
%p.modal-header {{'contact' | t}}
%p{"bo-if" => "enterprise.phone", "bo-text" => "enterprise.phone"}
%p{"ng-if" => "::enterprise.phone", "ng-bind" => "::enterprise.phone"}
%p.word-wrap{"ng-if" => "enterprise.email_address"}
%a{"bo-href" => "enterprise.email_address | stripUrl", target: "_blank", mailto: true}
%span.email{"bo-bind" => "enterprise.email_address | stripUrl"}
%p.word-wrap{"ng-if" => "::enterprise.email_address"}
%a{"ng-href" => "{{::enterprise.email_address | stripUrl}}", target: "_blank", mailto: true}
%span.email{"ng-bind" => "::enterprise.email_address | stripUrl"}
%p.word-wrap{"ng-if" => "enterprise.website"}
%a{"bo-href-i" => "http://{{enterprise.website | stripUrl}}", target: "_blank", "bo-bind" => "enterprise.website | stripUrl"}
%a{"ng-href" => "http://{{::enterprise.website | stripUrl}}", target: "_blank", "ng-bind" => "::enterprise.website | stripUrl"}

View File

@@ -1,4 +1,4 @@
.row{bindonce: true}
.row
.small-12.large-8.columns
/ TODO: Rob add logic for taxons and properties too:
/ %div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"}
@@ -22,8 +22,8 @@
-# / TODO: Rob - need popover, use will's directive or this? http://pineconellc.github.io/angular-foundation/
-#
.about-container.pad-top
%img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"}
%p.text-small{"ng-bind-html" => "enterprise.long_description"}
%img.enterprise-logo{"ng-src" => "{{::enterprise.logo}}", "ng-if" => "::enterprise.logo"}
%p.text-small{"ng-bind-html" => "::enterprise.long_description"}
.small-12.large-4.columns
%ng-include{src: "'partials/contact.html'"}
%ng-include{src: "'partials/follow.html'"}

View File

@@ -1,13 +1,13 @@
.highlight{bindonce: true, "ng-class" => "{'is_distributor' : enterprise.is_distributor}"}
.highlight{"ng-class" => "::{'is_distributor' : enterprise.is_distributor}"}
.highlight-top.row
.small-12.medium-7.large-8.columns
%h3{"ng-if" => "enterprise.is_distributor"}
%a{"bo-href" => "enterprise.path", "ofn-change-hub" => "enterprise"}
%i{"ng-class" => "enterprise.icon_font"}
%span{"bo-text" => "enterprise.name"}
%h3{"ng-if" => "!enterprise.is_distributor", "ng-class" => "{'is_producer' : enterprise.is_primary_producer}"}
%i{"ng-class" => "enterprise.icon_font"}
%span{"bo-text" => "enterprise.name"}
%h3{"ng-if" => "::enterprise.is_distributor"}
%a{"ng-href" => "{{::enterprise.path}}", "ofn-change-hub" => "enterprise"}
%i{"ng-class" => "::enterprise.icon_font"}
%span{"ng-bind" => "::enterprise.name"}
%h3{"ng-if" => "::!enterprise.is_distributor", "ng-class" => "::{'is_producer' : enterprise.is_primary_producer}"}
%i{"ng-class" => "::enterprise.icon_font"}
%span{"ng-bind" => "::enterprise.name"}
.small-12.medium-5.large-4.columns.text-right.small-only-text-left
%p{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"}
%img.hero-img{"bo-src" => "enterprise.promo_image"}
%p{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"}
%img.hero-img{"ng-src" => "{{::enterprise.promo_image}}"}

View File

@@ -1,19 +1,18 @@
%div.modal-centered{bindonce: true, "bo-if" => "enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"}
%div.modal-centered{ "ng-if" => "::enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"}
%p.modal-header {{'follow' | t}}
.follow-icons
%span{"bo-if" => "enterprise.twitter"}
%a{"bo-href-i" => "http://twitter.com/{{enterprise.twitter}}", target: "_blank"}
%span{"ng-if" => "::enterprise.twitter"}
%a{"ng-href" => "http://twitter.com/{{::enterprise.twitter}}", target: "_blank"}
%i.ofn-i_041-twitter
%span{"bo-if" => "enterprise.facebook"}
%a{"bo-href-i" => "http://{{enterprise.facebook | stripUrl}}", target: "_blank"}
%i.ofn-i_044-facebook
%span{"bo-if" => "enterprise.linkedin"}
%a{"bo-href-i" => "http://{{enterprise.linkedin | stripUrl}}", target: "_blank"}
%i.ofn-i_042-linkedin
%span{"bo-if" => "enterprise.instagram"}
%a{"bo-href-i" => "http://instagram.com/{{enterprise.instagram}}", target: "_blank"}
%i.ofn-i_043-instagram
%span{"ng-if" => "::enterprise.facebook"}
%a{"ng-href" => "http://{{::enterprise.facebook | stripUrl}}", target: "_blank"}
%i.ofn-i_044-facebook
%span{"ng-if" => "::enterprise.linkedin"}
%a{"ng-href" => "http://{{::enterprise.linkedin | stripUrl}}", target: "_blank"}
%i.ofn-i_042-linkedin
%span{"ng-if" => "::enterprise.instagram"}
%a{"ng-href" => "http://instagram.com/{{::enterprise.instagram}}", target: "_blank"}
%i.ofn-i_043-instagram

View File

@@ -1,24 +1,24 @@
.row.pad-top{bindonce: true, ng: { if: 'enterprise.is_distributor' } }
.row.pad-top{ng: { if: 'enterprise.is_distributor' } }
.cta-container.small-12.columns
.row
.small-4.columns
%label{"active-table-hub-link" => "enterprise", change: "{{'change_shop' | t}}", shop: "{{'shop_at' | t}}"}
.small-8.columns.right
%label.right{"bo-if" => "enterprise.pickup || enterprise.delivery"}
%label.right{"ng-if" => "::enterprise.pickup || enterprise.delivery"}
{{'hubs_delivery_options' | t}}:
%span{"bo-if" => "enterprise.pickup"}
%span{"ng-if" => "::enterprise.pickup"}
%i.ofn-i_038-takeaway
{{'hubs_pickup' | t}}
%span{"bo-if" => "enterprise.delivery"}
%span{"ng-if" => "::enterprise.delivery"}
%i.ofn-i_039-delivery
{{'hubs_delivery' | t}}
.row
.columns.small-12
%a.cta-hub{"bo-href" => "enterprise.path",
%a.cta-hub{"ng-href" => "{{::enterprise.path}}",
"ng-class" => "{primary: enterprise.active, secondary: !enterprise.active}",
"ofn-change-hub" => "enterprise"}
%i.ofn-i_033-open-sign{"bo-if" => "enterprise.active"}
%i.ofn-i_032-closed-sign{"bo-if" => "!enterprise.active"}
.hub-name{"bo-text" => "enterprise.name"}
.button-address{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"}
%i.ofn-i_033-open-sign{"ng-if" => "::enterprise.active"}
%i.ofn-i_032-closed-sign{"ng-if" => "::!enterprise.active"}
.hub-name{"ng-bind" => "::enterprise.name"}
.button-address{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"}
/ %i.ofn-i_007-caret-right

View File

@@ -1,20 +1,20 @@
-# Show places to buy products from this producer, when there are any
-# Do not show this for producer shops selling only their own produce,
-# Since a shopping link will already have been displayed in hub_details.html.haml
.row.active_table_row.pad-top{bindonce: true, "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"}
.row.active_table_row.pad-top{ "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"}
.columns.small-12
.row
.columns.small-12.fat
%div{"bo-if" => "enterprise.name"}
%label{"bo-html" => "'shop_for_products_html' | t:{enterprise: enterprise.name}"}
%div.show-for-medium-up{"bo-if" => "!enterprise.name"}
%div{"ng-if" => "::enterprise.name"}
%label{"ng-html" => "::'shop_for_products_html' | t:{enterprise: enterprise.name}"}
%div.show-for-medium-up{"ng-if" => "::!enterprise.name"}
&nbsp;
.row.cta-container
.columns.small-12
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'",
"bo-href" => "hub.path", "ofn-empties-cart" => "hub",
"bo-class" => "{primary: hub.active, secondary: !hub.active}"}
%i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
%i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
.hub-name{"bo-text" => "hub.name"}
.button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"}
"ng-href" => "{{::hub.path}}", "ofn-empties-cart" => "hub",
"ng-class" => "::{primary: hub.active, secondary: !hub.active}"}
%i.ofn-i_033-open-sign{"ng-if" => "::hub.active"}
%i.ofn-i_032-closed-sign{"ng-if" => "::!hub.active"}
.hub-name{"ng-bind" => "::hub.name"}
.button-address{"ng-bind" => "::[hub.address.city, hub.address.state_name] | printArray"}

View File

@@ -0,0 +1,12 @@
.small-5.medium-3.large-3.columns.text-right{"ng-if" => "::!variant.product.group_buy"}
%input{type: :number,
integer: true,
value: nil,
min: 0,
placeholder: "0",
"ofn-disable-scroll" => true,
"ng-model" => "variant.line_item.quantity",
"ofn-on-hand" => "{{variant.on_demand && 9999 || variant.count_on_hand }}",
"ng-disabled" => "!variant.on_demand && variant.count_on_hand == 0",
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}

View File

@@ -0,0 +1,23 @@
.small-5.medium-3.large-3.columns.text-right{"ng-if" => "::variant.product.group_buy"}
%span.bulk-input-container
%span.bulk-input
%input.bulk.first{type: :number,
value: nil,
integer: true,
min: 0,
"ng-model" => "variant.line_item.quantity",
placeholder: "{{'shop_variant_quantity_min' | t}}",
"ofn-disable-scroll" => true,
"ofn-on-hand" => "{{variant.on_demand && 9999 || variant.count_on_hand }}",
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
%span.bulk-input
%input.bulk.second{type: :number,
"ng-disabled" => "!variant.line_item.quantity",
integer: true,
min: 0,
"ng-model" => "variant.line_item.max_quantity",
placeholder: "{{'shop_variant_quantity_max' | t}}",
"ofn-disable-scroll" => true,
min: "{{variant.line_item.quantity}}",
name: "variant_attributes[{{variant.id}}][max_quantity]",
id: "variants_{{variant.id}}_max"}

View File

@@ -1,38 +1,37 @@
.joyride-tip-guide.price_breakdown{bindonce: true, "ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
.joyride-tip-guide.price_breakdown{"ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
%span.joyride-nub.right
.joyride-content-wrapper
.collapsed{"ng-show" => "!expanded"}
%price-percentage{percentage: 'variant.basePricePercentage'}
%a{"ng-click" => "expanded = !expanded"}
%span{"bo-text" => "'price_breakdown' | t"}
%a{"ng-click" => "expanded = !expanded"}
%span{"ng-bind" => "::'price_breakdown' | t"}
%i.ofn-i_005-caret-down
.expanded{"ng-show" => "expanded"}
%ul
%li.cost
.right {{ variant.price | localizeCurrency }}
%span{"bo-text" => "'item_cost' | t"}
%li.admin-fee{"bo-if" => "variant.fees.admin"}
%span{"ng-bind" => "::'item_cost' | t"}
%li.admin-fee{"ng-if" => "::variant.fees.admin"}
.right {{ variant.fees.admin | localizeCurrency }}
%span{"bo-text" => "'admin_fee' | t"}
%li.sales-fee{"bo-if" => "variant.fees.sales"}
%span{"ng-bind" => "::'admin_fee' | t"}
%li.sales-fee{"ng-if" => "::variant.fees.sales"}
.right {{ variant.fees.sales | localizeCurrency }}
%span{"bo-text" => "'sales_fee' | t"}
%li.packing-fee{"bo-if" => "variant.fees.packing"}
%span{"ng-bind" => "::'sales_fee' | t"}
%li.packing-fee{"ng-if" => "::variant.fees.packing"}
.right {{ variant.fees.packing | localizeCurrency }}
%span{"bo-text" => "'packing_fee' | t"}
%li.transport-fee{"bo-if" => "variant.fees.transport"}
%span{"ng-bind" => "::'packing_fee' | t"}
%li.transport-fee{"ng-if" => "::variant.fees.transport"}
.right {{ variant.fees.transport | localizeCurrency }}
%span{"bo-text" => "'transport_fee' | t"}
%li.fundraising-fee{"bo-if" => "variant.fees.fundraising"}
%span{"ng-bind" => "::'transport_fee' | t"}
%li.fundraising-fee{"ng-if" => "::variant.fees.fundraising"}
.right {{ variant.fees.fundraising | localizeCurrency }}
%span{"bo-text" => "'fundraising_fee' | t"}
%span{"ng-bind" => "::'fundraising_fee' | t"}
%li.total
%strong
.right = {{ variant.price_with_fees | localizeCurrency }}
&nbsp;
%a{"ng-click" => "expanded = !expanded"}
%span{"bo-text" => "'price_graph' | t"}
%a{"ng-click" => "expanded = !expanded"}
%span{"ng-bind" => "::'price_graph' | t"}
%i.ofn-i_006-caret-up

View File

@@ -1,26 +1,26 @@
.row{bindonce: true}
.row
.columns.small-12.large-6.product-header
%h3{"bo-text" => "product.name"}
%h3{"ng-bind" => "::product.name"}
%span
%em {{'products_from' | t}}
%span{"bo-text" => "enterprise.name"}
%span{"ng-bind" => "::enterprise.name"}
%br
.filter-shopfront.taxon-selectors.inline-block
%filter-selector{ objects: "[product] | taxonsOf" }
%filter-selector{ 'selector-set' => "productTaxonSelectors", objects: "[product] | taxonsOf" }
.filter-shopfront.property-selectors.inline-block
%filter-selector{ objects: "[product] | propertiesWithValuesOf" }
%filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" }
%div{"ng-if" => "product.description"}
%hr
%p.text-small{"bo-text" => "product.description"}
%p.text-small{"ng-bind" => "::product.description"}
%hr
.columns.small-12.large-6
%img.product-img{"bo-src" => "product.largeImage", "bo-if" => "product.largeImage"}
%img.product-img.placeholder{"bo-src" => "'/assets/noimage/large.png'", "bo-if" => "!product.largeImage"}
%img.product-img{"ng-src" => "{{::product.largeImage}}", "ng-if" => "::product.largeImage"}
%img.product-img.placeholder{ src: "/assets/noimage/large.png", "ng-if" => "::!product.largeImage"}
%ng-include{src: "'partials/close.html'"}

View File

@@ -1,19 +1,19 @@
.container#registration-details{bindonce: true}
.container#registration-details
%ng-include{ src: "'registration/steps.html'" }
.row
.small-12.columns
%header
%h2 {{'registration_detail_headline' | t}}
%h5{ bo: { if: "enterprise.type != 'own'" } } {{'registration_detail_enterprise' | t}}
%h5{ bo: { if: "enterprise.type == 'own'" } } {{'registration_detail_producer' | t}}
%h5{ ng: { if: "::enterprise.type != 'own'" } } {{'registration_detail_enterprise' | t}}
%h5{ ng: { if: "::enterprise.type == 'own'" } } {{'registration_detail_producer' | t}}
%form{ name: 'details', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('contact',details)" } }
.row
.small-12.medium-9.large-12.columns.end
.field
%label{ for: 'enterprise_name', bo: { if: "enterprise.type != 'own'" } } {{'registration_detail_name_enterprise' | t}}
%label{ for: 'enterprise_name', bo: { if: "enterprise.type == 'own'" } } {{'registration_detail_name_producer' | t}}
%label{ for: 'enterprise_name', ng: { if: "::enterprise.type != 'own'" } } {{'registration_detail_name_enterprise' | t}}
%label{ for: 'enterprise_name', ng: { if: "::enterprise.type == 'own'" } } {{'registration_detail_name_producer' | t}}
%input.chunky{ id: 'enterprise_name', name: 'name', placeholder: "{{'registration_detail_name_placeholder' | t}}", required: true, ng: { model: 'enterprise.name' } }
%span.error{ ng: { show: "details.name.$error.required && submitted" } }
{{'registration_detail_name_error' | t}}

View File

@@ -10,6 +10,6 @@
.small-12.columns.text-center
%h4{ "ng-bind" => "'registration_finished_activate' | t:{enterprise: enterprise.name}" }
%p{ "ng-bind-html" => "t('registration_finished_activate_instruction_html', {email: enterprise.email})"}
%p{ "ng-bind-html" => "'registration_finished_activate_instruction_html' | t:{email: enterprise.email}"}
%a.button.primary{ type: "button", href: "/" } {{'registration_finished_action' | t}} &gt;

View File

@@ -1,4 +1,4 @@
.container#registration-type{bindonce: true}
.container#registration-type
%ng-include{ src: "'registration/steps.html'" }
@@ -10,7 +10,7 @@
{{'registration_type_question' | t}}
%form{ name: 'type', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(type)" } }
.row#enterprise-types{ 'data-equalizer' => true, bo: { if: "enterprise.type != 'own'" } }
.row#enterprise-types{ 'data-equalizer' => true, ng: { if: "::enterprise.type != 'own'" } }
.small-12.columns.field
.row
.small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true }

View File

@@ -1,61 +1,26 @@
.variants.row
.small-12.medium-4.large-4.columns.variant-name
.table-cell
.table-cell
.inline {{ variant.name_to_display }}
.bulk-buy.inline{"bo-if" => "variant.product.group_buy"}
.bulk-buy.inline{"ng-if" => "::variant.product.group_buy"}
%i.ofn-i_056-bulk><
%em><
\ {{'bulk' | t}}
-# WITHOUT GROUP BUY
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"}
%input{type: :number,
integer: true,
value: nil,
min: 0,
placeholder: "0",
"ofn-disable-scroll" => true,
"ng-model" => "variant.line_item.quantity",
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
-# WITH GROUP BUY
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"}
%span.bulk-input-container
%span.bulk-input
%input.bulk.first{type: :number,
value: nil,
integer: true,
min: 0,
"ng-model" => "variant.line_item.quantity",
placeholder: "{{'shop_variant_quantity_min' | t}}",
"ofn-disable-scroll" => true,
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
%span.bulk-input
%input.bulk.second{type: :number,
"ng-disabled" => "!variant.line_item.quantity",
integer: true,
min: 0,
"ng-model" => "variant.line_item.max_quantity",
placeholder: "{{'shop_variant_quantity_max' | t}}",
"ofn-disable-scroll" => true,
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
name: "variant_attributes[{{variant.id}}][max_quantity]"}
%ng-include{src: "'partials/shop_variant_no_group_buy.html'"}
%ng-include{src: "'partials/shop_variant_with_group_buy.html'"}
.small-3.medium-1.large-1.columns.variant-unit
.table-cell
.table-cell
%em {{ variant.unit_to_display }}
.small-4.medium-2.large-2.columns.variant-price
.table-cell.price
%i.ofn-i_009-close
%i.ofn-i_009-close
{{ variant.price_with_fees | localizeCurrency }}
-# Now in a template in app/assets/javascripts/templates !
%price-breakdown{"price-breakdown" => "_", variant: "variant",
%price-breakdown{"price-breakdown" => "_", variant: "variant",
"price-breakdown-append-to-body" => "true",
"price-breakdown-placement" => "left",
"price-breakdown-animation" => true}
@@ -63,4 +28,4 @@
.small-12.medium-2.large-2.columns.total-price.text-right
.table-cell
%strong{"ng-class" => "{filled: variant.totalPrice()}"}
{{ variant.totalPrice() | localizeCurrency }}
{{ variant.totalPrice() | localizeCurrency }}

View File

@@ -1,8 +1,5 @@
%tab#sign-up-content{"ng-controller" => "SignupCtrl",
heading: "{{'label_signup' | t}}",
active: "active(path)",
select: "select(path)"}
%form{"ng-submit" => "submit()"}
%tab#sign-up-content{ heading: "{{'label_signup' | t}}", active: 'tabs.signup.active', select: "select(path)"}
%form{ ng: { controller: "SignupCtrl", submit: "submit()" } }
.row
.large-12.columns
%label{for: "email"} {{'signup_email' | t}}

View File

@@ -9,7 +9,7 @@
*= require admin/spree_promo
*= require shared/jquery-ui-timepicker-addon
*= require shared/textAngular.min
*= require shared/textAngular
*= require shared/ng-tags-input.min
*= require_self

View File

@@ -1,9 +1,14 @@
#save-bar
position: fixed
width: 100%
z-index: 100
bottom: 0px
padding: 8px 10px
left: 0
padding: 8px 8px
font-weight: bold
background-color: #fff
background-color: #eff5fc
color: #5498da
h5
color: #5498da
input
margin-right: 5px

View File

@@ -0,0 +1,3 @@
.tag-with-rules {
color: black;
}

View File

@@ -28,6 +28,9 @@ text-angular .ta-editor {
left: 275px;
}
span.error, div.error {
color: #DA5354;
}
/* Fix conflict between Spree and elRTE's styles */
.el-rte .toolbar {
@@ -37,6 +40,12 @@ text-angular .ta-editor {
input.red {
background-color: #DA5354;
margin-right: 5px;
}
input.orange {
background-color: #FF9848;
margin-right: 5px;
}
input.search {

View File

@@ -13,10 +13,6 @@ input.show-dirty {
}
}
span.error {
color: #DA5354;
}
input, div {
&.update-error {
border: solid 1px #DA5354;

View File

@@ -11,20 +11,20 @@
padding-bottom: 0em
display: table
line-height: 1.1
// outline: 1px solid red
// outline: 1px solid red
@media all and (max-width: 768px)
font-size: 0.875rem
@media all and (max-width: 768px)
font-size: 0.875rem
@media all and (max-width: 640px)
font-size: 0.75rem
@media all and (max-width: 640px)
font-size: 0.75rem
.table-cell
display: table-cell
vertical-align: middle
height: 37px
// ROW VARIANTS
// ROW VARIANTS
.row.variants
margin-left: 0
margin-right: 0
@@ -35,7 +35,10 @@
background-color: #f9f9f9
&:hover, &:focus, &:active
background-color: $clr-brick-ultra-light
&.out-of-stock
opacity: 0.2
// Variant name
.variant-name
padding-left: 7.9375rem
@@ -52,7 +55,7 @@
height: 27px
// Variant unit
.variant-unit
.variant-unit
padding-left: 0rem
padding-right: 0rem
color: #888
@@ -88,18 +91,18 @@
margin-left: 0
margin-right: 0
background: #fff
.columns
padding-top: 1em
padding-bottom: 1em
line-height: 1
@media all and (max-width: 768px)
padding-top: 0.65rem
padding-bottom: 0.65rem
.summary-header
padding-left: 7.9375rem
padding-left: 7.9375rem
@media all and (max-width: 768px)
padding-left: 4.9375rem
@media all and (max-width: 640px)
@@ -118,4 +121,3 @@
color: $clr-brick
i
font-size: 0.8em

View File

@@ -32,6 +32,13 @@
.debit
color: $clr-brick
.invalid
color: $ofn-grey
.credit
color: $ofn-grey
.debit
color: $ofn-grey
.distributor-balance.paid
visibility: hidden

View File

@@ -22,7 +22,29 @@
background: rgba(255,255,255,0.85)
width: 50%
margin-top: 1.2rem
margin-left: 1rem
@media all and (max-width: 768px)
width: 80%
&:active, &:focus, &.active
background: rgba(255,255,255, 1)
.map-footer
position: fixed
z-index: 2
width: 100%
height: 23px
left: 80px
right: 0
bottom: 6px
margin: 0
padding: 6px
font-size: 14px
font-weight: bold
text-shadow: 2px 2px #aaa
color: #fff
a, a:hover, a:active, a:focus
color: #fff
@media all and (max-width: 1025px)
left: 0px

View File

@@ -84,16 +84,25 @@
padding-right: 0rem
font-size: 0.8rem
.shopfront_message, .shopfront_closed_message, .shopfront_hidden_message
.alert-box.shopfront-message
border: 2px solid $clr-turquoise
border-radius: 5px
background-color: $clr-turquoise-light
color: $clr-turquoise
a
color: #0096ad
&:hover, &:focus, &:active
text-decoration: none
color: #4aadbd
.shopfront_closed_message, .shopfront_hidden_message
padding: 15px
border-radius: 5px
.shopfront_message, .shopfront_closed_message
.shopfront_closed_message
border: 2px solid #eb4c46
.shopfront_message
margin-top: 2em
.shopfront_closed_message
margin: 2em 0em

View File

@@ -74,6 +74,9 @@ table.order-summary
padding-left: 5px
padding-right: 5px
.text-right
text-align: right
.social .soc-btn
padding: 3px 7px
font-size: 12px

Some files were not shown because too many files have changed in this diff Show More