Compare commits

..

8 Commits

Author SHA1 Message Date
Jean-Baptiste Bellet
b17d3e447c Avoid using exception but simply errors attribute contained in object 2023-03-14 15:56:16 +01:00
Jean-Baptiste Bellet
2ae4234015 Sort each array to ensure the order
and then expect the right values.
2023-03-14 09:38:31 +01:00
binarygit
20874dec98 Replace angular for when adding a new unregistered manager to an enterprise
Co-Authored-By: David Cook <david@redcliffs.net>
2023-03-14 09:37:57 +01:00
Jean-Baptiste Bellet
288cd367bc Add I18N to all reflexes 2023-03-13 21:12:00 +01:00
Jean-Baptiste Bellet
68902021ab Delete manager invitation controller 2023-03-13 21:12:00 +01:00
Jean-Baptiste Bellet
7f9c578fca Create a concern for manager invitations
Can be used elsewhere
2023-03-13 21:12:00 +01:00
Jean-Baptiste Bellet
782c9150a2 Move $locationProvider configuration to another file
Actually the `config()` method of `admin_ofn` file did not run on `/admin/enterprises/*` pages for an unknown reason

Now those two files have the same configuration
2023-03-13 21:12:00 +01:00
binarygit
447b040020 Replace what's this tooltips
There are tooltips here that don't have a what's this?
There are many angular directives/methods being used that I haven't
looked into
Every select box is using select2
2023-03-13 21:12:00 +01:00
2439 changed files with 43955 additions and 177821 deletions

20
.env
View File

@@ -10,10 +10,10 @@ TIMEZONE="Melbourne"
DEFAULT_COUNTRY_CODE="AU"
# Locale for translation.
LOCALE="en_AU"
LOCALE="en"
# For multilingual - ENV doesn't have array so pass it as string with commas
AVAILABLE_LOCALES="en_AU,es"
AVAILABLE_LOCALES="en,es"
# Spree zone.
CHECKOUT_ZONE="Australia"
@@ -42,10 +42,16 @@ SMTP_PASSWORD="f00d"
# Javascript error reporting via Bugsnag.
# BUGSNAG_JS_KEY=""
# SingleSignOn login for Discourse
#
# DISCOURSE_SSO_SECRET should be a random string. It must be the same as provided to your Discourse instance.
# DISCOURSE_SSO_SECRET=""
#
# DISCOURSE_URL must be the URL of your Discourse instance.
# DISCOURSE_URL="https://noticeboard.openfoodnetwork.org.au"
# see="https://developers.google.com/maps/documentation/javascript/get-api-key
# GOOGLE_MAPS_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# see https://developers.google.com/maps/documentation/javascript/localization#Region
# GOOGLE_MAPS_REGION="XX"
# Stripe details for instance account
# Find these under 'Developers' -> 'API keys' in your Stripe account dashboard.
@@ -55,9 +61,3 @@ SMTP_PASSWORD="f00d"
# STRIPE_INSTANCE_PUBLISHABLE_KEY="pk_test_xxxx" # This can be a test key or a live key
# STRIPE_CLIENT_ID="ca_xxxx" # This can be a development ID or a production ID
# STRIPE_ENDPOINT_SECRET="whsec_xxxx"
# New relic settings
# see: https://one.eu.newrelic.com/admin-portal/, Administration > API keys to get the license key
# NEW_RELIC_AGENT_ENABLED=true
# NEW_RELIC_APP_NAME="Open Food Network"
# NEW_RELIC_LICENSE_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

View File

@@ -5,22 +5,9 @@
#
# cp .env.development .env.local
# Locale for translation. Using a locale other than `en` tests the
# successful fallback to `en`. You will also see up-to-date text used
# in production
LOCALE="en_AU"
VERBOSE_QUERY_LOGS=true
SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
OFN_REDIS_URL="redis://localhost:6379/1"
OFN_REDIS_JOBS_URL="redis://localhost:6379/2"
SITE_URL="0.0.0.0:3000"
# Deactivate rack-timeout in development.
# https://github.com/zombocom/rack-timeout#configuring
RACK_TIMEOUT_SERVICE_TIMEOUT="0"
RACK_TIMEOUT_WAIT_TIMEOUT="0"
RACK_TIMEOUT_WAIT_OVERTIME="0"

View File

@@ -1,20 +1,11 @@
# ENV vars for the test environment
# Override locally with `.env.test.local`
# Locale for translation.
LOCALE="en_TEST"
OFN_REDIS_JOBS_URL="redis://localhost:6379/2"
SECRET_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
STRIPE_INSTANCE_SECRET_KEY="bogus_key"
STRIPE_SECRET_TEST_API_KEY="bogus_key"
STRIPE_CUSTOMER="bogus_customer"
STRIPE_ACCOUNT="bogus_account"
STRIPE_CLIENT_ID="bogus_client_id"
STRIPE_PUBLIC_TEST_API_KEY="bogus_stripe_publishable_key"
SITE_URL="test.host"
OPENID_APP_ID="test-provider"
OPENID_APP_SECRET="12345"
OPENID_REFRESH_TOKEN="dummy-refresh-token"

View File

@@ -7,26 +7,21 @@ assignees: ''
---
## 1. Preparation on Thursday
## Preparation on Thursday
- [ ] Merge pull requests in the [Ready To Go] column
- [ ] Include translations: `script/release/update_locales`
- [ ] Increment version number: `git push upstream HEAD:refs/tags/vX.Y.Z`
- Major: if server changes are required (eg. provision with ofn-install)
- Minor: larger change that is irreversible (eg. migration deleting data)
- Patch: all others. Shortcut: `script/release/tag`
- [ ] Include translations: `tx pull --force`
- [ ] [Draft new release]. Look at previous [releases] for inspiration.
- Select new release tag
- _Generate release notes_ and check to ensure all items are arranged in the right category.
- [ ] Notify [#instance-managers] of user-facing :eyes:, API :warning: and experimental :construction: changes.
- [ ] Notify [#instance-managers] of user-facing changes.
## 2. Testing
## Testing
- [ ] [Find build] of the release commit and copy it below.
- [ ] Move this issue to Test Ready.
- [ ] Notify `@testers` in [#testing].
- [ ] Test build: [Deploy to Staging] with release tag.
- [ ] Test build: <!-- paste build link here, e.g. https://semaphore...builds/1234 -->
## 3. Finish on Tuesday
## Finish on Tuesday
- [ ] Publish and notify [#global-community] (this is automatically posted with a plugin)
- [ ] Deploy the new release to all managed instances.
@@ -34,22 +29,20 @@ assignees: ''
<pre>
cd ofn-install
git pull
ansible-playbook --limit all_prod --extra-vars "git_version=vX.Y.Z" playbooks/deploy.yml
ansible-playbook --limit all-prod --extra-vars "git_version=vx.y.z" playbooks/deploy.yml
</pre>
</details>
- [ ] Notify [#instance-managers]:
> @instance_managers The new release has been deployed.
- [ ] [Create issue] for next release and confirm with next release manager in [#core-devs].
- [ ] Nudge next release manager
The full process is described at https://github.com/openfoodfoundation/openfoodnetwork/wiki/Releasing.
[Ready To Go]: https://github.com/orgs/openfoodfoundation/projects/8?filterQuery=status%3A%22Ready+to+go+%F0%9F%9A%80%22
[Ready To Go]: #zenhub
[Transifex pull request]: https://github.com/openfoodfoundation/openfoodnetwork/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aopen+head%3Atransifex
[Draft new release]: https://github.com/openfoodfoundation/openfoodnetwork/releases/new?title=v+Code+Name&body=Congrats%0A%0ADescription%0A%0A
[Draft new release]: https://github.com/openfoodfoundation/openfoodnetwork/releases/new?tag=v&title=v+Code+Name&body=Congrats%0A%0ADescription%0A%0A%23%23+User+facing+changes+:eyes:%0A%0A%0A%0A%23%23+Technical+changes+:wrench:%0A%0A
[releases]: https://github.com/openfoodfoundation/openfoodnetwork/releases
[#instance-managers]: https://app.slack.com/client/T02G54U79/CG7NJ966B
[#testing]: https://openfoodnetwork.slack.com/app_redirect?channel=C02TZ6X00
[Deploy to Staging]: https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/stage.yml
[Find build]: https://semaphoreci.com/openfoodfoundation/openfoodnetwork-2/branches/master
[#global-community]: https://app.slack.com/client/T02G54U79/C59ADD8F2
[Create issue]: https://github.com/openfoodfoundation/openfoodnetwork/issues/new?assignees=&labels=&projects=&template=release.md&title=Release
[#core-devs]: https://openfoodnetwork.slack.com/archives/GK2T38QPJ

View File

@@ -20,12 +20,7 @@
<!-- Please select one for your PR and delete the other. -->
Changelog Category (reviewers may add a label for the release notes):
- [ ] User facing changes
- [ ] API changes (V0, V1, DFC or Webhook)
- [ ] Technical changes only
- [ ] Feature toggled
Changelog Category: User facing changes | Technical changes
<!-- Choose a pull request title above which explains your change to a
a user of the Open Food Network app. -->

View File

@@ -1,8 +1,3 @@
# Dependabot configuration
#
# The `directory` and `schedule.interval` options are mandatory.
# Most of the configuration here is not used for security updates though.
version: 2
updates:
@@ -10,7 +5,7 @@ updates:
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 10
# Only specific requirements are specified in Gemfile, so don't touch it.
versioning-strategy: lockfile-only
@@ -18,6 +13,5 @@ updates:
directory: "/"
schedule:
interval: "daily"
# All versions are specified in package.json, so please update them.
versioning-strategy: increase

37
.github/release.yml vendored
View File

@@ -1,37 +0,0 @@
changelog:
# Categorise according to what an instance manager needs to know
categories:
# Add the right label if anything appears in here.
# Then re-generate the release notes.
- title: "❓❓❓ Uncategorised ❓❓❓"
labels:
- '*'
exclude:
labels:
- api changes
- dependencies
- feature toggled
- technical changes only
- user facing changes
# Posted in advance for #instance-managers
- title: "User-facing changes 👀"
labels:
- user facing changes
- title: "API changes ⚠️"
labels:
- api changes
- title: "Experimental features for testing 🚧" # may be tested by instance managers
labels:
- feature toggled
# Instance managers ignore below
- title: "Technical changes 🛠️"
labels:
- technical changes only
- title: "Dependencies 📦"
labels:
- dependencies

View File

@@ -1,14 +0,0 @@
name: Auto Author Assign
on:
pull_request_target:
types: [opened, reopened]
permissions:
pull-requests: write
jobs:
assign-author:
runs-on: ubuntu-latest
steps:
- uses: toshimaru/auto-author-assign@v2.1.0

View File

@@ -41,7 +41,6 @@ jobs:
- name: Scan
continue-on-error: true
run: |
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
brakeman -f sarif -o output.sarif.json .
# Upload the SARIF file generated in the previous step

View File

@@ -17,8 +17,8 @@ permissions:
contents: read
jobs:
controllers:
runs-on: ubuntu-22.04
knapsack_rspec_controllers:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
@@ -55,20 +55,20 @@ jobs:
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
# JS is required in order for webpacker to compile, in order to render templates containing image urls
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
cache: yarn
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bin/rake db:create db:schema:load
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: 864ef557d85ea8e603e086c0387d5154
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
@@ -82,12 +82,12 @@ jobs:
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}"
run: |
git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
bin/rake knapsack_pro:rspec
bundle exec rake knapsack_pro:rspec
models:
runs-on: ubuntu-22.04
knapsack_rspec_models:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
@@ -107,69 +107,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [4]
ci_node_total: [7]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Set up database
run: |
bin/rake db:create db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: 09476e2ce491c12083df62768667c674
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
# KNAPSACK_PRO_FIXED_QUEUE_SPLIT: false
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}"
run: |
bin/rake knapsack_pro:rspec
system_admin:
runs-on: ubuntu-22.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [14]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
ci_node_index: [0, 1, 2, 3, 4, 5, 6]
steps:
- uses: actions/checkout@v3
@@ -186,14 +127,83 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
cache: yarn
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bin/rake db:create db:schema:load
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: 09476e2ce491c12083df62768667c674
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
# KNAPSACK_PRO_FIXED_QUEUE_SPLIT: false
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}"
run: |
bundle exec rake knapsack_pro:rspec
knapsack_rspec_system_admin:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [10]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
@@ -212,19 +222,19 @@ jobs:
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}"
run: |
bin/rake knapsack_pro:queue:rspec
bundle exec rake knapsack_pro:queue:rspec
- name: Archive failed tests screenshots
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: failed-admin-tests-screenshots
name: failed-tests-screenshots
path: tmp/capybara/screenshots/*.png
retention-days: 7
if-no-files-found: ignore
system_consumer:
runs-on: ubuntu-22.04
knapsack_rspec_system_consumer:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
@@ -244,10 +254,10 @@ jobs:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [12]
ci_node_total: [10]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
steps:
- uses: actions/checkout@v3
@@ -264,14 +274,14 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
cache: yarn
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bin/rake db:create db:schema:load
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
@@ -290,89 +300,19 @@ jobs:
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}"
run: |
bin/rake knapsack_pro:queue:rspec
bundle exec rake knapsack_pro:queue:rspec
- name: Archive failed tests screenshots
if: failure()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: failed-consumer-tests-screenshots
name: failed-tests-screenshots
path: tmp/capybara/screenshots/*.png
retention-days: 7
if-no-files-found: ignore
engines:
runs-on: ubuntu-22.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [2]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
# JS is required in order for webpacker to compile, in order to render templates linking to mail.css
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
cache: yarn
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bin/rake db:create db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: d6ea7ceb766404ccd016c19aa2c81b1c
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
# KNAPSACK_PRO_FIXED_QUEUE_SPLIT: false
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}"
run: |
bin/rake knapsack_pro:rspec
test_the_rest:
runs-on: ubuntu-22.04
knapsack_rspec_engines:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
@@ -409,20 +349,98 @@ jobs:
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
# JS is required in order for webpacker to compile, in order to render templates linking to mail.css
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
cache: yarn
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bin/rake db:create db:schema:load
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: d6ea7ceb766404ccd016c19aa2c81b1c
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
# KNAPSACK_PRO_FIXED_QUEUE_SPLIT: false
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}"
run: |
bundle exec rake knapsack_pro:rspec
- name: Archive failed tests screenshots
if: failure()
uses: actions/upload-artifact@v3
with:
name: failed-tests-screenshots
path: tmp/capybara/screenshots/*.png
retention-days: 7
if-no-files-found: ignore
knapsack_rspec_test_the_rest:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [5]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: e3b8800198d2d89b70c7edbdd85f8fd8
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
@@ -436,11 +454,13 @@ jobs:
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN: "{engines/**/*_spec.rb,spec/models/**/*_spec.rb,spec/controllers/**/*_spec.rb,spec/serializers/**/*_spec.rb,spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/system/**/*_spec.rb}"
run: |
bin/rake knapsack_pro:rspec
bundle exec rake knapsack_pro:rspec
non_knapsack_jest_karma:
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
@@ -457,7 +477,11 @@ jobs:
steps:
- uses: actions/checkout@v3
# Rails is required for the Karma rake script
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
@@ -466,13 +490,16 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version-file: .node-version
cache: yarn
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run JS tests
run: bin/rake karma:run
run: bundle exec rake karma:run
- name: Run jest tests
run: yarn jest

View File

@@ -7,23 +7,16 @@ jobs:
name: runner / rubocop
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check out code
uses: actions/checkout@v1
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- run: git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
- name: rubocop
uses: reviewdog/action-rubocop@v2
with:
rubocop_version: gemfile
rubocop_extensions: rubocop-rails:gemfile rubocop-rspec:gemfile
rubocop_extensions: rubocop-rails:gemfile
reporter: github-pr-check
level: error
filter_mode: nofilter
use_bundler: true
fail_on_error: true
prettier:
name: runner / prettier
@@ -39,8 +32,6 @@ jobs:
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- run: git show --no-patch # the commit being tested (which is often a merge due to actions/checkout@v3)
- name: prettier
uses: EPMatt/reviewdog-action-prettier@v1
with:

View File

@@ -28,7 +28,7 @@ jobs:
with:
mapi-token: ${{ secrets.MAPI_TOKEN }}
api-url: http://localhost:3000
api-spec: swagger/v1.yaml
api-spec: swagger/v1/swagger.yaml
target: openfoodfoundation/openfoodnetwork
duration: 1min
sarif-report: mapi.sarif

View File

@@ -1,66 +0,0 @@
name: "Deploy to Staging"
on:
pull_request_target:
types: [labeled]
workflow_dispatch:
inputs:
server:
description: "Staging Server"
type: choice
required: true
options:
- staging.openfoodnetwork.org.uk
- staging.openfoodnetwork.org.au
- staging.coopcircuits.fr
commit_ref:
description: "Commit Reference"
type: string
required: false
jobs:
deploy_pr:
if: contains(fromJSON('["pr-staged-uk", "pr-staged-au", "pr-staged-fr"]'), github.event.label.name)
runs-on: ubuntu-latest
steps:
- name: "Check user has write access"
uses: "lannonbr/repo-permission-check-action@2.0.2"
with:
permission: "write"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Configure deployment key
if: success()
run: |
install -m 600 -D /dev/null ~/.ssh/id_rsa
echo "${{ secrets.DEPLOYMENT_KEY }}" > ~/.ssh/id_rsa
echo "${{ secrets.DEPLOYMENT_HOSTS }}" > ~/.ssh/known_hosts
- name: Deploy to Staging
if: success()
run: |
ssh ofn-deploy@${{ github.event.label.description }} -o LogLevel=ERROR "pull-request-${{ github.event.pull_request.number }} ."
deploy_branch:
if: ${{ inputs.server }}
runs-on: ubuntu-latest
steps:
- name: "Check user has write access"
uses: "lannonbr/repo-permission-check-action@2.0.2"
with:
permission: "write"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Configure deployment key
if: success()
run: |
install -m 600 -D /dev/null ~/.ssh/id_rsa
echo "${{ secrets.DEPLOYMENT_KEY }}" > ~/.ssh/id_rsa
echo "${{ secrets.DEPLOYMENT_HOSTS }}" > ~/.ssh/known_hosts
- name: Deploy to Staging
if: success()
run: |
ssh ofn-deploy@${{ inputs.server }} -o LogLevel=ERROR "$GITHUB_REF_NAME ${{ inputs.commit_ref || github.sha }}"

4
.husky/pre-commit Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn pretty-quick --check --staged

View File

@@ -1 +1 @@
17.9.1
14.21.2

View File

@@ -1,27 +1,17 @@
# Ignore a lot of things, but we should enable where it can be helpful.
# Basically, ignore everythings expect app/webpacker/controllers/*.js and app/webpacker/packs/*.js
*.css
*.scss
# Except v2
!/app/webpacker/css/admin/v2/**/*.scss
*.md
*.yml
*.yaml
*.json
*.html
# JS
# Enabled: app/webpacker/controllers/*.js and app/webpacker/packs/*.js
babel.config.js
postcss.config.js
# SCSS
# Enabled: most of admin
/app/webpacker/css/admin/globals/mixins.scss
/app/webpacker/css/admin/globals/variables.scss
/app/webpacker/css/admin/shared/
/app/webpacker/css/admin_v3/globals/variables.scss
/app/webpacker/css/darkswarm/
/app/webpacker/css/mail/
/app/webpacker/css/shared/
# More
/app/assets/
/config/
/coverage/

View File

@@ -1,3 +1 @@
{
"printWidth": 100
}
{}

1
.rspec
View File

@@ -1 +0,0 @@
--require base_spec_helper

View File

@@ -4,13 +4,7 @@
#
# The configuration is split into three files. Look into those files for more details.
#
require:
- rubocop-capybara
- rubocop-factory_bot
- rubocop-rails
- rubocop-rspec
- rubocop-rspec_rails
require: rubocop-rails
inherit_from:
# The automatically generated todo list to ignore all current violations.
@@ -19,10 +13,9 @@ inherit_from:
# The relaxed style rules as a common starting point which we can refine.
- .rubocop_relaxed_styleguide.yml
# Our Open Food Network style guides. If you want to see all violations,
# Our Open Food Network style guide. If you want to see all violations,
# then use only that configuration:
#
# bundle exec rubocop -c .rubocop_styleguide.yml
#
- .rubocop_styleguide.yml
- .rubocop_rspec_styleguide.yml

View File

@@ -1,24 +0,0 @@
# OFN styleguide for rubocop-rspec
# Because there are so many, we will disable by default, and enable rules as needed.
Capybara:
Enabled: false
RSpec:
Enabled: false
FactoryBot:
Enabled: false
# Enabled rules
Capybara/NegationMatcher:
Enabled: true
EnforcedStyle: not_to
RSpec/ExpectChange:
Enabled: true
EnforcedStyle: block
RSpec/NotToNot:
Enabled: true

View File

@@ -2,62 +2,30 @@
#
# These are the rules we agreed upon and we work towards.
AllCops:
NewCops: enable
NewCops: disable
SuggestExtensions: false
TargetRailsVersion: 5.0
Exclude:
- bin/**/*
- db/**/*
- config/**/*
- script/**/*
- vendor/**/*
- node_modules/**/*
- 'bin/**/*'
- 'db/**/*'
- 'config/**/*'
- 'script/**/*'
- 'vendor/**/*'
- 'node_modules/**/*'
# Excluding: inadequate Naming/FileName rule rejects GemFile name with camelcase
- engines/web/Gemfile
- 'engines/web/Gemfile'
Bundler/DuplicatedGem:
Enabled: false
Layout/LineLength:
Enabled: true
Max: 100
Layout/MultilineMethodCallIndentation:
Enabled: true
EnforcedStyle: indented
# Don't think this is a big issue, mostly picking up RPSEC scope definitions
# with lamdas and RSpec '.to change{}' blocks
Lint/AmbiguousBlockAssociation:
Enabled: false
Lint/MissingSuper:
Exclude:
- app/components/**/*
Lint/RaiseException:
Enabled: true
Lint/StructNewOverride:
Enabled: true
# Heaps of offences (> 100) in specs, mostly in situations where two or more
# instances of a model are required, but only one is referenced. Difficult to
# fix without making the spec look messy or rewriting it.
# Should definitely fix at some point.
Lint/UselessAssignment:
Exclude:
- spec/**/*
## OFN SETTINGS
#
# Cop settings that have been agreed upon by the OFN community
Metrics:
Enabled: true
Metrics/AbcSize:
Max: 30 # default 17
Metrics/BlockLength:
AllowedMethods: [
"class_eval",
"collection",
"configure",
"context",
"delete",
"describe",
@@ -71,78 +39,29 @@ Metrics/BlockLength:
"put",
"resource",
"resources",
"response",
"scenario",
"shared_examples",
"shared_examples_for",
"xdescribe",
]
Metrics/MethodLength:
Enabled: true
Max: 25 # default 10
Metrics/ParameterLists:
CountKeywordArgs: false
Metrics/PerceivedComplexity:
Enabled: true
Max: 14 # default 8
Naming/PredicateName:
Enabled: false
Naming/VariableNumber:
AllowedIdentifiers:
- street_address_1
- street_address_2
AllowedPatterns:
- _v[\d]+
Rails/ApplicationRecord:
Exclude:
# Migrations should not contain application code:
- db/migrate/*.rb
# Allow many-to-many associations without explicit model.
# - It avoids the additional code of a model class.
# - It simplifies the declaration of the association.
# - Rails may know that there are no callbacks associated.
Rails/HasAndBelongsToMany:
Enabled: false
Rails/OutputSafety:
Exclude:
- spec/**/*
Rails/RedundantActiveRecordAllMethod:
AllowedReceivers:
- ActionMailer::Preview
- ActiveSupport::TimeZone
- "db/migrate/*.rb"
Rails/SkipsModelValidations:
AllowedMethods:
- touch
- touch_all
- update_all
- update_attribute
- update_column
- update_columns
Rails/UnknownEnv:
Environments:
- development
- production
- staging
- test
Rails/WhereExists:
EnforcedStyle: where # Cf. conversion https://github.com/openfoodfoundation/openfoodnetwork/pull/12363
- "touch"
- "touch_all"
- "update_all"
- "update_attribute"
- "update_column"
- "update_columns"
Style/Documentation:
Enabled: false
Style/FormatStringToken:
Style/StringLiterals:
Enabled: false
Style/HashSyntax:
@@ -152,5 +71,60 @@ Style/HashSyntax:
Style/Send:
Enabled: true
Style/StringLiterals:
Layout/MultilineMethodCallIndentation:
Enabled: true
EnforcedStyle: indented
Layout/LineLength:
Enabled: true
Max: 100
Lint/RaiseException:
Enabled: true
Lint/StructNewOverride:
Enabled: true
Naming/VariableNumber:
AllowedIdentifiers:
- street_address_1
- street_address_2
Bundler/DuplicatedGem:
Enabled: false
## TEMPORARY/CONTESTED SETTINGS
#
# These are still to be decided upon, but recommended for inclusion by
# oeoeaio after scrutinising offenses the codebase
# Don't think this is a big issue, mostly picking up RPSEC scope definitions
# with lamdas and RSpec '.to change{}' blocks
Lint/AmbiguousBlockAssociation:
Enabled: false
# Heaps of offences (> 100) in specs, mostly in situations where two or more
# instances of a model are required, but only one is referenced. Difficult to
# fix without making the spec look messy or rewriting it.
# Should definitely fix at some point.
Lint/UselessAssignment:
Exclude:
- spec/**/*
Lint/MissingSuper:
Exclude:
- 'app/components/**/*'
Metrics/AbcSize:
Max: 30 # default 17
Metrics/MethodLength:
Enabled: true
Max: 25 # default 10
Metrics/PerceivedComplexity:
Enabled: true
Max: 14 # default 8
Naming/PredicateName:
Enabled: false

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
3.1.4
3.0.3

View File

@@ -57,7 +57,6 @@ TL;DR:
* Maintain a clean commit history
* Use a style consistent with the rest of the codebase
* Before submitting, [rebase your work][rebase] on the current master branch
* After submitting, be sure to check the [CI test results](ci). Click on a ❌ result to view the logged results and investigate.
From here, your pull request will progress through the [Review, Test, Merge & Deploy process][process].
@@ -71,5 +70,4 @@ From here, your pull request will progress through the [Review, Test, Merge & De
[slack-dev]: https://openfoodnetwork.slack.com/messages/C2GQ45KNU
[ofn-transifex]: https://www.transifex.com/open-food-foundation/open-food-network/
[i18n]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Internationalisation-%28i18n%29
[welcome-dev]: https://github.com/orgs/openfoodfoundation/projects/5
[ci]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Continuous-Integration
[welcome-dev]: https://github.com/orgs/openfoodfoundation/projects/2

View File

@@ -28,9 +28,8 @@ RUN apt-get update && apt-get install -y \
gnupg
# Setup ENV variables
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:/usr/local/src/nodenv/shims:/usr/local/src/nodenv/bin:$PATH
ENV PATH /usr/local/src/rbenv/shims:/usr/local/src/rbenv/bin:$PATH
ENV RBENV_ROOT /usr/local/src/rbenv
ENV NODENV_ROOT /usr/local/src/nodenv
ENV CONFIGURE_OPTS --disable-install-doc
ENV BUNDLE_PATH /bundles
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so
@@ -54,19 +53,10 @@ RUN sh -c "echo 'deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-
apt-get update && \
apt-get install -yqq --no-install-recommends postgresql-client-10 libpq-dev
# trim spaces and line return from .node-version file
COPY .node-version .node-version.raw
RUN cat .node-version.raw | tr -d '\r\t ' > .node-version
# Install Node and Yarn with Nodenv
RUN git clone --depth 1 https://github.com/nodenv/nodenv.git ${NODENV_ROOT} && \
git clone --depth 1 https://github.com/nodenv/node-build.git ${NODENV_ROOT}/plugins/node-build && \
git clone --depth 1 https://github.com/pine/nodenv-yarn-install.git ${NODENV_ROOT}/plugins/nodenv-yarn-install && \
git clone --depth 1 https://github.com/nodenv/nodenv-package-rehash.git ${NODENV_ROOT}/plugins/nodenv-package-rehash && \
echo 'eval "$(nodenv init -)"' >> /etc/profile.d/nodenv.sh && \
nodenv install $(cat .node-version) && \
nodenv global $(cat .node-version)
# Install NodeJs and yarn
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - \
&& apt-get install --no-install-recommends -y nodejs \
&& npm install -g yarn
# Install Chrome
RUN wget --quiet -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \

View File

@@ -6,9 +6,28 @@ This is a general guide to setting up an Open Food Network **development environ
Head to our wiki on [Learning Rails](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Learning-Rails) to find some good starting points.
### Requirements
The fastest way to make it work locally is to use Docker, you only need to setup git, see the [Docker setup guide](docker/README.md).
Otherwise, for a local setup you will need:
* Ruby and bundler (check current Ruby version in [.ruby-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.ruby-version) file)
- To manage versions, it's recommended to use [rbenv](https://github.com/rbenv/rbenv) or [RVM](https://rvm.io/)
* Node and yarn (check current Node version in [.node-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.node-version) file)
- [nodevn](https://github.com/nodenv/nodenv) is recommended.
* PostgreSQL database
* Redis (for background jobs)
* Chrome (for testing)
The following guides will provide OS-specific step-by-step instructions to get these requirements installed:
- [Ubuntu Setup Guide][ubuntu]
- [Debian Setup Guide][debian]
- [OSX Setup Guide][osx]
For those new to Rails, the following tutorial will help get you up to speed with configuring a [Rails environment](http://guides.rubyonrails.org/getting_started.html).
### Get it
If you're planning on contributing code to the project (which we [LOVE](CONTRIBUTING.md)), it is a good idea to begin by forking this repo using the `Fork` button in the top-right corner of this screen. You should then be able to use `git clone` to copy your fork onto your local machine:
So you have set up your local environment according to the requirements listed above. If you're planning on contributing code to the project (which we [LOVE](CONTRIBUTING.md)), it is a good idea to begin by forking this repo using the `Fork` button in the top-right corner of this screen. You should then be able to use `git clone` to copy your fork onto your local machine:
git clone git@github.com:YOUR_GITHUB_USERNAME_HERE/openfoodnetwork.git
@@ -24,41 +43,19 @@ Fetch the latest version of `master` from `upstream` (ie. the main repo):
git fetch upstream master
### Installation
This project needs specific ruby/bundler versions as well as node/yarn specific versions. For a local setup you will need:
* Install or change your Ruby version according to the one specified at [.ruby-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.ruby-version) file.
- To manage versions, it's recommended to use [rbenv](https://github.com/rbenv/rbenv) or [RVM](https://rvm.io/).
* Install [nodenv](https://github.com/nodenv/nodenv) to ensure the correct [.node-version](https://github.com/openfoodfoundation/openfoodnetwork/blob/master/.node-version) is used.
- [nodevn](https://github.com/nodenv/nodenv) is recommended as a node version manager.
* PostgreSQL database
* Redis (for background jobs)
* Chrome (for testing)
The following guides will provide OS-specific step-by-step instructions to get these requirements installed:
- [Ubuntu Setup Guide][ubuntu]
- [Debian Setup Guide][debian]
- [OSX Setup Guide][osx]
For those new to Rails, the following tutorial will help get you up to speed with configuring a [Rails environment](http://guides.rubyonrails.org/getting_started.html).
Another way to make it work locally would be using Docker. See the [Docker setup guide](docker/README.md).
### Get it running
First, you need to create the database user the app will use by manually typing the following in your terminal:
```sh
sudo --login --user=postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PASSWORD 'f00d'"
$ sudo -u postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PASSWORD 'f00d'"
```
This will create the "ofn" user as superuser and allowing it to create databases. If this command fails, check the [troubleshooting section](#creating-the-database) for an alternative.
Next, it is _strongly recommended_ to run the setup script:
Next, it is _strongly recommended_ to run the setup script.
```sh
./script/setup
$ script/setup
```
If the script succeeds you're ready to start developing. If not, take a look at the output as it should be informative enough to help you troubleshoot.
@@ -117,13 +114,13 @@ Below are fixes to potential issues that can happen during the installation proc
#### Creating the database
If the `sudo -u postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PASSWORD 'f00d'"` command doesn't work, you can run the following commands instead:
If the ```$ sudo -u postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PASSWORD 'f00d'"``` command doesn't work, you can run the following commands instead:
```
createuser --superuser --pwprompt ofn
# Enter password for new role: f00d
# Enter it again: f00d
createdb open_food_network_dev --owner=ofn
createdb open_food_network_test --owner=ofn
$ createuser --superuser --pwprompt ofn
Enter password for new role: f00d
Enter it again: f00d
$ createdb open_food_network_dev --owner=ofn
$ createdb open_food_network_test --owner=ofn
```
If these commands succeed, you should be able to [continue the setup process](#get-it-running).

55
Gemfile
View File

@@ -1,13 +1,12 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby "3.0.3"
git_source(:github) { |repo_name| "https://github.com/#{repo_name}.git" }
ruby File.read('.ruby-version').chomp
gem 'dotenv-rails', require: 'dotenv/rails-now' # Load ENV vars before other gems
gem 'dotenv', require: 'dotenv/load' # Load ENV vars before other gems
gem 'rails'
gem 'rails', '>= 6.1.4'
# Active Storage
gem "active_storage_validations"
@@ -15,8 +14,10 @@ gem "aws-sdk-s3", require: false
gem "image_processing"
gem 'activemerchant', '>= 1.78.0'
gem 'rexml'
gem 'angular-rails-templates', '>= 0.3.0'
gem 'ransack', '~> 4.1.0'
gem 'awesome_nested_set'
gem 'ransack', '~> 2.6.0'
gem 'responders'
gem 'webpacker', '~> 5'
@@ -26,11 +27,10 @@ gem 'rails-i18n'
gem 'rails_safe_tasks', '~> 1.0'
gem "activerecord-import"
gem "db2fog", github: "openfoodfoundation/db2fog", branch: "rails-7"
gem "db2fog", github: "openfoodfoundation/db2fog", branch: "rails-6"
gem "fog-aws", "~> 2.0" # db2fog does not support v3
gem "mime-types" # required by fog
gem "validates_lengths_from_database"
gem "valid_email2"
gem "catalog", path: "./engines/catalog"
@@ -63,7 +63,6 @@ gem 'devise-token_authenticatable'
gem 'jwt', '~> 2.3'
gem 'oauth2', '~> 1.4.7' # Used for Stripe Connect
gem 'datafoodconsortium-connector'
gem 'jsonapi-serializer'
gem 'pagy', '~> 5.1'
@@ -71,8 +70,8 @@ gem 'rswag-api'
gem 'rswag-ui'
gem 'omniauth_openid_connect'
gem 'openid_connect', '~> 1.3'
gem 'omniauth-rails_csrf_protection'
gem 'openid_connect'
gem 'angularjs-rails', '1.8.0'
gem 'bugsnag'
@@ -91,20 +90,18 @@ gem 'bootsnap', require: false
gem 'geocoder'
gem 'gmaps4rails'
gem 'mimemagic', '> 0.3.5'
gem 'paper_trail'
gem 'paper_trail', '~> 12.1'
gem 'rack-rewrite'
gem 'rack-timeout'
gem 'roadie-rails'
gem 'hiredis'
gem 'puma'
gem 'redis'
gem 'redis', '>= 4.0', require: ['redis', 'redis/connection/hiredis']
gem 'sidekiq'
gem 'sidekiq-scheduler'
gem "cable_ready"
gem "stimulus_reflex"
gem "turbo-rails"
gem "cable_ready", "5.0.0.pre9"
gem "stimulus_reflex", "3.5.0.pre9"
gem 'combine_pdf'
gem 'wicked_pdf'
@@ -116,8 +113,12 @@ gem 'spreadsheet_architect' # write spreadsheets
gem 'whenever', require: false
gem 'test-unit', '~> 3.5'
gem 'coffee-rails', '~> 5.0.0'
gem 'mini_racer'
gem 'angular_rails_csrf'
gem 'jquery-rails', '4.4.0'
@@ -133,18 +134,11 @@ gem 'flipper-ui'
gem "view_component"
gem 'view_component_reflex', '3.1.14.pre9'
# mini_portile2 is needed when installing with Vargant
# https://openfoodnetwork.slack.com/archives/CEBMTRCNS/p1668439152992899
gem 'mini_portile2', '~> 2.8'
gem "faraday"
gem "private_address_check"
gem 'newrelic_rpm'
gem 'invisible_captcha'
group :production, :staging do
gem 'ddtrace'
gem 'rack-timeout'
gem 'sd_notify' # For better Systemd process management. Used by Puma.
end
@@ -153,7 +147,6 @@ group :test, :development do
gem 'capybara'
gem 'cuprite'
gem 'database_cleaner', require: false
gem 'debug', '>= 1.0.0'
gem "factory_bot_rails", '6.2.0', require: false
gem 'fuubar', '~> 2.5.1'
gem 'json_spec', '~> 1.1.4'
@@ -161,11 +154,10 @@ group :test, :development do
gem 'letter_opener', '>= 1.4.1'
gem 'rspec-rails', ">= 3.5.2"
gem 'rspec-retry', require: false
gem 'rspec-sql'
gem 'rswag'
gem 'rswag-specs'
gem 'shoulda-matchers'
gem 'stimulus_reflex_testing', github: "podia/stimulus_reflex_testing", branch: :main
gem 'timecop'
gem 'debug', '>= 1.0.0'
end
group :test do
@@ -180,17 +172,14 @@ end
group :development do
gem 'debugger-linecache'
gem 'rails-erd'
gem 'foreman'
gem 'listen'
gem 'pry', '~> 0.13.0'
gem 'query_count'
gem 'rails-erd'
gem 'rubocop'
gem 'rubocop-rails'
gem 'rubocop-rspec'
gem 'spring'
gem 'spring-commands-rspec'
gem 'spring-commands-rubocop'
gem 'web-console'
gem 'rack-mini-profiler', '< 3.0.0'

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
# Foreman Procfile. Start all dev server processes with: `foreman start`
rails: DEV_CACHING=true bundle exec rails s -p 3000
rails: bundle exec rails s -p 3000
webpack: ./bin/webpack-dev-server
sidekiq: DEV_CACHING=true bundle exec sidekiq -q mailers -q default
sidekiq: bundle exec sidekiq -q mailers -q default

View File

@@ -1,4 +1,5 @@
[![Build](https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/build.yml/badge.svg)](https://github.com/openfoodfoundation/openfoodnetwork/actions/workflows/build.yml)
[![Code Climate](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork.png)](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork)
# Open Food Network
@@ -32,7 +33,7 @@ We also have a [Super Admin Guide][super-admin-guide] to help with configuration
## Testing
If you'd like to help out with testing, please introduce yourself on the #testing channel on [Slack][slack-invite]. Also, do have a look in our [Welcome New QAs board][welcome-qa] for some good first issues, both on manual and automated testing (RSpec/Capybara).
If you'd like to help out with testing, please introduce yourself on the #testing channel on [Slack][slack-invite] and download the [ZenHub browser extension][zenhub] to view the development pipeline. Also, do have a look in our [Welcome New QAs board][welcome-qa] for some good first issues, both on manual and automated testing (RSpec/Capybara).
We use [BrowserStack](https://www.browserstack.com/) as a manual testing tool. BrowserStack provides open source projects with unlimited and free of charge accounts. A big thanks to them!
@@ -44,7 +45,7 @@ We use [KnapsackPro](https://knapsackpro.com/) for optimal parallelisation of ou
## Licence
Copyright (c) 2012 - 2024 Open Food Foundation, released under the AGPL licence.
Copyright (c) 2012 - 2022 Open Food Foundation, released under the AGPL licence.
[survey]: https://docs.google.com/a/eaterprises.com.au/forms/d/1zxR5vSiU9CigJ9cEaC8-eJLgYid8CR8er7PPH9Mc-30/edit#
[slack-invite]: https://join.slack.com/t/openfoodnetwork/shared_invite/zt-9sjkjdlu-r02kUMP1zbrTgUhZhYPF~A
@@ -53,3 +54,4 @@ Copyright (c) 2012 - 2024 Open Food Foundation, released under the AGPL licence.
[super-admin-guide]: https://ofn-user-guide.gitbook.io/ofn-super-admin-guide
[welcome-dev]: https://github.com/orgs/openfoodfoundation/projects/5
[welcome-qa]: https://github.com/orgs/openfoodfoundation/projects/6
[zenhub]: https://www.zenhub.com/extension

View File

@@ -7,3 +7,4 @@
require_relative 'config/application'
Openfoodnetwork::Application.load_tasks

View File

@@ -12,6 +12,5 @@ angular.module("ofn.admin", [
"admin.orders"
]).config ($httpProvider, $locationProvider, $qProvider) ->
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"
# for the next line, you should also probably check file: app/assets/javascripts/admin/utils/utils.js.coffee
$locationProvider.hashPrefix('')
$qProvider.errorOnUnhandledRejections(false)

View File

@@ -7,9 +7,11 @@
// jquery and angular
//= require jquery2
//= require jquery_ujs
//= require jquery.ui.all
//= require jquery.powertip
//= require jquery.cookie
//= require jquery.jstree/jquery.jstree
//= require jquery.vAlign
//= require angular
//= require angular-resource
@@ -67,6 +69,25 @@
//= require textAngular.min.js
//= require i18n/translations
//= require darkswarm/i18n.translate.js
//= require moment/min/moment.min.js
//= require moment/locale/ar.js
//= require moment/locale/ca.js
//= require moment/locale/de.js
//= require moment/locale/en-gb.js
//= require moment/locale/es.js
//= require moment/locale/fil.js
//= require moment/locale/fr.js
//= require moment/locale/it.js
//= require moment/locale/nb.js
//= require moment/locale/nl-be.js
//= require moment/locale/pt-br.js
//= require moment/locale/pt.js
//= require moment/locale/ru.js
//= require moment/locale/sv.js
//= require moment/locale/tr.js
//= require moment/locale/pl.js
//= require js-big-decimal/dist/web/js-big-decimal.min.js
// foundation
//= require ../shared/mm-foundation-tpls-0.9.0-20180826174721.min.js

View File

@@ -47,8 +47,8 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
removeClearedValues()
params = {
'q[name_cont]': $scope.q.query,
'q[variants_supplier_id_eq]': $scope.q.producerFilter,
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter,
'q[supplier_id_eq]': $scope.q.producerFilter,
'q[primary_taxon_id_eq]': $scope.q.categoryFilter,
'q[s]': $scope.sorting,
import_date: $scope.q.importDateFilter,
page: $scope.page,
@@ -113,7 +113,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
(DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
editProductUrl = (product, variant) ->
"/admin/products/" + product.id + ((if variant then "/variants/" + variant.id else "")) + "/edit"
"/admin/products/" + product.permalink_live + ((if variant then "/variants/" + variant.id else "")) + "/edit"
$scope.editWarn = (product, variant) ->
if confirm_unsaved_changes()
@@ -126,11 +126,8 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
DisplayProperties.setShowVariants 0, showVariants
$scope.addVariant = (product) ->
# Set new variant category to same as last product variant category to keep compactibility with deleted variant callback to set new variant category
newVariantId = $scope.nextVariantId();
newVariantCategoryId = product.variants[product.variants.length - 1]?.category_id
product.variants.push
id: newVariantId
id: $scope.nextVariantId()
unit_value: null
unit_description: null
on_demand: false
@@ -138,10 +135,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
display_name: null
on_hand: null
price: null
tax_category_id: null
category_id: newVariantCategoryId
DisplayProperties.setShowVariants product.id, true
DirtyProducts.addVariantProperty(product.id, newVariantId, 'category_id', newVariantCategoryId)
$scope.nextVariantId = ->
@@ -168,7 +162,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
if confirm(t("are_you_sure"))
$http(
method: "DELETE"
url: "/api/v0/products/" + product.id + "/variants/" + variant.id
url: "/api/v0/products/" + product.permalink_live + "/variants/" + variant.id
).then (response) ->
$scope.removeVariant(product, variant)
else
@@ -221,8 +215,8 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
products: productsToSubmit
filters:
'q[name_cont]': $scope.q.query
'q[variants_supplier_id_eq]': $scope.q.producerFilter
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter
'q[supplier_id_eq]': $scope.q.producerFilter
'q[primary_taxon_id_eq]': $scope.q.categoryFilter
'q[s]': $scope.sorting
import_date: $scope.q.importDateFilter
page: $scope.page
@@ -253,6 +247,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
else
product.variant_unit = product.variant_unit_scale = null
$scope.packVariant product, product.master if product.master
if product.variants
for id, variant of product.variants
@@ -303,6 +298,7 @@ filterSubmitProducts = (productsToFilter) ->
if product.hasOwnProperty("id")
filteredProduct = {id: product.id}
filteredVariants = []
filteredMaster = null
hasUpdatableProperty = false
if product.hasOwnProperty("variants")
@@ -312,12 +308,25 @@ filterSubmitProducts = (productsToFilter) ->
variantHasUpdatableProperty = result.hasUpdatableProperty
filteredVariants.push filteredVariant if variantHasUpdatableProperty
if product.master?.hasOwnProperty("unit_value")
filteredMaster ?= { id: product.master.id }
filteredMaster.unit_value = product.master.unit_value
if product.master?.hasOwnProperty("unit_description")
filteredMaster ?= { id: product.master.id }
filteredMaster.unit_description = product.master.unit_description
if product.master?.hasOwnProperty("display_as")
filteredMaster ?= { id: product.master.id }
filteredMaster.display_as = product.master.display_as
if product.hasOwnProperty("sku")
filteredProduct.sku = product.sku
hasUpdatableProperty = true
if product.hasOwnProperty("name")
filteredProduct.name = product.name
hasUpdatableProperty = true
if product.hasOwnProperty("producer_id")
filteredProduct.supplier_id = product.producer_id
hasUpdatableProperty = true
if product.hasOwnProperty("price")
filteredProduct.price = product.price
hasUpdatableProperty = true
@@ -334,9 +343,21 @@ filterSubmitProducts = (productsToFilter) ->
if product.hasOwnProperty("on_demand") and filteredVariants.length == 0 #only update if no variants present
filteredProduct.on_demand = product.on_demand
hasUpdatableProperty = true
if product.hasOwnProperty("category_id")
filteredProduct.primary_taxon_id = product.category_id
hasUpdatableProperty = true
if product.hasOwnProperty("tax_category_id")
filteredProduct.tax_category_id = product.tax_category_id
hasUpdatableProperty = true
if product.hasOwnProperty("inherits_properties")
filteredProduct.inherits_properties = product.inherits_properties
hasUpdatableProperty = true
if product.hasOwnProperty("available_on")
filteredProduct.available_on = product.available_on
hasUpdatableProperty = true
if filteredMaster?
filteredProduct.master_attributes = filteredMaster
hasUpdatableProperty = true
if filteredVariants.length > 0 # Note that the name of the property changes to enable mass assignment of variants attributes with rails
filteredProduct.variants_attributes = filteredVariants
hasUpdatableProperty = true
@@ -371,18 +392,9 @@ filterSubmitVariant = (variant) ->
if variant.hasOwnProperty("display_name")
filteredVariant.display_name = variant.display_name
hasUpdatableProperty = true
if variant.hasOwnProperty("tax_category_id")
filteredVariant.tax_category_id = variant.tax_category_id
hasUpdatableProperty = true
if variant.hasOwnProperty("category_id")
filteredVariant.primary_taxon_id = variant.category_id
hasUpdatableProperty = true
if variant.hasOwnProperty("display_as")
filteredVariant.display_as = variant.display_as
hasUpdatableProperty = true
if variant.hasOwnProperty("producer_id")
filteredVariant.supplier_id = variant.producer_id
hasUpdatableProperty = true
{filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty}

View File

@@ -0,0 +1,5 @@
angular.module("admin.dropdown").directive "linksDropdown", ($window)->
restrict: "C"
scope:
links: "="
templateUrl: "admin/links_dropdown.html"

View File

@@ -61,6 +61,9 @@ angular.module("admin.enterprises")
$scope.removePromoImage = ->
$scope.performEnterpriseAction("removePromoImage", "immediate_promo_image_removal_warning", "removed_promo_image_successfully")
$scope.removeTermsAndConditions = ->
$scope.performEnterpriseAction("removeTermsAndConditions", "immediate_terms_and_conditions_removal_warning", "removed_terms_and_conditions_successfully")
$scope.performEnterpriseAction = (enterpriseActionName, warning_message_key, success_message_key) ->
return unless confirm($scope.translation(warning_message_key))

View File

@@ -0,0 +1,32 @@
angular.module("admin.enterprises").directive 'termsAndConditionsWarning', ($rootScope, $compile, $templateCache, DialogDefaults, $timeout) ->
restrict: 'A'
scope: true
link: (scope, element, attr) ->
# This file input click handler will hold the browser file input dialog and show a warning modal
scope.hold_file_input_and_show_warning_modal = (event) ->
event.preventDefault()
scope.template = $compile($templateCache.get('admin/modals/terms_and_conditions_warning.html'))(scope)
if scope.template.dialog
scope.template.dialog(DialogDefaults)
scope.template.dialog('open')
$rootScope.$evalAsync()
element.bind 'click', scope.hold_file_input_and_show_warning_modal
# When the user presses continue in the warning modal, we open the browser file input dialog
scope.continue = ->
scope.template.dialog('close')
$rootScope.$evalAsync()
# unbind warning modal handler and click file input again to open the browser file input dialog
element.unbind('click').trigger('click')
# afterwards, bind warning modal handler again so that the warning is shown the next time
$timeout ->
element.bind 'click', scope.hold_file_input_and_show_warning_modal
return
scope.close = ->
scope.template.dialog('close')
$rootScope.$evalAsync()
return

View File

@@ -11,8 +11,14 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi
pendingChanges.remove(scope.object().id, scope.attr)
scope.clear()
else
change =
object: scope.object()
type: scope.type
attr: scope.attr
value: if value? then value else ""
scope: scope
scope.pending()
addPendingChange(scope.attr, value ? "")
pendingChanges.add(scope.object().id, scope.attr, change)
scope.reset = (value) ->
scope.savedValue = value
@@ -28,33 +34,3 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi
scope.clear = ->
switchClass( element, "", ["update-pending", "update-error", "update-success"], false )
# When a list of customer is filtered and we removed the "filtered value" from a customer, we
# want to make sure the customer is updated. IE. filtering by tag, and removing said tag.
# Deleting the "filtered value" from a customer will remove the customer entry, thus
# removing "objForUpdate" directive from the active scope. That means $watch won't pick up
# the attribute changed.
# To ensure the customer is still updated, we check on the $destroy event to see if
# the attribute has changed, if so we queue up the change.
scope.$on '$destroy', (value) ->
# No update
return if scope.object()[scope.attr] is scope.savedValue
# For some reason the code attribute is removed from the object when cleared, so we add
# an emptyvalue so it gets updated properly
if scope.attr is "code" and scope.object()[scope.attr] is undefined
scope.object()["code"] = ""
# Queuing up change
addPendingChange(scope.attr, scope.object()[scope.attr])
# private
addPendingChange = (attr, value) ->
change =
object: scope.object()
type: scope.type
attr: attr
value: value
scope: scope
pendingChanges.add(scope.object().id, attr, change)

View File

@@ -1,16 +1,12 @@
# Used like a regular angular filter where an object is passed
# Adds the additional special case that a value of 0 for the filter
# acts as a bypass for that particular attribute
# NOTE the name doesn't reflect what the filter does, it only fiters on the variant.producer_id
angular.module("admin.indexUtils").filter "attrFilter", ($filter) ->
return (objects, filters) ->
filter = filters["producer_id"]
return objects if !filter? || filter == 0
return $filter('filter')(objects, (product) ->
for variant in product.variants
return true if variant["producer_id"] == filter
false
, true)
Object.keys(filters).reduce (filtered, attr) ->
filter = filters[attr]
return filtered if !filter? || filter == 0
return $filter('filter')(filtered, (object) ->
object[attr] == filter
)
, objects

View File

@@ -31,7 +31,7 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, $injec
savePreferences: (action_name) =>
$http
method: "PUT"
url: "/admin/column_preferences/bulk_update.json"
url: "/admin/column_preferences/bulk_update"
data:
action_name: action_name
column_preferences: (preference for column_name, preference of @columns)

View File

@@ -11,6 +11,5 @@ angular.module("admin.indexUtils").factory 'SortOptions', ->
sortingExpr
toggle: (predicate) ->
# predicate is a string or an array of strings
@reverse = (JSON.stringify(@predicate) == JSON.stringify(predicate)) && !@reverse
@reverse = (@predicate == predicate) && !@reverse
@predicate = predicate

View File

@@ -9,7 +9,6 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.sharedResource = false
$scope.columns = Columns.columns
$scope.sorting = SortOptions
$scope.sorting.toggle("order_date")
$scope.pagination = LineItems.pagination
$scope.per_page_options = [
{id: 15, name: t('js.admin.orders.index.per_page', results: 15)},
@@ -18,19 +17,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
]
$scope.page = 1
$scope.per_page = $scope.per_page_options[0].id
$scope.filterByVariantId = null
searchThrough = ["order_distributor_name",
"order_bill_address_phone",
"order_bill_address_firstname",
"order_bill_address_lastname",
"order_bill_address_full_name",
"order_bill_address_full_name_reversed",
"order_bill_address_full_name_with_comma",
"order_bill_address_full_name_with_comma_reversed",
"variant_supplier_name",
"order_email",
"order_number",
"product_name"].join("_or_") + "_cont"
$scope.confirmRefresh = ->
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
@@ -39,7 +26,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.distributorFilter = ''
$scope.supplierFilter = ''
$scope.orderCycleFilter = ''
$scope.query = ''
$scope.quickSearch = ''
$scope.startDate = undefined
$scope.endDate = undefined
event = new CustomEvent('flatpickr:clear')
@@ -65,8 +52,6 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.dereferenceLoadedData()
$scope.loadOrders = ->
return $scope.orders = [] unless $scope.line_items.length
RequestMonitor.load $scope.orders = Orders.index(
"q[id_in][]": $scope.line_items.map((line_item) -> line_item.order.id)
)
@@ -75,17 +60,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
[formattedStartDate, formattedEndDate] = $scope.formatDates($scope.startDate, $scope.endDate)
RequestMonitor.load LineItems.index(
"q[#{searchThrough}]": $scope.query,
"q[variant_id_eq]": $scope.filterByVariantId if $scope.filterByVariantId,
"q[order_state_not_eq]": "canceled",
"q[order_shipment_state_not_eq]": "shipped",
"q[order_completed_at_not_null]": "true",
"q[order_distributor_id_eq]": $scope.distributorFilter,
"q[variant_supplier_id_eq]": $scope.supplierFilter,
"q[variant_product_supplier_id_eq]": $scope.supplierFilter,
"q[order_order_cycle_id_eq]": $scope.orderCycleFilter,
"q[order_completed_at_gteq]": if formattedStartDate then formattedStartDate else undefined,
"q[order_completed_at_lt]": if formattedEndDate then formattedEndDate else undefined,
"q[s]": "order_completed_at desc",
"page": $scope.page,
"per_page": $scope.per_page
)
@@ -105,7 +87,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
Dereferencer.dereferenceAttr $scope.line_items, "supplier", Enterprises.byID
$scope.loadOrders()
RequestMonitor.load $q.all([$scope.orders.$promise]).then ->
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
Dereferencer.dereferenceAttr $scope.line_items, "order", Orders.byID
Dereferencer.dereferenceAttr $scope.orders, "distributor", Enterprises.byID
Dereferencer.dereferenceAttr $scope.orders, "order_cycle", OrderCycles.byID
$scope.bulk_order_form.$setPristine()
@@ -129,16 +111,16 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
else
StatusMessage.display 'failure', t "unsaved_changes_error"
$scope.cancelOrder = (order, sendEmailCancellation, restock_items) ->
$scope.cancelOrder = (order, sendEmailCancellation) ->
return $http(
method: 'GET'
url: "/admin/orders/#{order.number}/fire?e=cancel&send_cancellation_email=#{sendEmailCancellation}&restock_items=#{restock_items}")
url: "/admin/orders/#{order.number}/fire?e=cancel&send_cancellation_email=#{sendEmailCancellation}")
$scope.deleteLineItem = (lineItem) ->
if lineItem.order.item_count == 1
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) ->
ofnCancelOrderAlert((confirm, sendEmailCancellation) ->
if confirm
$scope.cancelOrder(lineItem.order, sendEmailCancellation, restock_items).then(->
$scope.cancelOrder(lineItem.order, sendEmailCancellation).then(->
$scope.refreshData()
)
else
@@ -160,14 +142,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
willCancelOrders = true if (order.item_count == itemsPerOrder.get(order).length)
if willCancelOrders
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) ->
ofnCancelOrderAlert((confirm, sendEmailCancellation) ->
if confirm
itemsPerOrder.forEach (items, order) =>
if order.item_count == items.length
$scope.cancelOrder(order, sendEmailCancellation, restock_items).then(-> $scope.refreshData())
$scope.cancelOrder(order, sendEmailCancellation).then(-> $scope.refreshData())
else
Promise.all(LineItems.delete(item) for item in items).then(-> $scope.refreshData())
, "js.admin.deleting_item_will_cancel_order")
, "js.admin.deleting_item_will_cancel_order")
else
ofnDeleteLineItemsAlert(() ->
Promise.all(LineItems.delete(item) for item in lineItemsToDelete).then(-> $scope.refreshData())
@@ -186,20 +168,9 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.setSelectedUnitsVariant = (unitsProduct,unitsVariant) ->
$scope.selectedUnitsProduct = unitsProduct
$scope.selectedUnitsVariant = unitsVariant
$scope.filterByVariantId = unitsVariant.id
$scope.page = 1
$scope.refreshData()
$scope.resetSelectedUnitsVariant = ->
$scope.selectedUnitsProduct = { }
$scope.selectedUnitsVariant = { }
$scope.filterByVariantId = null
$scope.sharedResource = false
$scope.page = 1
$scope.refreshData()
$scope.getLineItemScale = (lineItem) ->
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
if lineItem.units_product && lineItem.units_variant && (lineItem.units_product.variant_unit == "weight" || lineItem.units_product.variant_unit == "volume")
lineItem.units_product.variant_unit_scale
else
1
@@ -252,7 +223,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
scale = $scope.getScale(unitsProduct, unitsVariant)
if scale
$scope.getFormattedValueWithUnitName(value, unitsProduct, unitsVariant, scale)
else
else
''
$scope.fulfilled = (sumOfUnitValues) ->

View File

@@ -0,0 +1,6 @@
angular.module("admin.lineItems").filter "variantFilter", ->
return (lineItems,selectedUnitsProduct,selectedUnitsVariant,sharedResource) ->
filtered = []
filtered.push lineItem for lineItem in lineItems when (angular.equals(selectedUnitsProduct,{}) ||
(lineItem.units_product.id == selectedUnitsProduct.id && (sharedResource || lineItem.units_variant.id == selectedUnitsVariant.id ) ) )
filtered

View File

@@ -3,7 +3,6 @@ angular.module('admin.orderCycles')
$controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance})
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
$scope.order_cycle_id = order_cycle_id
$scope.order_cycle = OrderCycle.load(order_cycle_id)
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
@@ -19,8 +18,7 @@ angular.module('admin.orderCycles')
$scope.submit = ($event, destination) ->
$event.preventDefault()
$scope.order_cycle?.trigger_action = $($event.target).data('trigger-action');
$scope.order_cycle?.confirm = $($event.target).data('confirm');
NavigationCheck.clear()
StatusMessage.display 'progress', t('js.saving')
OrderCycle.update(destination, $scope.order_cycle_form)
@@ -28,4 +26,4 @@ angular.module('admin.orderCycles')
if $scope.order_cycle_form?.$dirty
t('admin.unsaved_confirm_leave')
NavigationCheck.register(warnAboutUnsavedChanges)
NavigationCheck.register(warnAboutUnsavedChanges)

View File

@@ -1,9 +1,8 @@
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, EnterpriseFee, OrderCycle, ExchangeProduct, ocInstance) ->
angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $rootScope, $controller, $location, Enterprise, OrderCycle, ExchangeProduct, ocInstance) ->
$controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location})
$scope.view = 'incoming'
# NB: weirdly at this next line $scope.order_cycle.id comes out undefined so we use $scope.order_cycle_id instead
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: $scope.order_cycle_id, per_item: true)
$scope.exchangeTotalVariants = (exchange) ->
return unless $scope.enterprises? && $scope.enterprises[exchange.enterprise_id]?

View File

@@ -33,11 +33,9 @@ angular.module("admin.orderCycles").controller "OrderCyclesCtrl", ($scope, $q, C
StatusMessage.display 'notice', "You have unsaved changes" if newVal
$scope.showMore = (days) ->
orderCycles = OrderCycles.index(ams_prefix: "index",
"q[orders_close_at_gt]": "#{daysFromToday($scope.ordersCloseAtLimit - days)}",
"q[orders_close_at_lteq]": "#{daysFromToday($scope.ordersCloseAtLimit)}"
)
$scope.ordersCloseAtLimit -= days
existingIDs = Object.keys(OrderCycles.byID)
orderCycles = OrderCycles.index(ams_prefix: "index", "q[orders_close_at_gt]": "#{daysFromToday($scope.ordersCloseAtLimit)}", "q[id_not_in][]": existingIDs)
orderCycles.$promise.then ->
$scope.orderCycles.push(orderCycle) for orderCycle in orderCycles
compileData()

View File

@@ -22,8 +22,6 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
$scope.submit = ($event, destination) ->
$event.preventDefault()
$scope.order_cycle?.trigger_action = $($event.target).data('trigger-action');
$scope.order_cycle?.confirm = $($event.target).data('confirm');
StatusMessage.display 'progress', t('js.saving')
OrderCycle.mirrorIncomingToOutgoingProducts()
OrderCycle.update(destination, $scope.order_cycle_form) if OrderCycle.confirmNoDistributors()

View File

@@ -33,4 +33,6 @@ angular.module('admin.orderCycles').factory('Enterprise', ($resource) ->
variantsOf: (product) ->
if product.variants.length > 0
variant.id for variant in product.variants
else
[product.master_id]
})

View File

@@ -6,8 +6,6 @@ angular.module('admin.orderCycles').factory('EnterpriseFee', ($resource) ->
params:
order_cycle_id: '@order_cycle_id'
coordinator_id: '@coordinator_id'
per_item: '@per_item'
per_order: '@per_order'
})
{

View File

@@ -161,11 +161,7 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $
StatusMessage.display('failure', t('js.order_cycles.create_failure'))
update: (destination, form) ->
oc = new OrderCycleResource({
order_cycle: this.dataForSubmit(),
confirm: this.order_cycle.confirm,
trigger_action: this.order_cycle.trigger_action
})
oc = new OrderCycleResource({order_cycle: this.dataForSubmit()})
oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) =>
form.$setPristine() if form
if destination?
@@ -175,8 +171,6 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $
, (response) ->
if response.data.errors?
StatusMessage.display('failure', response.data.errors[0])
else if (response.data.trigger_action)
StatusMessage.display('notice', t('js.order_cycles.unsaved_changes'), response.data.trigger_action)
else
StatusMessage.display('failure', t('js.order_cycles.update_failure'))

View File

@@ -0,0 +1,32 @@
angular.module("admin.orders").controller "bulkInvoiceCtrl", ($scope, $http, $timeout) ->
$scope.createBulkInvoice = ->
$scope.invoice_id = null
$scope.poll = 1
$scope.loading = true
$scope.message = null
$scope.error = null
$scope.poll_wait = 5 # 5 Seconds between each check
$scope.poll_retries = 80 # Maximum checks before stopping
$http.post('/admin/orders/invoices', {order_ids: $scope.selected_orders}).then (response) ->
$scope.invoice_id = response.data
$scope.pollBulkInvoice()
$scope.pollBulkInvoice = ->
$timeout($scope.nextPoll, $scope.poll_wait * 1000)
$scope.nextPoll = ->
$http.get('/admin/orders/invoices/'+$scope.invoice_id+'/poll').then (response) ->
$scope.loading = false
$scope.message = t('js.admin.orders.index.bulk_invoice_created')
.catch (response) ->
$scope.poll++
if $scope.poll > $scope.poll_retries
$scope.loading = false
$scope.error = t('js.admin.orders.index.bulk_invoice_failed')
return
$scope.pollBulkInvoice()

View File

@@ -0,0 +1,117 @@
angular.module("admin.orders").controller "ordersCtrl", ($scope, $timeout, RequestMonitor, Orders, SortOptions, $window, $filter, $location, KeyValueMapStore) ->
$scope.RequestMonitor = RequestMonitor
$scope.pagination = Orders.pagination
$scope.orders = Orders.all
$scope.sortOptions = SortOptions
$scope.per_page_options = [
{id: 15, name: t('js.admin.orders.index.per_page', results: 15)},
{id: 50, name: t('js.admin.orders.index.per_page', results: 50)},
{id: 100, name: t('js.admin.orders.index.per_page', results: 100)}
]
$scope.selected_orders = []
$scope.checkboxes = {}
$scope.selected = false
$scope.select_all = false
$scope.poll = 0
$scope.rowStatus = {}
KeyValueMapStore.localStorageKey = 'ordersFilters'
KeyValueMapStore.storableKeys = ["q", "sorting", "page", "per_page"]
$scope.initialise = ->
unless KeyValueMapStore.restoreValues($scope)
$scope.setDefaults()
$scope.fetchResults()
$scope.setDefaults = ->
$scope.per_page = 15
$scope.q = {
completed_at_not_null: true
}
e = new CustomEvent("flatpickr_clear");
window.dispatchEvent(e)
$scope.clearFilters = () ->
KeyValueMapStore.clearKeyValueMap()
$scope.setDefaults()
$scope.fetchResults()
$scope.fetchResults = (page=1) ->
startDateWithTime = $scope.appendStringIfNotEmpty($scope.q?.completed_at_gteq, ' 00:00:00')
endDateWithTime = $scope.appendStringIfNotEmpty($scope.q?.completed_at_lteq, ' 23:59:59')
$scope.resetSelected()
params = {
'q[completed_at_gteq]': startDateWithTime,
'q[completed_at_lteq]': endDateWithTime,
'q[state_eq]': $scope.q?.state_eq,
'q[number_cont]': $scope.q?.number_cont,
'q[email_cont]': $scope.q?.email_cont,
'q[bill_address_firstname_start]': $scope.q?.bill_address_firstname_start,
'q[bill_address_lastname_start]': $scope.q?.bill_address_lastname_start,
# Set default checkbox values to null. See: https://github.com/openfoodfoundation/openfoodnetwork/pull/3076#issuecomment-440010498
'q[completed_at_not_null]': $scope.q?.completed_at_not_null || null,
'q[distributor_id_in][]': $scope.q?.distributor_id_in,
'q[order_cycle_id_in][]': $scope.q?.order_cycle_id_in,
'q[s]': $scope.sorting || 'completed_at desc',
shipping_method_id: $scope.q?.shipping_method_id,
per_page: $scope.per_page,
page: page
}
KeyValueMapStore.setStoredValues($scope)
RequestMonitor.load(Orders.index(params).$promise)
$scope.appendStringIfNotEmpty = (baseString, stringToAppend) ->
return baseString unless baseString
return baseString if baseString.endsWith(stringToAppend)
baseString + stringToAppend
$scope.resetSelected = ->
$scope.selected_orders.length = 0
$scope.selected = false
$scope.select_all = false
$scope.checkboxes = {}
$scope.toggleSelection = (id) ->
index = $scope.selected_orders.indexOf(id)
if index == -1
$scope.selected_orders.push(id)
else
$scope.selected_orders.splice(index, 1)
$scope.toggleAll = ->
$scope.selected_orders.length = 0
$scope.orders.forEach (order) ->
$scope.checkboxes[order.id] = $scope.select_all
$scope.selected_orders.push order.id if $scope.select_all
$scope.$watch 'sortOptions', (sort) ->
return unless sort && sort.predicate != ""
$scope.sorting = sort.getSortingExpr()
$scope.fetchResults()
, true
$scope.capturePayment = (order) ->
$scope.rowAction('capture', order)
$scope.shipOrder = (order) ->
$scope.rowAction('ship', order)
$scope.rowAction = (action, order) ->
$scope.rowStatus[order.id] = "loading"
Orders[action](order).$promise.then (data) ->
$scope.rowStatus[order.id] = "success"
$timeout(->
$scope.rowStatus[order.id] = null
, 1500)
, (error) ->
$scope.rowStatus[order.id] = "error"
$scope.changePage = (newPage) ->
$scope.page = newPage
$scope.fetchResults(newPage)

View File

@@ -0,0 +1,5 @@
angular.module("admin.orders").directive "invoicesModal", ($modal) ->
restrict: 'C'
link: (scope, elem, attrs, ctrl) ->
elem.on "click", (ev) =>
scope.uploadModal = $modal.open(templateUrl: 'admin/modals/bulk_invoice.html', controller: ctrl, scope: scope, windowClass: 'simple-modal')

View File

@@ -42,10 +42,8 @@ angular.module('admin.payments').factory 'Payment', (AdminStripeElements, curren
submit: =>
munged = @preprocess()
PaymentResource.create({order_id: munged.order_id}, munged, (response, headers, status) ->
rawHtml = Object.values(response).join('').replace('[object Object]true', '')
document.body.innerHTML = rawHtml
$window.history.pushState({}, '', "/admin/orders/" + munged.order_id + "/payments")
PaymentResource.create({order_id: munged.order_id}, munged, (response, headers, status)=>
$window.location.pathname = "/admin/orders/" + munged.order_id + "/payments"
, (response) ->
StatusMessage.display 'error', t("spree.admin.payments.source_forms.stripe.error_saving_payment")
)

View File

@@ -1,4 +1,3 @@
# Controller for "New Products" form (spree/admin/products/new)
angular.module("admin.products")
.controller "unitsCtrl", ($scope, VariantUnitManager, OptionValueNamer, UnitPrices, PriceParser) ->
$scope.product = { master: {} }
@@ -13,15 +12,13 @@ angular.module("admin.products")
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
# Extract variant_unit and variant_unit_scale from dropdown variant_unit_with_scale,
# and update hidden product fields
$scope.processVariantUnitWithScale = ->
if $scope.product.variant_unit_with_scale
match = $scope.product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/) # matches string like "weight_1000"
match = $scope.product.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
$scope.product.variant_unit = match[1]
$scope.product.variant_unit_scale = parseFloat(match[2])
else # "items"
else
$scope.product.variant_unit = $scope.product.variant_unit_with_scale
$scope.product.variant_unit_scale = null
else if $scope.product.variant_unit
@@ -35,8 +32,6 @@ angular.module("admin.products")
else
$scope.product.variant_unit = $scope.product.variant_unit_scale = null
# Extract unit_value and unit_description from text field unit_value_with_description,
# and update hidden variant fields
$scope.processUnitValueWithDescription = ->
if $scope.product.master.hasOwnProperty("unit_value_with_description")
match = $scope.product.master.unit_value_with_description.match(/^([\d\.,]+(?= *|$)|)( *)(.*)$/)
@@ -50,7 +45,6 @@ angular.module("admin.products")
value = window.bigDecimal.divide(value, $scope.product.variant_unit_scale, 2) if $scope.product.master.unit_value && $scope.product.variant_unit_scale
$scope.product.master.unit_value_with_description = value + " " + $scope.product.master.unit_description
# Calculate unit price based on product price and variant_unit_scale
$scope.processUnitPrice = ->
price = $scope.product.price
scale = $scope.product.variant_unit_scale

View File

@@ -1,5 +1,4 @@
angular.module("admin.products").factory "OptionValueNamer", (VariantUnitManager) ->
# Javascript clone of VariantUnits::OptionValueNamer, for bulk product editing.
class OptionValueNamer
constructor: (@variant) ->

View File

@@ -2,9 +2,6 @@ angular.module("admin.products").factory "VariantUnitManager", (availableUnits)
class VariantUnitManager
@units:
'weight':
0.001:
name: 'mg'
system: 'metric'
1.0:
name: 'g'
system: 'metric'
@@ -24,21 +21,12 @@ angular.module("admin.products").factory "VariantUnitManager", (availableUnits)
0.001:
name: 'mL'
system: 'metric'
0.01:
name: 'cL'
system: 'metric'
0.1:
name: 'dL'
system: 'metric'
1.0:
name: 'L'
system: 'metric'
1000.0:
name: 'kL'
system: 'metric'
4.54609:
name: 'gal'
system: 'metric'
'items':
1:
name: 'items'
@@ -72,13 +60,8 @@ angular.module("admin.products").factory "VariantUnitManager", (availableUnits)
@compatibleUnitScales: (scale, unitType) ->
scaleSystem = @units[unitType][scale]['system']
if availableUnits
available = availableUnits.split(",")
(parseFloat(scale) for scale, scaleInfo of @units[unitType] when scaleInfo['system'] == scaleSystem and available.includes(scaleInfo['name'])).sort (a, b) ->
a - b
else
(parseFloat(scale) for scale, scaleInfo of @units[unitType] when scaleInfo['system'] == scaleSystem).sort (a, b) ->
a - b
(parseFloat(scale) for scale, scaleInfo of @units[unitType] when scaleInfo['system'] == scaleSystem).sort (a, b) ->
a - b
@systemOfMeasurement: (scale, unitType) ->
if @units[unitType][scale]

View File

@@ -14,4 +14,7 @@ angular.module("admin.resources").factory 'EnterpriseResource', ($resource) ->
'removePromoImage':
url: '/api/v0/enterprises/:id/promo_image.json'
method: 'DELETE'
'removeTermsAndConditions':
url: '/api/v0/enterprises/:id/terms_and_conditions.json'
method: 'DELETE'
})

View File

@@ -63,3 +63,4 @@ angular.module("admin.resources").factory 'Enterprises', ($q, $filter, Enterpris
removeLogo: performActionOnEnterpriseResource(EnterpriseResource.removeLogo)
removePromoImage: performActionOnEnterpriseResource(EnterpriseResource.removePromoImage)
removeTermsAndConditions: performActionOnEnterpriseResource(EnterpriseResource.removeTermsAndConditions)

View File

@@ -32,6 +32,9 @@ jQuery(function($) {
});
}
// Make flash messages dissapear
setTimeout('$(".flash").fadeOut()', 5000);
// Highlight hovered table column
$('table tbody tr td.actions a').hover(function(){
var tr = $(this).closest('tr');

View File

@@ -0,0 +1,15 @@
$ ->
($ '#new_image_link').click (event) ->
event.preventDefault()
($ '.no-objects-found').hide()
($ this).hide()
$.ajax
type: 'GET'
url: @href
data: (
authenticity_token: AUTH_TOKEN
)
success: (r) ->
($ '#images').html r

View File

@@ -0,0 +1,7 @@
($ '#cancel_link').click (event) ->
event.preventDefault()
($ '.no-objects-found').show()
($ '#new_image_link').show()
($ '#images').html('')

View File

@@ -1,6 +1,22 @@
// Shipments AJAX API
$(document).ready(function() {
handle_ship_click = function(){
var link = $(this);
var shipment_number = link.data('shipment-number');
var url = Spree.url( Spree.routes.orders_api + "/" + order_number + "/shipments/" + shipment_number + "/ship.json");
$.ajax({
type: "PUT",
url: url
}).done(function( msg ) {
window.location.reload();
}).error(function( msg ) {
console.log(msg);
});
}
$('[data-hook=admin_order_edit_form] a.ship').click(handle_ship_click);
//handle shipping method edit click
$('a.edit-method').click(toggleMethodEdit);
$('a.cancel-method').click(toggleMethodEdit);
@@ -21,7 +37,7 @@ $(document).ready(function() {
console.log(msg);
});
}
$('.admin-order-edit-form a.save-method').click(handle_shipping_method_save);
$('[data-hook=admin_order_edit_form] a.save-method').click(handle_shipping_method_save);
//handle tracking info edit/delete
@@ -48,8 +64,8 @@ $(document).ready(function() {
return Spree.url( Spree.routes.orders_api + "/" + order_number + "/shipments/" + shipmentNumber + ".json");
}
$('.admin-order-edit-form a.save-tracking').click(saveTrackingInfo);
$('.admin-order-edit-form a.delete-tracking').click(deleteTrackingInfo);
$('[data-hook=admin_order_edit_form] a.save-tracking').click(saveTrackingInfo);
$('[data-hook=admin_order_edit_form] a.delete-tracking').click(deleteTrackingInfo);
// handle note edit/delete
@@ -80,8 +96,8 @@ $(document).ready(function() {
});
}
$('.admin-order-edit-form a.save-note').click(saveNote);
$('.admin-order-edit-form a.delete-note').click(deleteNote);
$('[data-hook=admin_order_edit_form] a.save-note').click(saveNote);
$('[data-hook=admin_order_edit_form] a.delete-note').click(deleteNote);
// Makes API call for notes/tracking info
makeApiCall = function(url, params) {

View File

@@ -4,8 +4,7 @@ $(document).ready(function() {
initAlert()
initConfirm()
initButtonCancel()
initLinkCancel()
initCancelOrder()
if ($('#variant_autocomplete_template').length > 0) {
window.variantTemplate = Handlebars.compile($('#variant_autocomplete_template').text());
@@ -50,11 +49,11 @@ $(document).ready(function() {
if (quantity > maxQuantity) {
quantity = maxQuantity;
save.parents('tr').find('input.line_item_quantity').val(maxQuantity);
ofnAlert(t("js.admin.orders.quantity_unavailable"));
} else {
adjustItems(shipment_number, variant_id, quantity, true);
ofnAlert(t("js.admin.orders.quantity_adjusted"));
}
toggleItemEdit();
adjustItems(shipment_number, variant_id, quantity, true);
return false;
}
$('a.save-item').click(handle_save_click);
@@ -100,7 +99,6 @@ adjustItems = function(shipment_number, variant_id, quantity, restock_item){
doAdjustItems(shipment_number, variant_id, quantity, inventory_units, restock_item, () => {
var redirectTo = new URL(Spree.routes.cancel_order.toString());
redirectTo.searchParams.append("send_cancellation_email", sendEmailCancellation);
redirectTo.searchParams.append("restock_item", restock_item);
window.location.href = redirectTo.toString();
});
}
@@ -278,23 +276,17 @@ ofnConfirm = function(callback) {
$('#custom-confirm').show();
}
initCancelAction = function(e){
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) => {
if (confirm) {
var redirectTo = new URL(Spree.routes.cancel_order.toString());
redirectTo.searchParams.append("send_cancellation_email", sendEmailCancellation);
redirectTo.searchParams.append("restock_items", restock_items);
window.location.href = redirectTo.toString();
}
initCancelOrder = function() {
$('#cancel_order_form').submit(function(e){
ofnCancelOrderAlert((confirm, sendEmailCancellation, restock_items) => {
if (confirm) {
var redirectTo = new URL(Spree.routes.cancel_order.toString());
redirectTo.searchParams.append("send_cancellation_email", sendEmailCancellation);
redirectTo.searchParams.append("restock_items", restock_items);
window.location.href = redirectTo.toString();
}
});
e.preventDefault();
return false;
});
e.preventDefault();
return false;
};
initButtonCancel = function() {
$('#cancel_order_form').submit(initCancelAction)
}
initLinkCancel = function() {
$('#links-dropdown a[href$="cancel"]').click(initCancelAction);
}

View File

@@ -0,0 +1,21 @@
root = exports ? this
root.taxon_tree_menu = (obj, context) ->
base_url = Spree.url(Spree.routes.taxonomy_taxons)
admin_base_url = Spree.url(Spree.routes.admin_taxonomy_taxons)
edit_url = Spree.url(Spree.routes.admin_taxonomy_taxons + '/' + obj.attr("id") + "/edit");
create:
label: "<i class='icon-plus'></i> " + Spree.translations.add,
action: (obj) -> context.create(obj)
rename:
label: "<i class='icon-pencil'></i> " + Spree.translations.rename,
action: (obj) -> context.rename(obj)
remove:
label: "<i class='icon-trash'></i> " + Spree.translations.remove,
action: (obj) -> context.remove(obj)
edit:
separator_before: true,
label: "<i class='icon-edit'></i> " + Spree.translations.edit,
action: (obj) -> window.location = edit_url.toString()

View File

@@ -0,0 +1,139 @@
handle_ajax_error = (XMLHttpRequest, textStatus, errorThrown) ->
$.jstree.rollback(last_rollback)
$("#ajax_error").show().html("<strong>" + server_error + "</strong><br />" + taxonomy_tree_error)
handle_move = (e, data) ->
last_rollback = data.rlbk
position = data.rslt.cp
node = data.rslt.o
new_parent = data.rslt.np
url = new URL(base_url)
url.pathname = url.pathname + '/' + node.attr("id")
data = {
_method: "put",
"taxon[position]": position,
"taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined
}
$.ajax
type: "POST",
dataType: "json",
url: url.toString(),
data: data,
error: handle_ajax_error
true
handle_create = (e, data) ->
last_rollback = data.rlbk
node = data.rslt.obj
name = data.rslt.name
position = data.rslt.position
new_parent = data.rslt.parent
data = {
"taxon[name]": name,
"taxon[position]": position
"taxon[parent_id]": if !isNaN(new_parent.attr("id")) then new_parent.attr("id") else undefined
}
$.ajax
type: "POST",
dataType: "json",
url: base_url.toString(),
data: data,
error: handle_ajax_error,
success: (data,result) ->
node.attr('id', data.id)
handle_rename = (e, data) ->
last_rollback = data.rlbk
node = data.rslt.obj
name = data.rslt.new_name
# change the name inside the main input field as well if taxon is the root one
document.getElementById("taxonomy_name").value = name if node.parents("[id]").attr("id") == "taxonomy_tree"
url = new URL(base_url)
url.pathname = url.pathname + '/' + node.attr("id")
$.ajax
type: "POST",
dataType: "json",
url: url.toString(),
data: {_method: "put", "taxon[name]": name },
error: handle_ajax_error
handle_delete = (e, data) ->
last_rollback = data.rlbk
node = data.rslt.obj
delete_url = new URL(base_url)
delete_url.pathname = delete_url.pathname + '/' + node.attr("id")
if confirm(Spree.translations.are_you_sure_delete)
$.ajax
type: "POST",
dataType: "json",
url: delete_url.toString(),
data: {_method: "delete"},
error: handle_ajax_error
else
$.jstree.rollback(last_rollback)
last_rollback = null
root = exports ? this
root.setup_taxonomy_tree = (taxonomy_id) ->
if taxonomy_id != undefined
# this is defined within admin/taxonomies/edit
root.base_url = Spree.url(Spree.routes.taxonomy_taxons)
$.ajax
url: base_url.pathname.replace("/taxons", "/jstree"),
success: (taxonomy) ->
last_rollback = null
conf =
json_data:
data: taxonomy,
ajax:
url: (e) ->
base_url.pathname + '/' + e.attr('id') + '/jstree'
themes:
theme: "apple",
url: "/assets/jquery.jstree/themes/apple/style.css"
strings:
new_node: new_taxon,
loading: Spree.translations.loading + "..."
crrm:
move:
check_move: (m) ->
position = m.cp
node = m.o
new_parent = m.np
# no parent or cant drag and drop
if !new_parent || node.attr("rel") == "root"
return false
# can't drop before root
if new_parent.attr("id") == "taxonomy_tree" && position == 0
return false
true
contextmenu:
items: (obj) ->
taxon_tree_menu(obj, this)
plugins: ["themes", "json_data", "dnd", "crrm", "contextmenu"]
$("#taxonomy_tree").jstree(conf)
.bind("move_node.jstree", handle_move)
.bind("remove.jstree", handle_delete)
.bind("create.jstree", handle_create)
.bind("rename.jstree", handle_rename)
.bind "loaded.jstree", ->
$(this).jstree("core").toggle_node($('.jstree-icon').first())
$("#taxonomy_tree a").on "dblclick", (e) ->
$("#taxonomy_tree").jstree("rename", this)
# surpress form submit on enter/return
$(document).keypress (e) ->
if e.keyCode == 13
e.preventDefault()

View File

@@ -10,12 +10,6 @@ angular.module("admin.utils").directive "variantAutocomplete", ($timeout) ->
element.select2
placeholder: t('admin.orders.select_variant')
minimumInputLength: 3
formatInputTooShort: ->
t('admin.select2.minimal_search_length', count: 3)
formatSearching: ->
t('admin.select2.searching')
formatNoMatches: ->
t('admin.select2.no_matches')
ajax:
url: Spree.routes.variants_search
datatype: "json"

View File

@@ -5,14 +5,12 @@ angular.module("admin.utils").factory "StatusMessage", ->
alert: {style: {color: 'grey'}}
notice: {style: {color: 'grey'}}
success: {style: {color: '#9fc820'}}
failure: {style: {color: '#C85136'}}
error: {style: {color: '#C85136'}}
failure: {style: {color: '#da5354'}}
error: {style: {color: '#da5354'}}
statusMessage:
text: ""
style: {},
type: null,
actionName: null
style: {}
invalidMessage: ""
@@ -25,15 +23,11 @@ angular.module("admin.utils").factory "StatusMessage", ->
active: ->
@statusMessage.text != ''
display: (type, text, actionName = null) ->
display: (type, text) ->
@statusMessage.text = text
@statusMessage.type = type
@statusMessage.actionName = actionName
@statusMessage.style = @types[type].style
null
clear: ->
@statusMessage.text = ''
@statusMessage.style = {}
@statusMessage.type = null
@statusMessage.actionName = null

View File

@@ -1,4 +1,3 @@
angular.module("admin.utils", ["templates", "ngSanitize"]).config ($httpProvider, $locationProvider) ->
# for the next line, you should also probably check file: app/assets/javascripts/admin/admin_ofn.js.coffee
$locationProvider.hashPrefix('')
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"

View File

@@ -2,10 +2,10 @@ angular.module("admin.variantOverrides").directive "trackInheritance", (VariantO
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
# This is a bit hacky, but it allows us to load the inherit property on the VO, but then not submit it
scope.inherit = angular.equals scope.variantOverrides[scope.hub_id][scope.variant.id], VariantOverrides.newFor scope.hub_id, scope.variant
scope.inherit = angular.equals scope.variantOverrides[scope.hub_id][scope.variant.id], VariantOverrides.newFor scope.hub_id, scope.variant.id
ngModel.$parsers.push (viewValue) ->
if ngModel.$dirty && viewValue
DirtyVariantOverrides.inherit scope.hub_id, scope.variant, scope.variantOverrides[scope.hub_id][scope.variant.id].id
DirtyVariantOverrides.inherit scope.hub_id, scope.variant.id, scope.variantOverrides[scope.hub_id][scope.variant.id].id
scope.displayDirty()
viewValue

View File

@@ -2,8 +2,4 @@ angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
return (products, hubPermissions, hub_id) ->
return [] if !hub_id
return [] if !hubPermissions[hub_id]
return $filter('filter')(products, ((product) ->
for variant in product.variants
return hubPermissions[hub_id].indexOf(variant.producer_id) > -1
), true)
return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)

View File

@@ -12,11 +12,11 @@ angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http
@add(hub_id, variant_id, vo_id)
@dirtyVariantOverrides[hub_id][variant_id][attr] = value
inherit: (hub_id, variant, vo_id) ->
@add(hub_id, variant.id, vo_id)
blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant))
inherit: (hub_id, variant_id, vo_id) ->
@add(hub_id, variant_id, vo_id)
blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant_id))
delete blankVo[attr] for attr, value of blankVo when attr not in @requiredAttrs()
@dirtyVariantOverrides[hub_id][variant.id] = blankVo
@dirtyVariantOverrides[hub_id][variant_id] = blankVo
count: ->
count = 0

View File

@@ -13,18 +13,17 @@ angular.module("admin.variantOverrides").factory "VariantOverrides", (variantOve
@variantOverrides[hub.id] ||= {}
for product in products
for variant in product.variants
@inherit(hub.id, variant) unless @variantOverrides[hub.id][variant.id]
@inherit(hub.id, variant.id) unless @variantOverrides[hub.id][variant.id]
inherit: (hub_id, variant) ->
inherit: (hub_id, variant_id) ->
# This method is called from the trackInheritance directive, to reinstate inheritance
@variantOverrides[hub_id][variant.id] ||= {}
angular.extend @variantOverrides[hub_id][variant.id], @newFor(hub_id, variant)
@variantOverrides[hub_id][variant_id] ||= {}
angular.extend @variantOverrides[hub_id][variant_id], @newFor hub_id, variant_id
newFor: (hub_id, variant) ->
newFor: (hub_id, variant_id) ->
# These properties need to match those checked in VariantOverrideSet.deletable?
hub_id: hub_id
variant_id: variant.id
producer_id: variant.producer_id
variant_id: variant_id
sku: null
price: null
count_on_hand: null

View File

@@ -1,15 +0,0 @@
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require jquery2
//= require admin/spree/spree-select2
//= require admin/spree/handlebar_extensions
//= require i18n/translations
//= require darkswarm/i18n.translate.js
window.angular = { module: function(noop){ return { value: function(){} } } }

View File

@@ -29,6 +29,24 @@
#
#= require angular-flash.min.js
#
#= require moment/min/moment.min.js
#= require moment/locale/ar.js
#= require moment/locale/ca.js
#= require moment/locale/de.js
#= require moment/locale/en-gb.js
#= require moment/locale/es.js
#= require moment/locale/fil.js
#= require moment/locale/fr.js
#= require moment/locale/it.js
#= require moment/locale/nb.js
#= require moment/locale/nl-be.js
#= require moment/locale/pt-br.js
#= require moment/locale/pt.js
#= require moment/locale/ru.js
#= require moment/locale/sv.js
#= require moment/locale/tr.js
#= require moment/locale/pl.js
#
#= require modernizr
#
#= require foundation-sites/js/foundation.js
@@ -49,3 +67,11 @@ document.addEventListener "turbo:before-render", ->
rootscope = null
window.injector = null
true
document.addEventListener "ajax:beforeSend", (event) =>
window.Turbo.navigator.adapter.progressBar.setValue(0)
window.Turbo.navigator.adapter.progressBar.show()
document.addEventListener "ajax:complete", (event) =>
window.Turbo.navigator.adapter.progressBar.setValue(100)
window.Turbo.navigator.adapter.progressBar.hide()

View File

@@ -0,0 +1,22 @@
angular.module('Darkswarm').controller "AccordionCtrl", ($scope, localStorageService, $timeout, $document, CurrentHub) ->
$scope.accordionSections = ["details", "billing", "shipping", "payment"]
$scope.accordion = { details: true, billing: true, shipping: true, payment: true }
$scope.show = (section) ->
$scope.accordion[section] = true
$scope.scrollTo = (section) ->
# Scrolling is confused by our position:fixed top bar - add an offset to scroll
# to the correct location, plus 5px buffer
offset_height = $("nav.top-bar").height() + 5
$document.scrollTo($("##{section}"), offset_height, 400)
$scope.$on 'purchaseFormInvalid', (event, form) ->
# Scroll to first invalid section
for section in $scope.accordionSections
if not form[section].$valid
$scope.show section
$timeout ->
$scope.scrollTo(section)
, 50
break

View File

@@ -0,0 +1,12 @@
angular.module('Darkswarm').controller "BillingCtrl", ($scope, $timeout, $controller) ->
angular.extend this, $controller('FieldsetMixin', {$scope: $scope})
$scope.name = "billing"
$scope.nextPanel = "shipping"
$scope.summary = ->
[$scope.order.bill_address.address1,
$scope.order.bill_address.city,
$scope.order.bill_address.zipcode]
$timeout $scope.onTimeout

View File

@@ -0,0 +1,43 @@
angular.module('Darkswarm').controller "CheckoutCtrl", ($scope, localStorageService, Checkout, CurrentUser, CurrentHub, $http) ->
$scope.Checkout = Checkout
$scope.submitted = false
# Bind to local storage
$scope.fieldsToBind = ["bill_address", "email", "payment_method_id", "shipping_method_id", "ship_address"]
prefix = "order_#{Checkout.order.id}#{CurrentUser.id or ""}#{CurrentHub.hub.id}"
for field in $scope.fieldsToBind
localStorageService.bind $scope, "Checkout.order.#{field}", Checkout.order[field], "#{prefix}_#{field}"
localStorageService.bind $scope, "Checkout.ship_address_same_as_billing", true, "#{prefix}_sameasbilling"
localStorageService.bind $scope, "Checkout.default_bill_address", false, "#{prefix}_defaultasbilladdress"
localStorageService.bind $scope, "Checkout.default_ship_address", false, "#{prefix}_defaultasshipaddress"
$scope.order = Checkout.order # Ordering is important
$scope.secrets = Checkout.secrets
$scope.enabled = !!CurrentUser.id?
$scope.purchase = (event, form) ->
event.preventDefault()
$scope.formdata = form
$scope.submitted = true
if CurrentUser.id
$scope.validateForm(form)
else
$scope.ensureUserIsGuest()
$scope.validateForm = ->
if $scope.formdata.$valid
$scope.Checkout.purchase()
else
$scope.$broadcast 'purchaseFormInvalid', $scope.formdata
$scope.ensureUserIsGuest = (callback = null) ->
$http.post("/user/registered_email", {email: $scope.order.email})
.then (response)->
window.CableReady.perform(response.data)
.catch ->
$scope.validateForm() if $scope.submitted
callback() if callback

View File

@@ -0,0 +1,8 @@
angular.module('Darkswarm').controller "CountryCtrl", ($scope, availableCountries) ->
$scope.countries = availableCountries
$scope.countriesById = $scope.countries.reduce (obj, country) ->
obj[country.id] = country
obj
, {}

View File

@@ -0,0 +1,24 @@
angular.module('Darkswarm').controller "DetailsCtrl", ($scope, $timeout, $http, CurrentUser, SpreeUser, $controller) ->
angular.extend this, $controller('FieldsetMixin', {$scope: $scope})
$scope.name = "details"
$scope.nextPanel = "billing"
$scope.login_or_next = (event) ->
event.preventDefault()
unless CurrentUser.id
$scope.ensureUserIsGuest($scope.next)
return
$scope.next()
$scope.summary = ->
[$scope.fullName(),
$scope.order.email,
$scope.order.bill_address.phone]
$scope.fullName = ->
[$scope.order.bill_address.firstname ? null,
$scope.order.bill_address.lastname ? null].join(" ").trim()
$timeout $scope.onTimeout

View File

@@ -0,0 +1,19 @@
angular.module('Darkswarm').controller "PaymentCtrl", ($scope, $timeout, savedCreditCards, Dates, $controller) ->
angular.extend this, $controller('FieldsetMixin', {$scope: $scope})
$scope.savedCreditCards = savedCreditCards
$scope.name = "payment"
$scope.months = Dates.months
$scope.years = Dates.years
$scope.secrets.card_month = "1"
$scope.secrets.card_year = moment().year()
for card in savedCreditCards when card.is_default
$scope.secrets.selected_card = card.id
break
$scope.summary = ->
[$scope.Checkout.paymentMethod()?.name]
$timeout $scope.onTimeout

View File

@@ -0,0 +1,11 @@
angular.module('Darkswarm').controller "ShippingCtrl", ($scope, $timeout, ShippingMethods, $controller) ->
angular.extend this, $controller('FieldsetMixin', {$scope: $scope})
$scope.ShippingMethods = ShippingMethods
$scope.name = "shipping"
$scope.nextPanel = "payment"
$scope.summary = ->
[$scope.Checkout.shippingMethod()?.name]
$timeout $scope.onTimeout

View File

@@ -25,8 +25,6 @@ angular.module('Darkswarm').controller "OrderCycleChangeCtrl", ($scope, $rootSco
Cart.reloadFinalisedLineItems()
ChangeableOrdersAlert.reload()
$rootScope.$broadcast 'orderCycleSelected'
event = new CustomEvent('orderCycleSelected')
window.dispatchEvent(event)
$scope.closesInLessThan3Months = () ->
moment().diff(moment(OrderCycle.orders_close_at(), "YYYY-MM-DD HH:mm:SS Z"), 'days') > -75

View File

@@ -0,0 +1,22 @@
angular.module('Darkswarm').controller "PageSelectionCtrl", ($scope, $rootScope, $location) ->
$scope.selectedPage = ->
# The path looks like `/contact` for the URL `https://ofn.org/shop#/contact`.
# We remove the slash at the beginning.
page = $location.path()[1..]
return $scope.whitelist[0] unless page
# If the path points to an unrelated path like `/login`, stay where we were.
return $scope.lastPage unless page in $scope.whitelist
$scope.lastPage = page
page
$scope.whitelistPages = (pages) ->
$scope.whitelist = pages
$scope.lastPage = pages[0]
# when an order cycle is changed, ensure the shop tab is active to save a click
$rootScope.$on "orderCycleSelected", ->
if $scope.selectedPage() != "shop"
$location.path("shop")

View File

@@ -4,18 +4,15 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
$scope.query = ""
$scope.taxonSelectors = FilterSelectorsService.createSelectors()
$scope.propertySelectors = FilterSelectorsService.createSelectors()
$scope.producerPropertySelectors = FilterSelectorsService.createSelectors()
$scope.filtersActive = true
$scope.page = 1
$scope.per_page = 10
$scope.order_cycle = OrderCycle.order_cycle
$scope.supplied_taxons = null
$scope.supplied_properties = null
$scope.supplied_producer_properties = null
$scope.showFilterSidebar = false
$scope.activeTaxons = []
$scope.activeProperties = []
$scope.activeProducerProperties = []
# Update filters after initial load of shop tab
$timeout =>
@@ -48,12 +45,6 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
$scope.supplied_properties[property.id] = Properties.properties_by_id[property.id]
)
OrderCycleResource.producerProperties params, (data)=>
$scope.supplied_producer_properties = {}
data.map( (property) ->
$scope.supplied_producer_properties[property.id] = Properties.properties_by_id[property.id]
)
$scope.loadMore = ->
if ($scope.page * $scope.per_page) <= Products.products.length
$scope.loadMoreProducts()
@@ -61,7 +52,6 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
$scope.$watch 'query', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
$scope.$watchCollection 'activeTaxons', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
$scope.$watchCollection 'activeProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
$scope.$watchCollection 'activeProducerProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue
$scope.loadProducts = ->
$scope.page = 1
@@ -76,10 +66,9 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
id: $scope.order_cycle.order_cycle_id,
page: page || $scope.page,
per_page: $scope.per_page,
'q[name_or_meta_keywords_or_variants_display_as_or_variants_display_name_or_variants_supplier_name_cont]': $scope.query,
'q[name_or_meta_keywords_or_variants_display_as_or_variants_display_name_or_supplier_name_cont]': $scope.query,
'q[with_properties][]': $scope.activeProperties,
'q[with_variants_supplier_properties][]': $scope.activeProducerProperties,
'q[variants_primary_taxon_id_in_any][]': $scope.activeTaxons
'q[primary_taxon_id_in_any][]': $scope.activeTaxons
}
$scope.searchKeypress = (e)->
@@ -97,12 +86,6 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
Properties.properties_by_id[property_id].name
).join($scope.filtersJoinWord()) if $scope.activeProperties?
$scope.appliedProducerPropertiesList = ->
$scope.activeProducerProperties.map( (property_id) ->
Properties.properties_by_id[property_id].name
).join($scope.filtersJoinWord()) if $scope.activeProducerProperties?
$scope.filtersJoinWord = ->
$sce.trustAsHtml(" <span class='join-word'>#{t('products_or')}</span> ")
@@ -116,7 +99,6 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
$scope.clearFilters = ->
$scope.taxonSelectors.clearAll()
$scope.propertySelectors.clearAll()
$scope.producerPropertySelectors.clearAll()
$scope.refreshStaleData = ->
# If the products template has already been loaded but the controller is being initialized
@@ -127,7 +109,7 @@ angular.module('Darkswarm').controller "ProductsCtrl", ($scope, $sce, $filter, $
$scope.loadProducts()
$scope.filtersCount = () ->
$scope.taxonSelectors.totalActive() + $scope.propertySelectors.totalActive() + $scope.producerPropertySelectors.totalActive()
$scope.taxonSelectors.totalActive() + $scope.propertySelectors.totalActive()
$scope.toggleFilterSidebar = ->
$scope.showFilterSidebar = !$scope.showFilterSidebar

View File

@@ -42,17 +42,8 @@ angular.module('Darkswarm').controller "RegistrationCtrl", ($scope, Registration
$scope.toggleAddressConfirmed = ->
$scope.addressConfirmed = !$scope.addressConfirmed
if $scope.addressConfirmed
$scope.setLatLongIfUsingOpenStreetMap()
$scope.enterprise.address.latitude = $scope.latLong.latitude
$scope.enterprise.address.longitude = $scope.latLong.longitude
else
$scope.enterprise.address.latitude = null
$scope.enterprise.address.longitude = null
# When OpenStreetMaps is enabled the latitude and longitude are calculated via a Stimulus
# controller, so they need to be read from data properties to be accessible here.
$scope.setLatLongIfUsingOpenStreetMap = ->
openStreetMap = document.getElementById("open-street-map")
if !$scope.latLong && openStreetMap && openStreetMap.dataset.latitude && openStreetMap.dataset.longitude
$scope.latLong = { latitude: openStreetMap.dataset.latitude, longitude: openStreetMap.dataset.longitude }

View File

@@ -9,9 +9,10 @@ angular.module('Darkswarm').controller "RegistrationFormCtrl", ($scope, Registra
$scope.create = (form) ->
if ($scope.valid(form))
$scope.disableButton()
EnterpriseRegistrationService.create($scope.enableButton).then(() ->
EnterpriseRegistrationService.create().then(() ->
$scope.enableButton()
)
end
$scope.update = (nextStep, form) ->
EnterpriseRegistrationService.update(nextStep) if $scope.valid(form)

View File

@@ -2,7 +2,6 @@ angular.module('Darkswarm').factory "EnterpriseModal", ($modal, $rootScope, $htt
# Build a modal popup for an enterprise.
new class EnterpriseModal
open: (enterprise)->
return if enterprise.visible == 'hidden'
scope = $rootScope.$new(true) # Spawn an isolate to contain the enterprise
scope.embedded_layout = window.location.search.indexOf("embedded_shopfront=true") != -1

View File

@@ -55,10 +55,6 @@ angular.module('Darkswarm').factory "EnterpriseRegistrationService", ($http, Reg
).catch((response) ->
Loading.clear()
alert(t('failed_to_update_enterprise_unknown'))
if response.data.errors.instagram
igErr = document.querySelector("#instagram-error")
igErr.style.display = 'block'
igErr.textContent = response.data.errors.instagram[0]
)
prepare: =>

View File

@@ -18,11 +18,4 @@ angular.module('Darkswarm').factory 'OrderCycleResource', ($resource) ->
url: '/api/v0/order_cycles/:id/properties.json'
params:
id: '@id'
'producerProperties':
method: 'GET'
isArray: true
url: '/api/v0/order_cycles/:id/producer_properties.json'
params:
id: '@id'
})

View File

@@ -33,13 +33,13 @@ angular.module('Darkswarm').factory 'Products', (OrderCycleResource, OrderCycle,
prices = (v.price for v in product.variants)
product.price = Math.min.apply(null, prices)
product.hasVariants = product.variants?.length > 0
product.primaryImage = product.image?.small_url if product.image
product.primaryImage = product.images[0]?.small_url if product.images
product.primaryImageOrMissing = product.primaryImage || "/noimage/small.png"
product.largeImage = product.image?.large_url if product.image
product.largeImage = product.images[0]?.large_url if product.images
dereference: ->
for product in @fetched_products
product.supplier = Shopfront.producers_by_id[product.variants[0].supplier.id]
product.supplier = Shopfront.producers_by_id[product.supplier.id]
Dereferencer.dereference product.taxons, Taxons.taxons_by_id
product.properties = angular.copy(product.properties_with_values)

View File

@@ -1,2 +1,3 @@
%li{ "ng-class": "{active: selector.active}" }
%a{ tooltip: "{{selector.object.value}}", "tooltip-placement": "bottom", "ng-transclude": true, "ng-class": "{active: selector.active, 'has-tip': selector.object.value}" }
%li{ ng: { class: "{active: selector.active}" } }
%a{ "tooltip" => "{{selector.object.value}}", "tooltip-placement" => "bottom",
ng: { transclude: true, class: "{active: selector.active, 'has-tip': selector.object.value}" } }

View File

@@ -1,8 +1,8 @@
.sixteen.columns.alpha.omega.alert-row{ "ng-show": '!dismissed' }
.sixteen.columns.alpha.omega.alert-row{ ng: { show: '!dismissed' } }
.fifteen.columns.pad.alpha
%span.message.text-big{ "ng-bind": 'message' }
%span.message.text-big{ ng: { bind: 'message'} }
&nbsp;&nbsp;&nbsp;
%input{ type: 'button', "ng-value": "buttonText", "ng-show": 'buttonText && buttonAction', "ng-click": "buttonAction()" }
%input{ type: 'button', ng: { value: "buttonText", show: 'buttonText && buttonAction', click: "buttonAction()" } }
.one.column.omega.pad.text-center
%a.close{ href: "#", "ng-click": "dismiss()" }
%a.close{ href: "#", ng: { click: "dismiss()" } }
&times;

View File

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

View File

@@ -1,8 +1,8 @@
#confirm-dialog{ "ng-class": "dialog_class" }
#confirm-dialog{ ng: { class: "dialog_class" } }
.message.clearfix.margin-bottom-30
.icon.text-center
%i.icon-question-sign
.text{ "ng-bind": "::message" }
.text{ ng: { bind: "::message" } }
.action-buttons.text-center
%button.cancel{ "ng-click": "close()", "ng-bind": "::cancelText" }
%button.confirm.red{ "ng-click": "confirm()", "ng-bind": "::confirmText" }
%button.cancel{ ng: { click: "close()", bind: "::cancelText" } }
%button.confirm.red{ ng: { click: "confirm()", bind: "::confirmText" } }

View File

@@ -1,12 +1,12 @@
#edit-address-dialog
%h2 {{ addressType === 'bill_address' ? "#{t('admin.customers.index.edit_bill_address')}" : "#{t('admin.customers.index.edit_ship_address')}" }}
%form{ name: 'edit_address_form', novalidate: true, "ng-submit": 'updateAddress()' }
%form{ name: 'edit_address_form', novalidate: true, ng: { submit: 'updateAddress()'}}
.row
{{ 'admin.customers.index.required_fileds' | t }}
(
%span.required *
)
.error{ "ng-repeat": "error in errors", "ng-bind": "error" }
.error{ ng: { repeat: "error in errors", bind: "error" } }
%table.no-borders
%tr
@@ -14,55 +14,61 @@
{{ 'first_name' | t }}
%span.required *
%td
%input{ type: 'text', name: 'firstname', required: true, "ng-model": 'address.firstname' }
%input{ type: 'text', name: 'firstname', required: true, ng: { model: 'address.firstname'} }
%tr
%td
{{ 'last_name' | t }}
%span.required *
%td
%input{ type: 'text', name: 'lastname', required: true, "ng-model": 'address.lastname' }
%input{ type: 'text', name: 'lastname', required: true, ng: { model: 'address.lastname'} }
%tr
%td
{{ 'address' | t }}
%span.required *
%td
%input{ type: 'text', name: 'address1', required: true, "ng-model": 'address.address1' }
%input{ type: 'text', name: 'address1', required: true, ng: { model: 'address.address1'} }
%tr
%td
{{ 'address2' | t }}
%td
%input{ type: 'text', name: 'address2', "ng-model": 'address.address2' }
%input{ type: 'text', name: 'address2', ng: { model: 'address.address2'} }
%tr
%td
{{ 'city' | t }}
%span.required *
%td
%input{ type: 'text', name: 'city', required: true, "ng-model": 'address.city' }
%input{ type: 'text', name: 'city', required: true, ng: { model: 'address.city'} }
%tr
%td
{{ 'postcode' | t }}
%span.required *
%td
%input{ type: 'text', name: 'zipcode', required: true, "ng-model": 'address.zipcode' }
%input{ type: 'text', name: 'zipcode', required: true, ng: { model: 'address.zipcode'} }
%tr
%td
{{ 'country' | t }}
%span.required *
%td
%input.ofn-select2.fullwidth#country_id{ type: 'number', name: 'country_id', required: true, placeholder: "{{ 'admin.customers.index.select_country' | t }}", data: 'availableCountries', "ng-model": 'address.country_id' }
%input.ofn-select2.fullwidth#country_id{ type: 'number',
name: 'country_id', required: true,
placeholder: "{{ 'admin.customers.index.select_country' | t }}",
data: 'availableCountries', ng: { model: 'address.country_id' } }
%tr
%td
{{ 'state' | t }}
%span.required *
%td
%input.ofn-select2.fullwidth#state_id{ type: 'number', name: 'state_id', required: true, placeholder: "{{ 'admin.customers.index.select_state' | t }}", data: 'states', "ng-model": 'address.state_id' }
%input.ofn-select2.fullwidth#state_id{ type: 'number',
name: 'state_id', required: true,
placeholder: "{{ 'admin.customers.index.select_state' | t }}",
data: 'states', ng: { model: 'address.state_id' } }
%tr
%td
{{ 'phone' | t }}
%span.required *
%td
%input{ type: 'text', name: 'phone', required: true, "ng-model": 'address.phone' }
%input{ type: 'text', name: 'phone', required: true, ng: { model: 'address.phone'} }
.text-center
%input.button.red.icon-plus{ type: 'submit', value: t('admin.customers.index.update_address')}

View File

@@ -1,9 +1,9 @@
#info-dialog{ "ng-class": "dialog_class" }
#info-dialog{ ng: { class: "dialog_class" } }
.message.clearfix.margin-bottom-30
.icon.text-center
%i{ "ng-class": "icon_class" }
%i{ ng: { class: "icon_class" } }
.text
{{ message }}
.action-buttons.text-center
%button{ "ng-click": "close()" }
%button{ ng: { click: "close()" } }
= t(:ok)

View File

@@ -0,0 +1,10 @@
.ofn-drop-down
%span
%i.icon-check
{{ 'admin.actions' | t }}
%i{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%a.menu_item{ 'ng-repeat' => "link in links", href: '{{link.url}}', target: "{{link.target || '_self'}}", data: { method: "{{ link.method || 'get' }}", confirm: "{{link.confirm}}" } }
%span
%i{ ng: { class: "link.icon" } }
%span {{ link.name }}

View File

@@ -1,15 +1,15 @@
%h4.modal-title
= t('js.admin.orders.index.compiling_invoices')
%p.message{ "ng-show": 'message' }
%p.message{ ng: { show: 'message' } }
{{message}}
%p.error{ "ng-show": 'error' }
%p.error{ ng: { show: 'error' } }
{{error}}
%img.spinner{ src: image_path("/spinning-circles.svg"), "ng-show": "loading" }
%p{ "ng-show": "loading" }
%img.spinner{ src: image_path("/spinning-circles.svg"), ng: { show: "loading" } }
%p{ ng: { show: "loading" } }
= t('js.admin.orders.index.please_wait')
%a.button{ target: '_blank', "ng-click": 'showBulkInvoice()', "ng-href": '/admin/orders/invoices/{{invoice_id}}', "ng-show": "!loading && !error" }
%a.button{ target: '_blank', ng: { click: 'showBulkInvoice()', href: '/admin/orders/invoices/{{invoice_id}}', show: "!loading && !error" } }
= t('js.admin.orders.index.view_file')

View File

@@ -1,10 +1,10 @@
%a.close-reveal-modal{"ng-click" => "$close()"}
%i.fa.fa-times-circle{'aria-hidden' => "true"}
%form#image_upload{ name: 'form', novalidate: true, enctype: 'multipart/form-data', multipart: true, "ng-controller": "ProductImageCtrl" }
%form#image_upload{ name: 'form', novalidate: true, enctype: 'multipart/form-data', multipart: true, ng: { controller: "ProductImageCtrl" } }
%div.image-preview
%img.spinner{ src: image_path("/spinning-circles.svg"), "ng-hide": "!imageUploader.isUploading" }
%img.preview{ "ng-src": "{{imagePreview}}", "ng-class": "{'faded': imageUploader.isUploading}" }
%img.spinner{ src: image_path("/spinning-circles.svg"), ng: { hide: "!imageUploader.isUploading" }}
%img.preview{ng: {src: "{{imagePreview}}", class: "{'faded': imageUploader.isUploading}"}}
%label{for: 'image-upload', class: 'button'} {{ 'admin.products.index.upload_an_image' | t }}
%input#image-upload{hidden: true, type: 'file', 'nv-file-select' => true, uploader: "imageUploader"}

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