diff --git a/app/assets/images/potatoes.png b/app/assets/images/potatoes.png new file mode 100644 index 0000000000..c0be04200e Binary files /dev/null and b/app/assets/images/potatoes.png differ diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 5ffb99ccb8..3fb5e69499 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -22,5 +22,6 @@ //= require ./payment_methods/payment_methods //= require ./products/products //= require ./shipping_methods/shipping_methods +//= require ./users/users //= require_tree . diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee index d545245eeb..4c1a319c1a 100644 --- a/app/assets/javascripts/admin/bulk_order_management.js.coffee +++ b/app/assets/javascripts/admin/bulk_order_management.js.coffee @@ -1,6 +1,7 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ - "$scope", "$http", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", - ($scope, $http, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer) -> + "$scope", "$http", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey" + ($scope, $http, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey) -> + $scope.loading = true $scope.initialiseVariables = -> start = daysFromToday -7 @@ -32,14 +33,14 @@ angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [ quantity: { name: "Quantity", visible: true } max: { name: "Max", visible: true } - $scope.initialise = (spree_api_key) -> + $scope.initialise = -> $scope.initialiseVariables() authorise_api_reponse = "" - dataFetcher("/api/users/authorise_api?token=" + spree_api_key).then (data) -> + 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"] = spree_api_key + $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 = data $scope.suppliers.unshift blankOption() diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 35ec6e694c..b68e3340e1 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -1,6 +1,8 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [ - "$scope", "$timeout", "$http", "dataFetcher", "DirtyProducts", "VariantUnitManager", "producers", "Taxons", - ($scope, $timeout, $http, dataFetcher, DirtyProducts, VariantUnitManager, producers, Taxons) -> + "$scope", "$timeout", "$http", "dataFetcher", "DirtyProducts", "VariantUnitManager", "producers", "Taxons", "SpreeApiKey", + ($scope, $timeout, $http, dataFetcher, DirtyProducts, VariantUnitManager, producers, Taxons, SpreeApiKey) -> + $scope.loading = true + $scope.updateStatusMessage = text: "" style: {} @@ -42,14 +44,13 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [ $scope.limit = 15 $scope.productsWithUnsavedVariants = [] - - $scope.initialise = (spree_api_key) -> + $scope.initialise = -> authorise_api_reponse = "" - dataFetcher("/api/users/authorise_api?token=" + spree_api_key).then (data) -> + 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"] = spree_api_key + $http.defaults.headers.common["X-Spree-Token"] = SpreeApiKey $scope.fetchProducts() else if authorise_api_reponse.hasOwnProperty("error") $scope.api_error_msg = authorise_api_reponse("error") diff --git a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee index cdf90cfb51..6189661035 100644 --- a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee +++ b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee @@ -1 +1 @@ -angular.module("admin.enterprises", ["admin.payment_methods", "admin.shipping_methods"]) \ No newline at end of file +angular.module("admin.enterprises", ["admin.payment_methods", "admin.shipping_methods", "admin.users"]) \ No newline at end of file diff --git a/app/assets/javascripts/admin/order_cycle.js.erb.coffee b/app/assets/javascripts/admin/order_cycle.js.erb.coffee index b8068681ca..64f2550466 100644 --- a/app/assets/javascripts/admin/order_cycle.js.erb.coffee +++ b/app/assets/javascripts/admin/order_cycle.js.erb.coffee @@ -197,12 +197,13 @@ angular.module('order_cycle', ['ngResource']) this.order_cycle.outgoing_exchanges.push({enterprise_id: new_distributor_id, incoming: false, active: true, variants: {}, enterprise_fees: []}) removeExchange: (exchange) -> - incoming_index = this.order_cycle.incoming_exchanges.indexOf exchange - this.order_cycle.incoming_exchanges.splice(incoming_index, 1) if incoming_index > -1 - outgoing_index = this.order_cycle.outgoing_exchanges.indexOf exchange - this.order_cycle.outgoing_exchanges.splice(outgoing_index, 1) if outgoing_index > -1 - - this.removeDistributionOfVariant(variant_id) for variant_id, active of exchange.variants when active + if exchange.incoming + incoming_index = this.order_cycle.incoming_exchanges.indexOf exchange + this.order_cycle.incoming_exchanges.splice(incoming_index, 1) + this.removeDistributionOfVariant(variant_id) for variant_id, active of exchange.variants when active + else + outgoing_index = this.order_cycle.outgoing_exchanges.indexOf exchange + this.order_cycle.outgoing_exchanges.splice(outgoing_index, 1) if outgoing_index > -1 addCoordinatorFee: -> this.order_cycle.coordinator_fees.push({}) diff --git a/app/assets/javascripts/admin/users/directives/user_autocomplete.js.coffee b/app/assets/javascripts/admin/users/directives/user_autocomplete.js.coffee new file mode 100644 index 0000000000..a904d1bcce --- /dev/null +++ b/app/assets/javascripts/admin/users/directives/user_autocomplete.js.coffee @@ -0,0 +1,18 @@ +angular.module("admin.users").directive "ofnUserAutocomplete", ($http) -> + link: (scope,element,attrs) -> + setTimeout -> + element.select2 + multiple: false + initSelection: (element, callback) -> + callback { id: element.val(), email: attrs.email } + ajax: + url: Spree.routes.user_search + datatype: 'json' + data:(term, page) -> + { q: term } + results: (data, page) -> + { results: data } + formatResult: (user) -> + user.email + formatSelection: (user) -> + user.email \ No newline at end of file diff --git a/app/assets/javascripts/admin/users/users.js.coffee b/app/assets/javascripts/admin/users/users.js.coffee new file mode 100644 index 0000000000..6bfd47a894 --- /dev/null +++ b/app/assets/javascripts/admin/users/users.js.coffee @@ -0,0 +1 @@ +angular.module("admin.users", []) \ No newline at end of file diff --git a/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee index d15ef4141d..123079b6b5 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee @@ -1,4 +1,4 @@ -Darkswarm.controller "SignupCtrl", ($scope, $http, $location, AuthenticationService) -> +Darkswarm.controller "SignupCtrl", ($scope, $http, $window, $location, Redirections, AuthenticationService) -> $scope.path = "/signup" $scope.errors = email: null @@ -6,6 +6,9 @@ Darkswarm.controller "SignupCtrl", ($scope, $http, $location, AuthenticationServ $scope.submit = -> $http.post("/user/spree_user", {spree_user: $scope.spree_user}).success (data)-> - location.href = location.origin + location.pathname # Strips out hash fragments + if Redirections.after_login + $window.location.href = $window.location.origin + Redirections.after_login + else + $window.location.href = $window.location.origin + $window.location.pathname # Strips out hash fragments .error (data) -> $scope.errors = data diff --git a/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee index 7c45fd53d1..1a22f34b0c 100644 --- a/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee @@ -1,7 +1,7 @@ Darkswarm.controller "AuthenticationCtrl", ($scope, AuthenticationService, SpreeUser)-> $scope.open = AuthenticationService.open $scope.toggle = AuthenticationService.toggle - + $scope.spree_user = SpreeUser.spree_user $scope.active = AuthenticationService.active $scope.select = AuthenticationService.select diff --git a/app/assets/javascripts/darkswarm/controllers/registration_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/registration_controller.js.coffee new file mode 100644 index 0000000000..2fcc0f32fd --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/registration_controller.js.coffee @@ -0,0 +1,11 @@ +Darkswarm.controller "RegistrationCtrl", ($scope, RegistrationService, EnterpriseRegistrationService, availableCountries) -> + $scope.currentStep = RegistrationService.currentStep + $scope.enterprise = EnterpriseRegistrationService.enterprise + $scope.select = RegistrationService.select + + $scope.steps = ['details','address','contact','about','images','social'] + + $scope.countries = availableCountries + + $scope.countryHasStates = -> + $scope.enterprise.country.states.length > 0 diff --git a/app/assets/javascripts/darkswarm/controllers/registration_form_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/registration_form_controller.js.coffee new file mode 100644 index 0000000000..84f133da54 --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/registration_form_controller.js.coffee @@ -0,0 +1,15 @@ +Darkswarm.controller "RegistrationFormCtrl", ($scope, RegistrationService, EnterpriseRegistrationService) -> + $scope.submitted = false + + $scope.valid = (form) -> + $scope.submitted = !form.$valid + form.$valid + + $scope.create = (form) -> + EnterpriseRegistrationService.create() if $scope.valid(form) + + $scope.update = (nextStep, form) -> + EnterpriseRegistrationService.update(nextStep) if $scope.valid(form) + + $scope.selectIfValid = (nextStep, form) -> + RegistrationService.select(nextStep) if $scope.valid(form) \ No newline at end of file diff --git a/app/assets/javascripts/darkswarm/directives/inline_flash.js.coffee b/app/assets/javascripts/darkswarm/directives/inline_flash.js.coffee new file mode 100644 index 0000000000..46550b854f --- /dev/null +++ b/app/assets/javascripts/darkswarm/directives/inline_flash.js.coffee @@ -0,0 +1,6 @@ +Darkswarm.directive "ofnInlineFlash", -> + restrict: 'E' + controller: ($scope) -> + $scope.visible = true + $scope.closeFlash = -> + $scope.visible = false diff --git a/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee b/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee index ff7db62f4a..5f5ae520e3 100644 --- a/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee +++ b/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee @@ -4,12 +4,14 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir selectedPath: "/login" constructor: -> - if $location.path() in ["/login", "/signup", "/forgot"] - @open() + if $location.path() in ["/login", "/signup", "/forgot"] && location.pathname isnt '/register/auth' + @open $location.path() + else if location.pathname is '/register/auth' + @open '/signup', 'registration_authentication.html' - open: (path = false)=> + open: (path = false, template = 'authentication.html') => @modalInstance = $modal.open - templateUrl: 'authentication.html' + templateUrl: template windowClass: "login-modal medium" @modalInstance.result.then @close, @close @selectedPath = path || @selectedPath diff --git a/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee b/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee new file mode 100644 index 0000000000..68915193ee --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee @@ -0,0 +1,57 @@ +Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService, CurrentUser, spreeApiKey, Loading, availableCountries, enterpriseAttributes) -> + new class EnterpriseRegistrationService + enterprise: + user_ids: [CurrentUser.id] + email: CurrentUser.email + address: {} + country: availableCountries[0] + + constructor: -> + for key, value of enterpriseAttributes + @enterprise[key] = value + + create: => + Loading.message = "Creating " + @enterprise.name + $http( + method: "POST" + url: "/api/enterprises" + data: + enterprise: @prepare() + params: + token: spreeApiKey + ).success((data) => + Loading.clear() + @enterprise.id = data + RegistrationService.select('about') + ).error((data) => + Loading.clear() + alert('Failed to create your enterprise.\nPlease ensure all fields are completely filled out.') + ) + # RegistrationService.select('about') + + update: (step) => + Loading.message = "Updating " + @enterprise.name + $http( + method: "PUT" + url: "/api/enterprises/#{@enterprise.id}" + data: + enterprise: @prepare() + params: + token: spreeApiKey + ).success((data) -> + Loading.clear() + RegistrationService.select(step) + ).error((data) -> + Loading.clear() + alert('Failed to update your enterprise.\nPlease ensure all fields are completely filled out.') + ) + # RegistrationService.select(step) + + prepare: => + enterprise = {} + excluded = [ 'address', 'country', 'id' ] + for key, value of @enterprise when key not in excluded + enterprise[key] = value + enterprise.address_attributes = @enterprise.address if @enterprise.address? + enterprise.address_attributes.country_id = @enterprise.country.id if @enterprise.country? + enterprise \ No newline at end of file diff --git a/app/assets/javascripts/darkswarm/services/registration_service.js.coffee b/app/assets/javascripts/darkswarm/services/registration_service.js.coffee new file mode 100644 index 0000000000..a2a1fe2dc4 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/registration_service.js.coffee @@ -0,0 +1,23 @@ +Darkswarm.factory "RegistrationService", (Navigation, $modal, Loading)-> + + new class RegistrationService + constructor: -> + @open() + + open: => + @modalInstance = $modal.open + templateUrl: 'registration.html' + windowClass: "login-modal large" + backdrop: 'static' + @modalInstance.result.then @close, @close + @select 'introduction' + + select: (step)=> + @current_step = step + + currentStep: => + @current_step + + close: -> + Loading.message = "Taking you back to the home page" + Navigation.go "/" \ No newline at end of file diff --git a/app/assets/javascripts/templates/registration.html.haml b/app/assets/javascripts/templates/registration.html.haml new file mode 100644 index 0000000000..10a12d712e --- /dev/null +++ b/app/assets/javascripts/templates/registration.html.haml @@ -0,0 +1,10 @@ +%div#registration-modal{"ng-controller" => "RegistrationCtrl"} + %div{ ng: { show: "currentStep() == 'introduction'" } } + %ng-include{ src: "'registration/introduction.html'" } + %div{ ng: { repeat: 'step in steps', show: "currentStep() == step" } } + %ng-include{ src: "'registration/'+ step + '.html'" } + %div{ ng: { show: "currentStep() == 'finished'" } } + %ng-include{ src: "'registration/finished.html'" } + +%a.close-reveal-modal{"ng-click" => "$close()"} + %i.ofn-i_009-close diff --git a/app/assets/javascripts/templates/registration/about.html.haml b/app/assets/javascripts/templates/registration/about.html.haml new file mode 100644 index 0000000000..07e631345f --- /dev/null +++ b/app/assets/javascripts/templates/registration/about.html.haml @@ -0,0 +1,44 @@ +.container#registration-about + .header + %h2 Nice one! + %h5 + Now let's flesh out the details about + %span.brick{"ng-show" => "enterprise.is_distributor"} + {{ enterprise.name }} + %span.turquoise{"ng-show" => "!enterprise.is_distributor" } + {{ enterprise.name }} + + %ng-include{ src: "'registration/steps.html'" } + %form{ name: 'about', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "update('social',about)" } } + .row + .small-12.columns + .alert-box.alert{"data-alert" => ""} + {{ enterprise.name }} won't be visible on the Open Food Network until you enter a long and short description. + %a.close{:href => "#"} × + + .alert-box.info{"data-alert" => ""} + {{ enterprise.name }} has been created on the Open Food Network. If you leave at any point from here onwards, your enterprise will be saved, and you can always login to the admin section to update or continue filling out your enterprise details. + %a.close{:href => "#"} × + + .small-12.large-8.columns + .row + .small-12.columns + %label{ for: 'enterprise_description' } Short Description: + %input.chunky.small-12.columns{ id: 'enterprise_description', placeholder: "A short sentence describing your enterprise", ng: { model: 'enterprise.description' } } + .row + .small-12.columns + %label{ for: 'enterprise_long_desc' } Long Description: + %textarea.chunky.small-12.columns{ id: 'enterprise_long_desc', placeholder: "We recommend keeping your description to under 600 characters or 150 words. Why? Cus people are lazy, and don't like to read too much text online. ;)", ng: { model: 'enterprise.long_description' } } + %small {{ enterprise.long_description.length }} characters used + .small-12.large-4.columns + .row + .small-12.columns + %label{ for: 'enterprise_abn' } ABN: + %input.chunky.small-12.columns{ id: 'enterprise_abn', placeholder: "eg. 99 123 456 789", ng: { model: 'enterprise.abn' } } + .row + .small-12.columns + %label{ for: 'enterprise_acn' } ACN: + %input.chunky.small-12.columns{ id: 'enterprise_acn', placeholder: "eg. 123 456 789", ng: { model: 'enterprise.acn' } } + .row.buttons.pad-top + .small-12.columns + %input.button.primary{ type: "submit", value: "Continue" } diff --git a/app/assets/javascripts/templates/registration/address.html.haml b/app/assets/javascripts/templates/registration/address.html.haml new file mode 100644 index 0000000000..6fe39e9285 --- /dev/null +++ b/app/assets/javascripts/templates/registration/address.html.haml @@ -0,0 +1,60 @@ +.container#registration-address + .header + %h2 + Greetings + %span{ ng: { class: "{brick: enterprise.is_distributor, turquoise: !enterprise.is_distributor}" } } + {{ enterprise.name }} + + %h5 Now we need to know where you are + %ng-include{ src: "'registration/steps.html'" } + %form{ name: 'address', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('contact',address)" } } + .row.content + .small-12.medium-12.large-7.columns + .row + .small-12.columns.field + %label{ for: 'enterprise_address' } Address: + %input.chunky.small-12.columns{ id: 'enterprise_address', name: 'address1', required: true, placeholder: "eg. 123 Cranberry Drive", required: true, ng: { model: 'enterprise.address.address1' } } + %span.error.small-12.columns{ ng: { show: "address.address1.$error.required && submitted" } } + You need to enter an address. + .row + .small-12.large-8.columns.field + %label{ for: 'enterprise_city' } Suburb: + %input.chunky.small-12.columns{ id: 'enterprise_city', name: 'city', required: true, placeholder: "eg. Northcote", ng: { model: 'enterprise.address.city' } } + %span.error.small-12.columns{ ng: { show: "address.city.$error.required && submitted" } } + You need to enter a suburb. + .small-12.large-4.columns.field + %label{ for: 'enterprise_zipcode' } Postcode: + %input.chunky.small-12.columns{ id: 'enterprise_zipcode', name: 'zipcode', required: true, placeholder: "eg. 3070", ng: { model: 'enterprise.address.zipcode' } } + %span.error.small-12.columns{ ng: { show: "address.zipcode.$error.required && submitted" } } + You need to enter a postcode. + .row + .small-12.large-8.columns.field + %label{ for: 'enterprise_country' } Country: + %select.chunky.small-12.columns{ id: 'enterprise_country', name: 'country', required: true, ng: { model: 'enterprise.country', options: 'c as c.name for c in countries' } } + %span.error.small-12.columns{ ng: { show: "address.country.$error.required && submitted" } } + You need to enter a country. + .small-12.large-4.columns.field + %label{ for: 'enterprise_state' } State: + %select.chunky.small-12.columns{ id: 'enterprise_state', name: 'state', ng: { model: 'enterprise.address.state_id', options: 's.id as s.abbr for s in enterprise.country.states', show: 'countryHasStates()', required: 'countryHasStates()' } } + %span.error.small-12.columns{ ng: { show: "address.state.$error.required && submitted" } } + You need to enter a state. + .small-12.medium-12.large-5.hide-for-small-only + // This is the location area + / %h6 + / Location display + / %i.ofn-i_013-help.has-tip{ 'data-tooltip' => true, title: "Choose how you want to display your enterprise's address on the Open Food Network. By default, full location is shown everywhere including street name and number."} + / .row + / .small-12.columns + / %label.indent-checkbox + / %input{ type: 'checkbox', id: 'enterpise_suburb_only', ng: { model: 'enterprise.suburb_only' } } + / Hide my street name and street number from the public (ie. only show the suburb) + / .small-12.columns + / %label.indent-checkbox + / %input{ type: 'checkbox', id: 'enterprise_on_map', ng: { model: 'enterprise.on_map' } } + / Blur my location on the map (show an approximate, not exact pin) + + .row.buttons + .small-12.columns + %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('details')" } } +   + %input.button.primary{ type: "submit", value: "Continue" } diff --git a/app/assets/javascripts/templates/registration/contact.html.haml b/app/assets/javascripts/templates/registration/contact.html.haml new file mode 100644 index 0000000000..c7262248d0 --- /dev/null +++ b/app/assets/javascripts/templates/registration/contact.html.haml @@ -0,0 +1,46 @@ +.container#registration-contact + .header + %h2 Last step to create your enterprise! + %h5 + Who is responsible for managing + %span{ ng: { class: "{brick: enterprise.is_distributor, turquoise: !enterprise.is_distributor}" } } + {{ enterprise.name }}? + %ng-include{ src: "'registration/steps.html'" } + %form{ name: 'contact', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(contact)" } } + .row.content + .small-12.medium-12.large-7.columns + .row + .small-12.columns.field + %label{ for: 'enterprise_contact' } Primary Contact: + %input.chunky.small-12.columns{ id: 'enterprise_contact', name: 'contact', required: true, placeholder: "Contact Name", ng: { model: 'enterprise.contact' } } + %span.error.small-12.columns{ ng: { show: "contact.contact.$error.required && submitted" } } + You need to enter a primary contact. + .row + .small-12.columns.field + %label{ for: 'enterprise_email' } Email address: + %input.chunky.small-12.columns{ id: 'enterprise_email', name: 'email', type: 'email', required: true, placeholder: "eg. charlie@thefarm.com", ng: { model: 'enterprise.email' } } + %span.error.small-12.columns{ ng: { show: "(contact.email.$error.email || contact.email.$error.required) && submitted" } } + You need to enter valid email address. + .row + .small-12.columns.field + %label{ for: 'enterprise_phone' } Phone number: + %input.chunky.small-12.columns{ id: 'enterprise_phone', name: 'phone', placeholder: "eg. (03) 1234 5678", ng: { model: 'enterprise.phone' } } + .small-12.medium-12.large-5.hide-for-small-only + / %h6 + / Contact display + / %i.ofn-i_013-help.has-tip{ 'data-tooltip' => true, title: "Choose how you want to display your contact details on the Open Food Network."} + / .row + / .small-12.columns + / %label.indent-checkbox + / %input{ type: 'checkbox', id: 'contact_name_profile', ng: { model: 'enterprise.name_in_profile' } }   Display name in profile + / .small-12.columns + / %label.indent-checkbox + / %input{ type: 'checkbox', id: 'contact_email_profile', ng: { model: 'enterprise.email_in_profile' } }   Display email in profile + / .small-12.columns + / %label.indent-checkbox + / %input{ type: 'checkbox', id: 'contact_phone_profile', ng: { model: 'enterprise.phone_in_profile' } }   Display phone in profile + .row.buttons + .small-12.columns + %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('address')" } } +   + %input.button.primary{ type: "submit", value: "Continue" } diff --git a/app/assets/javascripts/templates/registration/details.html.haml b/app/assets/javascripts/templates/registration/details.html.haml new file mode 100644 index 0000000000..bb358a1864 --- /dev/null +++ b/app/assets/javascripts/templates/registration/details.html.haml @@ -0,0 +1,42 @@ +.container#registration-details{bindonce: true} + .header + %h2 Let's Get Started + %h5{ bo: { if: "enterprise.type != 'single'" } } Woot! First we need to know what sort of enterprise you are: + %h5{ bo: { if: "enterprise.type == 'single'" } } Woot! First we need to know the name of your farm: + %ng-include{ src: "'registration/steps.html'" } + %form{ name: 'details', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('address',details)" } } + .row + .small-12.columns.field + %label{ for: 'enterprise_name', bo: { if: "enterprise.type != 'single'" } } Enterprise Name: + %label{ for: 'enterprise_name', bo: { if: "enterprise.type == 'single'" } } Farm Name: + %input.chunky.small-12.columns{ id: 'enterprise_name', name: 'name', placeholder: "eg. Charlie's Awesome Farm", required: true, ng: { model: 'enterprise.name' } } + %span.error.small-12.columns{ ng: { show: "details.name.$error.required && submitted" } } + You need to enter a name for your enterprise! + + .row#enterprise-types{ 'data-equalizer' => true, bo: { if: "enterprise.type != 'single'" } } + .small-12.columns.field + .row + .small-12.columns + %label Choose one: + .row + .small-12.medium-4.large-4.columns{ 'data-equalizer-watch' => true } + %a.panel#producer-panel{ href: "#", ng: { click: "enterprise.is_distributor = false; enterprise.is_primary_producer = true", class: "{selected: (!enterprise.is_distributor && enterprise.is_primary_producer)}" } } + .left + / %render-svg{ path: "/assets/map-icon-producer.svg" } + %h4 I'm A Producer + %p Producers make yummy things to eat &/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it. + .small-12.medium-4.large-4.columns{ 'data-equalizer-watch' => true } + %a.panel#hub-panel{ href: "#", ng: { click: "enterprise.is_distributor = true; enterprise.is_primary_producer = false", class: "{selected: (enterprise.is_distributor && !enterprise.is_primary_producer)}" } } + .left + / %render-svg{ path: "/assets/map-icon-hub.svg" } + %h4 I'm A Hub + %p Hubs connect the producer to the eater. Hubs can be co-ops, independent retailers, buying groups, wholesalers, CSA box schemes, farm-gate stalls, etc. + .small-12.medium-4.large-4.columns{ 'data-equalizer-watch' => true } + %a.panel#both-panel{ href: "#", ng: { click: "enterprise.is_distributor = true; enterprise.is_primary_producer = true", class: "{selected: (enterprise.is_distributor && enterprise.is_primary_producer)}" } } + .left + / %render-svg{path: "/assets/map-icon-both.svg"} + %h4 I'm Both + %p Hey there, Jack-of-all-trades! Not only do you produce things to eat &/or drink, you also want to sell your yummies through an Open Food Network shopfront. + .row.buttons + .small-12.columns + %input.button.primary.right{ type: "submit", value: "Continue" } diff --git a/app/assets/javascripts/templates/registration/finished.html.haml b/app/assets/javascripts/templates/registration/finished.html.haml new file mode 100644 index 0000000000..75489c607a --- /dev/null +++ b/app/assets/javascripts/templates/registration/finished.html.haml @@ -0,0 +1,18 @@ +.container#registration-finished + .header + %h2 Well done! + %h5 + You have successfully completed the profile for + %span.brick{"ng-show" => "enterprise.is_distributor"} + {{ enterprise.name }} + %span.turquoise{"ng-show" => "!enterprise.is_distributor" } + {{ enterprise.name }} + .content{ style: 'text-align: center'} + %h3 Why not check it out on the Open Food Network? + %a.button.primary{ type: "button", href: "/map" } Go to Map Page > + + %br + %br + + %h3 Next step - add some products: + %a.button.primary{ type: "button", href: "/admin/products/new" } Add a Product > diff --git a/app/assets/javascripts/templates/registration/images.html.haml b/app/assets/javascripts/templates/registration/images.html.haml new file mode 100644 index 0000000000..efd3688076 --- /dev/null +++ b/app/assets/javascripts/templates/registration/images.html.haml @@ -0,0 +1,14 @@ +.container#registration-images + .header + %h2 Thanks! + %h5 Let's upload some pretty pictures so your profile looks great! :) + %ng-include{ src: "'registration/steps.html'" } + .row.content + + .row.buttons + .small-12.columns + %input.button.primary.left{ type: "button", value: "Back", ng: { click: "select('about')" } } +   + %input.button.primary.right{ type: "button", value: "Continue", ng: { click: "select('social')" } } + + \ No newline at end of file diff --git a/app/assets/javascripts/templates/registration/introduction.html.haml b/app/assets/javascripts/templates/registration/introduction.html.haml new file mode 100644 index 0000000000..8a4f4f7e02 --- /dev/null +++ b/app/assets/javascripts/templates/registration/introduction.html.haml @@ -0,0 +1,40 @@ +%div + .header + %h2 Hi there! + %h4 This wizard will step you through creating a profile + .row + .small-12.medium-3.large-2.columns.text-right.hide-for-small-only + %img{:src => "/assets/potatoes.png"} + .small-12.medium-9.large-10.columns + %p + Your profile gives you an online presence on the + %strong Open Food Network, + allowing you to easily connect with potential customers or partners. You can always choose to update your info later, as well as choose to upgrade your Profile to and Online Store, where you can sell products, track orders and receive payments. Creating a profile takes about 5-10 minutes. + .row{ 'data-equalizer' => true } + .small-12.medium-6.large-6.columns.pad-top{ 'data-equalizer-watch' => true } + %h5 You'll need the following: + %ul.check-list + %li + Your enterprise address and contact details + %li + Your logo image + %li + A pretty picture for your profile header + %li + Some 'About Us' text + + .small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true} + .highlight-box + %h5 Your profile entitles you to: + %ul.small-block-grid-1 + %li + %i.ofn-i_020-search + A searchable listing + %li + %i.ofn-i_040-hub + A pin on the OFN map + .row + .small-12.columns + %hr + %input.button.primary{ type: "button", value: "Let's get started!", ng: { click: "select('details')" } } + \ No newline at end of file diff --git a/app/assets/javascripts/templates/registration/social.html.haml b/app/assets/javascripts/templates/registration/social.html.haml new file mode 100644 index 0000000000..2aa12bd08b --- /dev/null +++ b/app/assets/javascripts/templates/registration/social.html.haml @@ -0,0 +1,35 @@ +.container#registration-social + .header + %h2 Last step! + %h5 How can people find {{ enterprise.name }} online? + %ng-include{ src: "'registration/steps.html'" } + %form{ name: 'social', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "update('finished',social)" } } + .row.content + .small-12.large-7.columns + .row + .small-12.columns + %label{ for: 'enterprise_website' } Website: + %input.chunky.small-12.columns{ id: 'enterprise_website', placeholder: "eg. openfoodnetwork.org.au", ng: { model: 'enterprise.website' } } + .row + .small-12.columns + %label{ for: 'enterprise_facebook' } Facebook: + %input.chunky.small-12.columns{ id: 'enterprise_facebook', placeholder: "eg. www.facebook.com/PageNameHere", ng: { model: 'enterprise.facebook' } } + .row + .small-12.columns + %label{ for: 'enterprise_linkedin' } LinkedIn: + %input.chunky.small-12.columns{ id: 'enterprise_linkedin', placeholder: "eg. www.linkedin.com/YourNameHere", ng: { model: 'enterprise.linkedin' } } + .small-12.large-5.columns + .row + .small-12.columns + %label{ for: 'enterprise_twitter' } Twitter: + %input.chunky.small-12.columns{ id: 'enterprise_twitter', placeholder: "eg. @twitter_handle", ng: { model: 'enterprise.twitter' } } + .row + .small-12.columns + %label{ for: 'enterprise_instagram' } Instagram: + %input.chunky.small-12.columns{ id: 'enterprise_instagram', placeholder: "eg. @instagram_handle", ng: { model: 'enterprise.instagram' } } + + .row.buttons + .small-12.columns + %input.button.secondary{ type: "button", value: "Back", ng: { click: "select('about')" } } +   + %input.button.primary{ type: "submit", value: "Continue" } \ No newline at end of file diff --git a/app/assets/javascripts/templates/registration/steps.html.haml b/app/assets/javascripts/templates/registration/steps.html.haml new file mode 100644 index 0000000000..65e3cb6b48 --- /dev/null +++ b/app/assets/javascripts/templates/registration/steps.html.haml @@ -0,0 +1,5 @@ +.row#progress-bar + .small-12.medium-2.columns.item{ ng: { repeat: 'step in steps', class: "{active: (currentStep() == step),'show-for-medium-up': (currentStep() != step)}" } } + {{ $index+1 + ". " + step }} + + \ No newline at end of file diff --git a/app/assets/javascripts/templates/registration_authentication.html.haml b/app/assets/javascripts/templates/registration_authentication.html.haml new file mode 100644 index 0000000000..c7bfbabeca --- /dev/null +++ b/app/assets/javascripts/templates/registration_authentication.html.haml @@ -0,0 +1,17 @@ +.container + .row.modal-centered + %h2 Welcome to the Open Food Network! + %h5 Start By Signing Up (or logging in): + %div{"ng-controller" => "AuthenticationCtrl"} + %tabset + %ng-include{src: "'signup.html'"} + %ng-include{src: "'login.html'"} + %ng-include{src: "'forgot.html'"} + %div{ ng: { show: "active('/signup')"} } + %hr + Already have an account? + %a{ href: "", ng: { click: "select('/login')"}} + Log in now. + +%a.close-reveal-modal{"ng-click" => "$close()"} + %i.ofn-i_009-close diff --git a/app/assets/javascripts/templates/signup.html.haml b/app/assets/javascripts/templates/signup.html.haml index db066685f1..28bec19b49 100644 --- a/app/assets/javascripts/templates/signup.html.haml +++ b/app/assets/javascripts/templates/signup.html.haml @@ -1,12 +1,12 @@ -%tab#sign-up-content{"ng-controller" => "SignupCtrl", - heading: "Sign up", +%tab#sign-up-content{"ng-controller" => "SignupCtrl", + heading: "Sign up", active: "active(path)", select: "select(path)"} %form{"ng-submit" => "submit()"} .row .large-12.columns %label{for: "email"} Your email - %input.title.input-text{name: "email", + %input.title.input-text{name: "email", type: "email", id: "email", tabindex: 1, @@ -16,7 +16,7 @@ .row .large-12.columns %label{for: "password"} Choose a password - %input.title.input-text{name: "password", + %input.title.input-text{name: "password", type: "password", id: "password", autocomplete: "off", @@ -27,7 +27,7 @@ .row .large-12.columns %label{for: "password_confirmation"} Confirm password - %input.title.input-text{name: "password_confirmation", + %input.title.input-text{name: "password_confirmation", type: "password", id: "password_confirmation", autocomplete: "off", @@ -35,7 +35,7 @@ "ng-model" => "spree_user.password_confirmation"} .row .large-12.columns - %input.button.primary{name: "commit", - tabindex: "3", - type: "submit", + %input.button.primary{name: "commit", + tabindex: "3", + type: "submit", value: "Sign up now"} diff --git a/app/assets/stylesheets/darkswarm/modals.css.sass b/app/assets/stylesheets/darkswarm/modals.css.sass index c9c3d4e8c8..5e6c06dcfd 100644 --- a/app/assets/stylesheets/darkswarm/modals.css.sass +++ b/app/assets/stylesheets/darkswarm/modals.css.sass @@ -22,8 +22,6 @@ dialog, .reveal-modal top: 10% max-height: 80% - - .reveal-modal-bg background-color: rgba(0,0,0,0.65) diff --git a/app/assets/stylesheets/darkswarm/registration.css.sass b/app/assets/stylesheets/darkswarm/registration.css.sass new file mode 100644 index 0000000000..3e851709bc --- /dev/null +++ b/app/assets/stylesheets/darkswarm/registration.css.sass @@ -0,0 +1,115 @@ +@import branding +@import mixins + +#registration-modal + .header + text-align: center + background-color: #efefef + padding-bottom: 1rem + .container + background-color: #ffffff + .content + // margin-bottom: 15px + + i + font-size: 150% + + .buttons + + ofn-inline-flash + display: block + padding: 15px + position: relative + margin-bottom: 10px + &.brick + background-color: $clr-brick-light + border: 2px solid $clr-brick + color: $clr-brick + &.turquoise + background-color: $clr-turquoise-light + border: 2px solid $clr-turquoise + color: $clr-turquoise + .close-button + position: absolute + top: 0px + right: 0px + + .field + margin-bottom: 15px + + input.chunky + padding: 8px + font-size: 105% + + label.indent-checkbox + display: block + padding-left: 20px + text-indent: -17px + input + margin: 0px + + label + margin-bottom: 3px + + ol, ul + // font-size: 80% + font-size: 0.875rem + padding: 0 + margin: 0 + ol + list-style-type: decimal + + .highlight-box + background: white + padding: 1rem 1.2rem + @media all and (max-width: 640px) + margin-top: 1rem + + #progress-bar + margin-bottom: 15px + .item + padding: 12px 0px + text-transform: uppercase + text-align: center + background-color: #333 + border: 2px solid #333 + color: #fff + .item.active + background-color: #cccccc + border: 2px solid #333 + color: #333 + @include box-shadow(inset 0 0 1px 0 #fff) + +#registration-details + #enterprise-types + a.panel + display: block + background-color: #efefef + color: black + @media all and (min-width: 768px) + min-height: 200px + &:hover + background-color: #fff + &#producer-panel:hover + &, & * + color: $clr-turquoise + &#hub-panel:hover, &#both-panel:hover + &, & * + color: $clr-brick + &.selected + &, & * + color: #fff + &#hub-panel, &#both-panel + background-color: $clr-brick-bright + &:hover + &, & * + color: white + &#producer-panel + background-color: $clr-turquoise-bright + &:hover + &, & * + color: white + p + clear: both + font-size: 0.875rem + diff --git a/app/assets/stylesheets/darkswarm/typography.css.sass b/app/assets/stylesheets/darkswarm/typography.css.sass index b49ce867c1..eb1885d361 100644 --- a/app/assets/stylesheets/darkswarm/typography.css.sass +++ b/app/assets/stylesheets/darkswarm/typography.css.sass @@ -48,6 +48,9 @@ small, .small .turquoise color: $clr-turquoise +.brick + color: $clr-brick + @mixin avenir font-family: "AvenirBla_IE", "AvenirBla" @@ -55,8 +58,8 @@ h1, h2, h3, h4, h5, h6, .avenir @include avenir padding: 0px -ul.bullet-list - margin: 0 +ul.bullet-list, ul.check-list + margin: 0 0 0 1.25em !important li list-style: none line-height: 1.5 @@ -64,12 +67,16 @@ ul.bullet-list li:before content: "\e609" font-family: "OFN" + margin-left: -1.25em display: inline-block font-weight: normal font-style: normal font-variant: normal text-transform: none - + +ul.check-list + li:before + content: "\e632" .light-grey color: #666666 diff --git a/app/controllers/admin/enterprises_controller.rb b/app/controllers/admin/enterprises_controller.rb index ceeb26abfc..64ba997950 100644 --- a/app/controllers/admin/enterprises_controller.rb +++ b/app/controllers/admin/enterprises_controller.rb @@ -3,9 +3,10 @@ module Admin before_filter :load_enterprise_set, :only => :index before_filter :load_countries, :except => :index before_filter :load_methods_and_fees, :only => [:new, :edit, :update, :create] - create.after :grant_management before_filter :check_type, only: :update before_filter :check_bulk_type, only: :bulk_update + before_filter :override_owner, only: :create + before_filter :check_owner, only: :update helper 'spree/products' include OrderCyclesHelper @@ -39,14 +40,6 @@ module Admin private - # When an enterprise user creates another enterprise, it is granted management - # permission for it - def grant_management - unless spree_current_user.has_spree_role? 'admin' - spree_current_user.enterprise_roles.create(enterprise: @object) - end - end - def load_enterprise_set @enterprise_set = EnterpriseSet.new :collection => collection end @@ -81,6 +74,16 @@ module Admin params[:enterprise].delete :type unless spree_current_user.admin? end + def override_owner + params[:enterprise][:owner_id] = spree_current_user.id unless spree_current_user.admin? + end + + def check_owner + unless spree_current_user == @enterprise.owner || spree_current_user.admin? + params[:enterprise].delete :owner_id + end + end + # Overriding method on Spree's resource controller def location_after_save if params[:enterprise].key? :producer_properties_attributes diff --git a/app/controllers/api/enterprises_controller.rb b/app/controllers/api/enterprises_controller.rb index 3dee7962c4..9a3b715093 100644 --- a/app/controllers/api/enterprises_controller.rb +++ b/app/controllers/api/enterprises_controller.rb @@ -1,5 +1,8 @@ module Api class EnterprisesController < Spree::Api::BaseController + + before_filter :override_owner, only: [:create, :update] + before_filter :check_type, only: :update respond_to :json def managed @@ -11,5 +14,37 @@ module Api @enterprises = Enterprise.ransack(params[:q]).result.accessible_by(current_api_user) render params[:template] || :bulk_index end + + def create + authorize! :create, Enterprise + + @enterprise = Enterprise.new(params[:enterprise]) + if @enterprise.save + render text: @enterprise.id, :status => 201 + else + invalid_resource!(@enterprise) + end + end + + def update + authorize! :update, Enterprise + + @enterprise = Enterprise.find(params[:id]) + if @enterprise.update_attributes(params[:enterprise]) + render text: @enterprise.id, :status => 200 + else + invalid_resource!(@enterprise) + end + end + + private + + def override_owner + params[:enterprise][:owner_id] = current_api_user.id + end + + def check_type + params[:enterprise].delete :type unless current_api_user.admin? + end end end diff --git a/app/controllers/registration_controller.rb b/app/controllers/registration_controller.rb new file mode 100644 index 0000000000..d58b10bd0b --- /dev/null +++ b/app/controllers/registration_controller.rb @@ -0,0 +1,25 @@ +require 'open_food_network/spree_api_key_loader' + +class RegistrationController < BaseController + include OpenFoodNetwork::SpreeApiKeyLoader + before_filter :load_spree_api_key, only: :index + before_filter :check_user, except: :authenticate + layout 'registration' + + def index + @enterprise_attributes = { type: 'profile' } + end + + def store + @enterprise_attributes = { is_distributor: true, is_primary_producer: true, type: 'single' } + render :index + end + + private + + def check_user + if spree_current_user.nil? + redirect_to registration_auth_path(anchor: "signup?after_login=#{request.env['PATH_INFO']}") + end + end +end diff --git a/app/controllers/spree/admin/orders_controller_decorator.rb b/app/controllers/spree/admin/orders_controller_decorator.rb index 27d9a36943..3e3c255ab3 100644 --- a/app/controllers/spree/admin/orders_controller_decorator.rb +++ b/app/controllers/spree/admin/orders_controller_decorator.rb @@ -1,4 +1,7 @@ +require 'open_food_network/spree_api_key_loader' + Spree::Admin::OrdersController.class_eval do + include OpenFoodNetwork::SpreeApiKeyLoader before_filter :load_spree_api_key, :only => :bulk_management # We need to add expections for collection actions other than :index here @@ -14,11 +17,4 @@ Spree::Admin::OrdersController.class_eval do page(params[:page]). per(params[:per_page] || Spree::Config[:orders_per_page]) } } } - - private - - def load_spree_api_key - current_user.generate_spree_api_key! unless spree_current_user.spree_api_key - @spree_api_key = spree_current_user.spree_api_key - end end diff --git a/app/controllers/spree/admin/products_controller_decorator.rb b/app/controllers/spree/admin/products_controller_decorator.rb index a5b38126e5..4c9c32dbae 100644 --- a/app/controllers/spree/admin/products_controller_decorator.rb +++ b/app/controllers/spree/admin/products_controller_decorator.rb @@ -1,5 +1,9 @@ +require 'open_food_network/spree_api_key_loader' + Spree::Admin::ProductsController.class_eval do - before_filter :load_bpe_data, :only => :bulk_edit + include OpenFoodNetwork::SpreeApiKeyLoader + before_filter :load_form_data, :only => [:bulk_edit, :new, :edit] + before_filter :load_spree_api_key, :only => :bulk_edit alias_method :location_after_save_original, :location_after_save @@ -85,9 +89,7 @@ Spree::Admin::ProductsController.class_eval do private - def load_bpe_data - current_user.generate_spree_api_key! unless spree_current_user.spree_api_key - @spree_api_key = spree_current_user.spree_api_key + def load_form_data @producers = OpenFoodNetwork::Permissions.new(spree_current_user).managed_product_enterprises.is_primary_producer.by_name @taxons = Spree::Taxon.order(:name) end diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 9ce62b2719..7fec829fe7 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -37,7 +37,9 @@ module Admin admin_inject_json_ams_array "ofn.admin", "users", @users, Api::Admin::UserSerializer end - + def admin_inject_spree_api_key + render partial: "admin/json/injection_ams", locals: {ngModule: 'ofn.admin', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"} + end def admin_inject_json_ams(ngModule, name, data, serializer, opts = {}) diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index c639d22104..795dcb6a5c 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -2,18 +2,18 @@ module InjectionHelper def inject_enterprises inject_json_ams "enterprises", Enterprise.all, Api::EnterpriseSerializer, active_distributors: @active_distributors end - + def inject_current_order inject_json_ams "currentOrder", current_order, Api::CurrentOrderSerializer, current_distributor: current_distributor, current_order_cycle: current_order_cycle end def inject_available_shipping_methods - inject_json_ams "shippingMethods", available_shipping_methods, + inject_json_ams "shippingMethods", available_shipping_methods, Api::ShippingMethodSerializer, current_order: current_order end def inject_available_payment_methods - inject_json_ams "paymentMethods", current_order.available_payment_methods, + inject_json_ams "paymentMethods", current_order.available_payment_methods, Api::PaymentMethodSerializer end @@ -21,6 +21,18 @@ module InjectionHelper inject_json_ams "taxons", Spree::Taxon.all, Api::TaxonSerializer end + def inject_spree_api_key + render partial: "json/injection_ams", locals: {name: 'spreeApiKey', json: "'#{@spree_api_key.to_s}'"} + end + + def inject_available_countries + inject_json_ams "availableCountries", available_countries, Api::CountrySerializer + end + + def inject_enterprise_attributes + render partial: "json/injection_ams", locals: {name: 'enterpriseAttributes', json: "#{@enterprise_attributes.to_json}"} + end + def inject_json(name, partial, opts = {}) render partial: "json/injection", locals: {name: name, partial: partial}.merge(opts) end diff --git a/app/mailers/enterprise_mailer.rb b/app/mailers/enterprise_mailer.rb new file mode 100644 index 0000000000..73c09e1e56 --- /dev/null +++ b/app/mailers/enterprise_mailer.rb @@ -0,0 +1,12 @@ +class EnterpriseMailer < Spree::BaseMailer + def creation_confirmation(enterprise) + find_enterprise(enterprise) + subject = "#{@enterprise.name} is now on #{Spree::Config[:site_name]}" + mail(:to => @enterprise.owner.email, :from => from_address, :subject => subject) + end + + private + def find_enterprise(enterprise) + @enterprise = enterprise.is_a?(Enterprise) ? enterprise : Enterprise.find(enterprise) + end +end diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 004923097e..537ac78295 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -6,6 +6,8 @@ class Enterprise < ActiveRecord::Base acts_as_gmappable :process_geocoding => false + after_create :send_creation_email + has_and_belongs_to_many :groups, class_name: 'EnterpriseGroup' has_many :producer_properties, foreign_key: 'producer_id' has_many :supplied_products, :class_name => 'Spree::Product', :foreign_key => 'supplier_id', :dependent => :destroy @@ -16,6 +18,7 @@ class Enterprise < ActiveRecord::Base has_many :enterprise_fees has_many :enterprise_roles, :dependent => :destroy has_many :users, through: :enterprise_roles + belongs_to :owner, class_name: 'Spree::User', foreign_key: :owner_id, inverse_of: :owned_enterprises has_and_belongs_to_many :payment_methods, join_table: 'distributors_payment_methods', class_name: 'Spree::PaymentMethod', foreign_key: 'distributor_id' has_many :distributor_shipping_methods, foreign_key: :distributor_id has_many :shipping_methods, through: :distributor_shipping_methods @@ -46,7 +49,10 @@ class Enterprise < ActiveRecord::Base validates :name, presence: true validates :type, presence: true, inclusion: {in: TYPES} validates :address, presence: true, associated: true + validates_presence_of :owner + validate :enforce_ownership_limit, if: lambda { owner_id_changed? } + before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? } before_validation :set_unused_address_fields after_validation :geocode_address @@ -223,6 +229,10 @@ class Enterprise < ActiveRecord::Base private + def send_creation_email + EnterpriseMailer.creation_confirmation(self).deliver + end + def strip_url(url) url.andand.sub /(https?:\/\/)?/, '' end @@ -234,4 +244,14 @@ class Enterprise < ActiveRecord::Base def geocode_address address.geocode if address.changed? end + + def ensure_owner_is_manager + users << owner unless users.include?(owner) || owner.admin? + end + + def enforce_ownership_limit + unless owner.can_own_more_enterprises? + errors.add(:owner, "^You are not permitted to own own any more enterprises (limit is #{owner.enterprise_limit}).") + end + end end diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index 9dbc8226f5..06a413b469 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -1,13 +1,16 @@ Spree.user_class.class_eval do has_many :enterprise_roles, :dependent => :destroy has_many :enterprises, through: :enterprise_roles + has_many :owned_enterprises, class_name: 'Enterprise', foreign_key: :owner_id, inverse_of: :owner has_one :cart accepts_nested_attributes_for :enterprise_roles, :allow_destroy => true - attr_accessible :enterprise_ids, :enterprise_roles_attributes + attr_accessible :enterprise_ids, :enterprise_roles_attributes, :enterprise_limit after_create :send_signup_confirmation + validate :limit_owned_enterprises + def build_enterprise_roles Enterprise.all.each do |enterprise| unless self.enterprise_roles.find_by_enterprise_id enterprise.id @@ -19,4 +22,16 @@ Spree.user_class.class_eval do def send_signup_confirmation Spree::UserMailer.signup_confirmation(self).deliver end + + def can_own_more_enterprises? + owned_enterprises(:reload).size < enterprise_limit + end + + private + + def limit_owned_enterprises + if owned_enterprises.size > enterprise_limit + errors.add(:owned_enterprises, "^The nominated user is not permitted to own own any more enterprises (limit is #{enterprise_limit}).") + end + end end diff --git a/app/overrides/add_supplier_to_admin_product.rb b/app/overrides/add_supplier_to_admin_product.rb deleted file mode 100644 index a608f4e282..0000000000 --- a/app/overrides/add_supplier_to_admin_product.rb +++ /dev/null @@ -1,5 +0,0 @@ -Deface::Override.new(:virtual_path => "spree/admin/products/_form", - :insert_top => "[data-hook='admin_product_form_right']", - :partial => "spree/admin/products/supplier_form", - :name => "add_supplier_to_admin_product", - :original => '18bd94de3eb8bdf8b669932bf04fc59e2e85288b') \ No newline at end of file diff --git a/app/overrides/admin/enterprises/show/add_distributor_info.html.haml.deface b/app/overrides/admin/enterprises/show/add_distributor_info.html.haml.deface deleted file mode 100644 index 866d632e18..0000000000 --- a/app/overrides/admin/enterprises/show/add_distributor_info.html.haml.deface +++ /dev/null @@ -1,4 +0,0 @@ -/ insert_after "[data-hook='long_description']" -%tr{'data-hook' => 'distributor_info'} - %th Distributor Info: - %td= @enterprise.distributor_info.andand.html_safe \ No newline at end of file diff --git a/app/overrides/spree/admin/products/_form/add_supplier.html.haml.deface b/app/overrides/spree/admin/products/_form/add_supplier.html.haml.deface new file mode 100644 index 0000000000..1cc609bef4 --- /dev/null +++ b/app/overrides/spree/admin/products/_form/add_supplier.html.haml.deface @@ -0,0 +1,7 @@ +/ insert_top "[data-hook='admin_product_form_right']" + += f.field_container :supplier do + = f.label :supplier + %br + = f.collection_select(:supplier_id, @producers, :id, :name, {:include_blank => true}, {:class => "select2"}) + = f.error_message_on :supplier diff --git a/app/overrides/spree/admin/products/new/replace_form.html.haml.deface b/app/overrides/spree/admin/products/new/replace_form.html.haml.deface index 334739a4c2..7c627eec4e 100644 --- a/app/overrides/spree/admin/products/new/replace_form.html.haml.deface +++ b/app/overrides/spree/admin/products/new/replace_form.html.haml.deface @@ -7,7 +7,7 @@ = f.field_container :supplier do = f.label :supplier_id, t(:supplier) %span.required * - = f.collection_select(:supplier_id, Enterprise.is_primary_producer.managed_by(spree_current_user).by_name, :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"}) + = f.collection_select(:supplier_id, @producers, :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"}) = f.error_message_on :supplier .six.columns.omega = f.field_container :name do diff --git a/app/overrides/spree/admin/users/_form/add_enterprise_limit_form_element.html.haml.deface b/app/overrides/spree/admin/users/_form/add_enterprise_limit_form_element.html.haml.deface new file mode 100644 index 0000000000..0b00900b5c --- /dev/null +++ b/app/overrides/spree/admin/users/_form/add_enterprise_limit_form_element.html.haml.deface @@ -0,0 +1,5 @@ +/ insert_bottom "div[data-hook='admin_user_form_fields'] div.alpha" + += f.field_container :enterprise_limit do + = f.label :enterprise_limit, t(:enterprise_limit) + = f.text_field :enterprise_limit, :class => 'fullwidth' \ No newline at end of file diff --git a/app/overrides/spree/admin/users/index/add_enterprise_limit_column.html.haml.deface b/app/overrides/spree/admin/users/index/add_enterprise_limit_column.html.haml.deface new file mode 100644 index 0000000000..d16e186be8 --- /dev/null +++ b/app/overrides/spree/admin/users/index/add_enterprise_limit_column.html.haml.deface @@ -0,0 +1,3 @@ +/ insert_before "td[data-hook='admin_users_index_row_actions']" + +%td.user_enterprise_limit= user.enterprise_limit \ No newline at end of file diff --git a/app/overrides/spree/admin/users/index/add_enterprise_limit_column_header.html.haml.deface b/app/overrides/spree/admin/users/index/add_enterprise_limit_column_header.html.haml.deface new file mode 100644 index 0000000000..f2222ef012 --- /dev/null +++ b/app/overrides/spree/admin/users/index/add_enterprise_limit_column_header.html.haml.deface @@ -0,0 +1,3 @@ +/ insert_before "th[data-hook='admin_users_index_header_actions']" + +%th= sort_link @search,:enterprise_limit, t(:enterprise_limit) \ No newline at end of file diff --git a/app/overrides/spree/admin/users/index/reconfigure_column_spacing.html.haml.deface b/app/overrides/spree/admin/users/index/reconfigure_column_spacing.html.haml.deface new file mode 100644 index 0000000000..d666e1b7c5 --- /dev/null +++ b/app/overrides/spree/admin/users/index/reconfigure_column_spacing.html.haml.deface @@ -0,0 +1,6 @@ +/ replace "table#listing_users colgroup" + +%colgroup + %col{ style: "width: 65%" } + %col{ style: "width: 20%" } + %col{ style: "width: 15%" } \ No newline at end of file diff --git a/app/overrides/spree/admin/users/index/replace_show_link_with_edit_link.html.haml.deface b/app/overrides/spree/admin/users/index/replace_show_link_with_edit_link.html.haml.deface new file mode 100644 index 0000000000..330e06ea9d --- /dev/null +++ b/app/overrides/spree/admin/users/index/replace_show_link_with_edit_link.html.haml.deface @@ -0,0 +1,3 @@ +/ replace "code[erb-loud]:contains('link_to user.email, object_url(user)')" + += link_to user.email, edit_object_url(user) \ No newline at end of file diff --git a/app/overrides/users_add_enterprise_management.rb b/app/overrides/users_add_enterprise_management.rb deleted file mode 100644 index 4ca1dd9e49..0000000000 --- a/app/overrides/users_add_enterprise_management.rb +++ /dev/null @@ -1,6 +0,0 @@ -Deface::Override.new(:virtual_path => "spree/admin/users/_form", - :insert_after => "[data-hook='admin_user_form_fields']", - :partial => "spree/admin/users/enterprises_form", - :name => "add_enterprises_to_user" - ) - diff --git a/app/serializers/api/country_serializer.rb b/app/serializers/api/country_serializer.rb new file mode 100644 index 0000000000..6561692cf6 --- /dev/null +++ b/app/serializers/api/country_serializer.rb @@ -0,0 +1,5 @@ +class Api::CountrySerializer < ActiveModel::Serializer + attributes :id, :name, :states + + has_many :states, serializer: Api::StateSerializer +end \ No newline at end of file diff --git a/app/serializers/api/state_serializer.rb b/app/serializers/api/state_serializer.rb new file mode 100644 index 0000000000..bcf9221ec5 --- /dev/null +++ b/app/serializers/api/state_serializer.rb @@ -0,0 +1,3 @@ +class Api::StateSerializer < ActiveModel::Serializer + attributes :id, :name, :abbr +end \ No newline at end of file diff --git a/app/views/admin/enterprise_roles/index.html.haml b/app/views/admin/enterprise_roles/index.html.haml index 0881a8a50e..a6e0985a99 100644 --- a/app/views/admin/enterprise_roles/index.html.haml +++ b/app/views/admin/enterprise_roles/index.html.haml @@ -11,5 +11,5 @@ %table#enterprise-roles %tbody = render 'form' - %tr{"ng-repeat" => "enterprise_role in EnterpriseRoles.enterprise_roles | filter:query"} + %tr{"ng-repeat" => "enterprise_role in EnterpriseRoles.enterprise_roles | filter:query", id: "enterprise_role_{{enterprise_role.id}}"} = render 'enterprise_role' diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml index dbf986c7aa..6f86c3e703 100644 --- a/app/views/admin/enterprises/_form.html.haml +++ b/app/views/admin/enterprises/_form.html.haml @@ -1,6 +1,3 @@ -- content_for :head do - = render 'shared/cms_elrte_head' - - content_for :page_actions do %li= button_link_to "Back to enterprises list", main_app.admin_enterprises_path, icon: 'icon-arrow-left' @@ -23,6 +20,16 @@ .eight.columns.omega = f.collection_select :group_ids, EnterpriseGroup.all, :id, :name, {}, class: "select2 fullwidth", multiple: true, placeholder: "Start typing to search available groups..." + - if spree_current_user.admin? + .row + .three.columns.alpha + =f.label :owner_id, 'Owner' + .with-tip{'data-powertip' => "The primary user responsible for this enterprise."} + %a What's this? + .eight.columns + - owner_email = @enterprise.andand.owner.andand.email || "" + = f.hidden_field :owner_id, class: "select2 fullwidth", 'ofn-user-autocomplete' => true, email: owner_email + .row .three.columns.alpha %label Enterprise Type(s) @@ -174,20 +181,8 @@ .row .alpha.three.columns = f.label :long_description, 'About Us' - %br - Tell us about yourself. This information appears on your public profile (under "About Us") .omega.eight.columns - = f.text_area :long_description, class: 'rich_text', placeholder: 'Tell us about yourself. This information appears on your public profile (under "About Us")' - .row - .alpha.three.columns - = f.label :distributor_info, 'How does your hub work?' - %br - %em (Hub only) - %br - Explain your distribution offer/s - this information appears on your public profile (under "How does it work?") - .omega.eight.columns - = f.text_area :distributor_info, class: 'rich_text', placeholder: 'Hub only: Explain your distribution offer/s - this is more detailed information that the user can access by clicking on "How does it work?"' - / TODO: editor breaks scrolling with arrow keys + = f.text_area :long_description, rows: 6, placeholder: 'Tell us about yourself. This information appears on your public profile (under "About Us")', class: 'fullwidth' %fieldset.eleven.columns.alpha.no-border-bottom %legend IMAGES .row diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml index c8b3cce3d4..44008b3bd2 100644 --- a/app/views/admin/enterprises/index.html.haml +++ b/app/views/admin/enterprises/index.html.haml @@ -2,8 +2,9 @@ Enterprises - content_for :page_actions do - %li#new_product_link - = button_link_to "New Enterprise", main_app.new_admin_enterprise_path, :icon => 'icon-plus', :id => 'admin_new_enterprise_link' + - if spree_current_user.can_own_more_enterprises? + %li#new_product_link + = button_link_to "New Enterprise", main_app.new_admin_enterprise_path, :icon => 'icon-plus', :id => 'admin_new_enterprise_link' = render 'admin/shared/enterprises_sub_menu' @@ -11,16 +12,20 @@ %table#listing_enterprises.index %colgroup %col{style: "width: 25%;"}/ - %col{style: "width: 10%;"}/ + %col{style: "width: 15%;"}/ %col{style: "width: 5%;"}/ - %col{style: "width: 10%;"}/ - %col{style: "width: 20%;"}/ + - if spree_current_user.admin? + %col{style: "width: 12%;"}/ + %col{style: "width: 18%;"}/ + %col{style: "width: 25%;"}/ %thead %tr{"data-hook" => "enterprises_header"} %th Name %th Role %th Visible? - %th Type + - if spree_current_user.admin? + %th Type + %th Owner %th %tbody = f.fields_for :collection do |enterprise_form| @@ -34,7 +39,9 @@ = enterprise_form.check_box :is_distributor Hub %td= enterprise_form.check_box :visible - %td= enterprise_form.select :type, Enterprise::TYPES, {}, class: 'select2 fullwidth' + - if spree_current_user.admin? + %td= enterprise_form.select :type, Enterprise::TYPES, {}, class: 'select2 fullwidth' + %td= enterprise_form.select :owner_id, enterprise.users.map{ |e| [ e.email, e.id ] }, {}, class: "select2 fullwidth" %td{"data-hook" => "admin_users_index_row_actions"} = render 'actions', enterprise: enterprise - if @enterprises.empty? diff --git a/app/views/enterprise_mailer/creation_confirmation.html.haml b/app/views/enterprise_mailer/creation_confirmation.html.haml new file mode 100644 index 0000000000..0df3bf06a0 --- /dev/null +++ b/app/views/enterprise_mailer/creation_confirmation.html.haml @@ -0,0 +1,9 @@ +%h1 + = @enterprise.name + " has been created" + +%h3 + Why not check it out on + %a{ href: "#{map_url}" } + = Spree::Config[:site_name] + "?" + +If you have any questions, please get in touch with us at: hello@openfoodnetwork.org diff --git a/app/views/layouts/registration.html.haml b/app/views/layouts/registration.html.haml new file mode 100644 index 0000000000..8946a27de1 --- /dev/null +++ b/app/views/layouts/registration.html.haml @@ -0,0 +1,36 @@ +%html + %head + %meta{charset: 'utf-8'}/ + %meta{name: 'viewport', content: "width=device-width,initial-scale=1.0"}/ + + %title= content_for?(:title) ? yield(:title) : 'Welcome to Open Food Network' + - if Rails.env.production? + = favicon_link_tag + - else + = favicon_link_tag "/favicon-staging.ico" + %link{href: "https://fonts.googleapis.com/css?family=Open+Sans:400,700", rel: "stylesheet", type: "text/css"}/ + + = yield :scripts + %script{src: "//maps.googleapis.com/maps/api/js?libraries=places&sensor=false"} + = stylesheet_link_tag "darkswarm/all" + = javascript_include_tag "darkswarm/all" + + + = render "layouts/bugherd_script" + = csrf_meta_tags + + %body.off-canvas{"ng-app" => "Darkswarm", style: 'background-image: url("/assets/home/ofn_bg_1.jpg")' } + / [if lte IE 8] + = render partial: "shared/ie_warning" + = javascript_include_tag "iehack" + + = inject_json "user", "current_user" + + .off-canvas-wrap{offcanvas: true} + .inner-wrap + + %section{ role: "main" } + = yield + + #footer + %loading diff --git a/app/views/registration/authenticate.html.haml b/app/views/registration/authenticate.html.haml new file mode 100644 index 0000000000..2b65757a5d --- /dev/null +++ b/app/views/registration/authenticate.html.haml @@ -0,0 +1 @@ +%div{"ng-controller" => "AuthenticationCtrl"} \ No newline at end of file diff --git a/app/views/registration/index.html.haml b/app/views/registration/index.html.haml new file mode 100644 index 0000000000..de09d9494f --- /dev/null +++ b/app/views/registration/index.html.haml @@ -0,0 +1,4 @@ +=inject_spree_api_key +=inject_available_countries +=inject_enterprise_attributes +%div{ "ng-controller" => "RegistrationCtrl" } \ No newline at end of file diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 08a05595c5..f701813e9e 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -4,7 +4,9 @@ = render :partial => 'spree/admin/shared/order_sub_menu' -%div{ 'ng-app' => 'ofn.admin', 'ng-controller' => 'AdminOrderMgmtCtrl', 'ng-init' => "initialise('#{@spree_api_key}');loading=true;" } +=admin_inject_spree_api_key + +%div{ ng: { app: 'ofn.admin', controller: 'AdminOrderMgmtCtrl', init: 'initialise()' } } %div{ 'ng-show' => '!spree_api_key_ok' } {{ api_error_msg }} .filters{ :class => "sixteen columns alpha" } diff --git a/app/views/spree/admin/overview/_enterprises_header.html.haml b/app/views/spree/admin/overview/_enterprises_header.html.haml index d53828ca65..fcc7d269f2 100644 --- a/app/views/spree/admin/overview/_enterprises_header.html.haml +++ b/app/views/spree/admin/overview/_enterprises_header.html.haml @@ -1,7 +1,8 @@ %div.header.sixteen.columns.alpha{ :class => "#{@enterprises.count > 0 ? "" : "red"}"} %h3.thirteen.columns.alpha My Enterprises - if @enterprises.any? - %a.three.columns.omega.icon-plus.button.blue.white-bottom{ href: "#{main_app.new_admin_enterprise_path}" } - CREATE NEW + - if spree_current_user.can_own_more_enterprises? + %a.three.columns.omega.icon-plus.button.blue.white-bottom{ href: "#{main_app.new_admin_enterprise_path}" } + CREATE NEW - else %a.with-tip{ title: "Enterprises are Producers and/or Hubs and are the basic unit of organisation within the Open Food Network." } What's this? diff --git a/app/views/spree/admin/products/_supplier_form.html.haml b/app/views/spree/admin/products/_supplier_form.html.haml deleted file mode 100644 index 3e5c01c2a3..0000000000 --- a/app/views/spree/admin/products/_supplier_form.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -= f.field_container :supplier do - = f.label :supplier - %br - = f.collection_select(:supplier_id, Enterprise.is_primary_producer.managed_by(spree_current_user).by_name, :id, :name, {:include_blank => true}, {:class => "select2"}) - = f.error_message_on :supplier diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index fa322931cd..f230d1c331 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -1,7 +1,7 @@ = render 'spree/admin/products/bulk_edit/header' = render 'spree/admin/products/bulk_edit/data' -%div{ 'ng-app' => 'ofn.admin', 'ng-controller' => 'AdminProductEditCtrl', 'ng-init' => "initialise('#{@spree_api_key}');loading=true;" } +%div{ ng: { app: 'ofn.admin', controller: 'AdminProductEditCtrl', init: 'initialise()' } } = render 'spree/admin/products/bulk_edit/filters' %hr.sixteen.columns.alpha diff --git a/app/views/spree/admin/products/bulk_edit/_data.html.haml b/app/views/spree/admin/products/bulk_edit/_data.html.haml index 25d595bda1..8b727be3eb 100644 --- a/app/views/spree/admin/products/bulk_edit/_data.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_data.html.haml @@ -1,2 +1,3 @@ = admin_inject_producers = admin_inject_taxons += admin_inject_spree_api_key diff --git a/app/views/spree/admin/users/_enterprises_form.html.haml b/app/views/spree/admin/users/_enterprises_form.html.haml deleted file mode 100644 index 03cecd41d5..0000000000 --- a/app/views/spree/admin/users/_enterprises_form.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -%fieldset - %legend 'Manage Enterprises' - = f.field_container :enterprise_roles do - - f.object.build_enterprise_roles - %table - = f.fields_for :enterprise_roles do |enterprise_form| - %tr - %td - = hidden_field_tag "#{enterprise_form.object_name}[_destroy]", 1, :id => nil - = check_box_tag "#{enterprise_form.object_name}[_destroy]", 0, !enterprise_form.object.new_record? - %td - = label_tag "#{enterprise_form.object_name}[_destroy]", enterprise_form.object.enterprise.name - = enterprise_form.hidden_field :enterprise_id diff --git a/config/routes.rb b/config/routes.rb index 5230e798cc..f95b0c07df 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,10 @@ Openfoodnetwork::Application.routes.draw do get "/map", to: "map#index", as: :map + get "/register", to: "registration#index", as: :registration + get "/register/store", to: "registration#store", as: :store_registration + get "/register/auth", to: "registration#authenticate", as: :registration_auth + resource :shop, controller: "shop" do get :products post :order_cycle diff --git a/db/migrate/20140828023619_add_owner_to_enterprise.rb b/db/migrate/20140828023619_add_owner_to_enterprise.rb new file mode 100644 index 0000000000..0d859ff84b --- /dev/null +++ b/db/migrate/20140828023619_add_owner_to_enterprise.rb @@ -0,0 +1,20 @@ +class AddOwnerToEnterprise < ActiveRecord::Migration + def up + add_column :enterprises, :owner_id, :integer + add_index :enterprises, :owner_id + + Enterprise.all.each do |e| + owner = e.users.find{ |u| !u.admin? } + admin_owner = e.users.find &:admin? + any_admin = Spree::User.admin.first + e.update_column :owner_id, (owner || admin_owner || any_admin ) + end + + add_foreign_key :enterprises, :spree_users, column: :owner_id + change_column :enterprises, :owner_id, :integer, null: false + end + + def down + remove_column :enterprises, :owner_id + end +end diff --git a/db/migrate/20140904003026_add_enterprise_limit_to_spree_users.rb b/db/migrate/20140904003026_add_enterprise_limit_to_spree_users.rb new file mode 100644 index 0000000000..b76a3da2d7 --- /dev/null +++ b/db/migrate/20140904003026_add_enterprise_limit_to_spree_users.rb @@ -0,0 +1,18 @@ +class AddEnterpriseLimitToSpreeUsers < ActiveRecord::Migration + def up + add_column :spree_users, :enterprise_limit, :integer, default: 1, null: false + + Spree::User.all.each do |u| + e_count = u.owned_enterprises.length + if u.admin? || e_count > 1 + e_limit = 100 + e_limit = 1000 if u.admin? + u.update_column :enterprise_limit, e_limit + end + end + end + + def down + remove_column :spree_users, :enterprise_limit + end +end diff --git a/db/schema.rb b/db/schema.rb index 462e89c952..c8005a42e9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140826043521) do +ActiveRecord::Schema.define(:version => 20140904003026) do create_table "adjustment_metadata", :force => true do |t| t.integer "adjustment_id" @@ -265,9 +265,11 @@ ActiveRecord::Schema.define(:version => 20140826043521) do t.string "instagram" t.string "linkedin" t.string "type", :default => "profile", :null => false + t.integer "owner_id", :null => false end add_index "enterprises", ["address_id"], :name => "index_enterprises_on_address_id" + add_index "enterprises", ["owner_id"], :name => "index_enterprises_on_owner_id" create_table "exchange_fees", :force => true do |t| t.integer "exchange_id" @@ -969,6 +971,7 @@ ActiveRecord::Schema.define(:version => 20140826043521) do t.string "spree_api_key", :limit => 48 t.datetime "reset_password_sent_at" t.string "api_key", :limit => 40 + t.integer "enterprise_limit", :default => 1, :null => false end add_index "spree_users", ["email"], :name => "email_idx_unique", :unique => true @@ -1069,6 +1072,7 @@ ActiveRecord::Schema.define(:version => 20140826043521) do add_foreign_key "enterprise_roles", "spree_users", name: "enterprise_roles_user_id_fk", column: "user_id" add_foreign_key "enterprises", "spree_addresses", name: "enterprises_address_id_fk", column: "address_id" + add_foreign_key "enterprises", "spree_users", name: "enterprises_owner_id_fk", column: "owner_id" add_foreign_key "exchange_fees", "enterprise_fees", name: "exchange_fees_enterprise_fee_id_fk" add_foreign_key "exchange_fees", "exchanges", name: "exchange_fees_exchange_id_fk" diff --git a/lib/open_food_network/spree_api_key_loader.rb b/lib/open_food_network/spree_api_key_loader.rb new file mode 100644 index 0000000000..36fa4b9961 --- /dev/null +++ b/lib/open_food_network/spree_api_key_loader.rb @@ -0,0 +1,12 @@ +module OpenFoodNetwork + module SpreeApiKeyLoader + def load_spree_api_key + if spree_current_user + spree_current_user.generate_spree_api_key! unless spree_current_user.spree_api_key + @spree_api_key = spree_current_user.spree_api_key + else + @spree_api_key = nil + end + end + end +end \ No newline at end of file diff --git a/spec/controllers/admin/enterprises_controller_spec.rb b/spec/controllers/admin/enterprises_controller_spec.rb index 14faf26287..44421aad8e 100644 --- a/spec/controllers/admin/enterprises_controller_spec.rb +++ b/spec/controllers/admin/enterprises_controller_spec.rb @@ -2,7 +2,12 @@ require 'spec_helper' module Admin describe EnterprisesController do - let(:distributor) { create(:distributor_enterprise) } + let(:distributor_owner) do + user = create(:user) + user.spree_roles = [] + user + end + let(:distributor) { create(:distributor_enterprise, owner: distributor_owner ) } let(:user) do user = create(:user) user.spree_roles = [] @@ -22,6 +27,7 @@ module Admin it "grants management permission if the current user is an enterprise user" do controller.stub spree_current_user: user + enterprise_params[:enterprise][:owner_id] = user spree_put :create, enterprise_params enterprise = Enterprise.find_by_name 'zzz' @@ -30,11 +36,50 @@ module Admin it "does not grant management permission to admins" do controller.stub spree_current_user: admin_user + enterprise_params[:enterprise][:owner_id] = admin_user spree_put :create, enterprise_params enterprise = Enterprise.find_by_name 'zzz' admin_user.enterprise_roles.where(enterprise_id: enterprise).should be_empty end + + it "it overrides the owner_id submitted by the user unless current_user is super admin" do + controller.stub spree_current_user: user + enterprise_params[:enterprise][:owner_id] = admin_user + + spree_put :create, enterprise_params + enterprise = Enterprise.find_by_name 'zzz' + user.enterprise_roles.where(enterprise_id: enterprise).first.should be + end + end + + describe "updating an enterprise" do + it "allows current owner to change ownership" do + controller.stub spree_current_user: distributor_owner + update_params = { id: distributor, enterprise: { owner_id: user } } + spree_post :update, update_params + + distributor.reload + expect(distributor.owner).to eq user + end + + it "allows super admin to change ownership" do + controller.stub spree_current_user: admin_user + update_params = { id: distributor, enterprise: { owner_id: user } } + spree_post :update, update_params + + distributor.reload + expect(distributor.owner).to eq user + end + + it "does not allow managers to change ownership" do + controller.stub spree_current_user: user + update_params = { id: distributor, enterprise: { owner_id: user } } + spree_post :update, update_params + + distributor.reload + expect(distributor.owner).to eq distributor_owner + end end describe "updating an enterprise" do diff --git a/spec/controllers/registration_controller_spec.rb b/spec/controllers/registration_controller_spec.rb new file mode 100644 index 0000000000..49efc005f6 --- /dev/null +++ b/spec/controllers/registration_controller_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe RegistrationController do + describe "redirecting when user not logged in" do + it "index" do + get :index + response.should redirect_to registration_auth_path(anchor: "signup?after_login=/register") + end + + it "store" do + get :store + response.should redirect_to registration_auth_path(anchor: "signup?after_login=/register/store") + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 849d38abf3..d605b5b759 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -82,6 +82,7 @@ FactoryGirl.define do end factory :enterprise, :class => Enterprise do + owner { FactoryGirl.create :user } sequence(:name) { |n| "Enterprise #{n}" } type 'full' description 'enterprise' diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 9f530f860c..d47c699c8f 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -305,7 +305,6 @@ feature %q{ expect(page).to have_select "producer", selected: s1.name expect(page).to have_field "available_on", with: p.available_on.strftime("%F %T") expect(page).to have_field "price", with: "10.0" - save_screenshot '/Users/rob/Desktop/ss.png' expect(page).to have_selector "div#s2id_p#{p.id}_category a.select2-choice" expect(page).to have_select "variant_unit_with_scale", selected: "Volume (L)" expect(page).to have_field "on_hand", with: "6" @@ -745,8 +744,6 @@ feature %q{ permissions_list: [:manage_products]) end - use_short_wait - before do @enterprise_user = create_enterprise_user @enterprise_user.enterprise_roles.build(enterprise: supplier_managed1).save @@ -779,6 +776,28 @@ feature %q{ expect(page).to have_field 'product_name', with: product_supplied_inactive.name end + it "allows me to create a product" do + taxon = create(:taxon, name: 'Fruit') + + visit '/admin/products/bulk_edit' + + find("a", text: "NEW PRODUCT").click + expect(page).to have_content 'NEW PRODUCT' + expect(page).to have_select 'product_supplier_id', with_options: [supplier_managed1.name, supplier_managed2.name, supplier_permitted.name] + + within 'fieldset#new_product' do + fill_in 'product_name', with: 'Big Bag Of Apples' + select supplier_permitted.name, from: 'product_supplier_id' + fill_in 'product_price', with: '10.00' + select taxon.name, from: 'product_primary_taxon_id' + end + click_button 'Create' + + expect(URI.parse(current_url).path).to eq '/admin/products/bulk_edit' + expect(flash_message).to eq 'Product "Big Bag Of Apples" has been successfully created!' + expect(page).to have_field "product_name", with: 'Big Bag Of Apples' + end + it "allows me to update a product" do p = product_supplied_permitted diff --git a/spec/features/admin/enterprise_roles_spec.rb b/spec/features/admin/enterprise_roles_spec.rb index 0e44c9cfe3..7256b4e03c 100644 --- a/spec/features/admin/enterprise_roles_spec.rb +++ b/spec/features/admin/enterprise_roles_spec.rb @@ -71,7 +71,9 @@ feature %q{ visit admin_enterprise_roles_path page.should have_relationship u, e - first("a.delete-enterprise-role").click + within("#enterprise_role_#{er.id}") do + find("a.delete-enterprise-role").click + end page.should_not have_relationship u, e EnterpriseRole.where(id: er.id).should be_empty diff --git a/spec/features/admin/enterprise_user_spec.rb b/spec/features/admin/enterprise_user_spec.rb index ed46a1e0c1..77378f6cbd 100644 --- a/spec/features/admin/enterprise_user_spec.rb +++ b/spec/features/admin/enterprise_user_spec.rb @@ -17,42 +17,18 @@ feature %q{ let(:distributor_profile) { create(:distributor_enterprise, name: 'Distributor profile', type: 'profile') } describe "creating an enterprise user" do - context "with no enterprises managed" do - it "assigns an enterprise to a user" do + context "with a limitted number of owned enterprises" do + scenario "setting the enterprise ownership limit" do + user.enterprise_limit.should == 1 login_to_admin_section click_link 'Users' click_link user.email - click_link 'Edit' - check supplier2.name + fill_in "user_enterprise_limit", with: 2 click_button 'Update' - user.enterprises.count.should == 1 - user.enterprises.first.name.should == supplier2.name - end - end - - context "with existing enterprises managed" do - before do - user.enterprise_roles.create!(enterprise: supplier1) - user.enterprise_roles.create!(enterprise: distributor1) - end - - it "can remove and add enterprise management for a user" do - login_to_admin_section - - click_link 'Users' - click_link user.email - click_link 'Edit' - - uncheck distributor1.name # remove - check distributor2.name # add - - click_button 'Update' - - user.enterprises.count.should == 2 - user.enterprises.should include supplier1 - user.enterprises.should include distributor2 + user.reload + expect(user.enterprise_limit).to eq 2 end end end diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index a824716d95..abaab5cd2d 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -38,6 +38,9 @@ feature %q{ scenario "editing enterprises in bulk" do s = create(:supplier_enterprise) d = create(:distributor_enterprise, type: 'profile') + d_manager = create_enterprise_user + d_manager.enterprise_roles.build(enterprise: d).save + expect(d.owner).to_not eq d_manager login_to_admin_section click_link 'Enterprises' @@ -46,12 +49,14 @@ feature %q{ expect(page).to have_checked_field "enterprise_set_collection_attributes_0_visible" uncheck "enterprise_set_collection_attributes_0_visible" select 'full', from: "enterprise_set_collection_attributes_0_type" + select d_manager.email, from: 'enterprise_set_collection_attributes_0_owner_id' end click_button "Update" flash_message.should == 'Enterprises updated successfully' distributor = Enterprise.find(d.id) expect(distributor.visible).to eq false expect(distributor.type).to eq 'full' + expect(distributor.owner).to eq d_manager end scenario "viewing an enterprise" do @@ -64,35 +69,31 @@ feature %q{ page.should have_content e.name end - scenario "creating a new enterprise" do + scenario "creating a new enterprise", js:true do eg1 = create(:enterprise_group, name: 'eg1') eg2 = create(:enterprise_group, name: 'eg2') payment_method = create(:payment_method) shipping_method = create(:shipping_method) enterprise_fee = create(:enterprise_fee) - login_to_admin_section - - click_link 'Enterprises' + # Navigating + admin = quick_login_as_admin + visit '/admin/enterprises' click_link 'New Enterprise' - fill_in 'enterprise_name', :with => 'Eaterprises' - choose 'Full' - fill_in 'enterprise_description', :with => 'Connecting farmers and eaters' - fill_in 'enterprise_long_description', :with => 'Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro.' - fill_in 'enterprise_distributor_info', :with => 'Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro.' - + # Checking shipping and payment method sidebars work uncheck 'enterprise_is_primary_producer' check 'enterprise_is_distributor' - - select eg1.name, from: 'enterprise_group_ids' - page.should_not have_checked_field "enterprise_payment_method_ids_#{payment_method.id}" page.should_not have_checked_field "enterprise_shipping_method_ids_#{shipping_method.id}" + # Filling in details + fill_in 'enterprise_name', :with => 'Eaterprises' + select2_search admin.email, from: 'Owner' + choose 'Full' check "enterprise_payment_method_ids_#{payment_method.id}" check "enterprise_shipping_method_ids_#{shipping_method.id}" - + select2_search eg1.name, from: 'Groups' fill_in 'enterprise_contact', :with => 'Kirsten or Ren' fill_in 'enterprise_phone', :with => '0413 897 321' fill_in 'enterprise_email', :with => 'info@eaterprises.com.au' @@ -106,14 +107,16 @@ feature %q{ fill_in 'enterprise_address_attributes_address1', :with => '35 Ballantyne St' fill_in 'enterprise_address_attributes_city', :with => 'Thornbury' fill_in 'enterprise_address_attributes_zipcode', :with => '3072' - select('Australia', :from => 'enterprise_address_attributes_country_id') - select('Victoria', :from => 'enterprise_address_attributes_state_id') + select2_search 'Australia', :from => 'Country' + select2_search 'Victoria', :from => 'State' + fill_in 'enterprise_description', :with => 'Connecting farmers and eaters' + fill_in 'enterprise_long_description', :with => 'Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro.' click_button 'Create' flash_message.should == 'Enterprise "Eaterprises" has been successfully created!' end - scenario "editing an existing enterprise" do + scenario "editing an existing enterprise", js: true do @enterprise = create(:enterprise) e2 = create(:enterprise) eg1 = create(:enterprise_group, name: 'eg1') @@ -121,14 +124,18 @@ feature %q{ payment_method = create(:payment_method, distributors: [e2]) shipping_method = create(:shipping_method, distributors: [e2]) enterprise_fee = create(:enterprise_fee, enterprise: @enterprise ) + user = create(:user) - login_to_admin_section + admin = quick_login_as_admin - click_link 'Enterprises' - all("a", text:'Edit Profile').first.click + visit '/admin/enterprises' + within "tr.enterprise-#{@enterprise.id}" do + all("a", text: 'Edit Profile').first.click + end fill_in 'enterprise_name', :with => 'Eaterprises' choose 'Single' + select2_search user.email, from: 'Owner' fill_in 'enterprise_description', :with => 'Connecting farmers and eaters' fill_in 'enterprise_long_description', :with => 'Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro.' @@ -143,7 +150,7 @@ feature %q{ page.should have_selector "#shipping_methods" page.should have_selector "#enterprise_fees" - select eg1.name, from: 'enterprise_group_ids' + select2_search eg1.name, from: 'Groups' page.should_not have_checked_field "enterprise_payment_method_ids_#{payment_method.id}" page.should_not have_checked_field "enterprise_shipping_method_ids_#{shipping_method.id}" @@ -162,13 +169,15 @@ feature %q{ fill_in 'enterprise_address_attributes_address1', :with => '35 Ballantyne St' fill_in 'enterprise_address_attributes_city', :with => 'Thornbury' fill_in 'enterprise_address_attributes_zipcode', :with => '3072' - select('Australia', :from => 'enterprise_address_attributes_country_id') - select('Victoria', :from => 'enterprise_address_attributes_state_id') + select2_search 'Australia', :from => 'Country' + select2_search 'Victoria', :from => 'State' click_button 'Update' flash_message.should == 'Enterprise "Eaterprises" has been successfully updated!' page.should have_field 'enterprise_name', :with => 'Eaterprises' + @enterprise.reload + expect(@enterprise.owner).to eq user page.should have_checked_field "enterprise_payment_method_ids_#{payment_method.id}" page.should have_checked_field "enterprise_shipping_method_ids_#{shipping_method.id}" @@ -248,56 +257,91 @@ feature %q{ let(:supplier2) { create(:supplier_enterprise, name: 'Another Supplier') } let(:distributor1) { create(:distributor_enterprise, name: 'First Distributor') } let(:distributor2) { create(:distributor_enterprise, name: 'Another Distributor') } + let(:enterprise_user) { create_enterprise_user } before(:each) do - @new_user = create_enterprise_user - @new_user.enterprise_roles.build(enterprise: supplier1).save - @new_user.enterprise_roles.build(enterprise: distributor1).save + enterprise_user.enterprise_roles.build(enterprise: supplier1).save + enterprise_user.enterprise_roles.build(enterprise: distributor1).save - login_to_admin_as @new_user + login_to_admin_as enterprise_user end - scenario "can view enterprises I have permission to" do - oc_user_coordinating = create(:simple_order_cycle, { coordinator: supplier1, name: 'Order Cycle 1' } ) - oc_for_other_user = create(:simple_order_cycle, { coordinator: supplier2, name: 'Order Cycle 2' } ) + context "listing enterprises" do + scenario "displays enterprises I have permission to manage" do + oc_user_coordinating = create(:simple_order_cycle, { coordinator: supplier1, name: 'Order Cycle 1' } ) + oc_for_other_user = create(:simple_order_cycle, { coordinator: supplier2, name: 'Order Cycle 2' } ) - click_link "Enterprises" + click_link "Enterprises" - within("tr.enterprise-#{distributor1.id}") do - expect(page).to have_content distributor1.name - expect(page).to have_checked_field "enterprise_set_collection_attributes_0_is_distributor" - expect(page).to have_unchecked_field "enterprise_set_collection_attributes_0_is_primary_producer" - expect(page).to have_select "enterprise_set_collection_attributes_0_type" + within("tr.enterprise-#{distributor1.id}") do + expect(page).to have_content distributor1.name + expect(page).to have_checked_field "enterprise_set_collection_attributes_0_is_distributor" + expect(page).to have_unchecked_field "enterprise_set_collection_attributes_0_is_primary_producer" + expect(page).to_not have_select "enterprise_set_collection_attributes_0_type" + end + + within("tr.enterprise-#{supplier1.id}") do + expect(page).to have_content supplier1.name + expect(page).to have_unchecked_field "enterprise_set_collection_attributes_1_is_distributor" + expect(page).to have_checked_field "enterprise_set_collection_attributes_1_is_primary_producer" + expect(page).to_not have_select "enterprise_set_collection_attributes_1_type" + end + + expect(page).to_not have_content "supplier2.name" + expect(page).to_not have_content "distributor2.name" + + expect(find("#content-header")).to have_link "New Enterprise" end - within("tr.enterprise-#{supplier1.id}") do - expect(page).to have_content supplier1.name - expect(page).to have_unchecked_field "enterprise_set_collection_attributes_1_is_distributor" - expect(page).to have_checked_field "enterprise_set_collection_attributes_1_is_primary_producer" - expect(page).to have_select "enterprise_set_collection_attributes_1_type" - end + context "when I have reached my enterprise ownership limit" do + it "does not display the link to create a new enterprise" do + enterprise_user.owned_enterprises.push [supplier1] - expect(page).to_not have_content "supplier2.name" - expect(page).to_not have_content "distributor2.name" + click_link "Enterprises" + + page.should have_content supplier1.name + page.should have_content distributor1.name + expect(find("#content-header")).to_not have_link "New Enterprise" + end + end end - scenario "creating an enterprise" do - # When I create an enterprise - click_link 'Enterprises' - click_link 'New Enterprise' - fill_in 'enterprise_name', with: 'zzz' - fill_in 'enterprise_address_attributes_address1', with: 'z' - fill_in 'enterprise_address_attributes_city', with: 'z' - fill_in 'enterprise_address_attributes_zipcode', with: 'z' - click_button 'Create' + context "creating an enterprise" do + before do + # When I create an enterprise + click_link 'Enterprises' + click_link 'New Enterprise' + fill_in 'enterprise_name', with: 'zzz' + fill_in 'enterprise_address_attributes_address1', with: 'z' + fill_in 'enterprise_address_attributes_city', with: 'z' + fill_in 'enterprise_address_attributes_zipcode', with: 'z' + end - # Then it should be created - page.should have_content 'Enterprise "zzz" has been successfully created!' - enterprise = Enterprise.last - enterprise.name.should == 'zzz' + scenario "without violating rules" do + click_button 'Create' - # And I should be managing it - Enterprise.managed_by(@new_user).should include enterprise + # Then it should be created + page.should have_content 'Enterprise "zzz" has been successfully created!' + enterprise = Enterprise.last + enterprise.name.should == 'zzz' + + # And I should be managing it + Enterprise.managed_by(enterprise_user).should include enterprise + end + + context "overstepping my owned enterprises limit" do + before do + create(:enterprise, owner: enterprise_user) + end + + it "shows me an error message" do + click_button 'Create' + + # Then it should show me an error + expect(page).to_not have_content 'Enterprise "zzz" has been successfully created!' + expect(page).to have_content "You are not permitted to own own any more enterprises (limit is 1)." + end + end end scenario "editing enterprises I have permission to" do diff --git a/spec/features/admin/overview_spec.rb b/spec/features/admin/overview_spec.rb index 534f784ee0..184d68c80b 100644 --- a/spec/features/admin/overview_spec.rb +++ b/spec/features/admin/overview_spec.rb @@ -16,7 +16,7 @@ feature %q{ Spree::Admin::OverviewController.any_instance.stub(:spree_current_user).and_return @enterprise_user quick_login_as @enterprise_user end - + context "with no enterprises" do it "prompts the user to create a new enteprise" do visit '/admin' @@ -42,9 +42,8 @@ feature %q{ page.should have_selector ".dashboard_item#order_cycles" page.should have_selector ".dashboard_item#enterprises .list-item", text: d1.name page.should have_selector ".dashboard_item#enterprises .button.bottom", text: "MANAGE MY ENTERPRISES" - end - + context "but no products or order cycles" do it "prompts the user to create a new product and to manage order cycles" do visit '/admin' diff --git a/spec/features/admin/products_spec.rb b/spec/features/admin/products_spec.rb index 201381f852..1f61b07d2a 100644 --- a/spec/features/admin/products_spec.rb +++ b/spec/features/admin/products_spec.rb @@ -86,11 +86,14 @@ feature %q{ context "as an enterprise user" do - before(:each) do + before do @new_user = create_enterprise_user @supplier2 = create(:supplier_enterprise, name: 'Another Supplier') + @supplier_permitted = create(:supplier_enterprise, name: 'Permitted Supplier') @new_user.enterprise_roles.build(enterprise: @supplier2).save @new_user.enterprise_roles.build(enterprise: @distributors[0]).save + create(:enterprise_relationship, parent: @supplier_permitted, child: @supplier2, + permissions_list: [:manage_products]) login_to_admin_as @new_user end @@ -116,9 +119,8 @@ feature %q{ select taxon.name, from: "product_primary_taxon_id" # Should only have suppliers listed which the user can manage - within "#product_supplier_id" do - page.should_not have_content @supplier.name - end + page.should have_select 'product_supplier_id', with_options: [@supplier2.name, @supplier_permitted.name] + page.should_not have_select 'product_supplier_id', with_options: [@supplier.name] click_button 'Create' @@ -127,6 +129,18 @@ feature %q{ product.supplier.should == @supplier2 end + scenario "editing a product" do + product = create(:simple_product, name: 'a product', supplier: @supplier2) + + visit spree.edit_admin_product_path product + + select 'Permitted Supplier', from: 'product_supplier_id' + click_button 'Update' + flash_message.should == 'Product "a product" has been successfully updated!' + product.reload + product.supplier.should == @supplier_permitted + end + scenario "editing product distributions" do product = create(:simple_product, supplier: @supplier2) diff --git a/spec/features/consumer/authentication_spec.rb b/spec/features/consumer/authentication_spec.rb index cf0bc658bd..f8b405374e 100644 --- a/spec/features/consumer/authentication_spec.rb +++ b/spec/features/consumer/authentication_spec.rb @@ -10,7 +10,6 @@ feature "Authentication", js: true do visit groups_path(anchor: "login?after_login=#{producers_path}") fill_in "Email", with: user.email fill_in "Password", with: user.password - save_screenshot "/Users/willmarshall/Desktop/wtf.png" click_login_button page.should have_content "Find local producers" current_path.should == producers_path diff --git a/spec/features/consumer/registration_spec.rb b/spec/features/consumer/registration_spec.rb new file mode 100644 index 0000000000..ce5adc776e --- /dev/null +++ b/spec/features/consumer/registration_spec.rb @@ -0,0 +1,115 @@ +require 'spec_helper' + +feature "Registration", js: true do + include WebHelper + + describe "Registering a Profile", use_short_wait do + let(:user) { create(:user, password: "password", password_confirmation: "password") } + + it "Allows a logged in user to register a profile" do + visit registration_path + + expect(URI.parse(current_url).path).to eq registration_auth_path + + # Logging in + click_link "Log in" + fill_in "Email", with: user.email + fill_in "Password", with: user.password + click_button 'Log in' + + # Log in was successful, introduction shown + expect(page).to have_content "This wizard will step you through creating a profile" + expect(URI.parse(current_url).path).to eq registration_path + + # Done reading introduction + click_button "Let's get started!" + + # Filling in details + expect(page).to have_content "Woot! First we need to know what sort of enterprise you are:" + fill_in 'enterprise_name', with: "My Awesome Enterprise" + click_link 'both-panel' + click_button 'Continue' + + # Filling in address + expect(page).to have_content 'Greetings My Awesome Enterprise' + fill_in 'enterprise_address', with: '123 Abc Street' + fill_in 'enterprise_city', with: 'Northcote' + fill_in 'enterprise_zipcode', with: '3070' + select 'Australia', from: 'enterprise_country' + select 'Vic', from: 'enterprise_state' + click_button 'Continue' + + # Filling in Contact Details + expect(page).to have_content 'Who is responsible for managing My Awesome Enterprise?' + fill_in 'enterprise_contact', with: 'Saskia Munroe' + page.should have_field 'enterprise_email', with: user.email + fill_in 'enterprise_phone', with: '12 3456 7890' + click_button 'Continue' + + # Enterprise should be created + expect(page).to have_content 'Nice one!' + # e = Enterprise.find_by_name('My Awesome Enterprise') + # expect(e.address.address1).to eq "123 Abc Street" + # expect(e.is_distributor).to eq true + # expect(e.is_primary_producer).to eq true + # expect(e.contact).to eq "Saskia Munroe" + + # Filling in about + fill_in 'enterprise_description', with: 'Short description' + fill_in 'enterprise_long_desc', with: 'Long description' + fill_in 'enterprise_abn', with: '12345' + fill_in 'enterprise_acn', with: '54321' + click_button 'Continue' + + # Enterprise should be updated + expect(page).to have_content 'Last step!' + # e.reload + # expect(e.description).to eq "Short description" + # expect(e.long_description).to eq "Long description" + # expect(e.abn).to eq '12345' + # expect(e.acn).to eq '54321' + + # Filling in social + fill_in 'enterprise_website', with: 'www.shop.com' + fill_in 'enterprise_facebook', with: 'FaCeBoOk' + fill_in 'enterprise_linkedin', with: 'LiNkEdIn' + fill_in 'enterprise_twitter', with: '@TwItTeR' + fill_in 'enterprise_instagram', with: '@InStAgRaM' + click_button 'Continue' + + # Done + expect(page).to have_content "You have successfully completed the profile for My Awesome Enterprise" + # e.reload + # expect(e.website).to eq "www.shop.com" + # expect(e.facebook).to eq "FaCeBoOk" + # expect(e.linkedin).to eq "LiNkEdIn" + # expect(e.twitter).to eq "@TwItTeR" + # expect(e.instagram).to eq "@InStAgRaM" + end + + it "Allows a logged in user to register a store" do + visit store_registration_path + + expect(URI.parse(current_url).path).to eq registration_auth_path + + # Logging in + click_link "Log in" + fill_in "Email", with: user.email + fill_in "Password", with: user.password + click_button 'Log in' + + # Log in was successful, introduction shown + expect(page).to have_content "This wizard will step you through creating a profile" + expect(URI.parse(current_url).path).to eq store_registration_path + + # Done reading introduction + click_button "Let's get started!" + + # Details Page + expect(page).to have_content "Woot! First we need to know the name of your farm:" + expect(page).to_not have_selector '#enterprise-types' + + # Everything from here should be covered in 'profile' spec + end + end +end \ No newline at end of file diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index ab53befda5..7dae7a54e2 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -188,7 +188,7 @@ feature "As a consumer I want to check out my cart", js: true do # Order should have a payment with the correct amount o = Spree::Order.complete.first - o.payments.first.amount.should == 15.79 + o.payments.first.amount.should == 11.23 end it "shows the payment processing failed message when submitted with an invalid credit card" do diff --git a/spec/javascripts/unit/admin/services/enterprise_relationships_spec.js.coffee b/spec/javascripts/unit/admin/services/enterprise_relationships_spec.js.coffee index 9701377a73..d179f6857c 100644 --- a/spec/javascripts/unit/admin/services/enterprise_relationships_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/enterprise_relationships_spec.js.coffee @@ -12,5 +12,5 @@ describe "enterprise relationships", -> EnterpriseRelationships = _EnterpriseRelationships_ it "presents permission names", -> - expect(EnterpriseRelationships.permission_presentation("add_to_order_cycle")).toEqual "can add to order cycle" - expect(EnterpriseRelationships.permission_presentation("manage_products")).toEqual "can manage the products of" + expect(EnterpriseRelationships.permission_presentation("add_to_order_cycle")).toEqual "to add to order cycle" + expect(EnterpriseRelationships.permission_presentation("manage_products")).toEqual "to manage products" diff --git a/spec/javascripts/unit/bulk_order_management_spec.js.coffee b/spec/javascripts/unit/bulk_order_management_spec.js.coffee index 80884b855d..35b9e13d61 100644 --- a/spec/javascripts/unit/bulk_order_management_spec.js.coffee +++ b/spec/javascripts/unit/bulk_order_management_spec.js.coffee @@ -2,7 +2,9 @@ describe "AdminOrderMgmtCtrl", -> ctrl = scope = httpBackend = VariantUnitManager = null beforeEach -> - module "ofn.admin" + module "ofn.admin", ($provide) -> + $provide.value 'SpreeApiKey', 'API_KEY' + return beforeEach inject(($controller, $rootScope, $httpBackend, _VariantUnitManager_) -> scope = $rootScope.$new() ctrl = $controller @@ -18,7 +20,7 @@ describe "AdminOrderMgmtCtrl", -> returnedSuppliers = ["list of suppliers"] returnedDistributors = ["list of distributors"] returnedOrderCycles = [ "oc1", "oc2", "oc3" ] - httpBackend.expectGET("/api/users/authorise_api?token=api_key").respond success: "Use of API Authorised" + httpBackend.expectGET("/api/users/authorise_api?token=API_KEY").respond success: "Use of API Authorised" httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").respond returnedSuppliers httpBackend.expectGET("/api/enterprises/accessible?template=bulk_index&q[is_distributor_eq]=true").respond returnedDistributors httpBackend.expectGET("/api/order_cycles/accessible").respond returnedOrderCycles @@ -27,7 +29,7 @@ describe "AdminOrderMgmtCtrl", -> #spyOn(returnedSuppliers, "unshift") #spyOn(returnedDistributors, "unshift") #spyOn(returnedOrderCycles, "unshift") - scope.initialise "api_key" + scope.initialise() httpBackend.flush() expect(scope.suppliers).toEqual [{ id : '0', name : 'All' }, 'list of suppliers'] diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index b9081a1882..649ab99f52 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -165,7 +165,7 @@ describe "filtering products for submission to database", -> variant_unit: 'weight' variant_unit_scale: 1 ] - + # TODO Not an exhaustive test, is there a better way to do this? it "only returns the properties of products which ought to be updated", -> available_on = new Date() @@ -238,6 +238,7 @@ describe "AdminProductEditCtrl", -> module ($provide)-> $provide.value "producers", [] $provide.value "taxons", [] + $provide.value 'SpreeApiKey', 'API_KEY' null beforeEach inject((_$controller_, _$timeout_, $rootScope, _$httpBackend_, _DirtyProducts_) -> @@ -252,9 +253,9 @@ describe "AdminProductEditCtrl", -> describe "loading data upon initialisation", -> it "gets a list of producers and then resets products with a list of data", -> - $httpBackend.expectGET("/api/users/authorise_api?token=api_key").respond success: "Use of API Authorised" + $httpBackend.expectGET("/api/users/authorise_api?token=API_KEY").respond success: "Use of API Authorised" spyOn($scope, "fetchProducts").andReturn "nothing" - $scope.initialise "api_key" + $scope.initialise() $httpBackend.flush() expect($scope.fetchProducts.calls.length).toEqual 1 expect($scope.spree_api_key_ok).toEqual true diff --git a/spec/javascripts/unit/darkswarm/services/enterprise_registration_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/enterprise_registration_spec.js.coffee new file mode 100644 index 0000000000..1852dbd16c --- /dev/null +++ b/spec/javascripts/unit/darkswarm/services/enterprise_registration_spec.js.coffee @@ -0,0 +1,85 @@ +describe "EnterpriseRegistrationService", -> + EnterpriseRegistrationService = null + $httpBackend = null + availableCountries = [] + enterpriseAttributes = + name: "Enterprise 1" + something: true + spreeApiKey = "keykeykeykey" + CurrentUser = + id: 2 + email: 'lalala@email.com' + RegistrationServiceMock = + select: -> null + + beforeEach -> + module('Darkswarm') + angular.module('Darkswarm').value 'availableCountries', availableCountries + angular.module('Darkswarm').value 'enterpriseAttributes', enterpriseAttributes + angular.module('Darkswarm').value 'spreeApiKey', spreeApiKey + angular.module('Darkswarm').value 'CurrentUser', CurrentUser + angular.module('Darkswarm').value 'RegistrationService', RegistrationServiceMock + + inject ($injector, _$httpBackend_) -> + $httpBackend = _$httpBackend_ + EnterpriseRegistrationService = $injector.get("EnterpriseRegistrationService") + + it "adds the specified attributes to the ERS enterprise object", -> + expect(EnterpriseRegistrationService.enterprise.name).toBe "Enterprise 1" + expect(EnterpriseRegistrationService.enterprise.something).toBe true + + describe "creating an enterprise", -> + describe "success", -> + beforeEach -> + spyOn(RegistrationServiceMock, "select") + $httpBackend.expectPOST("/api/enterprises?token=keykeykeykey").respond 200, 6 + EnterpriseRegistrationService.create() + $httpBackend.flush() + + it "stores the id of the created enterprise", -> + expect(EnterpriseRegistrationService.enterprise.id).toBe 6 + + it "moves the user to the about page", -> + expect(RegistrationServiceMock.select).toHaveBeenCalledWith 'about' + + describe "failure", -> + beforeEach -> + spyOn(RegistrationServiceMock, "select") + spyOn(window, "alert") + $httpBackend.expectPOST("/api/enterprises?token=keykeykeykey").respond 400, 6 + EnterpriseRegistrationService.create() + $httpBackend.flush() + + it "alerts the user to failure", -> + expect(window.alert).toHaveBeenCalledWith 'Failed to create your enterprise.\nPlease ensure all fields are completely filled out.' + + it "does not move the user to the about page", -> + expect(RegistrationServiceMock.select).not.toHaveBeenCalled + + + describe "updating an enterprise", -> + beforeEach -> + EnterpriseRegistrationService.enterprise.id = 78 + spyOn(RegistrationServiceMock, "select") + + describe "success", -> + beforeEach -> + $httpBackend.expectPUT("/api/enterprises/78?token=keykeykeykey").respond 200, 6 + EnterpriseRegistrationService.update('step') + $httpBackend.flush() + + it "moves the user to the about page", -> + expect(RegistrationServiceMock.select).toHaveBeenCalledWith 'step' + + describe "failure", -> + beforeEach -> + spyOn(window, "alert") + $httpBackend.expectPUT("/api/enterprises/78?token=keykeykeykey").respond 400, 6 + EnterpriseRegistrationService.update('step') + $httpBackend.flush() + + it "alerts the user to failure", -> + expect(window.alert).toHaveBeenCalledWith 'Failed to update your enterprise.\nPlease ensure all fields are completely filled out.' + + it "does not move the user to the about page", -> + expect(RegistrationServiceMock.select).not.toHaveBeenCalled diff --git a/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee index 6b1238d1d1..f8ae8230bc 100644 --- a/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/hubs_spec.js.coffee @@ -8,18 +8,21 @@ describe "Hubs service", -> active: false orders_close_at: new Date() is_distributor: true + has_shopfront: true } { id: 3 active: false orders_close_at: new Date() is_distributor: true + has_shopfront: true } { id: 1 active: true orders_close_at: new Date() is_distributor: true + has_shopfront: true } ] diff --git a/spec/javascripts/unit/order_cycle_spec.js.coffee b/spec/javascripts/unit/order_cycle_spec.js.coffee index 0d5127314a..2a8d001804 100644 --- a/spec/javascripts/unit/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/order_cycle_spec.js.coffee @@ -516,30 +516,44 @@ describe 'OrderCycle services', -> ] describe 'removing exchanges', -> - it 'removes incoming exchanges', -> - exchange = {enterprise_id: '123', active: true, variants: {}, enterprise_fees: []} - OrderCycle.order_cycle.incoming_exchanges = [exchange] - OrderCycle.removeExchange(exchange) - expect(OrderCycle.order_cycle.incoming_exchanges).toEqual [] + exchange = null - it 'removes outgoing exchanges', -> - exchange = {enterprise_id: '123', active: true, variants: {}, enterprise_fees: []} - OrderCycle.order_cycle.outgoing_exchanges = [exchange] - OrderCycle.removeExchange(exchange) - expect(OrderCycle.order_cycle.outgoing_exchanges).toEqual [] - - it 'removes distribution of all exchange variants', -> + beforeEach -> spyOn(OrderCycle, 'removeDistributionOfVariant') exchange = enterprise_id: '123' active: true + incoming: false variants: {1: true, 2: false, 3: true} enterprise_fees: [] - OrderCycle.order_cycle.incoming_exchanges = [exchange] - OrderCycle.removeExchange(exchange) - expect(OrderCycle.removeDistributionOfVariant).toHaveBeenCalledWith('1') - expect(OrderCycle.removeDistributionOfVariant).not.toHaveBeenCalledWith('2') - expect(OrderCycle.removeDistributionOfVariant).toHaveBeenCalledWith('3') + + describe "removing incoming exchanges", -> + beforeEach -> + exchange.incoming = true + OrderCycle.order_cycle.incoming_exchanges = [exchange] + + it 'removes the exchange', -> + OrderCycle.removeExchange(exchange) + expect(OrderCycle.order_cycle.incoming_exchanges).toEqual [] + + it 'removes distribution of all exchange variants', -> + OrderCycle.removeExchange(exchange) + expect(OrderCycle.removeDistributionOfVariant).toHaveBeenCalledWith('1') + expect(OrderCycle.removeDistributionOfVariant).not.toHaveBeenCalledWith('2') + expect(OrderCycle.removeDistributionOfVariant).toHaveBeenCalledWith('3') + + describe "removing outgoing exchanges", -> + beforeEach -> + exchange.incoming = false + OrderCycle.order_cycle.outgoing_exchanges = [exchange] + + it 'removes the exchange', -> + OrderCycle.removeExchange(exchange) + expect(OrderCycle.order_cycle.outgoing_exchanges).toEqual [] + + it "does not remove distribution of any variants", -> + OrderCycle.removeExchange(exchange) + expect(OrderCycle.removeDistributionOfVariant).not.toHaveBeenCalled() it 'adds coordinator fees', -> OrderCycle.addCoordinatorFee() diff --git a/spec/mailers/enterprise_mailer_spec.rb b/spec/mailers/enterprise_mailer_spec.rb new file mode 100644 index 0000000000..412870bad7 --- /dev/null +++ b/spec/mailers/enterprise_mailer_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe EnterpriseMailer do + before do + @enterprise = create(:enterprise) + ActionMailer::Base.deliveries = [] + end + + it "should send an email when given an enterprise" do + EnterpriseMailer.creation_confirmation(@enterprise).deliver + ActionMailer::Base.deliveries.count.should == 1 + end +end \ No newline at end of file diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index c517a3ed41..f6b3c13d2d 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -1,8 +1,10 @@ require 'spec_helper' describe Enterprise do + include AuthenticationWorkflow describe "associations" do + it { should belong_to(:owner) } it { should have_many(:supplied_products) } it { should have_many(:distributed_orders) } it { should belong_to(:address) } @@ -51,11 +53,44 @@ describe Enterprise do e.suppliers end end + + describe "ownership" do + let(:u1) { create_enterprise_user } + let(:u2) { create_enterprise_user } + let!(:e) { create(:enterprise, owner: u1 ) } + + it "adds new owner to list of managers" do + expect(e.owner).to eq u1 + expect(e.users).to include u1 + expect(e.users).to_not include u2 + e.owner = u2 + e.save! + e.reload + expect(e.owner).to eq u2 + expect(e.users).to include u1, u2 + end + + it "validates ownership limit" do + expect(u1.enterprise_limit).to be 1 + expect(u1.owned_enterprises(:reload)).to eq [e] + e2 = create(:enterprise, owner: u2 ) + expect{ + e2.owner = u1 + e2.save! + }.to raise_error ActiveRecord::RecordInvalid, "Validation failed: You are not permitted to own own any more enterprises (limit is 1)." + end + end end describe "validations" do subject { FactoryGirl.create(:distributor_enterprise, :address => FactoryGirl.create(:address)) } it { should validate_presence_of(:name) } + + it "requires an owner" do + expect{ + e = create(:enterprise, owner: nil) + }.to raise_error ActiveRecord::RecordInvalid, "Validation failed: Owner can't be blank" + end end describe "delegations" do @@ -74,7 +109,7 @@ describe Enterprise do Enterprise.visible.should == [s1] end end - + describe "distributors_with_active_order_cycles" do it "finds active distributors by order cycles" do s = create(:supplier_enterprise) @@ -441,8 +476,8 @@ describe Enterprise do end describe "presentation of attributes" do - let(:distributor) { - create(:distributor_enterprise, + let(:distributor) { + create(:distributor_enterprise, website: "http://www.google.com", facebook: "www.facebook.com/roger", linkedin: "https://linkedin.com") diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb index 4e9ad94fac..f049c64b2f 100644 --- a/spec/models/spree/user_spec.rb +++ b/spec/models/spree/user_spec.rb @@ -1,6 +1,30 @@ describe Spree.user_class do - context "#create" do + describe "associations" do + it { should have_many(:owned_enterprises) } + describe "enterprise ownership" do + let(:u1) { create(:user, enterprise_limit: 2) } + let(:u2) { create(:user, enterprise_limit: 1) } + let!(:e1) { create(:enterprise, owner: u1) } + let!(:e2) { create(:enterprise, owner: u1) } + + it "provides access to owned enterprises" do + expect(u1.owned_enterprises(:reload)).to include e1, e2 + end + + it "enforces the limit on the number of enterprise owned" do + expect(u2.owned_enterprises(:reload)).to eq [] + u2.owned_enterprises << e1 + expect(u2.save!).to_not raise_error + expect { + u2.owned_enterprises << e2 + u2.save! + }.to raise_error ActiveRecord::RecordInvalid, "Validation failed: The nominated user is not permitted to own own any more enterprises (limit is 1)." + end + end + end + + context "#create" do it "should send a signup email" do Spree::UserMailer.should_receive(:signup_confirmation).and_return(double(:deliver => true)) create(:user) diff --git a/spec/support/request/authentication_workflow.rb b/spec/support/request/authentication_workflow.rb index add8de9f28..a2ee597f27 100644 --- a/spec/support/request/authentication_workflow.rb +++ b/spec/support/request/authentication_workflow.rb @@ -6,7 +6,7 @@ module AuthenticationWorkflow def quick_login_as_admin admin_role = Spree::Role.find_or_create_by_name!('admin') - admin_user = create(:user, + admin_user = create(:user, :password => 'passw0rd', :password_confirmation => 'passw0rd', :remember_me => false, @@ -25,7 +25,7 @@ module AuthenticationWorkflow def login_to_admin_section admin_role = Spree::Role.find_or_create_by_name!('admin') - admin_user = create(:user, + admin_user = create(:user, :password => 'passw0rd', :password_confirmation => 'passw0rd', :remember_me => false, @@ -38,7 +38,7 @@ module AuthenticationWorkflow end def create_enterprise_user(enterprises = []) - new_user = create(:user, email: 'enterprise@hub.com', password: 'blahblah', :password_confirmation => 'blahblah', ) + new_user = create(:user, password: 'blahblah', :password_confirmation => 'blahblah') new_user.spree_roles = [] # for some reason unbeknown to me, this new user gets admin permissions by default. for enterprise in enterprises do new_user.enterprise_roles.build(enterprise: enterprise).save