mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Merge pull request #5426 from luisramos0/3-0-stable-may13
[Spree 2.1] Merge master into 3-0-stable
This commit is contained in:
@@ -52,3 +52,5 @@ The default admin user is 'ofn@example.com' with 'ofn123' password.
|
||||
Check the app in the browser at `http://localhost:3000`.
|
||||
|
||||
You will then get the trace of the containers in the terminal. You can stop the containers using Ctrl-C in the terminal.
|
||||
|
||||
You can find some useful tips and commands [here](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Docker:-useful-tips-and-commands).
|
||||
|
||||
@@ -27,6 +27,9 @@ RUN sh -c "echo 'deb https://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main'
|
||||
apt-get update && \
|
||||
apt-get install -yqq --no-install-recommends postgresql-client-9.5 libpq-dev
|
||||
|
||||
# Install node
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# Install Chrome
|
||||
RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
|
||||
sh -c "echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' >> /etc/apt/sources.list.d/google-chrome.list" && \
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
This is a general guide to setting up an Open Food Network development environment on your local machine.
|
||||
|
||||
The fastest way to make it work locally is to use Docker, see the [Docker setup guide](DOCKER.md).
|
||||
|
||||
The following guides are located in the wiki and provide more OS-specific step-by-step instructions:
|
||||
|
||||
- [Ubuntu Setup Guide][ubuntu]
|
||||
@@ -11,7 +13,7 @@ The following guides are located in the wiki and provide more OS-specific step-b
|
||||
### Dependencies
|
||||
|
||||
* Rails 3.2.x
|
||||
* Ruby 2.1.9
|
||||
* Ruby 2.3.7
|
||||
* PostgreSQL database
|
||||
* PhantomJS (for testing)
|
||||
* See Gemfile for a list of gems required
|
||||
@@ -58,10 +60,10 @@ Now, your dreams of spinning up a development server can be realised:
|
||||
|
||||
bundle exec rails server
|
||||
|
||||
To login as Spree default user, use:
|
||||
To login as the default user, use:
|
||||
|
||||
email: spree@example.com
|
||||
password: spree123
|
||||
email: ofn@example.com
|
||||
password: ofn123
|
||||
|
||||
### Testing
|
||||
|
||||
|
||||
1
Gemfile
1
Gemfile
@@ -11,6 +11,7 @@ gem 'rails_safe_tasks', '~> 1.0'
|
||||
gem "activerecord-import"
|
||||
|
||||
gem "catalog", path: "./engines/catalog"
|
||||
gem 'dfc_provider', path: './engines/dfc_provider'
|
||||
gem "order_management", path: "./engines/order_management"
|
||||
gem 'web', path: './engines/web'
|
||||
|
||||
|
||||
10
Gemfile.lock
10
Gemfile.lock
@@ -60,6 +60,13 @@ PATH
|
||||
specs:
|
||||
catalog (0.0.1)
|
||||
|
||||
PATH
|
||||
remote: engines/dfc_provider
|
||||
specs:
|
||||
dfc_provider (0.0.1)
|
||||
jwt (~> 2.2)
|
||||
rspec (~> 3.9)
|
||||
|
||||
PATH
|
||||
remote: engines/order_management
|
||||
specs:
|
||||
@@ -198,7 +205,7 @@ GEM
|
||||
activerecord (>= 3.2.0, < 5.0)
|
||||
fog (~> 1.0)
|
||||
rails (>= 3.2.0, < 5.0)
|
||||
ddtrace (0.35.0)
|
||||
ddtrace (0.35.2)
|
||||
msgpack
|
||||
debugger-linecache (1.2.0)
|
||||
delayed_job (4.1.8)
|
||||
@@ -712,6 +719,7 @@ DEPENDENCIES
|
||||
delayed_job_web
|
||||
devise (~> 3.0.1)
|
||||
devise-encryptable
|
||||
dfc_provider!
|
||||
diffy
|
||||
eventmachine (>= 1.2.3)
|
||||
factory_bot_rails (= 4.10.0)
|
||||
|
||||
BIN
app/assets/images/icn-close.png
Normal file
BIN
app/assets/images/icn-close.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 540 B |
BIN
app/assets/images/icn-search-grey.png
Normal file
BIN
app/assets/images/icn-search-grey.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 534 B |
2
app/assets/images/map_009-cluster.svg
Normal file
2
app/assets/images/map_009-cluster.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="28px" height="33px" enable-background="new 0 0 28 33" version="1.1" viewBox="0 0 28 33" xmlns="http://www.w3.org/2000/svg"><path d="M14,25c-6.059,0-10.988,1.679-10.988,3.333c0,2.485,10.307,4.542,10.746,4.643 C13.828,32.992,13.914,33,14,33c0.084,0,0.17-0.008,0.239-0.023c0.439-0.099,10.749-2.114,10.749-4.643 C24.988,26.679,20.059,25,14,25z" fill="#282828" opacity=".25"/><path d="M14,0C6.28,0,0,6.717,0,13.332c0,9.941,13.132,18.169,13.691,18.572C13.78,31.968,13.891,32,14,32 c0.107,0,0.217-0.031,0.305-0.094C14.864,31.511,28,23.45,28,13.332C28,6.717,21.72,0,14,0z" fill="#fff"/><g><g fill="#0b8c61"><path d="m14 0c-7.72 0-14 6.717-14 13.333 0 9.941 13.132 18.169 13.691 18.571 0.089 0.064 0.2 0.096 0.309 0.096 0.107 0 0.217-0.031 0.305-0.094 0.559-0.395 13.695-8.456 13.695-18.573 0-6.616-6.28-13.333-14-13.333zm9.5 12.057c0 0.863-0.567 1.661-1.325 1.942l-1.025 5.889c-0.015 0.976-0.889 1.831-1.94 1.831h-10.466c-1.052 0-1.925-0.855-1.947-1.906l-1.024-5.828c-0.737-0.294-1.273-1.075-1.273-1.928v-0.827c0-1.074 0.874-1.948 1.948-1.948h2.302l1.396-2.247c0.4-0.698 1.417-0.978 2.145-0.555 0.755 0.435 1.015 1.403 0.58 2.159l-0.41 0.642h2.662l-0.39-0.61c-0.227-0.391-0.284-0.823-0.174-1.235 0.109-0.406 0.37-0.745 0.734-0.955 0.716-0.417 1.739-0.148 2.159 0.579l1.381 2.223h2.718c1.074 0 1.948 0.874 1.948 1.948v0.826z"/><rect x="3.6006" y="6.9915" width="20.415" height="15.521"/><rect x="8.9841" y="5.6631" width="10.487" height="3.6356"/></g><text x="5.0698848" y="20.243376" fill="#000000" font-family="sans-serif" font-size="16px" style="line-height:1.25" xml:space="preserve"><tspan x="5.0698848" y="20.243376" fill="#ffffff" font-family="Arial" font-size="16px" font-weight="bold">1+</tspan></text></g></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -29,7 +29,7 @@ angular.module("admin.enterprises")
|
||||
# from a directive "nav-check" in the page - if we pass it here it will be called in the test suite,
|
||||
# and on all new uses of this contoller, and we might not want that.
|
||||
enterpriseNavCallback = ->
|
||||
if $scope.enterprise_form != undefined && $scope.enterprise_form.$dirty
|
||||
if $scope.enterprise_form?.$dirty
|
||||
t('admin.unsaved_confirm_leave')
|
||||
|
||||
# Register the NavigationCheck callback
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
angular.module('admin.orderCycles')
|
||||
.controller 'AdminEditOrderCycleCtrl', ($scope, $controller, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor, ocInstance) ->
|
||||
.controller 'AdminEditOrderCycleCtrl', ($scope, $controller, $filter, $location, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor, NavigationCheck, ocInstance) ->
|
||||
$controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance})
|
||||
|
||||
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||
@@ -18,5 +18,12 @@ angular.module('admin.orderCycles')
|
||||
|
||||
$scope.submit = ($event, destination) ->
|
||||
$event.preventDefault()
|
||||
NavigationCheck.clear()
|
||||
StatusMessage.display 'progress', t('js.saving')
|
||||
OrderCycle.update(destination, $scope.order_cycle_form)
|
||||
|
||||
warnAboutUnsavedChanges = ->
|
||||
if $scope.order_cycle_form?.$dirty
|
||||
t('admin.unsaved_confirm_leave')
|
||||
|
||||
NavigationCheck.register(warnAboutUnsavedChanges)
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, OrderCycle, OrderCycleResource, FilterSelectorsService, Cart, Dereferencer, Taxons, Properties, currentHub, $timeout) ->
|
||||
Darkswarm.controller "ProductsCtrl", ($scope, $sce, $filter, $rootScope, Products, OrderCycle, OrderCycleResource, FilterSelectorsService, Cart, Dereferencer, Taxons, Properties, currentHub, $timeout) ->
|
||||
$scope.Products = Products
|
||||
$scope.Cart = Cart
|
||||
$scope.query = ""
|
||||
@@ -10,6 +10,7 @@ Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, Ord
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
$scope.supplied_taxons = null
|
||||
$scope.supplied_properties = null
|
||||
$scope.showFilterSidebar = false
|
||||
|
||||
$rootScope.$on "orderCycleSelected", ->
|
||||
$scope.update_filters()
|
||||
@@ -75,15 +76,24 @@ Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, Ord
|
||||
$scope.appliedTaxonsList = ->
|
||||
$scope.activeTaxons.map( (taxon_id) ->
|
||||
Taxons.taxons_by_id[taxon_id].name
|
||||
).join(" #{t('products_or')} ") if $scope.activeTaxons?
|
||||
).join($scope.filtersJoinWord()) if $scope.activeTaxons?
|
||||
|
||||
$scope.appliedPropertiesList = ->
|
||||
$scope.activeProperties.map( (property_id) ->
|
||||
Properties.properties_by_id[property_id].name
|
||||
).join(" #{t('products_or')} ") if $scope.activeProperties?
|
||||
).join($scope.filtersJoinWord()) if $scope.activeProperties?
|
||||
|
||||
$scope.filtersJoinWord = ->
|
||||
$sce.trustAsHtml(" <span class='join-word'>#{t('products_or')}</span> ")
|
||||
|
||||
$scope.clearAll = ->
|
||||
$scope.clearQuery()
|
||||
$scope.clearFilters()
|
||||
|
||||
$scope.clearQuery = ->
|
||||
$scope.query = ""
|
||||
|
||||
$scope.clearFilters = ->
|
||||
$scope.taxonSelectors.clearAll()
|
||||
$scope.propertySelectors.clearAll()
|
||||
|
||||
@@ -94,3 +104,9 @@ Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, Ord
|
||||
$scope.Products.products = []
|
||||
$scope.update_filters()
|
||||
$scope.loadProducts()
|
||||
|
||||
$scope.filtersCount = () ->
|
||||
$scope.taxonSelectors.totalActive() + $scope.propertySelectors.totalActive()
|
||||
|
||||
$scope.toggleFilterSidebar = ->
|
||||
$scope.showFilterSidebar = !$scope.showFilterSidebar
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
Darkswarm.controller "ProducersTabCtrl", ($scope, Shopfront, EnterpriseModal) ->
|
||||
Darkswarm.controller "ProducersTabCtrl", ($scope, Shopfront) ->
|
||||
$scope.shopfront = Shopfront.shopfront
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Darkswarm.directive "darkerBackground", ->
|
||||
restrict: "A"
|
||||
link: (scope, elm, attr)->
|
||||
toggleClass = (value) ->
|
||||
elm.closest('.page-view').toggleClass("with-darker-background", value)
|
||||
|
||||
toggleClass(true)
|
||||
|
||||
# if an OrderCycle is selected, disable darker background
|
||||
scope.$watch 'order_cycle.order_cycle_id', (newvalue, oldvalue) ->
|
||||
toggleClass(false) if newvalue
|
||||
@@ -7,4 +7,4 @@ Darkswarm.directive "enterpriseModal", (EnterpriseModal) ->
|
||||
elem.on "click", (event) =>
|
||||
event.stopPropagation()
|
||||
|
||||
scope.modalInstance = EnterpriseModal.open scope.enterprise
|
||||
scope.modalInstance = EnterpriseModal.open scope.enterprise
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Darkswarm.directive "focusSearch", ->
|
||||
restrict: 'A'
|
||||
link: (scope, element, attr)->
|
||||
element.bind 'click', (event) ->
|
||||
# Focus seach field, ready for typing
|
||||
$(element).siblings('#search').focus()
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService) ->
|
||||
Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $rootScope, $resource, localStorageService, RailsFlashLoader) ->
|
||||
# Handles syncing of current cart/order state to server
|
||||
new class Cart
|
||||
dirty: false
|
||||
@@ -50,7 +50,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo
|
||||
@popQueue() if @update_enqueued
|
||||
|
||||
.error (response, status)=>
|
||||
@scheduleRetry(status)
|
||||
RailsFlashLoader.loadFlash({error: t('js.cart.add_to_cart_failed')})
|
||||
@update_running = false
|
||||
|
||||
compareAndNotifyStockLevels: (stockLevels) =>
|
||||
@@ -87,13 +87,6 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, $modal, $roo
|
||||
max_quantity: li.max_quantity
|
||||
{variants: variants}
|
||||
|
||||
scheduleRetry: (status) =>
|
||||
console.log "Error updating cart: #{status}. Retrying in 3 seconds..."
|
||||
$timeout =>
|
||||
console.log "Retrying cart update"
|
||||
@orderChanged()
|
||||
, 3000
|
||||
|
||||
saved: =>
|
||||
@dirty = false
|
||||
$(window).unbind "beforeunload"
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Darkswarm.factory "EnterpriseListModal", ($modal, $rootScope, $http, EnterpriseModal)->
|
||||
new class EnterpriseListModal
|
||||
open: (enterprises)->
|
||||
scope = $rootScope.$new(true)
|
||||
scope.enterprises = enterprises
|
||||
scope.openModal = EnterpriseModal.open
|
||||
if Object.keys(enterprises).length > 1
|
||||
$modal.open(templateUrl: "enterprise_list_modal.html", scope: scope)
|
||||
else
|
||||
EnterpriseModal.open enterprises[Object.keys(enterprises)[0]]
|
||||
@@ -1,21 +1,39 @@
|
||||
Darkswarm.factory "OfnMap", (Enterprises, EnterpriseModal) ->
|
||||
Darkswarm.factory "OfnMap", (Enterprises, EnterpriseListModal, MapConfiguration) ->
|
||||
new class OfnMap
|
||||
constructor: ->
|
||||
@enterprises = @enterprise_markers(Enterprises.enterprises)
|
||||
@enterprises = @enterprises.filter (enterprise) ->
|
||||
enterprise.latitude != null || enterprise.longitude != null # Remove enterprises w/o lat or long
|
||||
@coordinates = {}
|
||||
@enterprises = Enterprises.enterprises.filter (enterprise) ->
|
||||
# Remove enterprises w/o lat or long
|
||||
enterprise.latitude != null || enterprise.longitude != null
|
||||
@enterprises = @enterprise_markers(@enterprises)
|
||||
|
||||
enterprise_markers: (enterprises) ->
|
||||
@extend(enterprise) for enterprise in enterprises
|
||||
|
||||
enterprise_hash: (hash, enterprise) ->
|
||||
hash[enterprise.id] = { id: enterprise.id, name: enterprise.name, icon: enterprise.icon_font }
|
||||
hash
|
||||
|
||||
extend_marker: (marker, enterprise) ->
|
||||
marker.latitude = enterprise.latitude
|
||||
marker.longitude = enterprise.longitude
|
||||
marker.icon = enterprise.icon
|
||||
marker.id = [enterprise.id]
|
||||
marker.enterprises = @enterprise_hash({}, enterprise)
|
||||
|
||||
# Adding methods to each enterprise
|
||||
extend: (enterprise) ->
|
||||
new class MapMarker
|
||||
# We cherry-pick attributes because GMaps tries to crawl
|
||||
# our data, and our data is cyclic, so it breaks
|
||||
latitude: enterprise.latitude
|
||||
longitude: enterprise.longitude
|
||||
icon: enterprise.icon
|
||||
id: enterprise.id
|
||||
reveal: =>
|
||||
EnterpriseModal.open enterprise
|
||||
marker = @coordinates[[enterprise.latitude, enterprise.longitude]]
|
||||
if marker
|
||||
marker.icon = MapConfiguration.options.cluster_icon
|
||||
@enterprise_hash(marker.enterprises, enterprise)
|
||||
marker.id.push(enterprise.id)
|
||||
else
|
||||
marker = new class MapMarker
|
||||
# We cherry-pick attributes because GMaps tries to crawl
|
||||
# our data, and our data is cyclic, so it breaks
|
||||
reveal: =>
|
||||
EnterpriseListModal.open this.enterprises
|
||||
@extend_marker(marker, enterprise)
|
||||
@coordinates[[enterprise.latitude, enterprise.longitude]] = marker
|
||||
marker
|
||||
|
||||
@@ -4,6 +4,7 @@ Darkswarm.factory "MapConfiguration", ->
|
||||
center:
|
||||
latitude: -37.4713077
|
||||
longitude: 144.7851531
|
||||
cluster_icon: 'assets/map_009-cluster.svg'
|
||||
zoom: 12
|
||||
additional_options:
|
||||
# mapTypeId: 'satellite'
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
%ng-include{src: "'partials/enterprise_listing.html'"}
|
||||
%ng-include{src: "'partials/close.html'"}
|
||||
@@ -1,4 +1,3 @@
|
||||
%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{"ng-bind" => "::selector.object.name"}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.modal-list
|
||||
.row{"ng-repeat" => "(id, enterprise) in ::enterprises"}
|
||||
.highlight
|
||||
.highlight-top.row.enterprise
|
||||
.small-12.medium-12.large-12.columns
|
||||
%h4
|
||||
%a.heading{"ng-click" => "::openModal(enterprise)"}
|
||||
%i{"ng-class" => "enterprise.icon"}
|
||||
%span{"ng-bind" => "enterprise.name"}
|
||||
%img.hero-img{"ng-src" => "{{::enterprise.promo_image}}"}
|
||||
@@ -1,2 +1,2 @@
|
||||
%button.graph-button{"ng-class" => "{open: tt_isOpen}"}
|
||||
%button.graph-button{"ng-class" => "{open: tt_isOpen}", type: 'button'}
|
||||
/ %i.ofn-i_058-graph
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@import "branding";
|
||||
@import "big-input";
|
||||
@import "animations";
|
||||
@import "variables";
|
||||
|
||||
@mixin filter-selector($base-clr, $border-clr, $hover-clr) {
|
||||
&.inline-block, ul.inline-block {
|
||||
@@ -14,7 +15,7 @@
|
||||
@include border-radius(0);
|
||||
|
||||
padding: 0;
|
||||
margin: 0 0 0.25rem 0.25rem;
|
||||
margin: 0 0.5rem 0.5rem 0;
|
||||
|
||||
&:hover, &:focus {
|
||||
background: transparent;
|
||||
@@ -107,26 +108,63 @@
|
||||
// Alert when search, taxon, filter is triggered
|
||||
|
||||
.alert-box.search-alert {
|
||||
background-color: $clr-yellow-light;
|
||||
border-color: $clr-yellow-light;
|
||||
background-color: $white;
|
||||
color: #777;
|
||||
font-size: 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 1em;
|
||||
padding: 0.35em 0 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
|
||||
span.applied-properties {
|
||||
color: #333;
|
||||
.clear-all {
|
||||
color: $grey-500;
|
||||
margin-left: 1.5em;
|
||||
|
||||
&:hover {
|
||||
color: $grey-600;
|
||||
}
|
||||
}
|
||||
|
||||
span.applied-taxons {
|
||||
color: $clr-blue;
|
||||
.no-results-bar {
|
||||
@include breakpoint(desktop) {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
span.applied-search {
|
||||
color: $clr-brick;
|
||||
.no-results {
|
||||
color: $grey-800;
|
||||
font-style: italic;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
span.filter-label {
|
||||
opacity: 0.75;
|
||||
.clear-search {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: $orange-500;
|
||||
font-size: 1.25em;
|
||||
|
||||
&:hover {
|
||||
color: $orange-400;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
color: $grey-800;
|
||||
font-style: italic;
|
||||
|
||||
&.applied-taxons, &.applied-properties {
|
||||
color: $clr-blue;
|
||||
font-weight: bold;
|
||||
|
||||
.join-word {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
&.applied-search {
|
||||
font-weight: bold;
|
||||
color: $teal-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,25 +178,6 @@
|
||||
.filter-shopfront {
|
||||
&.taxon-selectors, &.property-selectors {
|
||||
background: transparent;
|
||||
|
||||
single-line-selectors {
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
.f-dropdown {
|
||||
overflow-x: auto;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
ul, ul li {
|
||||
list-style: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Shopfront taxons
|
||||
@@ -170,4 +189,8 @@
|
||||
&.property-selectors {
|
||||
@include filter-selector(#666, #ccc, #777);
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,6 @@
|
||||
// #search
|
||||
@include placeholder(rgba(0, 0, 0, 0.4), #777);
|
||||
|
||||
input#search {
|
||||
@include medium-input(rgba(0, 0, 0, 0.3), #777, $clr-brick);
|
||||
|
||||
// avoid zoom on iphone, see issue #4535
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
// ordering
|
||||
product {
|
||||
input {
|
||||
@@ -30,22 +23,22 @@
|
||||
border-color: #b3b3b3;
|
||||
text-align: right;
|
||||
|
||||
@media all and (max-width: 1024px) {
|
||||
@include breakpoint(desktop) {
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
width: 7rem;
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
float: left !important;
|
||||
font-size: 0.75rem;
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
width: 5.8rem;
|
||||
}
|
||||
|
||||
@@ -69,15 +62,15 @@
|
||||
input.bulk {
|
||||
width: 5rem;
|
||||
|
||||
@media all and (max-width: 1024px) {
|
||||
@include breakpoint(desktop) {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
width: 3.5rem;
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
width: 2.8rem;
|
||||
}
|
||||
}
|
||||
@@ -93,7 +86,7 @@
|
||||
.bulk-input-container {
|
||||
float: right;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
float: left !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
@import "mixins";
|
||||
@import "typography";
|
||||
@import "variables";
|
||||
|
||||
ordercycle {
|
||||
float: right;
|
||||
background: $grey-050;
|
||||
color: $grey-800;
|
||||
width: 100%;
|
||||
border-radius: 0.5em 0.5em 0 0;
|
||||
border-radius: $radius-medium $radius-medium 0 0;
|
||||
margin-top: 1em;
|
||||
padding: 1em 1.25em 0;
|
||||
|
||||
@@ -17,7 +19,7 @@ ordercycle {
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
|
||||
@media all and (max-width: 1024px) {
|
||||
@include breakpoint(desktop) {
|
||||
float: none;
|
||||
padding: 0.5em 1em;
|
||||
width: 100%;
|
||||
@@ -33,7 +35,7 @@ ordercycle {
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
padding: 0.5em 1em 0.75em;
|
||||
}
|
||||
|
||||
@@ -41,15 +43,15 @@ ordercycle {
|
||||
border: 1px solid $teal-300;
|
||||
display: inline-block;
|
||||
font-size: 1em;
|
||||
border-radius: 0.25em;
|
||||
border-radius: $radius-small;
|
||||
|
||||
.select-label {
|
||||
background-color: rgba($teal-300, 0.5);
|
||||
display: inline-block;
|
||||
border-radius: 0.25em 0 0 0.25em;
|
||||
border-radius: $radius-small 0 0 $radius-small;
|
||||
float: left;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
line-height: 1.3em;
|
||||
padding: 0.5em 0.75em;
|
||||
height: 2.35em;
|
||||
|
||||
@@ -60,6 +62,15 @@ ordercycle {
|
||||
}
|
||||
|
||||
select {
|
||||
background-image: url('/assets/white-caret.svg');
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
select,
|
||||
p {
|
||||
width: inherit;
|
||||
display: inline-block;
|
||||
color: $white;
|
||||
@@ -67,29 +78,34 @@ ordercycle {
|
||||
border: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
line-height: 1.3em;
|
||||
padding: 0.5em 1.25em 0.5em 0.75em;
|
||||
height: 2.35em;
|
||||
background-image: url('/assets/white-caret.svg');
|
||||
background-size: 30px auto;
|
||||
border-radius: 0 0.25em 0.25em 0;
|
||||
border-radius: 0 $radius-small $radius-small 0;
|
||||
min-width: 13em;
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 1024px) {
|
||||
option {
|
||||
color: $grey-700;
|
||||
}
|
||||
|
||||
@include breakpoint(desktop) {
|
||||
float: none;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
float: none;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
@@ -102,7 +118,7 @@ ordercycle {
|
||||
padding: 0.5em 0;
|
||||
|
||||
span {
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
}
|
||||
@@ -136,7 +152,7 @@ shop ordercycle {
|
||||
color: $white;
|
||||
padding: 0 0 12px;
|
||||
|
||||
@media all and (max-width: 1024px) {
|
||||
@include breakpoint(desktop) {
|
||||
float: none;
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0;
|
||||
@@ -144,7 +160,7 @@ shop ordercycle {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
float: none;
|
||||
padding: 0 0 10px;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ button.graph-button {
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
// Hide for small
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import "mixins";
|
||||
@import "branding";
|
||||
@import "animations";
|
||||
|
||||
@@ -14,11 +15,11 @@
|
||||
|
||||
// outline: 1px solid red
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
@@ -56,13 +57,13 @@
|
||||
.variant-name {
|
||||
padding-left: 7.9375rem;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
padding-left: 4.9375rem;
|
||||
}
|
||||
}
|
||||
|
||||
.variant-name {
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
background: #333;
|
||||
color: white;
|
||||
padding-left: 0.9375rem;
|
||||
@@ -82,7 +83,7 @@
|
||||
font-size: 0.875rem;
|
||||
overflow: hidden;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +93,7 @@
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
@@ -106,7 +107,7 @@
|
||||
color: $med-drk-grey;
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
background: #777;
|
||||
color: $disabled-med;
|
||||
|
||||
@@ -132,7 +133,7 @@
|
||||
padding-bottom: 1em;
|
||||
line-height: 1;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
padding-top: 0.65rem;
|
||||
padding-bottom: 0.65rem;
|
||||
}
|
||||
@@ -141,11 +142,11 @@
|
||||
.summary-header {
|
||||
padding-left: 7.9375rem;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
padding-left: 4.9375rem;
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
padding-left: 0.9375rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import "mixins";
|
||||
@import "branding";
|
||||
@import "animations";
|
||||
|
||||
@@ -56,7 +57,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
top: 2px;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
@@ -70,7 +71,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
display: none;
|
||||
width: 0rem;
|
||||
height: 0rem;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import "mixins";
|
||||
|
||||
.darkswarm {
|
||||
products {
|
||||
product {
|
||||
@@ -10,7 +12,7 @@
|
||||
padding-top: 0.25rem;
|
||||
z-index: 999999;
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
background-size: 28px 32px;
|
||||
min-height: 32px;
|
||||
width: 28px;
|
||||
@@ -27,11 +29,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
margin-top: -0.85rem;
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
render-svg {
|
||||
svg {
|
||||
width: 18px;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import "branding";
|
||||
@import "mixins";
|
||||
@import "variables";
|
||||
|
||||
.account-summary {
|
||||
color: #4a4a4a;
|
||||
@@ -99,7 +100,7 @@
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-radius: 0.5em 0.5em 0 0;
|
||||
border-radius: $radius-medium $radius-medium 0 0;
|
||||
|
||||
tr:nth-of-type(even) {
|
||||
background: transparent;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
display: block;
|
||||
border: 0;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
}
|
||||
|
||||
// Generic text resize
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
&, & * {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
@@ -114,7 +114,7 @@
|
||||
.fat > div {
|
||||
border-top: 1px solid #aaa;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
margin-top: 2px;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
box-shadow: none;
|
||||
color: $inputactv;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,14 +38,18 @@ $med-drk-grey: #444;
|
||||
$dark-grey: #333;
|
||||
$light-grey: #ddd;
|
||||
$light-grey-transparency: rgba(0, 0, 0, .1);
|
||||
$very-light-grey-transparency: rgba(0, 0, 0, .05);
|
||||
$black: #000;
|
||||
$white: #fff;
|
||||
|
||||
$grey-050: #f7f7f7;
|
||||
|
||||
$grey-100: #e6e6e6;
|
||||
$grey-200: #ddd;
|
||||
$grey-300: #ccc;
|
||||
$grey-400: #bbb;
|
||||
$grey-500: #999;
|
||||
$grey-600: #777;
|
||||
$grey-650: #666;
|
||||
$grey-700: #555;
|
||||
$grey-800: #333;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
checkout {
|
||||
display: block;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
&.row .row {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
@@ -24,7 +24,7 @@ checkout {
|
||||
.button, table {
|
||||
width: 100%;
|
||||
}
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
form.edit_order {
|
||||
border: 1px solid $disabled-bright;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import "mixins";
|
||||
@import 'typography';
|
||||
|
||||
section {
|
||||
@@ -34,7 +35,7 @@ section {
|
||||
@include headingFont;
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
location, location + small {
|
||||
display: block;
|
||||
}
|
||||
@@ -44,7 +45,7 @@ section {
|
||||
margin-top: 0;
|
||||
padding-top: 0.45em;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import "mixins";
|
||||
@import "typography";
|
||||
|
||||
$large-menu-height: 4.6875rem;
|
||||
@@ -97,7 +98,7 @@ body.embedded {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
nav.top-bar {
|
||||
height: 3.4rem;
|
||||
padding: 0.2rem $gutter-width;
|
||||
@@ -141,7 +142,7 @@ body.embedded {
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
ul.left li.powered-by span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
h2 {
|
||||
font-size: 52px;
|
||||
}
|
||||
@@ -87,7 +87,7 @@
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
.row .row {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -139,7 +139,7 @@
|
||||
font-weight: 300;
|
||||
color: $brand-colour;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
font-size: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
}
|
||||
|
||||
//Hub Link
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
a.hub {
|
||||
display: block;
|
||||
}
|
||||
@@ -67,7 +67,7 @@
|
||||
.active_table_row {
|
||||
border: 1px solid transparent;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
border-color: $clr-brick-light;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
}
|
||||
|
||||
&.open, &.closed {
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
.active_table_row:first-child .skinny-head {
|
||||
background-color: $clr-brick-light;
|
||||
|
||||
@@ -164,7 +164,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
.active_table_row:first-child .skinny-head {
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
@@ -218,7 +218,7 @@
|
||||
}
|
||||
|
||||
// Small devices
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
.active_table_row:first-child .skinny-head {
|
||||
background-color: $disabled-bright;
|
||||
}
|
||||
@@ -226,7 +226,7 @@
|
||||
}
|
||||
|
||||
// Small devices
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
.active_table_row, .active_table_row:first-child, .active_table_row:last-child {
|
||||
border-color: $disabled-bright;
|
||||
background-color: transparent;
|
||||
@@ -253,7 +253,7 @@
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
&.placeholder {
|
||||
opacity: 0.35;
|
||||
|
||||
@media all and (max-width: 1024px) {
|
||||
@include breakpoint(desktop) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@
|
||||
max-height: 260px;
|
||||
overflow: hidden;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
min-height: 68px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Place all the styles related to the map controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
@import "mixins";
|
||||
@import "big-input";
|
||||
|
||||
.map-container {
|
||||
@@ -29,7 +30,7 @@
|
||||
margin-top: 1.2rem;
|
||||
margin-left: 1rem;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ nav.top-bar {
|
||||
.tab-bar {
|
||||
background-color: white;
|
||||
border-bottom: 1px solid $light-grey-transparency;
|
||||
height: 2.8em;
|
||||
height: $mobile-nav-height;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
@@ -210,6 +210,10 @@ nav.top-bar {
|
||||
}
|
||||
}
|
||||
|
||||
.off-canvas-wrap {
|
||||
overflow: inherit;
|
||||
}
|
||||
|
||||
.off-canvas-list li.language-switcher ul li {
|
||||
list-style-type: none;
|
||||
padding-left: 0.5em;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
padding-top: 100px;
|
||||
padding-bottom: 100px;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
padding-top: 25px;
|
||||
}
|
||||
}
|
||||
@@ -255,3 +255,18 @@
|
||||
background-repeat: no-repeat;
|
||||
background-size: 922px 922px;
|
||||
}
|
||||
|
||||
@mixin breakpoint($point) {
|
||||
@if $point == desktop {
|
||||
@media all and (max-width: 1024px) { @content; }
|
||||
}
|
||||
@else if $point == tablet {
|
||||
@media all and (max-width: 768px) { @content; }
|
||||
}
|
||||
@else if $point == phablet {
|
||||
@media all and (max-width: 640px) { @content; }
|
||||
}
|
||||
@else if $point == mobile {
|
||||
@media all and (max-width: 480px) { @content; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import "branding";
|
||||
@import "mixins";
|
||||
@import 'branding';
|
||||
@import 'mixins';
|
||||
@import 'admin/globals/variables';
|
||||
|
||||
// Generic styles for use
|
||||
|
||||
@@ -22,6 +23,24 @@
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.modal-list {
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid $light-grey;
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
a.heading {
|
||||
color: $color-link;
|
||||
|
||||
&:hover {
|
||||
color: $color-link-hover;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enterprise promo image and text
|
||||
|
||||
.highlight {
|
||||
@@ -54,10 +73,16 @@
|
||||
color: $clr-brick;
|
||||
}
|
||||
|
||||
&.enterprise {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 2.4;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
@@ -193,7 +218,7 @@
|
||||
display: inline-block;
|
||||
border-bottom: 1px solid transparent;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import "mixins";
|
||||
@import "branding";
|
||||
@import "animations";
|
||||
@import "compass/css3/transition";
|
||||
@@ -19,7 +20,7 @@ $page-alert-height: 55px;
|
||||
margin: 0;
|
||||
|
||||
h6 {
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
font-size: 10px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
.producers {
|
||||
.active_table .active_table_node {
|
||||
// Header row
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
.skinny-head {
|
||||
background-color: $clr-turquoise-light;
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
.active_table_row.closed {
|
||||
border: 1px solid transparent;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
border-color: $clr-turquoise-light;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,18 +11,98 @@
|
||||
@import "shop-taxon-flag";
|
||||
@import "shop-popovers";
|
||||
|
||||
$sidebar-small-width: 75%;
|
||||
$sidebar-medium-width: 65%;
|
||||
$sidebar-large-width: 45%;
|
||||
$sidebar-footer-height: 5em;
|
||||
|
||||
.darkswarm {
|
||||
.shop-filters-sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 200;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: $shop-sidebar-overlay;
|
||||
opacity: 0;
|
||||
transition: opacity $transition-sidebar;
|
||||
}
|
||||
|
||||
&.shown {
|
||||
.background {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sidebar, .sidebar-footer {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 210;
|
||||
height: 100%;
|
||||
width: $sidebar-large-width;
|
||||
margin-right: -$sidebar-large-width;
|
||||
background-color: rgba($white, 0.95);
|
||||
padding: 1em;
|
||||
transition: margin $transition-sidebar;
|
||||
overflow-y: scroll;
|
||||
|
||||
.property-selectors {
|
||||
margin-bottom: $sidebar-footer-height + 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
background-color: $grey-800;
|
||||
width: $sidebar-large-width;
|
||||
margin-right: -$sidebar-large-width;
|
||||
height: $sidebar-footer-height;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
transition: margin $transition-sidebar;
|
||||
padding: 1em;
|
||||
|
||||
button {
|
||||
width: 48%;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
.sidebar, .sidebar-footer {
|
||||
width: $sidebar-medium-width;
|
||||
margin-right: -$sidebar-medium-width;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(mobile) {
|
||||
.sidebar, .sidebar-footer {
|
||||
width: $sidebar-small-width;
|
||||
margin-right: -$sidebar-small-width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
products {
|
||||
display: block;
|
||||
padding-top: 20px;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
input.button.right {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
.add_to_cart {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
@@ -69,7 +149,7 @@
|
||||
.bulk-buy {
|
||||
font-size: 0.875rem;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +172,7 @@
|
||||
font-size: 0.75em;
|
||||
padding-right: 0.9375rem;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
@@ -114,7 +194,16 @@
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.shopfront-message {
|
||||
.select-oc-message {
|
||||
margin-top: 1rem;
|
||||
|
||||
.highlighted {
|
||||
color: $red-700;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.open-shop-message {
|
||||
a {
|
||||
color: #0096ad;
|
||||
|
||||
@@ -125,21 +214,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
.shopfront_closed_message, .shopfront_hidden_message {
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
.closed-shop-header {
|
||||
background-color: $grey-650;
|
||||
color: $white;
|
||||
|
||||
h4 {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1rem 0 0.4rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.shopfront_closed_message {
|
||||
border: 2px solid #eb4c46;
|
||||
}
|
||||
.warning-sign {
|
||||
margin: 0 10px 0 5px;
|
||||
display: inline-block;
|
||||
|
||||
.shopfront_closed_message {
|
||||
margin: 2em 0em;
|
||||
}
|
||||
strong {
|
||||
color: $grey-650;
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
width: 23px;
|
||||
}
|
||||
|
||||
.shopfront_hidden_message {
|
||||
border: 2px solid #db4;
|
||||
margin: 2em 0em;
|
||||
.rectangle {
|
||||
background-color: $white;
|
||||
border-radius: 4px;
|
||||
color: $grey-650;
|
||||
height: 23px;
|
||||
position: absolute;
|
||||
top: 27px;
|
||||
transform: rotate(-315deg);
|
||||
width: 23px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
75
app/assets/stylesheets/darkswarm/shop_search.css.scss
Normal file
75
app/assets/stylesheets/darkswarm/shop_search.css.scss
Normal file
@@ -0,0 +1,75 @@
|
||||
@import "mixins";
|
||||
@import "branding";
|
||||
@import "variables";
|
||||
|
||||
.shop-searchbar {
|
||||
background-color: $grey-100;
|
||||
height: 5em;
|
||||
padding: 1em 0;
|
||||
margin-bottom: 1em;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
|
||||
.search-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
|
||||
.clear {
|
||||
height: 1em;
|
||||
width: 1em;
|
||||
margin-top: 1em;
|
||||
position: absolute;
|
||||
right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
input#search {
|
||||
height: 3em;
|
||||
border-radius: $radius-small;
|
||||
border: solid 1px $grey-300;
|
||||
margin: 0;
|
||||
padding: 0 2.25em 0 2.75em;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
background: $white url("/assets/icn-search-grey.png") 1em center no-repeat;
|
||||
font-size: 1rem; // avoid zoom on iphone, see issue #4535
|
||||
|
||||
&::placeholder {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
// Remove conflicting "clear search" buttons added by Chrome
|
||||
&::-webkit-search-decoration,
|
||||
&::-webkit-search-cancel-button,
|
||||
&::-webkit-search-results-button,
|
||||
&::-webkit-search-results-decoration {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: $grey-600;
|
||||
margin-left: 1em;
|
||||
height: 3em;
|
||||
width: 7em;
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
border-radius: $radius-small;
|
||||
transition: none;
|
||||
|
||||
&:hover {
|
||||
background-color: $grey-700;
|
||||
}
|
||||
|
||||
@include breakpoint(mobile) {
|
||||
margin-left: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(desktop) {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: $mobile-nav-height;
|
||||
}
|
||||
}
|
||||
@@ -8,16 +8,18 @@
|
||||
.tab-buttons {
|
||||
color: $dark-grey;
|
||||
box-shadow: $distributor-header-shadow;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
.columns {
|
||||
display: flex;
|
||||
|
||||
@media all and (max-width: 1024px) {
|
||||
@include breakpoint(desktop) {
|
||||
display: table;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media all and (max-width: 480px) {
|
||||
@include breakpoint(mobile) {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
@@ -54,7 +56,7 @@
|
||||
background: none;
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
padding: 0.35em 0 0.65em 0;
|
||||
}
|
||||
}
|
||||
@@ -67,7 +69,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 1024px) {
|
||||
@include breakpoint(desktop) {
|
||||
display: table-cell;
|
||||
width: auto;
|
||||
}
|
||||
@@ -76,9 +78,9 @@
|
||||
// content revealed in accordion
|
||||
|
||||
.page-view {
|
||||
margin-bottom: 5em;
|
||||
background: none;
|
||||
border: none;
|
||||
padding-bottom: 5em;
|
||||
|
||||
.content {
|
||||
padding: 1.25em 0;
|
||||
@@ -104,7 +106,7 @@
|
||||
p {
|
||||
max-width: 100%;
|
||||
|
||||
@media all and (max-width: 768px) {
|
||||
@include breakpoint(tablet) {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
@@ -121,5 +123,13 @@
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&.with-darker-background {
|
||||
background-color: $very-light-grey-transparency;
|
||||
|
||||
a {
|
||||
color: $teal-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
.tab {
|
||||
text-align: center;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
padding: 1em;
|
||||
border: none;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
padding: 0.35em 0 0.65em 0;
|
||||
text-shadow: none;
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
border-bottom: 4px solid $clr-brick-bright;
|
||||
cursor: pointer;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
transition: none;
|
||||
color: white;
|
||||
background-color: $clr-brick-bright;
|
||||
@@ -46,7 +46,7 @@
|
||||
a {
|
||||
color: $clr-brick-bright;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
@@ -55,14 +55,14 @@
|
||||
&.selected {
|
||||
border-bottom: 4px solid $clr-brick;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
background-color: $clr-brick;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $clr-brick;
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 640px) {
|
||||
@include breakpoint(phablet) {
|
||||
render-svg {
|
||||
svg {
|
||||
width: 24px;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@import "branding";
|
||||
@import "mixins";
|
||||
@import "typography";
|
||||
@import "variables";
|
||||
|
||||
// Button class extensions
|
||||
|
||||
@@ -123,9 +124,37 @@ button.success, .button.success {
|
||||
}
|
||||
}
|
||||
|
||||
button.large {
|
||||
height: 3em;
|
||||
font-size: 1em;
|
||||
color: $white;
|
||||
border-radius: $radius-medium;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&.dark {
|
||||
background-color: $grey-800;
|
||||
border: 1px solid $grey-600;
|
||||
}
|
||||
|
||||
&.bright {
|
||||
background-color: $orange-500;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive
|
||||
@media screen and (min-width: 768px) {
|
||||
[role="main"] {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.no-gutter {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
@@ -30,3 +30,11 @@ $topbar-dropdown-link-color: $black;
|
||||
$topbar-dropdown-bg: $white;
|
||||
$topbar-dropdown-link-bg: $white;
|
||||
$topbar-dropdown-link-bg-hover: $white;
|
||||
|
||||
$mobile-nav-height: 2.8em;
|
||||
|
||||
$radius-small: 0.25em;
|
||||
$radius-medium: 0.5em;
|
||||
|
||||
$shop-sidebar-overlay: rgba(0, 0, 0, 0.5);
|
||||
$transition-sidebar: 250ms ease-in-out 0s;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
module Api
|
||||
class OrderCyclesController < Api::BaseController
|
||||
include EnterprisesHelper
|
||||
respond_to :json
|
||||
include ApiActionCaching
|
||||
|
||||
skip_authorization_check
|
||||
skip_before_filter :authenticate_user, :ensure_api_key, only: [:taxons, :properties]
|
||||
|
||||
caches_action :taxons, :properties,
|
||||
expires_in: CacheService::FILTERS_EXPIRY,
|
||||
cache_path: proc { |controller| controller.request.url }
|
||||
|
||||
def products
|
||||
render_no_products unless order_cycle.open?
|
||||
return render_no_products unless order_cycle.open?
|
||||
|
||||
products = ProductsRenderer.new(
|
||||
distributor,
|
||||
|
||||
@@ -18,9 +18,9 @@ class HomeController < BaseController
|
||||
|
||||
private
|
||||
|
||||
# Cache the value of the query count for 24 hours
|
||||
def cached_count(key, query)
|
||||
Rails.cache.fetch("home_stats_count_#{key}", expires_in: 1.day, race_condition_ttl: 10) do
|
||||
# Cache the value of the query count
|
||||
def cached_count(statistic, query)
|
||||
CacheService.home_stats(statistic) do
|
||||
query.count
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ module ShopHelper
|
||||
end
|
||||
|
||||
def require_customer?
|
||||
current_distributor.require_login? && !user_is_related_to_distributor?
|
||||
@require_customer ||= current_distributor.require_login? && !user_is_related_to_distributor?
|
||||
end
|
||||
|
||||
def user_is_related_to_distributor?
|
||||
@@ -48,6 +48,6 @@ module ShopHelper
|
||||
end
|
||||
|
||||
def no_open_order_cycles?
|
||||
@order_cycles && @order_cycles.empty?
|
||||
@no_open_order_cycles ||= @order_cycles&.empty?
|
||||
end
|
||||
end
|
||||
|
||||
18
app/jobs/job_logger.rb
Normal file
18
app/jobs/job_logger.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: false
|
||||
|
||||
module JobLogger
|
||||
class Formatter < ::Logger::Formatter
|
||||
def call(_severity, timestamp, _progname, msg)
|
||||
time = timestamp.strftime('%FT%T%z')
|
||||
"#{time}: #{msg.is_a?(String) ? msg : msg.inspect}\n"
|
||||
end
|
||||
end
|
||||
|
||||
def self.logger
|
||||
@logger ||= begin
|
||||
logger = Delayed::Worker.logger.clone
|
||||
logger.formatter = Formatter.new
|
||||
logger
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -24,7 +24,7 @@ class SubscriptionConfirmJob
|
||||
|
||||
# Confirm these proxy orders
|
||||
ProxyOrder.where(id: unconfirmed_proxy_orders_ids).each do |proxy_order|
|
||||
Rails.logger.info "Confirming Order for Proxy Order #{proxy_order.id}"
|
||||
JobLogger.logger.info "Confirming Order for Proxy Order #{proxy_order.id}"
|
||||
confirm_order!(proxy_order.order)
|
||||
end
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class SubscriptionPlacementJob
|
||||
end
|
||||
|
||||
def place_order_for(proxy_order)
|
||||
Rails.logger.info "Placing Order for Proxy Order #{proxy_order.id}"
|
||||
JobLogger.logger.info("Placing Order for Proxy Order #{proxy_order.id}")
|
||||
proxy_order.initialise_order!
|
||||
place_order(proxy_order.order)
|
||||
end
|
||||
|
||||
21
app/models/concerns/api_action_caching.rb
Normal file
21
app/models/concerns/api_action_caching.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# API controllers inherit from ActionController::Metal to keep them slim and fast.
|
||||
# This concern adds the minimum requirements needed to use Action Caching in the API.
|
||||
|
||||
module ApiActionCaching
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ActionController::Caching
|
||||
include ActionController::Caching::Actions
|
||||
include AbstractController::Layouts
|
||||
|
||||
# These configs are not assigned to the controller automatically with ActionController::Metal
|
||||
self.cache_store = Rails.configuration.cache_store
|
||||
self.perform_caching = true
|
||||
|
||||
# ActionController::Caching asks for a controller's layout, but they're not used in the API
|
||||
layout false
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
class DistributorShippingMethod < ActiveRecord::Base
|
||||
self.table_name = "distributors_shipping_methods"
|
||||
belongs_to :shipping_method, class_name: Spree::ShippingMethod
|
||||
belongs_to :shipping_method, class_name: Spree::ShippingMethod, touch: true
|
||||
belongs_to :distributor, class_name: Enterprise, touch: true
|
||||
end
|
||||
|
||||
@@ -16,7 +16,8 @@ class OrderCycle < ActiveRecord::Base
|
||||
has_many :suppliers, -> { uniq }, source: :sender, through: :cached_incoming_exchanges
|
||||
has_many :distributors, -> { uniq }, source: :receiver, through: :cached_outgoing_exchanges
|
||||
|
||||
has_and_belongs_to_many :schedules, join_table: 'order_cycle_schedules'
|
||||
has_many :schedules, through: :order_cycle_schedules
|
||||
has_many :order_cycle_schedules
|
||||
has_paper_trail meta: { custom_data: proc { |order_cycle| order_cycle.schedule_ids.to_s } }
|
||||
|
||||
attr_accessor :incoming_exchanges, :outgoing_exchanges
|
||||
|
||||
6
app/models/order_cycle_schedule.rb
Normal file
6
app/models/order_cycle_schedule.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class OrderCycleSchedule < ActiveRecord::Base
|
||||
belongs_to :schedule
|
||||
belongs_to :order_cycle
|
||||
end
|
||||
@@ -1,7 +1,8 @@
|
||||
class Schedule < ActiveRecord::Base
|
||||
has_and_belongs_to_many :order_cycles, join_table: 'order_cycle_schedules'
|
||||
has_paper_trail meta: { custom_data: proc { |schedule| schedule.order_cycle_ids.to_s } }
|
||||
|
||||
has_many :order_cycles, through: :order_cycle_schedules
|
||||
has_many :order_cycle_schedules, dependent: :destroy
|
||||
has_many :coordinators, -> { uniq }, through: :order_cycles
|
||||
|
||||
validates :order_cycles, presence: true
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Spree::Classification.class_eval do
|
||||
belongs_to :product, class_name: "Spree::Product", touch: true
|
||||
belongs_to :taxon, class_name: "Spree::Taxon", touch: true
|
||||
|
||||
before_destroy :dont_destroy_if_primary_taxon
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Spree::Product.class_eval do
|
||||
has_many :option_types, through: :product_option_types, dependent: :destroy
|
||||
|
||||
belongs_to :supplier, class_name: 'Enterprise', touch: true
|
||||
belongs_to :primary_taxon, class_name: 'Spree::Taxon'
|
||||
belongs_to :primary_taxon, class_name: 'Spree::Taxon', touch: true
|
||||
|
||||
delegate_belongs_to :master, :unit_value, :unit_description
|
||||
delegate :images_attributes=, :display_as=, to: :master
|
||||
|
||||
@@ -11,6 +11,10 @@ module Spree
|
||||
end
|
||||
|
||||
def total_on_hand
|
||||
# Associated stock_items no longer exist if the variant has been soft-deleted. A variant
|
||||
# may still be in an active cart after it's deleted, so this will mark it as out of stock.
|
||||
return 0 if @variant.deleted?
|
||||
|
||||
stock_items.sum(&:count_on_hand)
|
||||
end
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ class Api::Admin::OrderSerializer < ActiveModel::Serializer
|
||||
:edit_path, :state, :payment_state, :shipment_state,
|
||||
:payments_path, :ready_to_ship, :ready_to_capture, :created_at,
|
||||
:distributor_name, :special_instructions,
|
||||
:item_total, :adjustment_total, :payment_total, :total
|
||||
:item_total, :adjustment_total, :payment_total, :total, :display_outstanding_balance
|
||||
|
||||
has_one :distributor, serializer: Api::Admin::IdSerializer
|
||||
has_one :order_cycle, serializer: Api::Admin::IdSerializer
|
||||
@@ -16,6 +16,12 @@ class Api::Admin::OrderSerializer < ActiveModel::Serializer
|
||||
object.distributor.andand.name
|
||||
end
|
||||
|
||||
def display_outstanding_balance
|
||||
return "" if object.outstanding_balance.zero?
|
||||
|
||||
object.display_outstanding_balance.to_s
|
||||
end
|
||||
|
||||
def edit_path
|
||||
return '' unless object.id
|
||||
|
||||
|
||||
@@ -62,10 +62,14 @@ module Api
|
||||
end
|
||||
|
||||
def supplied_taxons
|
||||
return [] unless enterprise.is_primary_producer
|
||||
|
||||
ids_to_objs data.supplied_taxons[enterprise.id]
|
||||
end
|
||||
|
||||
def supplied_properties
|
||||
return [] unless enterprise.is_primary_producer
|
||||
|
||||
(product_properties + producer_properties).uniq do |property_object|
|
||||
property_object.property.presentation
|
||||
end
|
||||
@@ -113,7 +117,7 @@ module Api
|
||||
end
|
||||
|
||||
def active
|
||||
data.active_distributor_ids.andand.include? enterprise.id
|
||||
@active ||= data.active_distributor_ids.andand.include? enterprise.id
|
||||
end
|
||||
|
||||
# Map svg icons.
|
||||
|
||||
@@ -18,7 +18,8 @@ module Api
|
||||
end
|
||||
|
||||
def active
|
||||
enterprise.ready_for_checkout? && OrderCycle.active.with_distributor(enterprise).exists?
|
||||
@active ||=
|
||||
enterprise.ready_for_checkout? && OrderCycle.active.with_distributor(enterprise).exists?
|
||||
end
|
||||
|
||||
def pickup
|
||||
@@ -73,12 +74,16 @@ module Api
|
||||
end
|
||||
|
||||
def supplied_taxons
|
||||
return [] unless enterprise.is_primary_producer
|
||||
|
||||
ActiveModel::ArraySerializer.new(
|
||||
enterprise.supplied_taxons, each_serializer: Api::TaxonSerializer
|
||||
)
|
||||
end
|
||||
|
||||
def supplied_properties
|
||||
return [] unless enterprise.is_primary_producer
|
||||
|
||||
(product_properties + producer_properties).uniq do |property_object|
|
||||
property_object.property.presentation
|
||||
end
|
||||
@@ -118,7 +123,7 @@ module Api
|
||||
private
|
||||
|
||||
def product_properties
|
||||
enterprise.supplied_products.flat_map(&:properties)
|
||||
enterprise.supplied_products.includes(:properties).flat_map(&:properties)
|
||||
end
|
||||
|
||||
def producer_properties
|
||||
|
||||
62
app/services/cache_service.rb
Normal file
62
app/services/cache_service.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CacheService
|
||||
HOME_STATS_EXPIRY = 1.day.freeze
|
||||
FILTERS_EXPIRY = 30.seconds.freeze
|
||||
SHOPS_EXPIRY = 15.seconds.freeze
|
||||
|
||||
def self.cache(cache_key, options = {})
|
||||
Rails.cache.fetch cache_key.to_s, options do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
# Yields a cached query, expired by the most recently updated record for a given class.
|
||||
# E.g: if *any* Spree::Taxon record is updated, all keys based on Spree::Taxon will auto-expire.
|
||||
def self.cached_data_by_class(cache_key, cached_class)
|
||||
Rails.cache.fetch "#{cache_key}-#{cached_class}-#{latest_timestamp_by_class(cached_class)}" do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the :updated_at value of the most recently updated record for a given class, and returns
|
||||
# it as a timestamp, eg: `1583836069`.
|
||||
def self.latest_timestamp_by_class(cached_class)
|
||||
cached_class.maximum(:updated_at).to_i
|
||||
end
|
||||
|
||||
def self.home_stats(statistic)
|
||||
Rails.cache.fetch("home_stats_count_#{statistic}",
|
||||
expires_in: HOME_STATS_EXPIRY,
|
||||
race_condition_ttl: 10) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
module FragmentCaching
|
||||
# Rails' caching in views is called "Fragment Caching" and uses some slightly different logic.
|
||||
# Note: keys supplied here are actually prepended with "views/" under the hood.
|
||||
|
||||
def self.ams_all_taxons_key
|
||||
"inject-all-taxons-#{CacheService.latest_timestamp_by_class(Spree::Taxon)}"
|
||||
end
|
||||
|
||||
def self.ams_all_properties_key
|
||||
"inject-all-properties-#{CacheService.latest_timestamp_by_class(Spree::Property)}"
|
||||
end
|
||||
|
||||
def self.ams_shops
|
||||
[
|
||||
"shops/index/inject_enterprises",
|
||||
{ expires_in: SHOPS_EXPIRY }
|
||||
]
|
||||
end
|
||||
|
||||
def self.ams_shop(enterprise)
|
||||
[
|
||||
"enterprises/shop/inject_enterprise_shopfront-#{enterprise.id}",
|
||||
{ expires_in: SHOPS_EXPIRY }
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -29,11 +29,15 @@ class CartService
|
||||
|
||||
variants_data.each do |variant_data|
|
||||
loaded_variant = loaded_variants[variant_data[:variant_id].to_i]
|
||||
|
||||
if loaded_variant.deleted?
|
||||
remove_deleted_variant(loaded_variant)
|
||||
next
|
||||
end
|
||||
|
||||
next unless varies_from_cart(variant_data, loaded_variant)
|
||||
|
||||
attempt_cart_add(
|
||||
loaded_variant, variant_data[:quantity], variant_data[:max_quantity]
|
||||
)
|
||||
attempt_cart_add(loaded_variant, variant_data[:quantity], variant_data[:max_quantity])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,12 +45,16 @@ class CartService
|
||||
@indexed_variants ||= begin
|
||||
variant_ids_in_data = variants_data.map{ |v| v[:variant_id] }
|
||||
|
||||
Spree::Variant.where(id: variant_ids_in_data).
|
||||
Spree::Variant.with_deleted.where(id: variant_ids_in_data).
|
||||
includes(:default_price, :stock_items, :product).
|
||||
index_by(&:id)
|
||||
end
|
||||
end
|
||||
|
||||
def remove_deleted_variant(variant)
|
||||
line_item_for_variant(variant).andand.destroy
|
||||
end
|
||||
|
||||
def attempt_cart_add(variant, quantity, max_quantity = nil)
|
||||
quantity = quantity.to_i
|
||||
max_quantity = max_quantity.to_i if max_quantity
|
||||
|
||||
@@ -8,7 +8,7 @@ class VariantsStockLevels
|
||||
variant_stock_levels = variant_stock_levels(order.line_items.includes(variant: :stock_items))
|
||||
|
||||
order_variant_ids = variant_stock_levels.keys
|
||||
missing_variants = Spree::Variant.includes(:stock_items).
|
||||
missing_variants = Spree::Variant.with_deleted.includes(:stock_items).
|
||||
where(id: (requested_variant_ids - order_variant_ids))
|
||||
|
||||
missing_variants.each do |missing_variant|
|
||||
|
||||
24
app/views/enterprises/_change_order_cycle.html.haml
Normal file
24
app/views/enterprises/_change_order_cycle.html.haml
Normal file
@@ -0,0 +1,24 @@
|
||||
%div{"ng-controller" => "OrderCycleChangeCtrl", "ng-cloak" => true}
|
||||
%closing
|
||||
%div{"ng-if" => "OrderCycle.selected()"}
|
||||
= t :enterprises_next_closing
|
||||
%strong {{ OrderCycle.orders_close_at() | date_in_words }}
|
||||
%div{"ng-if" => "!OrderCycle.selected()"}
|
||||
= t :enterprises_choose
|
||||
|
||||
.order-cycle-select
|
||||
.select-label
|
||||
%span= t :enterprises_ready_for
|
||||
|
||||
- if oc_select_options.count == 1
|
||||
%p
|
||||
= oc_select_options.first[:time]
|
||||
|
||||
- else
|
||||
%select.select2.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id",
|
||||
"ofn-change-order-cycle" => true,
|
||||
"disabled" => require_customer?,
|
||||
"ng-options" => "oc.id as oc.time for oc in #{oc_select_options.to_json}"}
|
||||
|
||||
- if oc_select_options.count > 1
|
||||
%option{value: "", disabled: "", selected: ""}= t :shopping_oc_select
|
||||
@@ -6,7 +6,8 @@
|
||||
= current_distributor.logo.url
|
||||
|
||||
- content_for :injection_data do
|
||||
= inject_enterprise_shopfront(@enterprise)
|
||||
- cache(*CacheService::FragmentCaching.ams_shop(@enterprise)) do
|
||||
= inject_enterprise_shopfront(@enterprise)
|
||||
|
||||
%shop.darkswarm
|
||||
- if @shopfront_layout == 'embedded'
|
||||
@@ -17,26 +18,7 @@
|
||||
%a.close{ ng: { click: "alert.close()" } } ×
|
||||
|
||||
- content_for :order_cycle_form do
|
||||
|
||||
%div{"ng-controller" => "OrderCycleChangeCtrl", "ng-cloak" => true}
|
||||
%closing
|
||||
%div{"ng-if" => "OrderCycle.selected()"}
|
||||
= t :enterprises_next_closing
|
||||
%strong {{ OrderCycle.orders_close_at() | date_in_words }}
|
||||
%div{"ng-if" => "!OrderCycle.selected()"}
|
||||
= t :enterprises_choose
|
||||
|
||||
.order-cycle-select
|
||||
.select-label
|
||||
%span= t :enterprises_ready_for
|
||||
|
||||
%select.select2.avenir#order_cycle_id{"ng-model" => "order_cycle.order_cycle_id",
|
||||
"ofn-change-order-cycle" => true,
|
||||
"disabled" => require_customer?,
|
||||
"ng-options" => "oc.id as oc.time for oc in #{oc_select_options.to_json}"}
|
||||
|
||||
- if oc_select_options.count > 1
|
||||
%option{value: "", disabled: "", selected: ""}= t :shopping_oc_select
|
||||
= render partial: "change_order_cycle"
|
||||
|
||||
- content_for :ordercycle_sidebar do
|
||||
.show-for-large-up.large-4.columns
|
||||
|
||||
@@ -48,8 +48,10 @@
|
||||
= inject_current_hub
|
||||
= inject_current_user
|
||||
= inject_rails_flash
|
||||
= inject_taxons
|
||||
= inject_properties
|
||||
- cache CacheService::FragmentCaching.ams_all_taxons_key do
|
||||
= inject_taxons
|
||||
- cache CacheService::FragmentCaching.ams_all_properties_key do
|
||||
= inject_properties
|
||||
= inject_current_order
|
||||
= inject_currency_config
|
||||
= yield :injection_data
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.joyride-tip-guide{"ng-class" => "{ in: open }", "ng-show" => "open"}
|
||||
.cart-dropdown.joyride-tip-guide{"ng-class" => "{ in: open }", "ng-show" => "open"}
|
||||
%span.joyride-nub.top
|
||||
.joyride-content-wrapper
|
||||
%h5
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
|
||||
- if require_customer?
|
||||
.row.footer-pad
|
||||
.small-12.columns
|
||||
.shopfront_hidden_message
|
||||
= t '.require_customer_login'
|
||||
- if spree_current_user.nil?
|
||||
= t '.require_login_html',
|
||||
{login: ('<a auth="login">' + t('.login') + '</a>').html_safe,
|
||||
signup: ('<a auth="signup">' + t('.signup') + '</a>').html_safe,
|
||||
contact: link_to(t('.contact'), '#contact'),
|
||||
enterprise: current_distributor.name}
|
||||
- else
|
||||
= t '.require_customer_html',
|
||||
{contact: link_to(t('.contact'), '#contact'),
|
||||
enterprise: current_distributor.name}
|
||||
- elsif current_distributor.preferred_shopfront_message.present?
|
||||
.row
|
||||
.small-12.columns
|
||||
.shopfront-message
|
||||
= current_distributor.preferred_shopfront_message.html_safe
|
||||
21
app/views/shop/messages/_closed_shop.html.haml
Normal file
21
app/views/shop/messages/_closed_shop.html.haml
Normal file
@@ -0,0 +1,21 @@
|
||||
.row.closed-shop-header
|
||||
.small-12.columns
|
||||
.content{ "darker-background" => true }
|
||||
%h4
|
||||
.warning-sign
|
||||
.rectangle
|
||||
%strong !
|
||||
.message
|
||||
= t :shopping_oc_closed
|
||||
%p
|
||||
= render partial: "shopping_shared/next_order_cycle"
|
||||
= render partial: "shopping_shared/last_order_cycle"
|
||||
|
||||
.row
|
||||
.small-12.columns
|
||||
.content
|
||||
.shopfront_closed_message
|
||||
- if shopfront_closed_message?
|
||||
= current_distributor.preferred_shopfront_closed_message.html_safe
|
||||
- else
|
||||
= t :shopping_oc_closed_description
|
||||
19
app/views/shop/messages/_customer_required.html.haml
Normal file
19
app/views/shop/messages/_customer_required.html.haml
Normal file
@@ -0,0 +1,19 @@
|
||||
.content{ "darker-background" => true }
|
||||
.row.footer-pad
|
||||
.small-12.columns
|
||||
%strong
|
||||
= t '.require_customer_login'
|
||||
%p
|
||||
- if spree_current_user.nil?
|
||||
%p
|
||||
= t '.require_login_html',
|
||||
{login: ('<a auth="login">' + t('.login') + '</a>').html_safe,
|
||||
signup: ('<a auth="signup">' + t('.signup') + '</a>').html_safe}
|
||||
%p
|
||||
= t '.require_login_2_html',
|
||||
{contact: link_to(t('.contact'), '#contact'),
|
||||
enterprise: current_distributor.name}
|
||||
- else
|
||||
= t '.require_customer_html',
|
||||
{contact: link_to(t('.contact'), '#contact'),
|
||||
enterprise: current_distributor.name}
|
||||
5
app/views/shop/messages/_open_shop.html.haml
Normal file
5
app/views/shop/messages/_open_shop.html.haml
Normal file
@@ -0,0 +1,5 @@
|
||||
.content
|
||||
.row
|
||||
.small-12.columns
|
||||
.open-shop-message
|
||||
= current_distributor.preferred_shopfront_message.html_safe
|
||||
3
app/views/shop/messages/_select_oc.html.haml
Normal file
3
app/views/shop/messages/_select_oc.html.haml
Normal file
@@ -0,0 +1,3 @@
|
||||
.content.footer-pad{ "darker-background" => true, "ng-controller" => "ProductsCtrl", "ng-show" => "order_cycle.order_cycle_id == null" }
|
||||
.select-oc-message
|
||||
= t '.select_oc_html'
|
||||
9
app/views/shop/products/_applied_filters_feedback.haml
Normal file
9
app/views/shop/products/_applied_filters_feedback.haml
Normal file
@@ -0,0 +1,9 @@
|
||||
%span{ "ng-show" => "query && ( appliedPropertiesList() || appliedTaxonsList() )" }
|
||||
= t :products_filters_in
|
||||
|
||||
%span.applied-properties{'ng-bind-html' => 'appliedPropertiesList()'}
|
||||
|
||||
%span{ "ng-show" => "appliedPropertiesList() && appliedTaxonsList()" }
|
||||
= t :products_and
|
||||
|
||||
%span.applied-taxons{'ng-bind-html' => 'appliedTaxonsList()'}
|
||||
@@ -1,5 +1,5 @@
|
||||
.filter-shopfront.taxon-selectors.text-right{ng: {show: 'supplied_taxons != null'}}
|
||||
%single-line-selectors{ selectors: "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"}
|
||||
.filter-shopfront.taxon-selectors{ng: {show: 'supplied_taxons != null'}}
|
||||
%filter-selector{ 'selector-set' => "taxonSelectors", objects: "supplied_taxons", "active-selectors" => "activeTaxons"}
|
||||
|
||||
.filter-shopfront.property-selectors.text-right{ng: {show: 'supplied_properties != null'}}
|
||||
%single-line-selectors{ selectors: "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"}
|
||||
.filter-shopfront.property-selectors{ng: {show: 'supplied_properties != null'}}
|
||||
%filter-selector{ 'selector-set' => "propertySelectors", objects: "supplied_properties", "active-selectors" => "activeProperties"}
|
||||
|
||||
@@ -1,54 +1,48 @@
|
||||
.footer-pad.small-12.columns
|
||||
%form{action: main_app.cart_path}
|
||||
%products{"ng-controller" => "ProductsCtrl", "ng-init" => "refreshStaleData()", "ng-show" => "order_cycle.order_cycle_id != null", "ng-cloak" => true }
|
||||
|
||||
// TODO: Needs an ng-show to slide content down
|
||||
.row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedTaxonsList()" }
|
||||
= render partial: "shop/products/searchbar"
|
||||
|
||||
.row
|
||||
.small-12.columns
|
||||
.alert-box.search-alert.ng-scope
|
||||
%a.right{"ng-click" => "clearAll()"}
|
||||
= t :products_clear_all
|
||||
%i.ofn-i_009-close
|
||||
%span.filter-label
|
||||
= t :products_showing
|
||||
%span.applied-properties
|
||||
{{ appliedPropertiesList() }}
|
||||
%span.applied-taxons
|
||||
{{ appliedTaxonsList() }}
|
||||
%span{ ng: { hide: "!query"} }
|
||||
%span{ "ng-show" => "appliedPropertiesList() || appliedTaxonsList()" }
|
||||
= t :products_with
|
||||
%span.applied-search "{{ query }}"
|
||||
.row
|
||||
.small-12.medium-6.large-5.columns
|
||||
%input#search.text{"ng-model" => "query",
|
||||
placeholder: t(:products_search),
|
||||
"ng-debounce" => "200",
|
||||
"ofn-disable-enter" => true}
|
||||
|
||||
.small-12.medium-6.large-6.large-offset-1.columns
|
||||
= render partial: "shop/products/filters"
|
||||
|
||||
.row
|
||||
%div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading' }
|
||||
%product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"}
|
||||
= render "shop/products/summary"
|
||||
%shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"}
|
||||
|
||||
%product{"ng-show" => "Products.loading"}
|
||||
.row.summary
|
||||
.small-12.columns.text-center
|
||||
= t :products_loading
|
||||
.footer-pad.small-12.columns.no-gutter
|
||||
.row
|
||||
.small-12.columns.text-center
|
||||
%img.spinner{ src: "/assets/spinning-circles.svg" }
|
||||
.medium-12.large-10.columns
|
||||
= render partial: "shop/products/search_feedback"
|
||||
|
||||
%div{"ng-show" => "Products.products.length == 0 && !Products.loading"}
|
||||
.row.summary
|
||||
.small-12.columns
|
||||
%p.no-results
|
||||
= t :search_no_results_html, query: "<strong>{{query}}</strong>".html_safe
|
||||
.row
|
||||
.small-12.columns
|
||||
%form{action: main_app.cart_path}
|
||||
%i.ofn-i_011-spinner.cart-spinner{"ng-show" => "Cart.dirty"}
|
||||
%input.small.button.primary.right.add_to_cart{type: :submit, value: "{{ Cart.dirty ? '#{t(:products_updating_cart)}' : (Cart.empty() ? '#{t(:products_cart_empty)}' : '#{t(:products_edit_cart)}' ) }}", "ng-disabled" => "Cart.dirty || Cart.empty()", "ng-class" => "{ dirty: Cart.dirty }" }
|
||||
%div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading' }
|
||||
%product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"}
|
||||
= render "shop/products/summary"
|
||||
%shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"}
|
||||
|
||||
%product{"ng-show" => "Products.loading"}
|
||||
.row.summary
|
||||
.small-12.columns.text-center
|
||||
= t :products_loading
|
||||
.row
|
||||
.small-12.columns.text-center
|
||||
%img.spinner{ src: "/assets/spinning-circles.svg" }
|
||||
|
||||
.hide-for-medium-down.large-2.columns
|
||||
%h5
|
||||
= t(:products_filter_by)
|
||||
%span{ng: {show: 'filtersCount()' }}
|
||||
= "({{ filtersCount() }} #{t(:products_filter_selected)})"
|
||||
|
||||
= render partial: "shop/products/filters"
|
||||
|
||||
.shop-filters-sidebar.hide-for-large-up{ng: {show: 'showFilterSidebar', class: "{'shown': showFilterSidebar}"}}
|
||||
.background{ng: {click: 'toggleFilterSidebar()'}}
|
||||
.sidebar
|
||||
%h5
|
||||
= t(:products_filter_by)
|
||||
%span{ng: {show: 'filtersCount()' }}
|
||||
= "({{ filtersCount() }} #{t(:products_filter_selected)})"
|
||||
|
||||
= render partial: "shop/products/filters"
|
||||
|
||||
.sidebar-footer
|
||||
%button.large.dark.left{type: 'button', ng: {click: 'clearFilters()'}}
|
||||
= t(:products_filter_clear)
|
||||
%button.large.bright.right{type: 'button', ng: {click: 'toggleFilterSidebar()'}}
|
||||
= t(:products_filter_done)
|
||||
|
||||
24
app/views/shop/products/_search_feedback.haml
Normal file
24
app/views/shop/products/_search_feedback.haml
Normal file
@@ -0,0 +1,24 @@
|
||||
.row.animate-slide{ "ng-show" => "query || appliedPropertiesList() || appliedTaxonsList()" }
|
||||
.small-12.columns
|
||||
.alert-box.search-alert.ng-scope
|
||||
%div{"ng-show" => "Products.products.length > 0"}
|
||||
|
||||
%a.clear-all.right{"ng-click" => "clearAll()"}
|
||||
= t :products_clear
|
||||
%i.ofn-i_009-close
|
||||
|
||||
%span.filter-label
|
||||
= t :products_results_for
|
||||
%span{ ng: { hide: "!query"} }
|
||||
%span.applied-search
|
||||
{{ query }}
|
||||
= render partial: 'shop/products/applied_filters_feedback'
|
||||
|
||||
%div.no-results-bar{"ng-show" => "Products.products.length == 0 && !Products.loading"}
|
||||
.row.summary
|
||||
.small-12.columns
|
||||
%p.no-results
|
||||
= t :products_no_results_html, query: "<span class='applied-search'>{{query}}</span>".html_safe
|
||||
= render partial: 'shop/products/applied_filters_feedback'
|
||||
%button.clear-search{type: 'button', ng: {click: 'clearAll()'}}
|
||||
= t :products_clear_search
|
||||
17
app/views/shop/products/_searchbar.haml
Normal file
17
app/views/shop/products/_searchbar.haml
Normal file
@@ -0,0 +1,17 @@
|
||||
.shop-searchbar
|
||||
.row
|
||||
.small-12.large-5.columns.flex
|
||||
%div.search-wrap
|
||||
%input#search.text{"ng-model" => "query",
|
||||
type: 'search',
|
||||
placeholder: t(:products_search),
|
||||
"ng-debounce" => "200",
|
||||
"ofn-disable-enter" => true}
|
||||
%a.clear{type: 'button', ng: {show: 'query', click: 'clearQuery()'}, 'focus-search' => true}
|
||||
%img{ src: "/assets/icn-close.png" }
|
||||
|
||||
.hide-for-large-up
|
||||
%button{type: 'button', ng: {click: 'toggleFilterSidebar()'}}
|
||||
= t(:products_filter_heading)
|
||||
%span{ng: {show: 'filtersCount()' }}
|
||||
({{ filtersCount() }})
|
||||
@@ -1,4 +1,2 @@
|
||||
- if most_recently_closed = OrderCycle.most_recently_closed_for(@distributor)
|
||||
(
|
||||
= t :shopping_oc_last_closed, distance_of_time: distance_of_time_in_words_to_now(most_recently_closed.orders_close_at)
|
||||
)
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
- if next_oc = OrderCycle.first_opening_for(@distributor)
|
||||
(
|
||||
= t :shopping_oc_next_open, distance_of_time: distance_of_time_in_words_to_now(next_oc.orders_open_at)
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- content_for :injection_data do
|
||||
= inject_current_order_cycle
|
||||
|
||||
- unless no_open_order_cycles?
|
||||
- unless no_open_order_cycles? || require_customer?
|
||||
%ordercycle{"ng-controller" => "OrderCycleCtrl", "ng-cloak" => true,
|
||||
"ng-class" => "{'requires-selection': !OrderCycle.selected()}"}
|
||||
%form.custom
|
||||
|
||||
@@ -2,5 +2,12 @@
|
||||
.order-cycle-bar.hide-for-large-up
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
|
||||
.content
|
||||
= render partial: 'shop/messages'
|
||||
- if require_customer?
|
||||
= render partial: "shop/messages/customer_required"
|
||||
|
||||
- else
|
||||
- if no_open_order_cycles?
|
||||
= render partial: "shop/messages/closed_shop"
|
||||
|
||||
- else
|
||||
= render partial: "shop/messages/open_shop"
|
||||
|
||||
@@ -2,23 +2,9 @@
|
||||
.order-cycle-bar.hide-for-large-up
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
|
||||
.row
|
||||
.small-12.columns
|
||||
- if no_open_order_cycles?
|
||||
.content
|
||||
%h4
|
||||
%i.ofn-i_012-warning
|
||||
= t :shopping_oc_closed
|
||||
%small
|
||||
%em
|
||||
= render partial: "shopping_shared/next_order_cycle"
|
||||
= render partial: "shopping_shared/last_order_cycle"
|
||||
%p
|
||||
= t :shopping_oc_closed_description
|
||||
- if no_open_order_cycles?
|
||||
= render partial: "shop/messages/closed_shop"
|
||||
|
||||
- if shopfront_closed_message?
|
||||
.shopfront_closed_message
|
||||
= current_distributor.preferred_shopfront_closed_message.html_safe
|
||||
|
||||
- unless require_customer?
|
||||
= render partial: "shop/products/form"
|
||||
- else
|
||||
= render partial: "shop/messages/select_oc"
|
||||
= render partial: "shop/products/form"
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
= t :shops_title
|
||||
|
||||
- content_for :injection_data do
|
||||
= inject_enterprises(@enterprises)
|
||||
- cache(*CacheService::FragmentCaching.ams_shops) do
|
||||
= inject_enterprises(@enterprises)
|
||||
|
||||
#panes
|
||||
#shops.pane
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
- shipment.manifest.each do |item|
|
||||
- line_item = order.find_line_item_by_variant(item.variant)
|
||||
- break if line_item.blank?
|
||||
|
||||
%tr.stock-item{ "data-item-quantity" => "#{item.quantity}" }
|
||||
%td.item-image
|
||||
= mini_image(item.variant)
|
||||
%td.item-name
|
||||
= item.variant.product_and_full_name
|
||||
%td.item-price.align-center
|
||||
= line_item.single_money.to_html
|
||||
%td.item-qty-show.align-center
|
||||
- item.states.each do |state,count|
|
||||
= "#{count} x #{t(state.humanize.downcase, scope: [:spree, :shipment_states], default: [:missing, "none"])}"
|
||||
- unless shipment.shipped?
|
||||
%td.item-qty-edit.hidden
|
||||
= number_field_tag :quantity, item.quantity, :min => 0, :class => "line_item_quantity", :size => 5
|
||||
%td.item-total.align-center
|
||||
= line_item_shipment_price(line_item, item.quantity)
|
||||
- if line_item.present?
|
||||
%tr.stock-item{ "data-item-quantity" => "#{item.quantity}" }
|
||||
%td.item-image
|
||||
= mini_image(item.variant)
|
||||
%td.item-name
|
||||
= item.variant.product_and_full_name
|
||||
%td.item-price.align-center
|
||||
= line_item.single_money.to_html
|
||||
%td.item-qty-show.align-center
|
||||
- item.states.each do |state,count|
|
||||
= "#{count} x #{t(state.humanize.downcase, scope: [:spree, :shipment_states], default: [:missing, "none"])}"
|
||||
- unless shipment.shipped?
|
||||
%td.item-qty-edit.hidden
|
||||
= number_field_tag :quantity, item.quantity, :min => 0, :class => "line_item_quantity", :size => 5
|
||||
%td.item-total.align-center
|
||||
= line_item_shipment_price(line_item, item.quantity)
|
||||
|
||||
%td.cart-item-delete.actions{ "data-hook" => "cart_item_delete" }
|
||||
- if !shipment.shipped? && can?(:update, shipment)
|
||||
= link_to '', '#', :class => 'save-item icon_link icon-ok no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'save'}, :title => t('actions.save'), :style => 'display: none'
|
||||
= link_to '', '#', :class => 'cancel-item icon_link icon-cancel no-text with-tip', :data => {:action => 'cancel'}, :title => t('actions.cancel'), :style => 'display: none'
|
||||
= link_to '', '#', :class => 'edit-item icon_link icon-edit no-text with-tip', :data => {:action => 'edit'}, :title => t('actions.edit')
|
||||
= link_to '', '#', :class => 'delete-item icon-trash no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'remove', :confirm => t(:are_you_sure)}, :title => t('actions.delete')
|
||||
%td.cart-item-delete.actions{ "data-hook" => "cart_item_delete" }
|
||||
- if !shipment.shipped? && can?(:update, shipment)
|
||||
= link_to '', '#', :class => 'save-item icon_link icon-ok no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'save'}, :title => t('actions.save'), :style => 'display: none'
|
||||
= link_to '', '#', :class => 'cancel-item icon_link icon-cancel no-text with-tip', :data => {:action => 'cancel'}, :title => t('actions.cancel'), :style => 'display: none'
|
||||
= link_to '', '#', :class => 'edit-item icon_link icon-edit no-text with-tip', :data => {:action => 'edit'}, :title => t('actions.edit')
|
||||
= link_to '', '#', :class => 'delete-item icon-trash no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'remove', :confirm => t(:are_you_sure)}, :title => t('actions.delete')
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
%span.state{'ng-class' => 'order.payment_state', 'ng-if' => 'order.payment_state'}
|
||||
%a{'ng-href' => '{{order.payments_path}}' }
|
||||
{{'js.admin.orders.payment_states.' + order.payment_state | t}}
|
||||
%span{'ng-if' => 'order.display_outstanding_balance'}
|
||||
({{order.display_outstanding_balance}})
|
||||
%td.align-center
|
||||
%span.state{'ng-class' => 'order.shipment_state', 'ng-if' => 'order.shipment_state'}
|
||||
{{'js.admin.orders.shipment_states.' + order.shipment_state | t}}
|
||||
|
||||
@@ -856,6 +856,10 @@ ar:
|
||||
save_and_back_to_list: "حفظ والعودة إلى القائمة"
|
||||
choose_products_from: "اختر المنتجات من:"
|
||||
incoming:
|
||||
incoming: "الوارد"
|
||||
supplier: "المورد"
|
||||
products: "منتجات"
|
||||
fees: "رسوم"
|
||||
save: "حفظ"
|
||||
save_and_next: "حفظ والتالي"
|
||||
next: "التالى"
|
||||
@@ -2869,8 +2873,9 @@ ar:
|
||||
blank: "لا يمكن أن تكون فارغة"
|
||||
layouts:
|
||||
admin:
|
||||
header:
|
||||
store: متجر
|
||||
login_nav:
|
||||
header:
|
||||
store: متجر
|
||||
admin:
|
||||
tab:
|
||||
dashboard: "لوحة العرض"
|
||||
|
||||
@@ -865,6 +865,10 @@ ca:
|
||||
save_and_back_to_list: "Desa i torna a la llista"
|
||||
choose_products_from: "Trieu Productes des de:"
|
||||
incoming:
|
||||
incoming: "Entrant"
|
||||
supplier: "Proveïdora"
|
||||
products: "Productes "
|
||||
fees: "Comissions"
|
||||
save: "Desa"
|
||||
save_and_next: "Desa i següent"
|
||||
next: "Següent"
|
||||
@@ -2912,8 +2916,9 @@ ca:
|
||||
blank: "no es pot deixar en blanc"
|
||||
layouts:
|
||||
admin:
|
||||
header:
|
||||
store: Botiga
|
||||
login_nav:
|
||||
header:
|
||||
store: Botiga
|
||||
admin:
|
||||
tab:
|
||||
dashboard: "Panell"
|
||||
|
||||
@@ -863,6 +863,10 @@ de_DE:
|
||||
save_and_back_to_list: "Speichern und zurück zur Liste"
|
||||
choose_products_from: "Wählen Sie Produkte von:"
|
||||
incoming:
|
||||
incoming: "Eingehend"
|
||||
supplier: "Anbieter"
|
||||
products: "Produkte"
|
||||
fees: "Gebühren"
|
||||
save: "Speichern"
|
||||
save_and_next: "Speichern und weiter"
|
||||
next: "Weiter"
|
||||
@@ -1210,7 +1214,7 @@ de_DE:
|
||||
menu_5_title: "Über Uns"
|
||||
menu_5_url: "https://wp.openfoodnetwork.de/"
|
||||
menu_6_title: "Verbinden"
|
||||
menu_6_url: "https://openfoodnetwork.org/au/connect/"
|
||||
menu_6_url: "https://wp.openfoodnetwork.de/support/"
|
||||
menu_7_title: "Mehr Erfahren"
|
||||
menu_7_url: "https://openfoodnetwork.org/au/learn/"
|
||||
logo: "Logo (640x130)"
|
||||
@@ -2906,8 +2910,9 @@ de_DE:
|
||||
blank: "kann nicht leer sein"
|
||||
layouts:
|
||||
admin:
|
||||
header:
|
||||
store: openfoodnetwork.de
|
||||
login_nav:
|
||||
header:
|
||||
store: openfoodnetwork.de
|
||||
admin:
|
||||
tab:
|
||||
dashboard: "Übersicht"
|
||||
|
||||
@@ -915,6 +915,11 @@ en:
|
||||
save_and_back_to_list: "Save and Back to List"
|
||||
choose_products_from: "Choose Products From:"
|
||||
incoming:
|
||||
incoming: "Incoming"
|
||||
supplier: "Supplier"
|
||||
products: "Products"
|
||||
receival_details: "Receival Details"
|
||||
fees: "Fees"
|
||||
save: "Save"
|
||||
save_and_next: "Save and Next"
|
||||
next: "Next"
|
||||
@@ -1234,12 +1239,16 @@ en:
|
||||
footer_data_cookies_policy: "cookies policy"
|
||||
shop:
|
||||
messages:
|
||||
login: "login"
|
||||
signup: "signup"
|
||||
contact: "contact"
|
||||
require_customer_login: "Only approved customers can access this shop."
|
||||
require_login_html: "If you're already an approved customer, %{login} or %{signup} to proceed. Want to start shopping here? Please %{contact} %{enterprise} and ask about joining."
|
||||
require_customer_html: "If you'd like to start shopping here, please %{contact} %{enterprise} to ask about joining."
|
||||
customer_required:
|
||||
login: "login"
|
||||
signup: "signup"
|
||||
contact: "contact"
|
||||
require_customer_login: "Only approved customers can access this shop."
|
||||
require_login_html: "If you're already an approved customer, %{login} or %{signup} to proceed."
|
||||
require_login_2_html: "Want to start shopping here? Please %{contact} %{enterprise} and ask about joining."
|
||||
require_customer_html: "If you'd like to start shopping here, please %{contact} %{enterprise} to ask about joining."
|
||||
select_oc:
|
||||
select_oc_html: "Please <span class='highlighted'>choose when you want your order</span>, to see what products are available."
|
||||
|
||||
# Front-end controller translations
|
||||
card_could_not_be_updated: Card could not be updated
|
||||
@@ -1639,11 +1648,19 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
other: You have <a href='%{path}' target='_blank'>%{count} orders with %{shop}</a> currently open for review. You can make changes until %{oc_close}.
|
||||
orders_changeable_orders_alert_html: This order has been confirmed, but you can make changes until <strong>%{oc_close}</strong>.
|
||||
|
||||
products_clear_all: Clear all
|
||||
products_clear: Clear
|
||||
products_showing: "Showing:"
|
||||
products_or: "OR"
|
||||
products_results_for: "Results for"
|
||||
products_or: "or"
|
||||
products_and: "and"
|
||||
products_filters_in: "in"
|
||||
products_with: with
|
||||
products_search: "Search by product or producer"
|
||||
products_search: "Search..."
|
||||
products_filter_by: "Filter by"
|
||||
products_filter_selected: "selected"
|
||||
products_filter_heading: "Filters"
|
||||
products_filter_clear: "Clear"
|
||||
products_filter_done: "Done"
|
||||
products_loading: "Loading products..."
|
||||
products_updating_cart: "Updating cart..."
|
||||
products_cart_empty: "Cart empty"
|
||||
@@ -1654,6 +1671,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
products_update_error_msg: "Saving failed."
|
||||
products_update_error_data: "Save failed due to invalid data:"
|
||||
products_changes_saved: "Changes saved."
|
||||
products_no_results_html: "Sorry, no results found for %{query}"
|
||||
products_clear_search: "Clear search"
|
||||
|
||||
search_no_results_html: "Sorry, no results found for %{query}. Try another search?"
|
||||
|
||||
@@ -2438,6 +2457,10 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
resolve_errors: Please resolve the following errors
|
||||
more_items: "+ %{count} More"
|
||||
default_card_updated: Default Card Updated
|
||||
cart:
|
||||
add_to_cart_failed: >
|
||||
There was a problem adding this product to the cart.
|
||||
Perhaps it has become unavailable or the shop is closing.
|
||||
admin:
|
||||
enterprise_limit_reached: "You have reached the standard limit of enterprises per account. Write to %{contact_email} if you need to increase it."
|
||||
modals:
|
||||
@@ -3071,8 +3094,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
blank: "can't be blank"
|
||||
layouts:
|
||||
admin:
|
||||
header:
|
||||
store: Store
|
||||
login_nav:
|
||||
header:
|
||||
store: Store
|
||||
admin:
|
||||
tab:
|
||||
dashboard: "Dashboard"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user